If you let agents call tools freely, you will accidentally create an ungoverned “shadow API layer” with production credentials and no audit trail. MCP helps standardise tool access, but it does not magically solve authentication, authorisation, failure semantics, or versioning — you still have to design those boundaries.
Why MCP is showing up in real stacks now
MCP (Model Context Protocol) is an open protocol for connecting LLM applications to external tools and data sources, using JSON-RPC 2.0 as the message format. (modelcontextprotocol.io)
The ecosystem is moving beyond toy demos because MCP is being treated as a first-class tool interface in agent frameworks and SDKs. OpenAI’s Agents SDK has explicitly called out tool use via MCP as one of the standardised primitives it supports. (openai.com)
At the same time, the “easy” MCP path (install a server, point an agent at it, ship) is exactly how you end up with:
- Production data access mediated by prompts.
- Tool behaviour that changes under you with no compatibility contract.
- Debugging that stops at “the model decided to do it”.
So the interesting work now is integration and governance, not the protocol handshake.
MCP basics that matter for architecture (not the marketing)
A few protocol realities shape everything else:
- MCP uses JSON-RPC 2.0 messages, which are transport-independent and do not define auth. (en.wikipedia.org)
- MCP transports include stdio and Streamable HTTP (with SSE streaming or single JSON responses depending on how you reply). Streamable HTTP replaced the earlier HTTP+SSE approach in the 2025-era specs. (modelcontextprotocol.io)
- MCP has explicit concepts like tools/resources/prompts and also supports server-to-client notifications (for example, “tools changed”) and “sampling” requests where a server asks the client to call an LLM. (github.com)
None of that tells you whether a tool is safe to run, who can run it, or how to prove what happened afterwards. That’s on you.
Pattern 1: Tool Gateway (thin MCP facade over an existing API)
This is the default pattern most teams should start with: treat MCP as a controlled front door, not a new business-logic tier.
What it is
- An MCP server exposes a curated set of tools.
- Each tool maps to an existing internal API or service.
- The MCP server is boring: input validation, auth context, request shaping, retries, redaction, logging.
Why it works
- You keep your existing API governance (service ownership, access reviews, rate limits).
- You don’t turn prompt text into de facto business logic.
Failure mode to watch
- The gateway quietly becomes an orchestration engine because “it was quicker to put it in the MCP server”.
When to say “do not do this”
- Do not implement complex multi-step workflows inside the MCP server if you cannot test them deterministically without an LLM.
Pattern 2: Token Broker with per-tool token isolation (no shared god token)
Most MCP incidents start as “we gave the agent a token that can do too much, for too long”. This is explicitly called out in AWS guidance: implement token isolation so different tools retrieve different access tokens. (docs.aws.amazon.com)
What it is
- The client authenticates the human (or service) once.
- A broker exchanges that identity for short-lived, tool-scoped tokens.
- Each MCP tool call is authorised with the minimum privileges for that tool.
The MCP authorisation spec also pushes you toward standard OAuth-style resource request handling (it references OAuth 2.1 resource request requirements). (modelcontextprotocol.io)
Practical implementation decisions
- One token per tool (preferred): tight blast radius, more moving parts.
- One token per MCP server (acceptable for small estates): simpler, easier to misuse.
Failure mode to watch
- A “helpful” server caches a broad token and reuses it across tools, collapsing your isolation.
Pattern 3: Policy Enforcement at the boundary (tool allow/deny is not enough)
Authorization isn’t only “who can call the tool”. It is also “what parameters are allowed” and “what data can come back”. OWASP’s MCP Top 10 explicitly flags insufficient authentication/authorisation as a core risk area for MCP systems. (owasp.org)
What it is
Add a policy layer that evaluates:
- Actor: user/service identity, device posture (if you have it), environment.
- Action: the specific tool and operation (read vs write vs admin).
- Resource: which tenant/project/repo/customer record.
- Context: time, region, sensitivity labels.
A useful rule of thumb
- If you cannot describe the policy in a sentence that a security reviewer can understand, it probably belongs in your existing API layer, not inside MCP.
Pattern 4: Explicit failure semantics (design for “tool didn’t happen”)
Agents are optimistic: they will assume a tool call succeeded unless you make failure unambiguous and observable.
What to standardise
- Timeouts: per tool, with budgets (don’t let a tool call hang the agent’s entire run).
- Retries: idempotent reads can retry; writes usually should not.
- Partial results: decide whether to return them or fail closed.
- “Soft” failures: rate limiting, upstream degradation, permission denied.
Failure mode to watch
- A tool returns a friendly error string as “successful” output. The agent then treats it as real data and continues.
What to do instead
- Use structured JSON-RPC errors consistently.
- Categorise errors so the orchestrator can choose: retry, ask user, degrade gracefully, or stop.
Pattern 5: Observability as a first-class contract (the agent is not your debugger)
If you cannot answer “who called what tool, with which inputs, using whose authority, and what changed”, you do not have a production integration.
Minimum viable instrumentation
- Correlation IDs: thread through agent run → MCP request → downstream API.
- Audit log: tool name, identity, scopes, resource identifiers, decision (allow/deny), latency, result hash.
- Redaction: never log full prompts or sensitive payloads; log structural metadata.
This matters more with MCP because the protocol intentionally limits server visibility into prompts, so you can’t rely on “just look at the prompt” post-incident. (modelcontextprotocol.io)
Treat every tool call as if it will be reviewed after an incident, because sooner or later it will.
Pattern 6: Versioning and compatibility gates (stop silent tool drift)
The operational problem is not that MCP changes; it’s that your tool behaviour changes without an explicit compatibility boundary.
What to version
- Tool contract: name, parameters, schema, error codes.
- Behavioural contract: side effects, idempotency, rate limits, pagination.
- Policy contract: required scopes and which identities are allowed.
Tactics that work in practice
- Freeze tool schemas in a repo and require review for changes.
- Add a compatibility test suite that runs tools against mocked downstream services.
- Require explicit client opt-in for breaking changes (new tool name or explicit version suffix).
MCP itself has evolving specifications and SDKs; Streamable HTTP and other 2025-era changes are a good reminder that your integration needs an upgrade path. (modelcontextprotocol.io)
Pattern 7: Aggregator vs “one server per domain” (choose your blast radius)
You’ll see two common deployment shapes:
- Domain servers: separate MCP servers per system (GitHub, billing, data warehouse).
- Aggregator: one MCP endpoint that fans out to multiple backends.
Reference servers and community servers exist in public repos, which makes it easy to bootstrap — but also easy to pull in an estate you don’t actually govern. (github.com)
Trade-offs
- Domain servers:
- Better ownership and least privilege.
- More operational overhead.
- Aggregator:
- Easier client configuration.
- Higher blast radius, harder auth separation unless you invest in it.
A pragmatic default
- Start with domain servers for anything that can mutate data.
- Use an aggregator only for read-mostly, low-risk tools, or when you can enforce per-tool token isolation and clear ownership.
Pattern 8: Hosting and transport choices (stdio is not “dev only”)
Transport is not just “how bytes move”; it changes your security posture.
- stdio: great for local, tightly-coupled deployments where the client launches the server as a child process. Lower network surface area; still needs strict permissioning on what that process can reach.
- Streamable HTTP: designed for remote servers and multi-client connections; every JSON-RPC message is an HTTP POST and the server may respond with SSE or JSON. (modelcontextprotocol.io)
Failure mode to watch
- Turning on remote HTTP early because “we might need it later”, then discovering you accidentally built a new public API tier with weaker controls than your real APIs.
A checklist you can apply before you ship the first MCP tool
- Identify the tool’s blast radius: what data can it read/write, and what’s the worst plausible misuse.
- Decide the auth model: user-delegated vs service identity, and how tokens are scoped.
- Implement per-tool token isolation for anything non-trivial. (docs.aws.amazon.com)
- Define failure semantics: timeouts, retries, idempotency, and explicit error typing.
- Add audit-grade logging and correlation IDs end-to-end.
- Freeze a versioned tool contract and add compatibility tests.
- Assign an owner per tool (not “the AI team”).
Where Codeversols tends to fit (briefly)
When teams come to us for MCP work, it’s usually not because they can’t stand up an MCP server. It’s because they want to integrate tool use into an existing product safely: OAuth/OIDC alignment, tenancy boundaries, policy enforcement, and observability that stands up in incident review. If you already have internal APIs, the best outcome is often a thin MCP gateway with strong governance — not a parallel system.
Closing
MCP is useful precisely because it standardises the mechanics of tool use — but that standardisation can also hide the moment you accidentally created a shadow API layer. The teams getting value in production are the ones treating MCP servers like any other critical integration surface: least privilege, explicit failure behaviour, auditability, and deliberate versioning. If you do that, tool-using agents become easier to ship and safer to operate, rather than a new source of late-night incidents.



