Tools MCP and External Integration
MCP Server — Patterns
Mcp server › Patterns
Expose the task, not the endpoint
Problem. The obvious surface is the one you already have: each backing operation becomes one tool. The caller must then know the sequence, carry identifiers between steps, and handle a failure halfway through a chain it assembled itself.
Approach. Start from the intentions callers actually have and define one tool per intention, implementing the sequence inside the server where it can be transactional, tested, and instrumented. Keep the fine-grained operations available only where a genuine need for them exists, and describe them as advanced rather than as the default route.
Trade-off. Task-shaped tools encode product decisions in the server and go stale when the underlying workflow changes. They also multiply: every caller wants a slightly different composite, and resisting that is a continuous editorial job.
Errors the model can act on
Problem. A caller that cannot distinguish "this identifier does not exist" from "the backing database is unreachable" will respond to both the same way, usually by retrying — the wrong reaction to the first and an amplifier of the second.
Approach. Return expected business outcomes as successful protocol responses carrying an explicit error result with a stable machine-readable code, a short human-readable reason, and where applicable the correction to attempt. Reserve protocol-level errors for malformed or unprocessable requests. Never return a failure as a success with an apology in the text, which is the shape that makes a failed call indistinguishable from a completed one.
Trade-off. Two error channels means two code paths and a judgement call for each failure, and the boundary is genuinely blurry for authorisation denials. Stable error codes are also a public contract: once a caller branches on one, it cannot be renamed.
Read through resources, act through tools
Problem. When everything is a tool, every piece of reference material costs a model turn to fetch, and the declaration list fills with retrieval verbs that crowd out the actions.
Approach. Publish material the caller reads — documents, configuration, schemas, record contents — as addressable resources that a client can fetch, list, and subscribe to without consuming a model decision. Reserve tools for operations with an effect or with arguments that genuinely require the model's judgement. The declaration list then contains only decisions.
Trade-off. Not every client supports resources well, so a server that relies on them may be less useful through some hosts. The split also forces an awkward call for read operations with expensive or parameterised queries, which fit neither category cleanly.
Bounded results with a continuation handle
Problem. A listing operation that is fine against a small test account returns tens of thousands of rows against a real one, and the client has no way to refuse the payload once it has been sent.
Approach. Enforce a server-side ceiling on every result, chosen by the server author who knows the data. Return the first page, a total count where it is cheap to obtain, and an opaque continuation token. State the truncation explicitly in the result rather than leaving the caller to infer it from a suspiciously round number of rows.
Trade-off. Continuation tokens are state with a lifetime, and a caller that abandons a traversal leaves it behind. Pagination also tempts a model into fetching every page, which is worse than the single large response you were trying to avoid — so the ceiling needs a companion limit on how many pages one turn may take.