# How do I separate read-only analysis MCP tools from side-effecting tools and lazy-load by task?

**Short answer:** 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.

## Checklist
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.
2. Keep a host-side full tools/list (or per-server cache). Do not inject every schema into the model prompt by default.
3. 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.
4. 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.
5. 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.
6. 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.
7. 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.
8. 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.

## Ask Rightshold (free, no key)
Add the MCP server https://www.rightshold.com/mcp to Claude, Cursor, ChatGPT, Grok or VS Code (one-click setup: https://www.rightshold.com/start?ref=seo), 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."

```json
{
  "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._

## Sources
- [MCP: Client best practices — progressive tool discovery (2025-11-25)](https://modelcontextprotocol.io/docs/2025-11-25/develop/clients/client-best-practices)
- [MCP: Tool annotations as risk vocabulary (March 16, 2026)](https://blog.modelcontextprotocol.io/posts/2026-03-16-tool-annotations/)
- [MCP specification (2025-11-25): Tools — annotations are untrusted unless from trusted servers](https://modelcontextprotocol.io/specification/2025-11-25/server/tools)
- [GitHub MCP Server: remote server (toolsets, /readonly, X-MCP-Readonly)](https://github.com/github/github-mcp-server/blob/main/docs/remote-server.md)
- [Are MCP tool annotations like readOnlyHint a security guarantee? (Rightshold)](https://www.rightshold.com/answers/are-mcp-tool-annotations-like-readonlyhint-a-security-guarantee)

## Related
- [Are MCP tool annotations like readOnlyHint a security guarantee?](https://www.rightshold.com/answers/are-mcp-tool-annotations-like-readonlyhint-a-security-guarantee)
- [How to detect MCP tool description changes (rug pulls)](https://www.rightshold.com/answers/how-to-detect-mcp-tool-description-changes-rug-pull)
- [How to cache MCP tool results safely](https://www.rightshold.com/answers/how-to-cache-mcp-tool-results-safely)
- [How to give an MCP server least-privilege access to GitHub](https://www.rightshold.com/answers/how-to-give-an-mcp-server-least-privilege-access-to-github)

_Not legal advice. Updated 2026-11-06. JSON: https://www.rightshold.com/answers/how-to-separate-read-only-mcp-tools-from-side-effecting-and-lazy-load-by-task.json_