Anatomy of an MCP Server: Handlers, Schemas, and Transport
An MCP server is a process that speaks JSON-RPC 2.0 over a transport and advertises capabilities (tools, resources, prompts) to an MCP host or client.
Search across all documentation pages
An MCP server is a process that speaks JSON-RPC 2.0 over a transport and advertises capabilities (tools, resources, prompts) to an MCP host or client.
The host connects, negotiates protocol version and features, then lists and invokes those capabilities on behalf of a model-driven agent.
MCP (Model Context Protocol) is an open standard for connecting hosts (Claude Desktop, VS Code, agent SDKs) to external systems through a shared protocol rather than one-off plugins.
A server does not embed the LLM. It exposes data and actions. The client (inside a host) discovers them and the host decides when the model may call them.
At startup the client sends initialize. The server replies with protocol version, server info (name, version), and a capabilities object.
After notifications/initialized, the client may list tools, resources, and prompts, then invoke them with JSON-RPC methods such as tools/call, resources/read, and prompts/get (exact method set is defined by the current MCP revision - verify at build).
Conceptually the server is:
| Piece | Role |
|---|---|
| Identity | Name, version, optional instructions for the host |
| Capability flags | Which primitives you support and list-change notifications |
| Handlers | Code that lists definitions and executes each primitive |
| Schemas | Machine-readable argument shapes (usually JSON Schema) |
| Transport | stdio subprocess pipe, or Streamable HTTP endpoint |
Without a clean separation between schema (what the model sees) and handler (what runs), hosts cannot safely present tools, and agents call the wrong parameters.
Handlers are the functions (or methods) that answer list and invoke requests.
Typical pairs:
High-level SDKs hide the JSON-RPC method names. In Python, FastMCP turns decorated functions into tools. In TypeScript, McpServer.registerTool binds name, description, input schema, and handler.
Handlers should:
Tool (and often prompt) arguments are described with JSON Schema so the host can show forms, and models can produce structured arguments.
Python FastMCP often derives schema from type hints and docstrings. TypeScript commonly uses Zod objects mapped into the SDK input schema.
Good schemas are narrow:
The description on the tool and each field is part of the schema surface. Models follow descriptions more than they invent APIs.
MCP messages are JSON-RPC. The transport defines how those messages move.
stdio
Streamable HTTP (current remote standard; replaces older dedicated HTTP+SSE split - verify at build)
Mcp-Session-Id after initialize for stateful sessions.MCP-Protocol-Version on subsequent requests when using HTTP.Older HTTP+SSE layouts still appear in the wild. New servers should target Streamable HTTP and document any compatibility shim.
# Pattern only: identity + one tool + stdio transport (FastMCP)
from mcp.server.fastmcp import FastMCP
mcp = FastMCP("demo")
@mcp.tool()
def ping() -> str:
"""Health check tool for the host."""
return "pong"
if __name__ == "__main__":
mcp.run(transport="stdio")initialize / capability negotiation.tools/call → handler runs → content returns → model continues.| Approach | Strength | Weakness | Best fit |
|---|---|---|---|
| Tools only | Simple agent actions | No first-class context files or templates | CRUD APIs, actions |
| Tools + resources | Agents can pull large or static context by URI | Host must support resource reads | Codebases, configs, docs |
| Tools + prompts | Reusable task templates | Overlap with host-side prompts | Shared org playbooks |
| stdio | Easy local install | Not multi-tenant | IDE and desktop |
| Streamable HTTP | Shared remote service | Auth, ops, session design | Team and product agents |
Design handlers as small, composable units. One tool that "does the whole product" is hard to describe, hard to authorize, and hard to evaluate.
Version the server identity string when schemas change. Hosts and agents cache tool lists; silent renames look like random model failures.
At minimum: respond to initialize with identity and capabilities, speak valid JSON-RPC over a supported transport, and implement handlers for every capability you advertise. An empty tools list is legal but useless for agents.
They are invoked through MCP methods with structured arguments chosen by a model (or host UI), and they return content blocks for the conversation, not arbitrary HTTP response codes alone. Side effects still need the same care as REST.
In tool (and related) definitions exchanged on list operations. SDKs generate them from types or Zod; the wire format is JSON Schema shaped per the MCP revision in use.
Hosts usually map one config entry to one process or URL. You can modularize code, but capability sets are negotiated per connection. Prefer clear server names per domain.
A typed unit in a tool result (commonly text, sometimes images or resource references). Hosts flatten these into model-visible observations.
No. Tools alone ship most integrations. Add resources when agents need readable context by URI; add prompts when you want shared templates across hosts.
stdio is easiest with a subprocess test client. HTTP needs a live port, session headers, and often auth. Test both if you ship both entrypoints.
The client will not list or call tools. Check protocol version mismatches, crashed process, broken stdout, or HTTP 4xx/5xx before debugging handlers.
You declare them at initialize. Some servers support list-changed notifications when tools/resources change; hosts that care will re-list. Verify notification support for your SDK and host.
MCP tools are the portable cousin of provider function-calling schemas. The host translates MCP tool definitions into whatever the model API expects.
Thin handlers that call a service layer are easier to unit test without spinning MCP. Keep transport code at the edge.
Long tools should respect host cancel notifications where supported, and set their own upstream timeouts. A hung handler freezes an agent turn.
Related: Building & Deploying MCP Basics
Related: Building Your First MCP Server in TypeScript or Python
Related: Exposing Resources and Prompts, Not Just Tools, from an MCP Server
Related: Deploying an MCP Server as a Remote HTTP Service
Related: MCP's Three Primitives: Tools, Resources, and Prompts
Stack versions: Pins from the category manifest (verify at build): OpenRouter (~315+ models, July 2026 pricing/fees); LangGraph 1.0+; CrewAI 1.14+; Microsoft Agent Framework 1.0; Vercel AI SDK 6; Pydantic AI (latest); LlamaIndex (latest); OpenAI Agents SDK (latest + MCP); MCP (Linux Foundation governance); A2A (HTTP+SSE+JSON-RPC 2.0); Solana
@solana/web3.js+@solana/spl-token.
Reviewed by Chris St. John·Last updated Jul 16, 2026