Skip to content

feat(core): dynamic tool annotations for multi-mode tools on read-only sources - #3816

Merged
anubhav756 merged 1 commit into
feat/read-onlyfrom
anubhav-readonly-annotation
Aug 27, 2026
Merged

anubhav756 merged 1 commit into
feat/read-onlyfrom
anubhav-readonly-annotation

Conversation

@anubhav756

@anubhav756 anubhav756 commented Aug 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Enables multi-mode tools (e.g., SQL execution tools) to dynamically advertise readOnlyHint: true and destructiveHint: false in MCP tool manifests when bound to a read-only data source, and centralizes tool suppression logic across the server.

Context & Motivation

  • Tools like postgres-execute-sql and mysql-execute-sql are multi-mode (capable of both reads and writes) and default to readOnlyHint: false / destructiveHint: true. When connected to a readOnly: true database source (where session-level locks prevent modifications), these tools should dynamically report readOnlyHint: true and destructiveHint: false to MCP clients without requiring separate read-only tool implementations.
  • Refactored ShouldSuppress from a BaseTool method into a package-level function tools.ShouldSuppress(ctx, t, src) that operates on the Tool interface, allowing suppression to dynamically evaluate t.GetAnnotations(src) without requiring concrete tool method overrides.

Changes

  • Updated Tool.GetAnnotations(sources.Source) *ToolAnnotations to make annotations source-aware (matching the design pattern of GetParameters(sources.Source) and Manifest(sources.Source)).
  • Updated BaseTool.GetAnnotations(_ sources.Source) to return static annotations by default.
  • Added DynamicReadOnlyAnnotations(base *ToolAnnotations) *ToolAnnotations in internal/tools/tools.go to safely copy base annotations and set ReadOnlyHint: true / DestructiveHint: false while preserving other custom hints (e.g. idempotentHint, openWorldHint).
  • Updated postgres-execute-sql and mysql-execute-sql to implement GetAnnotations(src) using tools.DynamicReadOnlyAnnotations(t.BaseTool.GetAnnotations(src)) when src.IsReadOnly().
  • Removed redundant ShouldSuppress method overrides from concrete SQL tool structs.
  • Updated all 5 MCP schema version generators (v20241105, v20250326, v20250618, v20251125, v20260728) in internal/server/mcp/ to pass src into tool.GetAnnotations(src).
  • Added table-driven tests for tools.DynamicReadOnlyAnnotations in internal/tools/tools_test.go verifying nil handling, default flipping, custom hint preservation, and pointer immutability.
  • Updated internal/server/server_test.go and internal/tools/tools_test.go to test tools.ShouldSuppress with both write and destructive tools using tools.NewWriteAnnotations() and tools.NewDestructiveAnnotations().
  • Updated Looker unit test suites to pass tool.GetAnnotations(nil).

@gemini-code-assist

Copy link
Copy Markdown
Contributor

Note

Gemini is unable to generate a review for this pull request due to the file types involved not being currently supported.

