Skip to content

MCP Client Support#

What it is#

The Model Context Protocol (MCP) is a standardized client-server protocol (JSON-RPC based, over stdio, SSE, or streaming HTTP) that lets a harness connect to external "MCP servers" and pull in additional tools and resources at runtime, without the harness's own developers writing bespoke integration code for each one. Concretely, "MCP client support" in a coding harness means: the harness process embeds an MCP client, connects to zero or more user-or-operator-configured MCP servers at session start (or on demand), enumerates each server's advertised tools (and optionally its resources — addressable, read-only data like files, DB schemas, or app state), and merges those tools into the same tool-calling surface the model already sees for the harness's own built-in tools.

This makes MCP a tool-sourcing mechanism, not an operation in its own right. It doesn't do anything a coding agent directly cares about (it doesn't read a file or run a command) — it's the plumbing that lets a third party's file-read, database-query, ticket-system, or browser-automation tool show up in the same function-calling namespace as a harness's native read/exec/edit tools. This distinguishes it sharply from every other capability discussed in this reference set, which are all operations a tool performs; MCP client support is a distribution channel for operations, arbitrarily many of which could duplicate, extend, or have nothing to do with a harness's existing operation set.

In a coding agent's workflow, MCP client support typically enters at two points: (1) session/tool-registry initialization, where the harness connects to configured servers and registers their tools (sometimes eagerly, increasingly via on-demand/lazy schema loading — see below); and (2) the model's own tool-selection turn, where an MCP-sourced tool is indistinguishable from a built-in one except by its name and description. A second, less universal facet is MCP resources — some harnesses additionally expose generic list_resources/read_resource-style built-in tools that let the model browse and fetch arbitrary MCP resources by URI, mirroring MCP's own resource primitive rather than requiring a bespoke tool per resource type.

Common implementation patterns#

Naming convention diversity. Once an MCP tool is merged into a harness's flat function-calling namespace, the harness must disambiguate <server>.<tool> collisions somehow, and at least half a dozen distinct conventions exist with no sign of convergence: double-underscore prefixing (mcp__server__tool), single-underscore variants, a colon-delimited form (mcp:server_id:tool_name), a slash-delimited form (server-name/tool-name), an @-prefixed form, or mcp:// URI routing rather than a flattened name at all. Some implementations don't rewrite the name at all, exposing the server's declared tool name largely as-is.

Dispatch vs. flatten patterns. Most implementations use the "flatten" pattern: each MCP tool is individually registered as its own named function-calling entry, indistinguishable in mechanism from a built-in tool. An older, now largely abandoned counterexample is a single generic dispatch-proxy tool (use_mcp_tool(server_name, tool_name, arguments)) plus a companion resource-access tool, handling all MCP calls through one dispatch point with the actual server/tool selection passed as arguments rather than encoded in the tool name.

Resources as a second bridged primitive. Beyond tools, a common-but-not-universal subset of implementations additionally expose list_resources/read_resource-shaped built-in tools that mirror MCP's separate resource concept (addressable, read-only data distinct from invocable tools).

Deferred/lazy schema loading. A maturing pattern implements on-demand MCP tool-schema loading rather than sending every connected server's full schema set on every model turn — only tool names consume context until a schema is searched and loaded, sometimes via a BM25 or similar search index over available tool descriptions. This is a direct response to the specific class of context-budget problem MCP creates: a session can suddenly acquire dozens-to-hundreds of extra tool schemas at connect time. An alternative, simpler approach hard-caps the total number of MCP tools registered across all servers rather than deferring schema delivery.

MCP as extensibility surface vs. first-party delivery mechanism. In most implementations MCP is a pure third-party extension point: the operator or user adds arbitrary servers via a config file or settings UI, and the harness has no opinion about what capability those servers provide. Occasionally a harness instead uses MCP as the delivery mechanism for its own default capabilities — bundling a first-party MCP server (for a hosted API, or a browser-automation server) rather than writing a native tool for the same purpose. In that case MCP is an implementation-reuse choice, not a user-facing extensibility story.

Transport and configuration diversity. Transports in common use include stdio (the universal fallback), SSE, and streaming HTTP, with OAuth support appearing in some implementations. Configuration surfaces vary from TOML or JSON files to dedicated settings UIs.

Permission, sandbox & safety#

MCP tool calls generally ride the same generic approval machinery a harness already applies to its own tools (per-call prompt, category toggle, auto-approve, etc.), but a recurring, distinct pattern is that harnesses single out "use MCP" as its own approval category even when they otherwise slice permissions by operation type (read/write/execute): a category-based auto-approval model commonly has an independent "use MCP" toggle alongside "read files"/"edit files"/"execute commands"; some implementations require individual approval per MCP tool call by default, overridable only via an explicit allowlist, even when the harness's own read-only built-ins need no approval at all; some gate all MCP tool calls unconditionally, regardless of the underlying tool's actual read/write nature; some require explicit user approval before a given MCP server's first execution, distinct from an otherwise-autonomous default.

The underlying reason this recurs: a harness can classify the risk of its own built-in tools (a bash tool it wrote, an edit_file tool it wrote) because it controls their semantics, but an MCP tool is an arbitrary external capability whose actual read/write/destructive behavior is opaque — the harness only has the server's self-reported name and description to go on, with no external guarantee those are accurate. This structurally motivates a blanket "ask by default" posture toward MCP even in harnesses that otherwise auto-approve broad categories of their own tools.

