Interview prep
PatternTools and action

MCP Adapter

Adaptador MCP

MCP Adapter is an AI design pattern for LLM apps and agents, in the Tools and action group. Expose systems as MCP servers so any agent uses the same contract.

Element
35/70
Level
Intermediate
Read
8 min
Updated
On this page 18
  1. 01Intent
  2. 02Problem
  3. 03Solution
  4. 04Structure
  5. 05Pseudocode
  6. 06Real-world scenarios
  7. 07Applicability
  8. 08When not to use
  9. 09How to implement
  10. 10Variations
  11. 11Cost, latency, and failure modes
  12. 12How to evaluate
  13. 13In production
  14. 14Security
  15. 15Pros and cons
  16. 16Relations
  17. 17Code
  18. 18Check your understanding
  19. ··Quick answers

Intent

Expose internal systems as MCP (Model Context Protocol) servers, so any agent uses the same tool contract. It is for companies with several agents, clients, and models that need to reach the same systems.

In one sentence
A standard wall socket: any appliance with the right plug works, with no rewiring.

Problem

Vitor works on the platform team at a company with four agents in production: support, back-office, engineering, and an internal assistant. All four need to query the ERP, the CRM, and the ticket system.

Each team wrote its own tools. The ERP has four implementations of “find order”, with different names, parameters, and return formats. One returns the date as 2026-09-30, another as 30/09/2026, and a third returns the whole ERP object, with 80 fields.

When the ERP’s API changed its authentication, four teams had to fix their code the same week. When the company wanted to try a coding assistant that already speaks MCP, nobody could connect the ERP to it without writing the integration from scratch.

And nobody could answer a simple audit question: which agents can create returns, and on whose behalf?

The problem is not any single tool. It is the lack of a common contract between the systems and whoever uses them.

The question this pattern answers: how do you write each integration once and use it from any agent, model, or client?

Solution

For each system, build an MCP server that exposes tools (actions), resources (data to read), and, where useful, ready-made prompts. The server owns authentication, permissions, validation, and output format. Agents connect as MCP clients and receive the tool list with name, description, and schema.

MCP is an open protocol, released by Anthropic in November 2024 and later adopted by other providers and tools. The same server works with your own agents, with coding assistants, with IDEs, and with model APIs’ MCP connectors. The integration stops belonging to an agent and starts belonging to the system.

Count the integrations. With 4 agents and 3 systems, each team writing its own, you have up to 12 integrations to maintain. With one server per system, you have 3 servers, and each new agent only needs an MCP client, which the framework usually ships.

Worked example

MCP uses JSON-RPC messages. When the support agent connects to the ERP server, the client asks for the tool list (tools/list) and gets something like this (trimmed):

{
  "tools": [
    {
      "name": "find_order",
      "description": "Gets an order's status, items, and dates. Use it when the user mentions an order number.",
      "inputSchema": {
        "type": "object",
        "properties": { "order_id": { "type": "string", "pattern": "^[0-9]+$" } },
        "required": ["order_id"]
      }
    }
  ]
}

The client hands that list to the model as tool definitions. When the model decides to use find_order, the client sends a tools/call:

{ "method": "tools/call", "params": { "name": "find_order", "arguments": { "order_id": "88231" } } }

The server checks the user’s scope, calls the ERP, and returns only what matters:

{
  "content": [
    { "type": "text", "text": "{\"id\": \"88231\", \"status\": \"delivered\", \"delivered_at\": \"2026-09-30\", \"items\": [{\"sku\": \"CAD-112\", \"qty\": 2}]}" }
  ],
  "isError": false
}

Notice what the agent does not see: the ERP’s URL, the service token, the 80 raw fields. All four agents get the same answer, in the same format, with the same date.

Check +20 XP on first tryAt Vitor's company, the ERP is about to change its authentication again. With the MCP Adapter in place, where does the change have to happen?

Structure

flowchart LR
  A1(["1. Support agent"]):::llm --> C["2. MCP client"]:::tool
  A2(["1. Back-office agent"]):::llm --> C
  A3(["1. Coding assistant"]):::llm --> C
  C --> S1["3. ERP MCP server"]:::tool
  C --> S2["3. CRM MCP server"]:::tool
  S1 --> P{"4. Auth and permissions"}
  P --> ERP[("5. ERP")]:::store
  S2 --> CRM[("5. CRM")]:::store
  1. Several agents, from different teams and stacks.
  2. Each one speaks MCP through a standard client.
  3. One server per system exposes tools with a schema and description.
  4. The server applies the user’s or agent’s authentication and permissions, and validates inputs.
  5. Only the server talks to the internal system, and it returns normalized data.

Pseudocode

server erp_mcp:
    tool find_order(order_id: string) -> Order               # read
        require scope("orders:read")
        return normalize(erp.get_order(order_id))

    tool create_return(order_id: string, reason: string) -> ReturnLabel   # side effect
        require scope("returns:write")
        audit.log("create_return", caller, order_id)
        return erp.create_return(order_id, reason, idempotency_key=request_id())

    resource "erp://policies/returns" -> text                # data to read