@anubhav756 anubhav756 assigned Yuan325 and unassigned duwenxin99 Aug 13, 2026
@anubhav756
anubhav756 force-pushed the anubhav-readonly-annotation branch from 8930b29 to 53be9fe Compare August 13, 2026 07:49
@anubhav756
anubhav756 requested review from a team as code owners August 13, 2026 07:49
@anubhav756
anubhav756 force-pushed the anubhav-readonly-annotation branch from 53be9fe to 5fade7d Compare August 13, 2026 08:14
@anubhav756
anubhav756 force-pushed the anubhav-readonly-annotation branch from 5fade7d to f36035b Compare August 13, 2026 08:33
@anubhav756
anubhav756 force-pushed the anubhav-readonly-core branch from a05fcb6 to c81fa4f Compare August 13, 2026 08:38
@anubhav756
anubhav756 requested review from a team as code owners August 13, 2026 08:38
@anubhav756
anubhav756 force-pushed the anubhav-readonly-annotation branch 3 times, most recently from ee8f29a to 9dc7d07 Compare August 13, 2026 10:24
@anubhav756
anubhav756 force-pushed the anubhav-readonly-core branch from c81fa4f to e5ff38c Compare August 13, 2026 13:09
@anubhav756
anubhav756 force-pushed the anubhav-readonly-annotation branch 2 times, most recently from dd8d903 to 2b6758a Compare August 13, 2026 13:11
Comment thread internal/tools/tools.go Outdated
Comment thread internal/tools/tools.go
@Yuan325 Yuan325 added the priority: p1 Important issue which blocks shipping the next release. Will be fixed prior to next release. label Aug 25, 2026
@anubhav756
anubhav756 force-pushed the anubhav-readonly-core branch from 132d067 to 3d72b41 Compare August 25, 2026 08:43
@anubhav756
anubhav756 force-pushed the anubhav-readonly-annotation branch from 093efc9 to 3cc4d05 Compare August 25, 2026 08:43
@anubhav756
anubhav756 force-pushed the anubhav-readonly-core branch from 3d72b41 to 8ab1506 Compare August 25, 2026 18:11
@anubhav756
anubhav756 force-pushed the anubhav-readonly-annotation branch from 3cc4d05 to adf943c Compare August 25, 2026 18:11
@anubhav756
anubhav756 force-pushed the anubhav-readonly-core branch from 8ab1506 to d2f77c7 Compare August 26, 2026 21:06
@anubhav756
anubhav756 force-pushed the anubhav-readonly-annotation branch from adf943c to e6f7564 Compare August 26, 2026 21:06
Base automatically changed from anubhav-readonly-core to feat/read-only August 27, 2026 06:45
@anubhav756
anubhav756 force-pushed the anubhav-readonly-annotation branch from e6f7564 to a96d2ab Compare August 27, 2026 06:48
@anubhav756
anubhav756 force-pushed the anubhav-readonly-annotation branch from a96d2ab to 0a4a063 Compare August 27, 2026 06:48
@anubhav756
anubhav756 merged commit 03d6553 into feat/read-only Aug 27, 2026
15 checks passed
@github-actions

Copy link
Copy Markdown
Contributor

🧨 Preview deployments removed.

Cloudflare Pages environments for pr-3816 have been deleted.