Sandboxing shows a documented gap: OS-level sandboxing mechanisms (macOS Seatbelt, Linux bubblewrap/Landlock, Docker) are uniformly scoped to a harness's own exec/bash tool, not to MCP server processes themselves. An MCP server, once configured, effectively runs as a separate process (local stdio or remote SSE/WebSocket) trusted by configuration, with no additional isolation layer distinct from the harness's general filesystem/network controls — it runs with whatever ambient trust the harness process itself has, independent of how tightly the harness sandboxes its own shell execution.

MCP is also a documented risk vector distinct from its individual tool calls: a prompt-injection vulnerability that lets an agent write configuration outside the project directory can, as a consequence, auto-load new MCP servers — i.e. the registration of an MCP server, not merely invoking one of its tools, is itself an exploitable mutation. Enterprise/operator-side controls addressing this include admin server allowlists (sometimes switching to deny-everything-else the moment even one entry is added), registry-based allowlisting, and scoping a default first-party MCP server to read-only or localhost-only access.

Design considerations#

Essentially every implementation merges MCP-sourced tools into the same function-calling channel used for built-ins — MCP is treated purely as a tool source feeding an existing pipeline, not a parallel execution mechanism. A meaningful subset also converges on bridging MCP's resource concept (not just tools) via generic list_resources/read_resource built-ins, inherited directly from MCP's own protocol shape rather than invented independently. And there's near-universal agreement that MCP tool calls deserve tighter or separately-configurable approval than a harness's own built-ins get — a genuinely convergent safety instinct even without a shared mechanism for expressing it.

Where implementations split: the naming-convention question remains completely unresolved across many distinct schemes with no dominant winner. There's also a live divergence in intent — MCP as third-party extensibility (the large majority) vs. MCP as internal implementation-reuse for a harness's own default capabilities — which are functionally the same wire protocol serving structurally different purposes. Deferred/lazy schema loading is one clear directional trend, appearing in the most MCP-forward, context-budget-conscious implementations; the dispatch-vs-flatten split reads as a closed question the field has already resolved in favor of flattening.

Implications for PluggableHarness Agent#

MCP client support is not a tool-provider operation and does not map onto any kind/risk row in the tool reference catalog — it's a mechanism by which other harnesses source additional tools into their own flat, in-process function-calling namespace. PluggableHarness Agent's architecture already differs in the relevant respect: a tool provider in PluggableHarness Agent is itself a first-class, versioned, out-of-process plugin communicating over gRPC, not an in-process registry entry the harness merges ad hoc from multiple sources. This means the specific problem MCP solves elsewhere — "how does a single in-process tool registry absorb tools from an external, independently-versioned server without bespoke code per server" — is a problem PluggableHarness Agent's plugin architecture has already solved structurally, for every tool provider, MCP-sourced or not.

Given that, an "MCP bridge" is best understood as one specific driver/implementation of the tool-provider protocol, not a new protocol concept requiring its own spec section: a tool-provider plugin whose GetSchema proxies to a connected MCP server's tools/list (translating MCP's tool schemas into PluggableHarness Agent's ToolSchema/input_schema/output_schema shape, per tool/protocol.md#getschema and the common JSON-Schema subset) and whose Invoke proxies to tools/call. This is directly analogous to how the rest of PluggableHarness Agent's internal swappable backends are structured as one driver among several implementing a shared interface — an MCP-server-backed tool provider is a driver of the tool-provider interface, exactly like a hypothetical ripgrep-backed grep provider or a sqlite-backed memory driver, not a parallel plugin category. Concretely this argues for a reference mcp-bridge tool-provider plugin (one instance per configured MCP server, or one plugin handling several configured servers) living alongside the other reference providers, rather than a bespoke kernel-level MCP subsystem.

Two points bear directly on how that bridge plugin would need to behave, worth flagging as open considerations rather than settled design:

  • Risk/kind classification is the hard part, and it's genuinely unresolved. Every operation must declare a kind (resource/data_source/interactive) and risk at GetSchema time — but an MCP server's own tool descriptions carry no such taxonomy, and even harnesses with rich native approval systems fall back to a blanket "ask regardless of what the tool actually does" posture for MCP specifically, precisely because the underlying behavior is opaque. An mcp-bridge driver has no principled way to auto-derive kind/risk per proxied tool from MCP's protocol alone; the safest default mirrors what implementations converge on in practice — classify every bridged operation conservatively as resource/high unless an operator has supplied an explicit per-tool override in agent.hcl. This is worth an explicit note in a future revision of the reference catalog discussing mcp-bridge as a driver, rather than leaving it implicit.
  • PluggableHarness Agent's own architecture sidesteps the naming-convention question, and that's worth stating as a validating design point, not a gap. The prefixing schemes described above exist specifically because those harnesses merge built-ins and several MCP servers' tools into one flat namespace and must disambiguate collisions. A ToolSchema.name is scoped to be unique within a provider's namespace, and each MCP server would naturally be bridged as its own distinct provider (or a distinct instance of an mcp-bridge driver) — so PluggableHarness Agent never needs a global prefixing convention at all; provider identity already disambiguates what the prefix schemes above are working around.

Separately, deferred/lazy tool-schema loading does not belong to the tool-provider protocol — GetSchema is required to be cheaply re-queryable and available without a network call, which is a provider-side guarantee, not a statement about whether the kernel includes every available provider's schemas in every model request. Whether the kernel should defer some tool schemas out of a given turn's prompt to manage context budget looks structurally closer to a prompt-assembly/budget concern than a tool-provider protocol concern — a concrete data point for whether a JIT-loading philosophy for context-provider content should explicitly extend to tool schemas too.