feat: add read-only MCP tool annotations - #209
Conversation
Every tool returned from tools/list now carries readOnlyHint: true and openWorldHint: true. Previously the annotations key was absent entirely, since adk_to_mcp_tool_type sets only name, description, and inputSchema. readOnlyHint reflects what the code calls. Across the nine tools the only methods invoked on the Data and Admin API clients are get_property, get_metadata, the list_* methods, and the run_*_report query methods; none of them writes. The analytics.readonly scope in tools/client.py is defense in depth rather than the guarantee, because google.auth.default applies scopes through with_scopes_if_required, which leaves the user credentials from 'gcloud auth application-default login' untouched. destructiveHint and idempotentHint are omitted because ToolAnnotations documents both as meaningful only when readOnlyHint is false.
|
In-depth implementation and verification notes — written so both human reviewers and coding agents evaluating this PR have the full mechanism in one place. The description above is the short version; this comment carries the verification detail and the judgment calls a reviewer may want to interrogate. Why Why Why the other two hints are omitted rather than set. Wire verification. Ran the server over stdio ( Suite. The tool-name pin, and PR #178. Two things you will notice in CI output. The tool count here is nine (from |
Sets
annotationson every tool this server returns fromtools/list:Today the key is absent from the wire —
adk_to_mcp_tool_typebuilds eachmcp.types.Toolfrom name, description, and inputSchema only, and nothing downstream sets the field. Annotations are how a client learns a tool only reads, which it can use when describing the tool and when deciding whether a call needs a confirmation prompt.readOnlyHint: truerests on what the code calls: across all nine tools, the only methods invoked on the Data and Admin API clients are reads (get_property,get_metadata, thelist_*methods, and therun_report/run_realtime_report/run_funnel_reportmethods).destructiveHintandidempotentHintare omitted because themcppackage documents both as meaningful only whenreadOnlyHint == false.Verified over stdio:
tools/listshows the key on 0/9 tools before this change and 9/9 after. Tests pin the exact annotations object per tool and the tool-name set — the name pin is a deliberate tripwire so a future tool can't inheritreadOnlyHintsilently; say the word if you'd rather not carry it.A follow-up comment below carries the full mechanism and verification detail for in-depth review.