Two tool sets, one context window

When a Claude Code session reaches for a tool, it is calling one of two categorically different things, and the difference is not really about what the tool does — it is about where the code lives and who is responsible for it. Read, Write, Edit, Bash, Grep and Glob ship inside the Claude Code binary itself. They require no handshake, no external process and no separate trust decision: they are present in every session, read-only by default until a specific action needs write or execute access, gated by Claude Code’s own permission system rather than by anything the tool itself declares [7].

The second category exists only because a session has been told, explicitly, to go get it. These are MCP tools, named with a fixed pattern — mcp__ followed by the server’s name, followed by the tool’s own name inside that server, for example mcp__sentry__get_issue_details or mcp__github__create_pull_request. They are supplied at runtime by a separate process called an MCP server, which the user or the project has registered with the command claude mcp add, and which Claude Code then starts, or connects to, before any of its tools become callable [4].

This is the practical shape of the Model Context Protocol inside Claude Code, an open standard Anthropic published in November 2024 specifically to replace one-off integrations between AI applications and the tools or data sources they need to reach with a single, reusable connection [1]. It is worth stating plainly, because MCP itself, as a specification, doesn’t care which application is implementing it. MCP defines a JSON-RPC message format and a small set of primitives a server can expose to a client — tools the model can execute, resources the model or user can read, and prompt templates the user can invoke — plus a smaller set a client can expose back to a server, including the ability to let the server ask the model to generate something on its behalf [2]. Claude Code is one specific, opinionated implementation of the client half of that exchange, and the decisions it has made about scope, approval and context management are where the protocol actually becomes a product rather than a diagram.

ADVERTISEMENT

One of those decisions is about memory rather than trust. Every MCP server a session connects to adds its tool names and server instructions to the conversation, and for a long time that meant a session with a dozen well-stocked servers connected could spend a meaningful fraction of its context window just listing what it could call before doing any work. Claude Code’s current default defers those definitions: tool search keeps only names and instructions in context at session start, and loads a tool’s full schema only once the model has actually decided it needs that tool, so that adding another server has, in Anthropic’s own description, “minimal impact” on the context budget rather than a fixed per-server cost [5]. From inside a conversation this is invisible — a tool call behaves exactly as it would if it had been loaded upfront — but it is the reason a session can plausibly stay connected to dozens of servers without every one of them competing for the same fixed space that Read and Bash already occupy for free.

A rack bay with a sealed hardwired backplane module on one side and a row of patch jacks on the other, one connector caught just seating into an empty jack while its neighbours sit empty or landed
Figure 1. The backplane module answers every session without being asked; the jacks beside it only carry current when something has been plugged in for that particular conversation.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

How a session actually reaches a server

Adding a server is a configuration act, not a conversation. The base case is a single command: claude mcp add --transport http <name> <url> for anything reachable over HTTP, which is the recommended transport for hosted services — Sentry, Notion, Linear and dozens of others in Anthropic’s directory of reviewed connectors all work this way, most authenticating through an OAuth browser flow the first time a tool from that server is actually called rather than at add time [4]. A server that instead needs to run on the user’s own machine, because it drives a local browser, reads a local filesystem, or wraps a script that only makes sense on that host, is registered as a stdio server instead: Claude Code starts it as a subprocess and talks to it over standard input and output rather than a network socket, an arrangement a persistent WebSocket transport supplements for servers that need to push events at Claude unprompted rather than only answer requests [5].

Where that configuration is written matters as much as what it says. Claude Code recognizes three installation scopes. A local-scoped server, the default, is written into the user’s own ~/.claude.json under the specific project directory it was added from, and is invisible anywhere else. A user-scoped server goes into the same file but at the top level, so it loads in every project that same person opens. A project-scoped server is written instead to .mcp.json at the repository root, meant to be committed, so that everyone who clones the project inherits the same connected tools — a database client pointed at a shared staging environment, say, or an internal ticketing server every engineer on the team needs [5]. That last case is deliberately the one Claude Code treats with the most suspicion: a server defined in a file that arrived by git clone is not run automatically. The first time Claude Code sees a project-scoped server in an interactive session, it stops and asks for approval before connecting, precisely because a cloned repository being able to launch an arbitrary process on a new machine, sight unseen, is exactly the threat model a supply-chain attack would exploit [4].

