Answers · MCP security
How do I separate read-only analysis MCP tools from side-effecting tools and lazy-load by task?
Last updated
TL;DR: Do not dump every connected MCP server's full tools/list into the model at session start. Split the catalog into a read-only analysis set (search, fetch, get, list) and a side-effecting set (write, send, pay, delete, deploy), keep side-effecting tools out of context until the task actually needs them, and record which tool name plus which schema/version you exposed when you load it. MCP client best practices (2025-11-25) call this progressive discovery: fetch tools/list host-side, give the model a lightweight search or catalog meta-tool, and inject full definitions only as needed — especially once tool definitions take more than a small share of the context window. Treat readOnlyHint and destructiveHint as untrusted hints unless the server is one you pin and trust; enforce the split with your own allowlists, separate credentials, and human approval for anything that writes, sends, or spends. Pair check_tool_integrity (or precall_check) before enabling a write toolset, and record_ai_run when you want a signed receipt of the selected tool fingerprint for the run.
What should I check, step by step?
1. Inventory every connected MCP server's tools into two buckets you own: analysis/read (no environment change) vs side-effecting (write, send, pay, delete, mutate). Do not trust the tool name alone.
- Keep a host-side full tools/list (or per-server cache). Do not inject every schema into the model prompt by default.
- Expose a small always-on catalog: search_tools / list_toolsets / enable_toolset (or your host's equivalent), with one-line descriptions and a read_only vs side_effect flag per entry.
- Lazy-load by task: when the model (or a skill) picks a toolset, inject only that tool's full inputSchema — append after the cache breakpoint, or route calls through a stable call_tool meta-tool so the tools array stays cache-friendly.
- Gate side-effecting enablement: require a separate credential or scope, and request_human_approval (or your own confirm) before the first write tool of a session or task.
- On enable, record tool name, server URL/serverInfo.version, and a hash of the tool definition (name + description + inputSchema + annotations). Re-check on notifications/tools/list_changed.
- Treat annotations as routing hints for trusted servers only. readOnlyHint defaults to false and destructiveHint to true; unannotated tools are side-effecting until proven otherwise by your allowlist and credentials.
- After the run, seal which tools were selected and which versions with record_ai_run (tool names + definition hashes in params). Use check_tool_integrity or precall_check before unfamiliar write servers.
How do I split the catalog before the model sees it?
MCP client best practices warn that naive hosts pass every connected server's tool definitions into the model at the start of each conversation; with dozens of servers that can consume most of the context window before the user message is read.
- Progressive discovery: the host still calls tools/list, but defers injecting definitions. A lightweight search_tools (or list_toolsets) meta-tool returns names and one-line descriptions; get_tool_details or enable_toolset loads the full schema only for the chosen tool or source.
- Put the risk split in your catalog metadata, not only in the model's judgment: mark each entry analysis vs side_effect from your allowlist. A tool that mostly reads but also writes a cache, bumps a counter, or marks notifications seen is not analysis-only.
- GitHub's MCP server is a concrete pattern at the server layer: read-only mode and toolsets let you load repos/issues without write tools. Your host should mirror that idea across every server, including ones that lack a built-in /readonly mode.
How do I lazy-load by task and record what was selected?
Switch to progressive discovery when tool definitions take a meaningful share of the context window (MCP's client guidance suggests thresholds around 1%–5% as a starting point). For a handful of tools, loading all is fine.
- Preserve prompt caching: appending newly discovered definitions after the cache breakpoint, or routing every call through one stable call_tool({name, args}) meta-tool, avoids reshuffling the tools array mid-conversation.
- Refresh the search index when a server sends notifications/tools/list_changed. A write tool that appears after you reviewed a read-only set is a rug-pull class event — surface it before enable_toolset succeeds.
- Record the selection: tool name, serverInfo.version (or package pin), and SHA-256 of the canonical tool definition you actually injected. That is the schema/version the agent reasoned over; chat transcripts alone are not enough for audit.
Are annotations and hints the security boundary?
Tool annotations (readOnlyHint, destructiveHint, idempotentHint, openWorldHint) shipped in the 2025-03-26 MCP revision. The schema says they are hints and are not guaranteed to describe behavior faithfully; clients must treat them as untrusted unless they come from trusted servers.
- Use annotations from servers you pin to decide confirmation UX (for example auto-approve a trusted readOnlyHint: true fetch). Enforce safety with least-privilege credentials, a client allowlist, and human approval for side effects — the same posture as Are MCP tool annotations like readOnlyHint a security guarantee?.
- Lazy-loading fewer schemas is not authorization. A read-only analysis pass that later enables a write toolset still needs the write credential and approval path; withholding the schema only reduces accidental selection.
What can go wrong?
Every server's full tools/list is pasted into the system prompt; the model picks a send_email tool while "just researching".
- You rely on readOnlyHint: true from an unpinned third-party server; the tool still writes.
- enable_toolset loads write tools for the whole session after one yes, with no per-task re-check or definition hash.
- You lazy-load by name but never record schema version; after list_changed the agent is using a different tool than the one you reviewed.
- Prompt-cache thrash from re-sorting the tools array every turn costs more tokens than progressive discovery saved.
How do I ask Rightshold about this (free, no key)?
Add the MCP server https://www.rightshold.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code, then ask: “Before I enable write tools for this task, check that this MCP server's tools/list fingerprint still matches what I recorded for the read-only analysis set, and flag any new or changed side-effecting tools.”
{
"tool": "check_tool_integrity",
"arguments": {
"url": "https://mcp.example.com",
"expected_tools_sha256": "<sha256 of the reviewed tools/list or selected tool definitions>"
}
}- Free on listed servers when used via precall_check; check_tool_integrity reports tools added, removed, or changed since the fingerprint you recorded.
- precall_check (free, no key) combines registry listing, integrity probe, and invisible-text scan before you trust a new server.
- request_human_approval before the first side-effecting call of a task; put tool name and arguments_sha256 in the action object.
- record_ai_run can seal which tool names and definition hashes were selected for the run without uploading the full transcripts.
Rightshold checks MCP listings and recorded tool fingerprints and can record human approvals and AI-run receipts for agents that call it. It does not sit in your host's context window, split your tool catalog, or stop a client that injects every tools/list into the model. Enforce progressive discovery and the read vs write split in your MCP host. Not legal advice.
What are the sources?
- MCP: Client best practices — progressive tool discovery (2025-11-25)
- MCP: Tool annotations as risk vocabulary (March 16, 2026)
- MCP specification (2025-11-25): Tools — annotations are untrusted unless from trusted servers
- GitHub MCP Server: remote server (toolsets, /readonly, X-MCP-Readonly)
- Are MCP tool annotations like readOnlyHint a security guarantee? (Rightshold)
Related
- Are MCP tool annotations like readOnlyHint a security guarantee?
- How to detect MCP tool description changes (rug pulls)
- How to cache MCP tool results safely
- How to give an MCP server least-privilege access to GitHub
Not legal advice. JSON · Markdown · All answers
Get a free API key in one call
No card and no email needed. 100 free credits; the license and policy checks stay free without a key.
curl -s -X POST https://www.rightshold.com/v1/accounts/signup -H "Content-Type: application/json" -d '{"accept_terms":true}'
export RIGHTSHOLD_API_KEY=rs_... # the api_key from the reply (shown once)
curl -s https://www.rightshold.com/v1/licenses/check -H "Authorization: Bearer $RIGHTSHOLD_API_KEY" \
-H "Content-Type: application/json" -d '{"license":"MIT","intended_use":{"commercial_use":true}}'
