Tool Servers & MCP
So far, tools have lived inside the agent runtime — functions registered in the same process, called directly. That works for demos and small projects, but it breaks down fast. You want to add a tool backed by a third-party API. Another team builds a useful capability, and you want your agent to use it. You need to run a tool in a sandbox so it cannot touch the agent's memory. In each case, the tool needs to live outside the agent, running as a separate service.
This is where tool servers come in. A tool server is a standalone process that exposes tools over a protocol. The agent's runtime connects to it, discovers what tools are available, calls them, and reads the results — all through a standardized interface. The tools run in their own process, with their own permissions, their own dependencies, and their own failure modes.
The Model Context Protocol (MCP) is the open standard that defines how this connection works. Understanding it matters because it is rapidly becoming the common language between agents and the tools they use.
The Problem with Embedded Tools #
When tools are compiled into the agent's runtime, you face three problems as the system grows.
Coupling. Every tool shares the agent's process, its dependencies, and its security context. A buggy tool can crash the runtime. A tool that imports a conflicting library version breaks other tools. Updating one tool means redeploying the entire agent.
Reusability. If two agents need the same tool — say, a database query function — you either duplicate the code or build a shared library. Neither scales well. Shared libraries still require each agent to import and configure the tool, and version mismatches across agents become a constant headache.
Security boundaries. An embedded tool runs with the same permissions as the agent. If the agent can read the filesystem, so can every tool. If a tool needs database credentials, those credentials live in the agent's process. You cannot isolate a tool's blast radius without isolating it as a process.
Tool servers solve all three by moving each tool into its own process and defining a protocol for communication. The agent talks to the tool server over that protocol. The tool server manages its own dependencies, credentials, and lifecycle.
MCP Architecture #
The Model Context Protocol defines three roles in the system:
The host is the AI application — the thing the user interacts with. It coordinates one or more MCP clients.
The client is a connector inside the host that maintains a dedicated connection to a single MCP server. One client per server. If the host connects to three tool servers, it creates three clients.
The server is the process that exposes tools (and optionally resources and prompts) over the protocol.
┌───────────────────────────────────────────┐
│ MCP Host │
│ (AI application) │
│ │
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
│ │ Client 1 │ │ Client 2 │ │ Client 3 │ │
│ └────┬─────┘ └────┬─────┘ └────┬─────┘ │
│ │ │ │ │
└───────┼─────────────┼─────────────┼───────┘
│ │ │
┌────▼─────┐ ┌────▼─────┐ ┌────▼─────┐
│ Server A │ │ Server B │ │ Server C │
│ (local │ │ (local │ │ (remote │
│ files) │ │ database│ │ API) │
└──────────┘ └──────────┘ └──────────┘
Communication uses JSON-RPC 2.0 messages. The protocol supports two transport mechanisms: stdio for local servers (the host spawns the server as a child process and talks over stdin/stdout) and Streamable HTTP for remote servers (the client sends HTTP POST requests and the server can stream responses back via Server-Sent Events). The transport is abstracted away — the same JSON-RPC messages work regardless of whether the server is local or across the network.
Lifecycle and Discovery #
An MCP connection starts with a handshake. The client sends an initialize request that includes the protocol version it speaks and the capabilities it supports. The server responds with its own capabilities — which primitives it offers (tools, resources, prompts) and whether it supports features like change notifications. If the versions are compatible, the client sends an initialized notification, and the connection is live.
This capability negotiation matters because it makes the protocol extensible without breaking older implementations. A server that does not support resources simply does not declare that capability, and the client never asks for them.
After initialization, the client discovers tools by calling tools/list. The server returns a list of tool definitions — each with a name, description, and a JSON schema for its parameters. This is the same schema structure we covered earlier, but now it travels over a protocol rather than being registered in code.
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/list"
}
The server responds with something like:
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"tools": [
{
"name": "query_inventory",
"description": "Search current inventory levels by product ID or category.",
"inputSchema": {
"type": "object",
"properties": {
"product_id": { "type": "string" },
"category": { "type": "string" }
},
"required": [],
"additionalProperties": false
}
}
]
}
}
The host aggregates tools from all connected servers into a single registry that the model sees. From the model's perspective, there is no difference between a tool served locally over stdio and one served remotely over HTTP — they all show up in the same tool list.
Tool Execution Over the Protocol #
When the model decides to call a tool, the host's MCP client sends a tools/call request to the server that owns that tool:
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "query_inventory",
"arguments": {
"category": "electronics"
}
}
}
The server executes the tool and returns the result as a content array — which can include text, structured data, or even images. The host receives the result, formats it as an observation, and feeds it back into the model's context. The model never knows it talked to a separate process. It sees the same tool-call/observation cycle it would see with an embedded function.
The indirection adds latency — a network round trip for remote servers, a process boundary crossing for local ones. For most tools, this overhead is negligible compared to the tool's own execution time (a database query or API call already involves network latency). Where it matters is for tools that execute in microseconds; those are better left embedded.
Beyond Tools #
MCP servers can expose more than tools. The protocol defines two additional primitives:
Resources are data sources that provide context to the model — file contents, database schemas, configuration data. Unlike tools, resources are read by the client and packed into the prompt rather than being invoked as actions. A server might expose a project_readme resource that the host retrieves and includes in the model's context before the first turn.
Prompts are reusable templates for structuring model interactions — system instructions, few-shot examples, task-specific framing. A server can expose a set of prompts that the host selects from based on the task at hand.
These primitives follow the same discovery pattern: resources/list, resources/read, prompts/list, prompts/get. The host decides what to use and when. This separation — servers offer, hosts decide — is a deliberate design choice that keeps the server from controlling the model's behavior.
Isolation and Security Boundaries #
Moving tools to separate servers is not just an architectural choice — it is a security boundary. Each server runs in its own process with its own credentials, its own network access, and its own filesystem scope. The host does not share its secrets with the server, and the server does not share its secrets with the host. They communicate only through JSON-RPC messages.
This gives you several controls that embedded tools cannot provide.
Credential isolation. A tool that queries a customer database holds the database credentials in its own process. The agent runtime never sees them. If the agent's process is compromised, the database credentials are not exposed.
Blast radius control. A tool server that only has read access to an inventory system cannot be tricked into writing to it, regardless of what the model asks. The server's own permissions are the hard boundary — not the model's instructions.
Sandboxing. You can run tool servers in containers, VMs, or restricted OS-level sandboxes. A code execution tool running in a locked-down container cannot escape to the host filesystem, even if the code it runs is malicious. This is the kind of isolation you cannot achieve with in-process tools.
Audit boundaries. Every tool call crosses the protocol boundary as a JSON-RPC message. You can log, inspect, and rate-limit these messages at the transport layer without modifying the tool or the agent. This is the same observability pattern used in microservice architectures — the protocol boundary is the natural place to instrument.
MCP's own specification is explicit about security: tool descriptions should be treated as untrusted unless the server is trusted, hosts must obtain user consent before invoking tools, and users should understand what each tool does before authorizing its use. These are guidelines for implementers, not protocol-level enforcement — the security model depends on the host implementing consent and authorization correctly.
Trade-offs #
Tool servers and MCP add moving parts. Whether that complexity is worth it depends on the system you are building.
Latency. Every tool call crosses a process boundary. For local stdio servers, this is milliseconds. For remote HTTP servers, it is a network round trip. If your agent makes many tool calls per task and latency is critical, this overhead compounds.
Operational complexity. Each tool server is a service to deploy, monitor, and keep running. If you have three tool servers, you have three things that can go down independently. You need health checks, restart policies, and version management for each one.
Discovery dynamics. MCP supports change notifications — a server can tell the client when its tool list changes. This is powerful for dynamic environments but adds complexity to the host, which must handle tool registry updates mid-conversation.
When to use it. If your agent has a small, stable set of tools that run in the same process, embedding them is simpler and faster. MCP pays off when you need reusable tools across multiple agents, isolation between the agent and the tools, or the ability to evolve tools independently of the agent runtime.
It is worth noting what MCP does not cover. MCP defines how an agent talks to tools — discovery, invocation, results. It does not define how agents talk to each other. In multi-agent systems, that is a separate problem with its own emerging standard: the Agent-to-Agent (A2A) protocol, which handles task delegation, status updates, and result exchange between agents that may run on different platforms. The two protocols are complementary — MCP for agent-to-tool, A2A for agent-to-agent.
Conclusion #
Tool servers move tools out of the agent's process and behind a protocol boundary. MCP is the open standard that defines how that boundary works — discovery, execution, and real-time updates, all over JSON-RPC.
Key takeaways:
- Embedded tools couple the tool's lifecycle, dependencies, and security context to the agent — tool servers decouple all three
- MCP defines three roles: hosts (AI apps), clients (connectors), and servers (tool providers), communicating over JSON-RPC 2.0
- Discovery happens at connection time through capability negotiation and
tools/list— the model sees a unified tool registry regardless of where tools actually run - The protocol boundary is a natural security boundary: credential isolation, blast radius control, sandboxing, and audit logging all happen at the transport layer
- MCP servers can expose resources and prompts alongside tools, but the host decides what to use — servers offer, hosts decide
- MCP handles agent-to-tool communication; the A2A protocol handles agent-to-agent communication — the two are complementary, not competing
- The added latency and operational complexity are worth it when you need isolation, reusability, or independent tool evolution