None of this establishes what the connection is for. A database server pointed at a production instance and a GitHub server pointed at a repository’s issue tracker both register the same way, with the same claude mcp add syntax, and the difference between a safe connection and a dangerous one lives entirely in what credentials that server was handed and what it is capable of doing with them. Anthropic’s own worked example for the database case makes this explicit by recommending a read-only database user in the connection string, so that whatever a session asks for, the query that actually reaches the database cannot modify anything, regardless of what the model intended [5]. Registration answers “can this session reach that process.” It says nothing yet about what that process is allowed to do once reached, which Claude Code answers separately, at the moment of the first tool call rather than at the moment of connection.

A short copper jumper cord curling within one rack bay beside a fibre trunk rising out of the same bay toward an overhead riser, the fibre's connector caught mid-dress into the vertical manager
Figure 2. A local server is a short jumper that never leaves the rack; a remote server is a run that has to climb out through the riser and cross the room before it answers.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

The prompt that fires exactly once

Connecting a server and using one of its tools are different events, separated by an approval Claude Code inserts between them. “The first time Claude calls the server, it asks for permission to use the new tool. Approve it to continue” is how Anthropic’s own quickstart describes the default behaviour, and it is the same mechanism that governs a Bash command that isn’t on the built-in read-only allowlist: a prompt, answerable once, that a user can also pre-approve permanently through a permission rule [4].

ADVERTISEMENT

That rule is where the mcp__server__tool naming stops being cosmetic. Because every MCP tool’s callable name carries its server, a permission rule can allow mcp__github__list_issues without extending that trust to a hypothetical mcp__github__delete_repository, even though both would live behind the same connection and the same credential. This is finer-grained than approving or rejecting a server wholesale, and it is the mechanism an organization actually reaches for when it wants engineers to read from a system without being able to write to it. Claude Code’s broader permission modes sit on top of this per-tool granularity rather than replacing it: a stricter mode auto-denies anything not already on an explicit allowlist and prompts for nothing further, a looser one skips confirmation for everything, and the ordinary interactive default asks once per new tool and remembers the answer for the rest of the session.

Two structural features narrow how much this single approval can actually be trusted to mean, and both cut against the intuition that approving something once implies knowing what it will keep doing. First, the MCP specification’s own security guidance flags tool descriptions and annotations as untrusted input unless they come from a server the client already trusts by some other means — the text a user reads when approving a tool is supplied by the server itself, not independently verified by the protocol [2]. Second, Claude Code bounds what a tool call can cost rather than what it can do: MCP tool output is capped at 25,000 tokens by default with a warning above 10,000, and a call that runs past two minutes moves to a background task so it can’t silently stall the session, but neither limit says anything about whether the call itself was the right one to make [5]. The approval prompt is a consent gate, not a behavioural audit, and the rest of Claude Code’s MCP design is built around that limitation rather than pretending it away — which is exactly the ground the next two sections cover: what a credential is actually scoped to do once approved, and what happens when a server’s behaviour turns out not to match what its description promised at approval time.

A mechanical interlock lever mounted beside a patch frame caught mid-throw, its gate just clearing the slot while a waiting cord's connector hovers short of the now-open port
Figure 3. The first time a session reaches for a new tool, the frame will not pass current until the interlock is thrown; after that one release, the same port stays open for the rest of the session.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

What capability-scoped access actually means

“Capability-scoped access” sounds like a property a server either has or lacks. In Claude Code’s implementation it is closer to a stack of independent narrowing mechanisms, applied at different layers by different parties, any one of which can be tighter than the others — and the actual scope a connected server operates under is the intersection of all of them, not any single setting.

The first layer sits inside the authorization protocol itself. MCP’s authorization specification is built on OAuth 2.1, and it requires an MCP server to publish, and a client to consult, standard OAuth metadata describing which authorization server issues its tokens and what scopes that server supports, following RFC 9728 on the resource-server side and RFC 8414 on the authorization-server side [3]. Claude Code exposes a knob directly on top of that machinery: an oauth.scopes field in a server’s configuration that pins the exact scopes Claude Code will request during sign-in, described in its own documentation as “the supported way to restrict an MCP server to a security-team-approved subset when the upstream authorization server advertises more scopes than you want to grant” — so a Slack connector that could technically request access to every channel and every admin action can instead be pinned to channels:read, chat:write and search:read alone, and the token that comes back is only ever valid for that narrower set [5]. The same specification separately requires that a token issued for one MCP server can’t be replayed against another: servers must validate that a presented token names them specifically in its audience claim, and must never forward a client’s token on to a third API unmodified, a failure mode the spec calls the confused deputy problem and treats as a required defence rather than a suggestion [3].

