349 lines
8.8 KiB
Text
349 lines
8.8 KiB
Text
---
|
|
title: "Core architecture"
|
|
description: "Understand how MCP connects clients, servers, and LLMs"
|
|
---
|
|
|
|
The Model Context Protocol (MCP) is built on a flexible, extensible architecture that enables seamless communication between LLM applications and integrations. This document covers the core architectural components and concepts.
|
|
|
|
## Overview
|
|
|
|
MCP follows a client-server architecture where:
|
|
|
|
- **Hosts** are LLM applications (like Claude Desktop or IDEs) that initiate connections
|
|
- **Clients** maintain 1:1 connections with servers, inside the host application
|
|
- **Servers** provide context, tools, and prompts to clients
|
|
|
|
```mermaid
|
|
flowchart LR
|
|
subgraph " Host (e.g., Claude Desktop) "
|
|
client1[MCP Client]
|
|
client2[MCP Client]
|
|
end
|
|
subgraph "Server Process"
|
|
server1[MCP Server]
|
|
end
|
|
subgraph "Server Process"
|
|
server2[MCP Server]
|
|
end
|
|
|
|
client1 <-->|Transport Layer| server1
|
|
client2 <-->|Transport Layer| server2
|
|
```
|
|
|
|
## Core components
|
|
|
|
### Protocol layer
|
|
|
|
The protocol layer handles message framing, request/response linking, and high-level communication patterns.
|
|
|
|
<Tabs>
|
|
<Tab title="TypeScript">
|
|
```typescript
|
|
class Protocol<Request, Notification, Result> {
|
|
// Handle incoming requests
|
|
setRequestHandler<T>(schema: T, handler: (request: T, extra: RequestHandlerExtra) => Promise<Result>): void
|
|
|
|
// Handle incoming notifications
|
|
setNotificationHandler<T>(schema: T, handler: (notification: T) => Promise<void>): void
|
|
|
|
// Send requests and await responses
|
|
request<T>(request: Request, schema: T, options?: RequestOptions): Promise<T>
|
|
|
|
// Send one-way notifications
|
|
notification(notification: Notification): Promise<void>
|
|
}
|
|
```
|
|
</Tab>
|
|
<Tab title="Python">
|
|
```python
|
|
class Session(BaseSession[RequestT, NotificationT, ResultT]):
|
|
async def send_request(
|
|
self,
|
|
request: RequestT,
|
|
result_type: type[Result]
|
|
) -> Result:
|
|
"""
|
|
Send request and wait for response. Raises McpError if response contains error.
|
|
"""
|
|
# Request handling implementation
|
|
|
|
async def send_notification(
|
|
self,
|
|
notification: NotificationT
|
|
) -> None:
|
|
"""Send one-way notification that doesn't expect response."""
|
|
# Notification handling implementation
|
|
|
|
async def _received_request(
|
|
self,
|
|
responder: RequestResponder[ReceiveRequestT, ResultT]
|
|
) -> None:
|
|
"""Handle incoming request from other side."""
|
|
# Request handling implementation
|
|
|
|
async def _received_notification(
|
|
self,
|
|
notification: ReceiveNotificationT
|
|
) -> None:
|
|
"""Handle incoming notification from other side."""
|
|
# Notification handling implementation
|
|
```
|
|
</Tab>
|
|
</Tabs>
|
|
|
|
Key classes include:
|
|
|
|
* `Protocol`
|
|
* `Client`
|
|
* `Server`
|
|
|
|
### Transport layer
|
|
|
|
The transport layer handles the actual communication between clients and servers. MCP supports multiple transport mechanisms:
|
|
|
|
1. **Stdio transport**
|
|
- Uses standard input/output for communication
|
|
- Ideal for local processes
|
|
|
|
2. **HTTP with SSE transport**
|
|
- Uses Server-Sent Events for server-to-client messages
|
|
- HTTP POST for client-to-server messages
|
|
|
|
All transports use [JSON-RPC](https://www.jsonrpc.org/) 2.0 to exchange messages. See the [specification](https://spec.modelcontextprotocol.io) for detailed information about the Model Context Protocol message format.
|
|
|
|
### Message types
|
|
|
|
MCP has these main types of messages:
|
|
|
|
1. **Requests** expect a response from the other side:
|
|
```typescript
|
|
interface Request {
|
|
method: string;
|
|
params?: { ... };
|
|
}
|
|
```
|
|
|
|
2. **Notifications** are one-way messages that don't expect a response:
|
|
```typescript
|
|
interface Notification {
|
|
method: string;
|
|
params?: { ... };
|
|
}
|
|
```
|
|
|
|
3. **Results** are successful responses to requests:
|
|
```typescript
|
|
interface Result {
|
|
[key: string]: unknown;
|
|
}
|
|
```
|
|
|
|
4. **Errors** indicate that a request failed:
|
|
```typescript
|
|
interface Error {
|
|
code: number;
|
|
message: string;
|
|
data?: unknown;
|
|
}
|
|
```
|
|
|
|
## Connection lifecycle
|
|
|
|
### 1. Initialization
|
|
|
|
```mermaid
|
|
sequenceDiagram
|
|
participant Client
|
|
participant Server
|
|
|
|
Client->>Server: initialize request
|
|
Server->>Client: initialize response
|
|
Client->>Server: initialized notification
|
|
|
|
Note over Client,Server: Connection ready for use
|
|
```
|
|
|
|
1. Client sends `initialize` request with protocol version and capabilities
|
|
2. Server responds with its protocol version and capabilities
|
|
3. Client sends `initialized` notification as acknowledgment
|
|
4. Normal message exchange begins
|
|
|
|
### 2. Message exchange
|
|
|
|
After initialization, the following patterns are supported:
|
|
|
|
- **Request-Response**: Client or server sends requests, the other responds
|
|
- **Notifications**: Either party sends one-way messages
|
|
|
|
### 3. Termination
|
|
|
|
Either party can terminate the connection:
|
|
- Clean shutdown via `close()`
|
|
- Transport disconnection
|
|
- Error conditions
|
|
|
|
## Error handling
|
|
|
|
MCP defines these standard error codes:
|
|
|
|
```typescript
|
|
enum ErrorCode {
|
|
// Standard JSON-RPC error codes
|
|
ParseError = -32700,
|
|
InvalidRequest = -32600,
|
|
MethodNotFound = -32601,
|
|
InvalidParams = -32602,
|
|
InternalError = -32603
|
|
}
|
|
```
|
|
|
|
SDKs and applications can define their own error codes above -32000.
|
|
|
|
Errors are propagated through:
|
|
- Error responses to requests
|
|
- Error events on transports
|
|
- Protocol-level error handlers
|
|
|
|
## Implementation example
|
|
|
|
Here's a basic example of implementing an MCP server:
|
|
|
|
<Tabs>
|
|
<Tab title="TypeScript">
|
|
```typescript
|
|
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
|
|
const server = new Server({
|
|
name: "example-server",
|
|
version: "1.0.0"
|
|
}, {
|
|
capabilities: {
|
|
resources: {}
|
|
}
|
|
});
|
|
|
|
// Handle requests
|
|
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
|
return {
|
|
resources: [
|
|
{
|
|
uri: "example://resource",
|
|
name: "Example Resource"
|
|
}
|
|
]
|
|
};
|
|
});
|
|
|
|
// Connect transport
|
|
const transport = new StdioServerTransport();
|
|
await server.connect(transport);
|
|
```
|
|
</Tab>
|
|
<Tab title="Python">
|
|
```python
|
|
import asyncio
|
|
import mcp.types as types
|
|
from mcp.server import Server
|
|
from mcp.server.stdio import stdio_server
|
|
|
|
app = Server("example-server")
|
|
|
|
@app.list_resources()
|
|
async def list_resources() -> list[types.Resource]:
|
|
return [
|
|
types.Resource(
|
|
uri="example://resource",
|
|
name="Example Resource"
|
|
)
|
|
]
|
|
|
|
async def main():
|
|
async with stdio_server() as streams:
|
|
await app.run(
|
|
streams[0],
|
|
streams[1],
|
|
app.create_initialization_options()
|
|
)
|
|
|
|
if __name__ == "__main__":
|
|
asyncio.run(main)
|
|
```
|
|
</Tab>
|
|
</Tabs>
|
|
|
|
## Best practices
|
|
|
|
### Transport selection
|
|
|
|
1. **Local communication**
|
|
- Use stdio transport for local processes
|
|
- Efficient for same-machine communication
|
|
- Simple process management
|
|
|
|
2. **Remote communication**
|
|
- Use SSE for scenarios requiring HTTP compatibility
|
|
- Consider security implications including authentication and authorization
|
|
|
|
### Message handling
|
|
|
|
1. **Request processing**
|
|
- Validate inputs thoroughly
|
|
- Use type-safe schemas
|
|
- Handle errors gracefully
|
|
- Implement timeouts
|
|
|
|
2. **Progress reporting**
|
|
- Use progress tokens for long operations
|
|
- Report progress incrementally
|
|
- Include total progress when known
|
|
|
|
3. **Error management**
|
|
- Use appropriate error codes
|
|
- Include helpful error messages
|
|
- Clean up resources on errors
|
|
|
|
## Security considerations
|
|
|
|
1. **Transport security**
|
|
- Use TLS for remote connections
|
|
- Validate connection origins
|
|
- Implement authentication when needed
|
|
|
|
2. **Message validation**
|
|
- Validate all incoming messages
|
|
- Sanitize inputs
|
|
- Check message size limits
|
|
- Verify JSON-RPC format
|
|
|
|
3. **Resource protection**
|
|
- Implement access controls
|
|
- Validate resource paths
|
|
- Monitor resource usage
|
|
- Rate limit requests
|
|
|
|
4. **Error handling**
|
|
- Don't leak sensitive information
|
|
- Log security-relevant errors
|
|
- Implement proper cleanup
|
|
- Handle DoS scenarios
|
|
|
|
## Debugging and monitoring
|
|
|
|
1. **Logging**
|
|
- Log protocol events
|
|
- Track message flow
|
|
- Monitor performance
|
|
- Record errors
|
|
|
|
2. **Diagnostics**
|
|
- Implement health checks
|
|
- Monitor connection state
|
|
- Track resource usage
|
|
- Profile performance
|
|
|
|
3. **Testing**
|
|
- Test different transports
|
|
- Verify error handling
|
|
- Check edge cases
|
|
- Load test servers
|