agent support:
    tools = mcp.connect("erp_mcp", token=user_token).list_tools()      # 2
    run_agent(SUPPORT_PROMPT, tools)                                    # 1

Real-world scenarios

  • Coding agent. The task tracker, CI, and internal docs are MCP servers. The same agent runs in the developer’s terminal and in automation, with the same tools. What changes: switching coding assistants does not require rewriting any integration.
  • Back-office automation with durable execution. Bank, ERP, and invoicing become MCP servers with separate read and side-effect tools, and each process connects only to the ones it needs. What changes: the reconciliation process gets a token with read-only scope.
  • Internal knowledge base chat. The wiki and shared folders are exposed as MCP resources, with the systems’ own access control. What changes: people only see in the chat the documents they could already open in the wiki.

Applicability

  • More than one agent or client uses the same system.
  • You want to switch model, framework, or client without rewriting integrations.
  • Authentication, permissions, and auditing must be the same for every agent.
  • Third-party tools (IDEs, assistants) should reach your systems in a controlled way.

When not to use

  • A single agent with two simple tools. A local function does the job with fewer moving parts.
  • Minimum latency is the main requirement and every millisecond counts. A separate server adds a network hop.
  • The system has no clear owner to maintain the server. An abandoned server becomes a shared point of failure.

How to implement

  1. Pick a system used by several agents and list the operations they actually perform.
  2. Design tools by intent, with clear names, descriptions that say when to use them, and strict schemas.
  3. Separate reads from side effects, and require different scopes.
  4. Normalize output: only the useful fields, in a stable format.
  5. Implement authentication that identifies who is calling (the end user, not only the agent).
  6. Record side-effect calls in an audit log.
  7. Publish the server, document it, and migrate one agent at a time, running each one’s eval.
  8. Version the server and treat schema changes as API changes.

Common traps

  • Mirroring the internal API. Turning every ERP endpoint into a tool gives you 60 tools with technical names. The model needs operations by intent: find_order, not GET /v2/sales/orders/{id}/header.
  • One service token for everything. If the server always calls the ERP as “admin”, any connected agent can do anything. Propagate the end user’s identity and apply their scope.
  • Returning the raw object. Eighty fields per answer fill the context and confuse the model. See Normalize Tool Output.
  • Changing the schema without notice. Renaming order_id to orderId in a deploy breaks every agent at once. Add fields; to remove or rename, publish a new version.

Variations

  • Local server (stdio). Runs as a process next to the agent. Simple for developer tools.
  • Remote server (HTTP). A shared service, with authentication and scaling. The path to production. The MCP specification defines OAuth for authorizing clients on remote servers.
  • The API’s MCP connector. The model provider connects directly to the remote server, without your code in the middle of the loop.
  • MCP gateway. A single entry point that aggregates several servers, applies a common policy, and logs everything.
Variation Where it runs Good for Watch out for
Local (stdio) the agent’s machine developer tools credentials on each person’s machine
Remote (HTTP) shared service production, several agents availability and authentication
API connector the provider calls your server agents without their own loop the server must be reachable by the provider
Gateway in front of the servers central policy and auditing one more hop and one more point of failure

Cost, latency, and failure modes

Aspect What happens How it shows up in the trace
Calls Each tool becomes a request to the server mcp.call spans with server and tool
Latency One more network hop per remote call mcp.call span duration
Context The server’s tool list goes into the prompt tool tokens per connection
Failure: server down Every agent that depends on it stops mcp.call errors from several agents at once
Failure: schema changed Old agents send invalid arguments validation errors after a server deploy
Failure: too many tools A server exposes dozens of operations wrong tool choices in the agent

The context cost is easy to forget. Connecting three servers with 20 tools each puts 60 definitions in every agent call. Expose only what each agent needs, or combine this with the Tool Router.

Check +20 XP on first tryThe ERP team renames the order_id parameter to orderId on the MCP server and deploys on a Friday. On Monday, the support and back-office agents are failing. What is the main lesson?

How to evaluate

  • Each agent’s eval before and after migrating to the MCP server: quality must not drop.
  • Contract tests for the server: valid and invalid inputs, permissions, output format.
  • Tool choice: with the new list, does the agent pick the right tool?
{ "agent": "support", "task": "status of order 88231", "expected_tool": "find_order", "expected_scope": "orders:read" }

Contract tests need no model. Call the server directly with a test MCP client: an order_id with letters must be rejected, a user without returns:write cannot create a return, and find_order always returns the same fields. These tests run on every server deploy, before any agent feels the change.

In production

  • One span per MCP call with server, tool, user, duration, and error.
  • A per-server dashboard: calls per agent, error rate, latency.
  • Availability alerts per server, because one failure hits several agents.
  • Periodic review of exposed tools: the ones nobody uses go.

Security

The MCP server is the security boundary. It authenticates the caller, applies least privilege per tool, and never hands credentials to the model.