The second layer is a property of the server’s own code, not the protocol. GitHub’s MCP server, developed jointly with Anthropic and now maintained by GitHub as an official integration since its April 2025 public preview [9], exposes an explicit --read-only startup flag under which every tool that would create, update or delete anything on GitHub is refused before it can even be offered to the model — write tools are skipped whenever that flag is set, even if a caller explicitly asks for one by name — alongside a toolset system that lets an operator expose only the categories of tool a given deployment actually needs, such as issue triage without repository administration [10]. Anthropic’s own listing criteria for its connector directory push submitted servers toward the same shape from the outside: a connector is rejected if a single tool mixes safe and unsafe HTTP methods behind one name, and every accepted tool must carry a readOnlyHint or destructiveHint annotation that Claude Code then uses to decide, automatically, whether that specific tool can run without a confirmation prompt at all [8].

The third layer belongs to whoever administers the fleet of machines Claude Code runs on, and it is the only one of the three that can be made non-optional for an individual engineer. An organization can deploy a managed-mcp.json file that gives Claude Code an exclusive, fixed set of servers, so that nothing else loads and claude mcp add for anything not on that list fails outright with an explicit enterprise-policy error, or it can apply an allowedMcpServers list matched by exact URL or exact command, which the documentation is explicit is the only version of the control that actually enforces anything: a name-based rule is not treated as a security boundary at all, because the name is a label the user who added the server chose, and nothing stops two different servers from choosing the same one [6].

ADVERTISEMENT
A multi-compartment equipment cabinet with individual badge-reader locks on each door, one door's latch caught just releasing while the doors either side remain fully seated and locked
Figure 4. A credential does not open the whole cabinet; it releases exactly the compartment it was scoped for, and the compartments on either side stay shut regardless of what is asked of them.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

Trusting a server you did not write

Every one of those scoping mechanisms narrows what an authorized tool call can reach. None of them answer a separate, harder question: whether the description a server offered at connection time is actually a truthful account of what a given call will do — and independent security researchers, not Anthropic, have spent the past two years establishing that the gap between the two is not a corner case.

The clearest documented version is what Invariant Labs, an AI security research group, named a tool poisoning attack in April 2025: hidden instructions embedded directly in a tool’s description, often wrapped in tags such as <IMPORTANT>, which the model reads as part of every prompt but which a client’s simplified user interface may never surface to the person who approved the connection [11]. Invariant’s own demonstration against a popular AI coding tool showed a poisoned addition tool whose description quietly instructed the model to first read the contents of a local MCP configuration file and the user’s SSH private key, pass both as parameters disguised as part of an arithmetic explanation, and route them to an attacker while the visible chat output remained a plausible-looking sum. The same write-up documents two harder variants: a rug pull, where a server changes its tool’s description after a user has already approved it, so the approval a person remembers giving no longer describes what they actually authorized; and cross-server shadowing, where a malicious server’s tool description reaches across the session to rewrite how the model treats a completely different, legitimately trusted server’s tool — in Invariant’s example, a bogus addition tool that silently redirected a trusted email tool’s recipient address, with the agent complying despite the user’s explicit instructions to the contrary [11]. OWASP’s community write-up of the same attack class frames the underlying failure the same way: tool descriptions get reviewed once, at connection time, but the responses a tool returns during actual use are folded into the model’s context with no equivalent check, so a server that behaved exactly as described during evaluation can still return a poisoned response on the hundredth call [12].

The scale of this is now measured rather than anecdotal. A 2025 benchmark called MCPTox tested twenty prominent LLM agents against 1,312 adversarial tool-poisoning cases built from 353 real tools on forty-five live, currently operating MCP servers, and found attack success rates exceeding sixty percent across many of the agents tested, with one model, o1-mini, compromised in 72.8 percent of cases — against a refusal rate, across the full test set, of under three percent [13]. The paper’s most uncomfortable finding is not the headline rate but its direction: models with stronger instruction-following, including reasoning-enabled ones, were more susceptible rather than less, because the same capability that makes an agent good at following a legitimate multi-step instruction makes it equally good at following an illegitimate one buried in a tool’s metadata [13]. This is independent academic research, not a claim about Claude Code specifically, and it should be read as evidence about the attack class MCP clients face in general rather than a measured result for any one product.

None of this is a surprise Anthropic’s own documentation elides. The connector directory’s listing criteria explicitly reject tool descriptions that try to direct Claude to pull instructions from external sources or that contain hidden or encoded content, and every submission is automatically scanned before listing, with a subset escalated to a slower, higher-touch review in which an Anthropic reviewer functionally tests each tool by hand [8]. But Claude Code’s own security documentation states the boundary of that review in one sentence, without qualification: Anthropic reviews connectors against its listing criteria before adding them to the directory, “but does not security-audit or manage any MCP server” [7]. A listing is evidence a server passed a policy scan. It is not evidence that its code has been read.

