Tools MCP and External Integration
MCP Client — Patterns
Mcp client › Patterns
Pin the surface, re-verify on notification
Problem. The set of offerings a server advertises can change at any time, including after a user has reviewed and approved it. A client that re-reads the surface on every turn gives a server a fresh opportunity to alter what the model is told on each one.
Approach. Enumerate once at connection time, hash the resulting declarations, and serve the model from that pinned copy. When the server signals a change, fetch the new surface, compare hashes, and apply the difference as an explicit event — logged, and where the change touches descriptions or argument schemas of an already-approved tool, re-confirmed with whoever approved it.
Trade-off. Legitimately dynamic servers, whose offerings genuinely depend on the authenticated user or on backend state, now need a re-confirmation path that fires often enough to become routine and therefore ignored. Hashing descriptions also makes harmless documentation edits look like security events.
Namespaced merge with an alias table
Problem. Several connected servers are collapsed into one list for the model. Names collide, and the longer names that prevent collisions are exactly the names the model finds hardest to choose between.
Approach. Keep two representations. Internally, every offering is keyed by connection identifier plus its original name, which is unambiguous and stable. Externally, the model sees a short alias chosen by the host, with collisions resolved by a deterministic rule that is recorded rather than implicit. Resolution goes through the alias table on every call, so a call can always be attributed back to the connection that will serve it.
Trade-off. The table is state that must survive reconnection and must be regenerated when a server's offerings change, and an alias that moves between servers across restarts produces confusing traces. Shortening names also discards information the server author put there.
Lazy connection behind a startup budget
Problem. Connecting to everything at launch means the host is as slow as the slowest configured server and as available as the least available one. Connecting to nothing until first use means the model cannot be told what exists.
Approach. Split enumeration from connection. Keep a locally cached copy of each server's last known surface so the offered list can be assembled immediately, and open the actual connection on first invocation, inside a deadline. If the connection or its initialisation exceeds the deadline, fail that one call with an explicit unavailable status rather than blocking the turn, and mark the server degraded for a cool-off period.
Trade-off. The model can be offered something that turns out not to be connectable, which converts a startup failure into a mid-conversation one. The cached surface also has to be invalidated on protocol-version or capability changes, which are precisely the changes a cached copy cannot detect without connecting.
Treat everything the server sends as content, never as direction
Problem. Tool descriptions, resource contents, prompt templates, and error strings all arrive from a party outside your trust boundary and all end up in the model's context, where imperative text is indistinguishable from the host's own instructions.
Approach. Wrap every server-supplied span in an explicit, structurally distinguishable envelope that marks its origin and its untrusted status, keep host instructions in a channel the server cannot write to, and strip or escape control sequences that attempt to close that envelope. Apply the same handling to descriptions as to results; the description is the more attractive target because it is read on every turn.
Trade-off. Envelopes consume tokens on every turn and no envelope is proof against a sufficiently determined instruction. The real benefit is that it makes the trust boundary explicit in the transcript, so an incident can be reconstructed and the model can be trained or prompted to respect the distinction.