Third-party servers are third-party code: review them before connecting. Tool descriptions go straight into the model’s context, so a malicious description works as prompt injection. In 2025, researchers at Invariant Labs demonstrated this attack under the name “tool poisoning”: instructions hidden in one tool’s description led the agent to leak data from another.

Results are untrusted content too. A public issue, an email, or a page read by a tool can carry instructions for the agent. That is the indirect injection described by Greshake et al. (2023). See Untrusted Instructions and Narrow Tool Permissions.

Pros and cons

Pros Cons
One integration per system, used by everyone One more service to operate
Switch model or client without rewriting tools One more network hop
Centralized permissions and auditing A server outage takes down several agents
Works with third-party tools that speak MCP Third-party servers require security review

Relations

  • Refactorings that lead here: Expose Tool via MCP and Normalize Tool Output.
  • Neighbors: Tool Use is the mechanism by which the model asks for an action; the MCP Adapter decides where that action lives and who owns it. The Tool Router picks which tools from the connected servers the agent sees on each task.

Code

Both tabs build an MCP server with one read tool and one side-effect tool, separated by scope.

Python. Use this when the platform team writes servers in Python with the official MCP SDK.

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("erp")  # 3. one server per system


@mcp.tool()
def find_order(order_id: str) -> dict:
    """Gets an order's status, items, and dates. Use it when the user mentions an order number."""
    require_scope("orders:read")  # 4. permission of the calling user
    order = erp_client.get_order(order_id)
    return {  # 5. useful fields only, stable format
        "id": order.id,
        "status": order.status,
        "delivered_at": order.delivered_at.isoformat() if order.delivered_at else None,
        "items": [{"sku": i.sku, "qty": i.qty} for i in order.items],
    }


@mcp.tool()
def create_return(order_id: str, reason: str) -> dict:
    """Creates a return for a delivered order. Only use it after the customer confirms the return."""
    require_scope("returns:write")
    audit_log("create_return", order_id=order_id, reason=reason)
    label = erp_client.create_return(order_id, reason, idempotency_key=current_request_id())
    return {"return_id": label.id, "label_url": label.url}


@mcp.resource("erp://policies/returns")
def returns_policy() -> str:
    """Current exchange and return policy."""
    return erp_client.get_policy("returns")


if __name__ == "__main__":
    mcp.run()  # stdio by default; for a shared server, mcp.run(transport="streamable-http")

What you gained: any MCP client can now use the ERP with the same contract and permissions. What you lost: require_scope and user identity depend on the transport and your authentication; settle that before exposing the server.

TypeScript. Use this when the server runs on Node with the official MCP TypeScript SDK.

import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({ name: "erp", version: "1.4.0" });

server.registerTool(
  "find_order",
  {
    description: "Gets an order's status, items, and dates. Use it when the user mentions an order number.",
    inputSchema: { order_id: z.string().regex(/^\d+$/) },
  },
  async ({ order_id }) => {
    requireScope("orders:read"); // 4
    const o = await erp.getOrder(order_id);
    const data = { id: o.id, status: o.status, delivered_at: o.deliveredAt, items: o.items.map((i) => ({ sku: i.sku, qty: i.qty })) };
    return { content: [{ type: "text", text: JSON.stringify(data) }] }; // 5
  },
);

server.registerTool(
  "create_return",
  {
    description: "Creates a return for a delivered order. Only use it after the customer confirms the return.",
    inputSchema: { order_id: z.string().regex(/^\d+$/), reason: z.string().max(500) },
  },
  async ({ order_id, reason }) => {
    requireScope("returns:write");
    await auditLog("create_return", { order_id, reason });
    const label = await erp.createReturn(order_id, reason, { idempotencyKey: currentRequestId() });
    return { content: [{ type: "text", text: JSON.stringify({ return_id: label.id, label_url: label.url }) }] };
  },
);

await server.connect(new StdioServerTransport());

What you gained: the Zod schema validates input on the server, for every client. What you lost: same as Python; authentication and scopes must be wired to the chosen transport.

Check your understanding

Check +20 XP on first tryThe CRM's MCP server always calls the CRM with an admin service token, and every connected agent gets every tool. What is wrong?

Check +20 XP on first tryYour team wants to connect a public MCP server from a spreadsheet vendor to the back-office agent, which also has ERP access. What do you do first?

Check +20 XP on first tryA startup has a single agent with two tools that read its own database. The CTO wants to start by building MCP servers for everything. What is the best advice?

Quick answers

What is MCP Adapter?

MCP Adapter is an AI design pattern for LLM apps and agents, in the Tools and action group. Expose systems as MCP servers so any agent uses the same contract.

Which smells does the MCP Adapter pattern help cure?

Raw HTML Tool Output and Provider Format Leak.

Where does MCP Adapter show up in practice?

In the real-world scenarios Coding agent, Back-office automation with durable execution, and Internal knowledge base chat.

Go further

Relations

Search