A small inline optical tap clamped onto a live patch cord mid-clip, its second output leg coiled loose and not yet connected to the monitoring frame beside the run
Figure 5. A listed server has been reviewed for how it behaves at the front door; whether it keeps doing what its description says on every call is a separate question, and this is the part of the room built to keep asking it.Image prompt and art direction by Brecht Corbeel; image generated to that direction.

What changes when the servers change

Everything above describes a single session’s-eye view of one server. A fleet of engineers connecting servers independently is a different, additive risk, and it is the reason the managed-mcp.json and allowlist machinery described earlier exists at the organizational rather than the individual layer. Left unconfigured, Claude Code’s default is permissive by design: anyone running Claude Code can connect any MCP server they choose, and the burden of deciding whether a given server is trustworthy sits with whoever typed the claude mcp add command [6]. An administrator who wants a different default has a genuine range of intermediate options rather than a binary choice between that and disabling MCP entirely: a published, approved catalog that users still choose from voluntarily; a denylist that blocks specific known-bad servers while leaving everything else open; or, at the strict end, an exclusive fixed set where the fleet’s servers are decided centrally and claude mcp add for anything outside it fails before ever attempting a connection [6].

What is notable about this layer is what it does not require: touching Claude Code itself. Built-in tools change only when the Claude Code binary is upgraded, which pins their behaviour to a version number an organization can adopt on its own schedule. MCP servers are not pinned to anything Claude Code controls — a hosted server’s operator can change its tools, its scopes or its behaviour at any time, for every client connected to it, without anyone touching a line of Claude Code configuration, which is precisely the surface a rug pull exploits. The closest thing to a check on that drift, short of re-reading a server’s code before every session, is visibility after the fact: with OpenTelemetry export configured and detailed tool logging enabled, Claude Code will record which MCP servers and tools were actually invoked, letting an administrator ask what a fleet is really calling rather than only what it was approved to call [6]. That is a monitoring answer to a trust problem, not a preventive one, and it is offered as exactly that: a way to notice drift after it has happened, not a way to stop it beforehand.

What this changes in practice

Put the pieces together and the practical difference between a built-in tool and an MCP tool in Claude Code is not really about capability — a well-written MCP server can do almost anything Bash could do anyway, and often more legibly, through a purpose-built interface such as the GitHub, Sentry or database examples above rather than a shell command assembled on the fly. The difference is about who is answerable for it. A built-in tool’s behaviour is Anthropic’s to fix, versioned with the product, and evaluated once for the whole user base. An MCP tool’s behaviour belongs to whoever operates that server, can change unannounced, and has been evaluated, at best, against a policy checklist rather than an audit of the code path any specific call takes.

That reframes what capability-scoped access is actually protecting against. It is not a claim that a connected server is safe. It is a claim that if a server turns out not to be, the blast radius of its tools is bounded by the narrowest of: the OAuth scopes it was granted, the toolset or read-only mode it was deployed under, the specific mcp__server__tool names an administrator’s allowlist actually permits, and the working-directory and network boundaries Claude Code’s own permission system enforces regardless of what any tool asks for. Each of those is a real, separately configurable constraint, and none of them, alone, is designed to survive a server behaving adversarially by design rather than by bug.

Two observations follow, stated as claims about the present state of the ecosystem rather than predictions about its future. First, the asymmetry between tool-poisoning research and defensive tooling is presently wide: MCPTox’s under-three-percent refusal rate describes 2025-era agents against a benchmark built from real, currently listed servers, not a hypothetical worst case, and closing that gap is an open technical problem rather than a solved one [13]. Second, the mechanisms that do exist, scoped OAuth tokens, read-only server modes, tool-level allowlists, organization-wide server catalogs, all require someone to deliberately configure them; examined individually, every one of Claude Code’s defaults resolves toward asking a human rather than refusing on the model’s own initiative. That is a reasonable design choice for a tool meant to be useful across an enormous range of legitimate integrations, from issue trackers to production databases to design tools, each with a different owner and a different acceptable risk. It also means the actual security posture of any given Claude Code deployment is a property of how carefully it was configured, not of the protocol or the client in the abstract — which is a fair place to end a piece about integration rather than protocol: the interesting risk was never in the wire format. It is in the gap between what a connected server was approved to say about itself and what it actually does on the calls nobody happens to be watching.