MCP Architecture Deep Dive: How the Protocol Works Under the Hood
A technical deep dive into MCP's architecture, including the transport layer, JSON-RPC protocol, and lifecycle management.
Understanding MCP's architecture helps you build better integrations and debug issues more effectively. If you're new to MCP, we recommend starting with What is MCP? first. Let's dive deep into how the protocol actually works.
The Two-Layer Architecture
MCP consists of two distinct layers:
1. Data Layer (Inner): Defines the JSON-RPC protocol for client-server communication, including lifecycle management and core primitives (tools, resources, prompts).
2. Transport Layer (Outer): Handles the actual communication channels — how messages get from client to server and back.
The Transport Layer
MCP supports two transport mechanisms:
STDIO Transport
Standard input/output streams for local process communication.
How it works:
1. Host spawns server as a child process
2. Messages sent via process stdin
3. Responses received via process stdout
4. stderr used for logging (not protocol messages)
Advantages:
Message Format:
Content-Length: 123\r\n
\r\n
{"jsonrpc":"2.0","id":1,"method":"tools/list"}
Streamable HTTP Transport
HTTP-based communication for remote servers.
How it works:
1. Client sends requests via HTTP POST
2. Server can respond immediately or use Server-Sent Events (SSE) for streaming
3. Supports standard HTTP authentication
Advantages:
The Data Layer Protocol
MCP uses JSON-RPC 2.0 for all communication. There are three message types:
Requests
Messages that expect a response:
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "city": "NYC" }
}
}
Responses
Replies to requests:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [{
"type": "text",
"text": "Weather in NYC: 72°F, Sunny"
}]
}
}
Notifications
One-way messages (no response expected):
{
"jsonrpc": "2.0",
"method": "notifications/tools/list_changed"
}
Lifecycle Management
Every MCP connection follows a specific lifecycle:
1. Initialization
Client sends initialize request:
{
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": {},
"resources": {}
},
"clientInfo": {
"name": "claude-desktop",
"version": "1.0.0"
}
}
}
Server responds with its capabilities:
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"protocolVersion": "2025-06-18",
"capabilities": {
"tools": { "listChanged": true },
"resources": { "listChanged": true },
"prompts": {}
},
"serverInfo": {
"name": "my-server",
"version": "1.0.0"
}
}
}
2. Initialized Notification
Client confirms initialization complete:
{
"jsonrpc": "2.0",
"method": "notifications/initialized"
}
3. Normal Operation
Now the connection is ready for tool calls, resource reads, etc.
4. Shutdown
Clean disconnection via transport-specific mechanism.
Capability Negotiation
During initialization, both sides declare their capabilities. This enables:
Version Compatibility: Different protocol versions can negotiate common features.
Feature Discovery: Clients know what the server supports before making requests.
Graceful Degradation: Missing capabilities handled without errors.
Common capabilities:
{
tools: {
listChanged: boolean // Server will notify when tools change
},
resources: {
listChanged: boolean,
subscribe: boolean // Server supports subscriptions
},
prompts: {
listChanged: boolean
},
logging: {}, // Server can log to client
sampling: {}, // Client supports LLM sampling
elicitation: {} // Client can ask user for input
}
Tool Invocation Flow
Here's the complete flow when AI calls a tool:
AI Application MCP Client MCP Server
| | |
|-- "call weather" --> | |
| |-- tools/call -----> |
| | |-- fetch API
| | |<- API response
| |<---- result ------- |
|<-- tool result ----- | |
| | |
Request:
{
"jsonrpc": "2.0",
"id": 42,
"method": "tools/call",
"params": {
"name": "get_weather",
"arguments": { "city": "London" }
}
}
Response:
{
"jsonrpc": "2.0",
"id": 42,
"result": {
"content": [{
"type": "text",
"text": "London: 18°C, Cloudy"
}],
"isError": false
}
}
Resource Subscriptions
For real-time data, MCP supports resource subscriptions:
Subscribe:
{
"method": "resources/subscribe",
"params": { "uri": "file:///logs/app.log" }
}
Update Notification:
{
"method": "notifications/resources/updated",
"params": { "uri": "file:///logs/app.log" }
}
Error Handling
MCP uses standard JSON-RPC error codes:
| Code | Meaning |
|------|---------|
| -32700 | Parse error |
| -32600 | Invalid request |
| -32601 | Method not found |
| -32602 | Invalid params |
| -32603 | Internal error |
Tool execution errors use the isError flag instead:
{
"result": {
"content": [{ "type": "text", "text": "API timeout" }],
"isError": true
}
}
Client Primitives
Servers can also request things from clients:
Sampling
Request LLM completion from the host:
{
"method": "sampling/createMessage",
"params": {
"messages": [{ "role": "user", "content": "Summarize this..." }],
"maxTokens": 1000
}
}
Elicitation
Ask user for input:
{
"method": "elicitation/create",
"params": {
"message": "Please confirm deletion",
"options": ["Yes", "No"]
}
}
Performance Considerations
Batching: JSON-RPC supports batching multiple requests:
[
{"jsonrpc":"2.0","id":1,"method":"tools/list"},
{"jsonrpc":"2.0","id":2,"method":"resources/list"}
]
Streaming: Long operations can use progress notifications:
{
"method": "notifications/progress",
"params": {
"progressToken": "task-123",
"progress": 50,
"total": 100
}
}
Caching: Clients can cache tool/resource lists until listChanged notifications.
Debugging Tips
1. Inspect with MCP Inspector: npx @modelcontextprotocol/inspector your-server
2. Log JSON-RPC messages: Add logging at the transport layer
3. Check stderr: Servers should log debug info to stderr
4. Validate schemas: Use the official schemas to validate messages
Conclusion
MCP's architecture is elegant in its simplicity — JSON-RPC over configurable transports with clear lifecycle management. Understanding these internals helps you build more robust integrations, debug issues faster, and contribute to the ecosystem.
The two-layer design means you can focus on what matters: building great tools, resources, and prompts for AI applications. Learn more about these primitives in our Tools vs Resources vs Prompts guide, or get hands-on by building your first MCP server.