@anubhav756
anubhav756 deleted the anubhav-readonly-annotation branch August 27, 2026 06:49
anubhav756 added a commit that referenced this pull request Aug 27, 2026
…y sources (#3816)

## Summary

Enables multi-mode tools (e.g., SQL execution tools) to dynamically
advertise `readOnlyHint: true` and `destructiveHint: false` in MCP tool
manifests when bound to a read-only data source, and centralizes tool
suppression logic across the server.

## Context & Motivation

* Tools like `postgres-execute-sql` and `mysql-execute-sql` are
multi-mode (capable of both reads and writes) and default to
`readOnlyHint: false` / `destructiveHint: true`. When connected to a
`readOnly: true` database source (where session-level locks prevent
modifications), these tools should dynamically report `readOnlyHint:
true` and `destructiveHint: false` to MCP clients without requiring
separate read-only tool implementations.
* Refactored `ShouldSuppress` from a `BaseTool` method into a
package-level function `tools.ShouldSuppress(ctx, t, src)` that operates
on the `Tool` interface, allowing suppression to dynamically evaluate
`t.GetAnnotations(src)` without requiring concrete tool method
overrides.

## Changes

* Updated `Tool.GetAnnotations(sources.Source) *ToolAnnotations` to make
annotations source-aware (matching the design pattern of
`GetParameters(sources.Source)` and `Manifest(sources.Source)`).
* Updated `BaseTool.GetAnnotations(_ sources.Source)` to return static
annotations by default.
* Added `DynamicReadOnlyAnnotations(base *ToolAnnotations)
*ToolAnnotations` in `internal/tools/tools.go` to safely copy base
annotations and set `ReadOnlyHint: true` / `DestructiveHint: false`
while preserving other custom hints (e.g. `idempotentHint`,
`openWorldHint`).
* Updated `postgres-execute-sql` and `mysql-execute-sql` to implement
`GetAnnotations(src)` using
`tools.DynamicReadOnlyAnnotations(t.BaseTool.GetAnnotations(src))` when
`src.IsReadOnly()`.
* Removed redundant `ShouldSuppress` method overrides from concrete SQL
tool structs.
* Updated all 5 MCP schema version generators (`v20241105`, `v20250326`,
`v20250618`, `v20251125`, `v20260728`) in `internal/server/mcp/` to pass
`src` into `tool.GetAnnotations(src)`.
* Added table-driven tests for `tools.DynamicReadOnlyAnnotations` in
`internal/tools/tools_test.go` verifying nil handling, default flipping,
custom hint preservation, and pointer immutability.
* Updated `internal/server/server_test.go` and
`internal/tools/tools_test.go` to test `tools.ShouldSuppress` with both
write and destructive tools using `tools.NewWriteAnnotations()` and
`tools.NewDestructiveAnnotations()`.
   * Updated Looker unit test suites to pass `tool.GetAnnotations(nil)`.
anubhav756 added a commit that referenced this pull request Aug 27, 2026
…y sources (#3816)

## Summary

Enables multi-mode tools (e.g., SQL execution tools) to dynamically
advertise `readOnlyHint: true` and `destructiveHint: false` in MCP tool
manifests when bound to a read-only data source, and centralizes tool
suppression logic across the server.

## Context & Motivation

* Tools like `postgres-execute-sql` and `mysql-execute-sql` are
multi-mode (capable of both reads and writes) and default to
`readOnlyHint: false` / `destructiveHint: true`. When connected to a
`readOnly: true` database source (where session-level locks prevent
modifications), these tools should dynamically report `readOnlyHint:
true` and `destructiveHint: false` to MCP clients without requiring
separate read-only tool implementations.
* Refactored `ShouldSuppress` from a `BaseTool` method into a
package-level function `tools.ShouldSuppress(ctx, t, src)` that operates
on the `Tool` interface, allowing suppression to dynamically evaluate
`t.GetAnnotations(src)` without requiring concrete tool method
overrides.

## Changes

* Updated `Tool.GetAnnotations(sources.Source) *ToolAnnotations` to make
annotations source-aware (matching the design pattern of
`GetParameters(sources.Source)` and `Manifest(sources.Source)`).
* Updated `BaseTool.GetAnnotations(_ sources.Source)` to return static
annotations by default.
* Added `DynamicReadOnlyAnnotations(base *ToolAnnotations)
*ToolAnnotations` in `internal/tools/tools.go` to safely copy base
annotations and set `ReadOnlyHint: true` / `DestructiveHint: false`
while preserving other custom hints (e.g. `idempotentHint`,
`openWorldHint`).
* Updated `postgres-execute-sql` and `mysql-execute-sql` to implement
`GetAnnotations(src)` using
`tools.DynamicReadOnlyAnnotations(t.BaseTool.GetAnnotations(src))` when
`src.IsReadOnly()`.
* Removed redundant `ShouldSuppress` method overrides from concrete SQL
tool structs.
* Updated all 5 MCP schema version generators (`v20241105`, `v20250326`,
`v20250618`, `v20251125`, `v20260728`) in `internal/server/mcp/` to pass
`src` into `tool.GetAnnotations(src)`.
* Added table-driven tests for `tools.DynamicReadOnlyAnnotations` in
`internal/tools/tools_test.go` verifying nil handling, default flipping,
custom hint preservation, and pointer immutability.
* Updated `internal/server/server_test.go` and
`internal/tools/tools_test.go` to test `tools.ShouldSuppress` with both
write and destructive tools using `tools.NewWriteAnnotations()` and
`tools.NewDestructiveAnnotations()`.
   * Updated Looker unit test suites to pass `tool.GetAnnotations(nil)`.
anubhav756 added a commit that referenced this pull request Aug 27, 2026
…y sources (#3816)

## Summary

Enables multi-mode tools (e.g., SQL execution tools) to dynamically
advertise `readOnlyHint: true` and `destructiveHint: false` in MCP tool
manifests when bound to a read-only data source, and centralizes tool
suppression logic across the server.

## Context & Motivation

* Tools like `postgres-execute-sql` and `mysql-execute-sql` are
multi-mode (capable of both reads and writes) and default to
`readOnlyHint: false` / `destructiveHint: true`. When connected to a
`readOnly: true` database source (where session-level locks prevent
modifications), these tools should dynamically report `readOnlyHint:
true` and `destructiveHint: false` to MCP clients without requiring
separate read-only tool implementations.
* Refactored `ShouldSuppress` from a `BaseTool` method into a
package-level function `tools.ShouldSuppress(ctx, t, src)` that operates
on the `Tool` interface, allowing suppression to dynamically evaluate
`t.GetAnnotations(src)` without requiring concrete tool method
overrides.

## Changes

* Updated `Tool.GetAnnotations(sources.Source) *ToolAnnotations` to make
annotations source-aware (matching the design pattern of
`GetParameters(sources.Source)` and `Manifest(sources.Source)`).
* Updated `BaseTool.GetAnnotations(_ sources.Source)` to return static
annotations by default.
* Added `DynamicReadOnlyAnnotations(base *ToolAnnotations)
*ToolAnnotations` in `internal/tools/tools.go` to safely copy base
annotations and set `ReadOnlyHint: true` / `DestructiveHint: false`
while preserving other custom hints (e.g. `idempotentHint`,
`openWorldHint`).
* Updated `postgres-execute-sql` and `mysql-execute-sql` to implement
`GetAnnotations(src)` using
`tools.DynamicReadOnlyAnnotations(t.BaseTool.GetAnnotations(src))` when
`src.IsReadOnly()`.
* Removed redundant `ShouldSuppress` method overrides from concrete SQL
tool structs.
* Updated all 5 MCP schema version generators (`v20241105`, `v20250326`,
`v20250618`, `v20251125`, `v20260728`) in `internal/server/mcp/` to pass
`src` into `tool.GetAnnotations(src)`.
* Added table-driven tests for `tools.DynamicReadOnlyAnnotations` in
`internal/tools/tools_test.go` verifying nil handling, default flipping,
custom hint preservation, and pointer immutability.
* Updated `internal/server/server_test.go` and
`internal/tools/tools_test.go` to test `tools.ShouldSuppress` with both
write and destructive tools using `tools.NewWriteAnnotations()` and
`tools.NewDestructiveAnnotations()`.
   * Updated Looker unit test suites to pass `tool.GetAnnotations(nil)`.
anubhav756 added a commit that referenced this pull request Aug 27, 2026
…y sources (#3816)

## Summary

Enables multi-mode tools (e.g., SQL execution tools) to dynamically
advertise `readOnlyHint: true` and `destructiveHint: false` in MCP tool
manifests when bound to a read-only data source, and centralizes tool
suppression logic across the server.

## Context & Motivation

* Tools like `postgres-execute-sql` and `mysql-execute-sql` are
multi-mode (capable of both reads and writes) and default to
`readOnlyHint: false` / `destructiveHint: true`. When connected to a
`readOnly: true` database source (where session-level locks prevent
modifications), these tools should dynamically report `readOnlyHint:
true` and `destructiveHint: false` to MCP clients without requiring
separate read-only tool implementations.
* Refactored `ShouldSuppress` from a `BaseTool` method into a
package-level function `tools.ShouldSuppress(ctx, t, src)` that operates
on the `Tool` interface, allowing suppression to dynamically evaluate
`t.GetAnnotations(src)` without requiring concrete tool method
overrides.

## Changes

* Updated `Tool.GetAnnotations(sources.Source) *ToolAnnotations` to make
annotations source-aware (matching the design pattern of
`GetParameters(sources.Source)` and `Manifest(sources.Source)`).
* Updated `BaseTool.GetAnnotations(_ sources.Source)` to return static
annotations by default.
* Added `DynamicReadOnlyAnnotations(base *ToolAnnotations)
*ToolAnnotations` in `internal/tools/tools.go` to safely copy base
annotations and set `ReadOnlyHint: true` / `DestructiveHint: false`
while preserving other custom hints (e.g. `idempotentHint`,
`openWorldHint`).
* Updated `postgres-execute-sql` and `mysql-execute-sql` to implement
`GetAnnotations(src)` using
`tools.DynamicReadOnlyAnnotations(t.BaseTool.GetAnnotations(src))` when
`src.IsReadOnly()`.
* Removed redundant `ShouldSuppress` method overrides from concrete SQL
tool structs.
* Updated all 5 MCP schema version generators (`v20241105`, `v20250326`,
`v20250618`, `v20251125`, `v20260728`) in `internal/server/mcp/` to pass
`src` into `tool.GetAnnotations(src)`.
* Added table-driven tests for `tools.DynamicReadOnlyAnnotations` in
`internal/tools/tools_test.go` verifying nil handling, default flipping,
custom hint preservation, and pointer immutability.
* Updated `internal/server/server_test.go` and
`internal/tools/tools_test.go` to test `tools.ShouldSuppress` with both
write and destructive tools using `tools.NewWriteAnnotations()` and
`tools.NewDestructiveAnnotations()`.
   * Updated Looker unit test suites to pass `tool.GetAnnotations(nil)`.
anubhav756 added a commit that referenced this pull request Aug 27, 2026
…y sources (#3816)

## Summary

Enables multi-mode tools (e.g., SQL execution tools) to dynamically
advertise `readOnlyHint: true` and `destructiveHint: false` in MCP tool
manifests when bound to a read-only data source, and centralizes tool
suppression logic across the server.

## Context & Motivation

* Tools like `postgres-execute-sql` and `mysql-execute-sql` are
multi-mode (capable of both reads and writes) and default to
`readOnlyHint: false` / `destructiveHint: true`. When connected to a
`readOnly: true` database source (where session-level locks prevent
modifications), these tools should dynamically report `readOnlyHint:
true` and `destructiveHint: false` to MCP clients without requiring
separate read-only tool implementations.
* Refactored `ShouldSuppress` from a `BaseTool` method into a
package-level function `tools.ShouldSuppress(ctx, t, src)` that operates
on the `Tool` interface, allowing suppression to dynamically evaluate
`t.GetAnnotations(src)` without requiring concrete tool method
overrides.

## Changes

* Updated `Tool.GetAnnotations(sources.Source) *ToolAnnotations` to make
annotations source-aware (matching the design pattern of
`GetParameters(sources.Source)` and `Manifest(sources.Source)`).
* Updated `BaseTool.GetAnnotations(_ sources.Source)` to return static
annotations by default.
* Added `DynamicReadOnlyAnnotations(base *ToolAnnotations)
*ToolAnnotations` in `internal/tools/tools.go` to safely copy base
annotations and set `ReadOnlyHint: true` / `DestructiveHint: false`
while preserving other custom hints (e.g. `idempotentHint`,
`openWorldHint`).
* Updated `postgres-execute-sql` and `mysql-execute-sql` to implement
`GetAnnotations(src)` using
`tools.DynamicReadOnlyAnnotations(t.BaseTool.GetAnnotations(src))` when
`src.IsReadOnly()`.
* Removed redundant `ShouldSuppress` method overrides from concrete SQL
tool structs.
* Updated all 5 MCP schema version generators (`v20241105`, `v20250326`,
`v20250618`, `v20251125`, `v20260728`) in `internal/server/mcp/` to pass
`src` into `tool.GetAnnotations(src)`.
* Added table-driven tests for `tools.DynamicReadOnlyAnnotations` in
`internal/tools/tools_test.go` verifying nil handling, default flipping,
custom hint preservation, and pointer immutability.
* Updated `internal/server/server_test.go` and
`internal/tools/tools_test.go` to test `tools.ShouldSuppress` with both
write and destructive tools using `tools.NewWriteAnnotations()` and
`tools.NewDestructiveAnnotations()`.
   * Updated Looker unit test suites to pass `tool.GetAnnotations(nil)`.
anubhav756 added a commit that referenced this pull request Aug 27, 2026
…y sources (#3816)

## Summary

Enables multi-mode tools (e.g., SQL execution tools) to dynamically
advertise `readOnlyHint: true` and `destructiveHint: false` in MCP tool
manifests when bound to a read-only data source, and centralizes tool
suppression logic across the server.

## Context & Motivation

* Tools like `postgres-execute-sql` and `mysql-execute-sql` are
multi-mode (capable of both reads and writes) and default to
`readOnlyHint: false` / `destructiveHint: true`. When connected to a
`readOnly: true` database source (where session-level locks prevent
modifications), these tools should dynamically report `readOnlyHint:
true` and `destructiveHint: false` to MCP clients without requiring
separate read-only tool implementations.
* Refactored `ShouldSuppress` from a `BaseTool` method into a
package-level function `tools.ShouldSuppress(ctx, t, src)` that operates
on the `Tool` interface, allowing suppression to dynamically evaluate
`t.GetAnnotations(src)` without requiring concrete tool method
overrides.

## Changes

* Updated `Tool.GetAnnotations(sources.Source) *ToolAnnotations` to make
annotations source-aware (matching the design pattern of
`GetParameters(sources.Source)` and `Manifest(sources.Source)`).
* Updated `BaseTool.GetAnnotations(_ sources.Source)` to return static
annotations by default.
* Added `DynamicReadOnlyAnnotations(base *ToolAnnotations)
*ToolAnnotations` in `internal/tools/tools.go` to safely copy base
annotations and set `ReadOnlyHint: true` / `DestructiveHint: false`
while preserving other custom hints (e.g. `idempotentHint`,
`openWorldHint`).
* Updated `postgres-execute-sql` and `mysql-execute-sql` to implement
`GetAnnotations(src)` using
`tools.DynamicReadOnlyAnnotations(t.BaseTool.GetAnnotations(src))` when
`src.IsReadOnly()`.
* Removed redundant `ShouldSuppress` method overrides from concrete SQL
tool structs.
* Updated all 5 MCP schema version generators (`v20241105`, `v20250326`,
`v20250618`, `v20251125`, `v20260728`) in `internal/server/mcp/` to pass
`src` into `tool.GetAnnotations(src)`.
* Added table-driven tests for `tools.DynamicReadOnlyAnnotations` in
`internal/tools/tools_test.go` verifying nil handling, default flipping,
custom hint preservation, and pointer immutability.
* Updated `internal/server/server_test.go` and
`internal/tools/tools_test.go` to test `tools.ShouldSuppress` with both
write and destructive tools using `tools.NewWriteAnnotations()` and
`tools.NewDestructiveAnnotations()`.
   * Updated Looker unit test suites to pass `tool.GetAnnotations(nil)`.
anubhav756 added a commit that referenced this pull request Aug 27, 2026
…y sources (#3816)

## Summary

Enables multi-mode tools (e.g., SQL execution tools) to dynamically
advertise `readOnlyHint: true` and `destructiveHint: false` in MCP tool
manifests when bound to a read-only data source, and centralizes tool
suppression logic across the server.

## Context & Motivation

* Tools like `postgres-execute-sql` and `mysql-execute-sql` are
multi-mode (capable of both reads and writes) and default to
`readOnlyHint: false` / `destructiveHint: true`. When connected to a
`readOnly: true` database source (where session-level locks prevent
modifications), these tools should dynamically report `readOnlyHint:
true` and `destructiveHint: false` to MCP clients without requiring
separate read-only tool implementations.
* Refactored `ShouldSuppress` from a `BaseTool` method into a
package-level function `tools.ShouldSuppress(ctx, t, src)` that operates
on the `Tool` interface, allowing suppression to dynamically evaluate
`t.GetAnnotations(src)` without requiring concrete tool method
overrides.

## Changes

* Updated `Tool.GetAnnotations(sources.Source) *ToolAnnotations` to make
annotations source-aware (matching the design pattern of
`GetParameters(sources.Source)` and `Manifest(sources.Source)`).
* Updated `BaseTool.GetAnnotations(_ sources.Source)` to return static
annotations by default.
* Added `DynamicReadOnlyAnnotations(base *ToolAnnotations)
*ToolAnnotations` in `internal/tools/tools.go` to safely copy base
annotations and set `ReadOnlyHint: true` / `DestructiveHint: false`
while preserving other custom hints (e.g. `idempotentHint`,
`openWorldHint`).
* Updated `postgres-execute-sql` and `mysql-execute-sql` to implement
`GetAnnotations(src)` using
`tools.DynamicReadOnlyAnnotations(t.BaseTool.GetAnnotations(src))` when
`src.IsReadOnly()`.
* Removed redundant `ShouldSuppress` method overrides from concrete SQL
tool structs.
* Updated all 5 MCP schema version generators (`v20241105`, `v20250326`,
`v20250618`, `v20251125`, `v20260728`) in `internal/server/mcp/` to pass
`src` into `tool.GetAnnotations(src)`.
* Added table-driven tests for `tools.DynamicReadOnlyAnnotations` in
`internal/tools/tools_test.go` verifying nil handling, default flipping,
custom hint preservation, and pointer immutability.
* Updated `internal/server/server_test.go` and
`internal/tools/tools_test.go` to test `tools.ShouldSuppress` with both
write and destructive tools using `tools.NewWriteAnnotations()` and
`tools.NewDestructiveAnnotations()`.
   * Updated Looker unit test suites to pass `tool.GetAnnotations(nil)`.
anubhav756 added a commit that referenced this pull request Aug 27, 2026
…y sources (#3816)

## Summary

Enables multi-mode tools (e.g., SQL execution tools) to dynamically
advertise `readOnlyHint: true` and `destructiveHint: false` in MCP tool
manifests when bound to a read-only data source, and centralizes tool
suppression logic across the server.

## Context & Motivation

* Tools like `postgres-execute-sql` and `mysql-execute-sql` are
multi-mode (capable of both reads and writes) and default to
`readOnlyHint: false` / `destructiveHint: true`. When connected to a
`readOnly: true` database source (where session-level locks prevent
modifications), these tools should dynamically report `readOnlyHint:
true` and `destructiveHint: false` to MCP clients without requiring
separate read-only tool implementations.
* Refactored `ShouldSuppress` from a `BaseTool` method into a
package-level function `tools.ShouldSuppress(ctx, t, src)` that operates
on the `Tool` interface, allowing suppression to dynamically evaluate
`t.GetAnnotations(src)` without requiring concrete tool method
overrides.

## Changes

* Updated `Tool.GetAnnotations(sources.Source) *ToolAnnotations` to make
annotations source-aware (matching the design pattern of
`GetParameters(sources.Source)` and `Manifest(sources.Source)`).
* Updated `BaseTool.GetAnnotations(_ sources.Source)` to return static
annotations by default.
* Added `DynamicReadOnlyAnnotations(base *ToolAnnotations)
*ToolAnnotations` in `internal/tools/tools.go` to safely copy base
annotations and set `ReadOnlyHint: true` / `DestructiveHint: false`
while preserving other custom hints (e.g. `idempotentHint`,
`openWorldHint`).
* Updated `postgres-execute-sql` and `mysql-execute-sql` to implement
`GetAnnotations(src)` using
`tools.DynamicReadOnlyAnnotations(t.BaseTool.GetAnnotations(src))` when
`src.IsReadOnly()`.
* Removed redundant `ShouldSuppress` method overrides from concrete SQL
tool structs.
* Updated all 5 MCP schema version generators (`v20241105`, `v20250326`,
`v20250618`, `v20251125`, `v20260728`) in `internal/server/mcp/` to pass
`src` into `tool.GetAnnotations(src)`.
* Added table-driven tests for `tools.DynamicReadOnlyAnnotations` in
`internal/tools/tools_test.go` verifying nil handling, default flipping,
custom hint preservation, and pointer immutability.
* Updated `internal/server/server_test.go` and
`internal/tools/tools_test.go` to test `tools.ShouldSuppress` with both
write and destructive tools using `tools.NewWriteAnnotations()` and
`tools.NewDestructiveAnnotations()`.
   * Updated Looker unit test suites to pass `tool.GetAnnotations(nil)`.
anubhav756 added a commit that referenced this pull request Aug 27, 2026
…y sources (#3816)

## Summary

Enables multi-mode tools (e.g., SQL execution tools) to dynamically
advertise `readOnlyHint: true` and `destructiveHint: false` in MCP tool
manifests when bound to a read-only data source, and centralizes tool
suppression logic across the server.

## Context & Motivation

* Tools like `postgres-execute-sql` and `mysql-execute-sql` are
multi-mode (capable of both reads and writes) and default to
`readOnlyHint: false` / `destructiveHint: true`. When connected to a
`readOnly: true` database source (where session-level locks prevent
modifications), these tools should dynamically report `readOnlyHint:
true` and `destructiveHint: false` to MCP clients without requiring
separate read-only tool implementations.
* Refactored `ShouldSuppress` from a `BaseTool` method into a
package-level function `tools.ShouldSuppress(ctx, t, src)` that operates
on the `Tool` interface, allowing suppression to dynamically evaluate
`t.GetAnnotations(src)` without requiring concrete tool method
overrides.

## Changes

* Updated `Tool.GetAnnotations(sources.Source) *ToolAnnotations` to make
annotations source-aware (matching the design pattern of
`GetParameters(sources.Source)` and `Manifest(sources.Source)`).
* Updated `BaseTool.GetAnnotations(_ sources.Source)` to return static
annotations by default.
* Added `DynamicReadOnlyAnnotations(base *ToolAnnotations)
*ToolAnnotations` in `internal/tools/tools.go` to safely copy base
annotations and set `ReadOnlyHint: true` / `DestructiveHint: false`
while preserving other custom hints (e.g. `idempotentHint`,
`openWorldHint`).
* Updated `postgres-execute-sql` and `mysql-execute-sql` to implement
`GetAnnotations(src)` using
`tools.DynamicReadOnlyAnnotations(t.BaseTool.GetAnnotations(src))` when
`src.IsReadOnly()`.
* Removed redundant `ShouldSuppress` method overrides from concrete SQL
tool structs.
* Updated all 5 MCP schema version generators (`v20241105`, `v20250326`,
`v20250618`, `v20251125`, `v20260728`) in `internal/server/mcp/` to pass
`src` into `tool.GetAnnotations(src)`.
* Added table-driven tests for `tools.DynamicReadOnlyAnnotations` in
`internal/tools/tools_test.go` verifying nil handling, default flipping,
custom hint preservation, and pointer immutability.
* Updated `internal/server/server_test.go` and
`internal/tools/tools_test.go` to test `tools.ShouldSuppress` with both
write and destructive tools using `tools.NewWriteAnnotations()` and
`tools.NewDestructiveAnnotations()`.
   * Updated Looker unit test suites to pass `tool.GetAnnotations(nil)`.
anubhav756 added a commit that referenced this pull request Aug 27, 2026
…y sources (#3816)

## Summary

Enables multi-mode tools (e.g., SQL execution tools) to dynamically
advertise `readOnlyHint: true` and `destructiveHint: false` in MCP tool
manifests when bound to a read-only data source, and centralizes tool
suppression logic across the server.

## Context & Motivation

* Tools like `postgres-execute-sql` and `mysql-execute-sql` are
multi-mode (capable of both reads and writes) and default to
`readOnlyHint: false` / `destructiveHint: true`. When connected to a
`readOnly: true` database source (where session-level locks prevent
modifications), these tools should dynamically report `readOnlyHint:
true` and `destructiveHint: false` to MCP clients without requiring
separate read-only tool implementations.
* Refactored `ShouldSuppress` from a `BaseTool` method into a
package-level function `tools.ShouldSuppress(ctx, t, src)` that operates
on the `Tool` interface, allowing suppression to dynamically evaluate
`t.GetAnnotations(src)` without requiring concrete tool method
overrides.

## Changes

* Updated `Tool.GetAnnotations(sources.Source) *ToolAnnotations` to make
annotations source-aware (matching the design pattern of
`GetParameters(sources.Source)` and `Manifest(sources.Source)`).
* Updated `BaseTool.GetAnnotations(_ sources.Source)` to return static
annotations by default.
* Added `DynamicReadOnlyAnnotations(base *ToolAnnotations)
*ToolAnnotations` in `internal/tools/tools.go` to safely copy base
annotations and set `ReadOnlyHint: true` / `DestructiveHint: false`
while preserving other custom hints (e.g. `idempotentHint`,
`openWorldHint`).
* Updated `postgres-execute-sql` and `mysql-execute-sql` to implement
`GetAnnotations(src)` using
`tools.DynamicReadOnlyAnnotations(t.BaseTool.GetAnnotations(src))` when
`src.IsReadOnly()`.
* Removed redundant `ShouldSuppress` method overrides from concrete SQL
tool structs.
* Updated all 5 MCP schema version generators (`v20241105`, `v20250326`,
`v20250618`, `v20251125`, `v20260728`) in `internal/server/mcp/` to pass
`src` into `tool.GetAnnotations(src)`.
* Added table-driven tests for `tools.DynamicReadOnlyAnnotations` in
`internal/tools/tools_test.go` verifying nil handling, default flipping,
custom hint preservation, and pointer immutability.
* Updated `internal/server/server_test.go` and
`internal/tools/tools_test.go` to test `tools.ShouldSuppress` with both
write and destructive tools using `tools.NewWriteAnnotations()` and
`tools.NewDestructiveAnnotations()`.
   * Updated Looker unit test suites to pass `tool.GetAnnotations(nil)`.
anubhav756 added a commit that referenced this pull request Aug 27, 2026
…ions (#3872)

## Description

This PR introduces end-to-end Read-Only mode support across Toolbox,
spanning core framework tool suppression, source-aware dynamic MCP tool
annotations, and protocol/session-level enforcement for database
sources.

When a data source is configured in read-only mode:
1. **Agent-Level Tool Suppression**: Write-capable tools bound to
read-only sources are suppressed from tool registration and pruned from
tool groups to save LLM context window space and prevent hallucinated
write attempts.
2. **Dynamic Tool Annotations**: Multi-mode tools (such as SQL execution
tools) dynamically advertise `readOnlyHint: true` and `destructiveHint:
false` in MCP tool manifests when connected to read-only sources.
3. **Database Session-Level Enforcement**: Database drivers enforce
strict, protocol-level session locking directly within the database
engine/client driver, preventing prompt injection or multi-statement
write breakouts.

## PRs Included

- #3615
- #3816
- #3618
- #3851
- #3619
- #3617
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

priority: p1 Important issue which blocks shipping the next release. Will be fixed prior to next release.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants