MCP Web Tools Server
by surya-madhav
- docs
# MCP Communication Protocols
This document provides a detailed exploration of the communication protocols used in the Model Context Protocol (MCP). Understanding these protocols is essential for developing robust MCP servers and clients, and for troubleshooting connection issues.
## Protocol Overview
MCP uses a layered protocol architecture:
```mermaid
flowchart TB
subgraph Application
Tools["Tools, Resources, Prompts"]
end
subgraph Protocol
Messages["MCP Message Format"]
JSONRPC["JSON-RPC 2.0"]
end
subgraph Transport
STDIO["STDIO Transport"]
SSE["SSE Transport"]
end
Tools <--> Messages
Messages <--> JSONRPC
JSONRPC <--> STDIO
JSONRPC <--> SSE
```
The layers are:
1. **Application Layer**: Defines tools, resources, and prompts
2. **Protocol Layer**: Specifies message formats and semantics
3. **Transport Layer**: Handles the physical transmission of messages
## Message Format
MCP uses [JSON-RPC 2.0](https://www.jsonrpc.org/specification) as its message format. This provides a standardized way to structure requests, responses, and notifications.
### JSON-RPC Structure
There are three types of messages in JSON-RPC:
1. **Requests**: Messages that require a response
2. **Responses**: Replies to requests (success or error)
3. **Notifications**: One-way messages that don't expect a response
### Request Format
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "tool_name",
"arguments": {
"param1": "value1",
"param2": 42
}
}
}
```
Key components:
- `jsonrpc`: Always "2.0" to indicate JSON-RPC 2.0
- `id`: A unique identifier for matching responses to requests
- `method`: The operation to perform (e.g., "tools/call")
- `params`: Parameters for the method
### Response Format (Success)
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"content": [
{
"type": "text",
"text": "Operation result"
}
]
}
}
```
Key components:
- `jsonrpc`: Always "2.0"
- `id`: Matches the id from the request
- `result`: The operation result (structure depends on the method)
### Response Format (Error)
```json
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Invalid parameters",
"data": {
"details": "Parameter 'param1' is required"
}
}
}
```
Key components:
- `jsonrpc`: Always "2.0"
- `id`: Matches the id from the request
- `error`: Error information with code, message, and optional data
### Notification Format
```json
{
"jsonrpc": "2.0",
"method": "notifications/resources/list_changed",
"params": {}
}
```
Key components:
- `jsonrpc`: Always "2.0"
- `method`: The notification type
- `params`: Parameters for the notification (if any)
- No `id` field (distinguishes notifications from requests)
## Transport Methods
MCP supports two main transport methods:
### STDIO Transport
Standard Input/Output (STDIO) transport uses standard input and output streams for communication. This is particularly useful for local processes.
```mermaid
flowchart LR
Client["MCP Client"]
Server["MCP Server Process"]
Client -->|stdin| Server
Server -->|stdout| Client
```
#### Message Framing
STDIO transport uses a simple message framing format:
```
Content-Length: <length>\r\n
\r\n
<message>
```
Where:
- `<length>` is the length of the message in bytes
- `<message>` is the JSON-RPC message
Example:
```
Content-Length: 76
{"jsonrpc":"2.0","method":"initialize","id":0,"params":{"version":"1.0.0"}}
```
#### Implementation Details
STDIO transport is implemented by:
1. Starting a child process
2. Writing to the process's standard input
3. Reading from the process's standard output
4. Parsing messages according to the framing format
Python implementation example:
```python
async def read_message(reader):
# Read headers
headers = {}
while True:
line = await reader.readline()
line = line.decode('utf-8').strip()
if not line:
break
key, value = line.split(': ', 1)
headers[key] = value
# Get content length
content_length = int(headers.get('Content-Length', 0))
# Read content
content = await reader.read(content_length)
return json.loads(content)
async def write_message(writer, message):
# Serialize message
content = json.dumps(message).encode('utf-8')
# Write headers
header = f'Content-Length: {len(content)}\r\n\r\n'
writer.write(header.encode('utf-8'))
# Write content
writer.write(content)
await writer.drain()
```
#### Advantages and Limitations
Advantages:
- Simple to implement
- Works well for local processes
- No network configuration required
- Natural process lifecycle management
Limitations:
- Only works for local processes
- Limited to one client per server
- No built-in authentication
- Potential blocking issues
### SSE Transport
Server-Sent Events (SSE) transport uses HTTP for client-to-server requests and SSE for server-to-client messages. This is suitable for web applications and remote servers.
```mermaid
flowchart LR
Client["MCP Client"]
Server["MCP Server (HTTP)"]
Client -->|HTTP POST| Server
Server -->|SSE Events| Client
```
#### Client-to-Server Messages
Client-to-server messages are sent using HTTP POST requests:
```
POST /message HTTP/1.1
Content-Type: application/json
{"jsonrpc":"2.0","method":"tools/call","id":1,"params":{...}}
```
#### Server-to-Client Messages
Server-to-client messages are sent using SSE events:
```
event: message
data: {"jsonrpc":"2.0","id":1,"result":{...}}
```
#### Implementation Details
SSE transport implementation requires:
1. An HTTP server endpoint for accepting client POST requests
2. An SSE endpoint for sending server messages to clients
3. Proper HTTP and SSE headers and formatting
Python implementation example (using aiohttp):
```python
from aiohttp import web
import json
# For server-to-client messages (SSE)
async def sse_handler(request):
response = web.Response(
content_type='text/event-stream',
headers={
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'Access-Control-Allow-Origin': '*'
}
)
response.enable_chunked_encoding()
# Get the response writer
writer = response.write
# Store the client connection
client_id = request.query.get('id', 'unknown')
clients[client_id] = writer
# Keep the connection open
while True:
await asyncio.sleep(1)
return response
# For client-to-server messages (HTTP POST)
async def message_handler(request):
# Parse the message
data = await request.json()
# Process the message
result = await process_message(data)
# If it's a request (has an ID), send the response via SSE
if 'id' in data:
client_id = request.query.get('id', 'unknown')
if client_id in clients:
writer = clients[client_id]
message = json.dumps(result)
await writer(f'event: message\ndata: {message}\n\n')
# Return an acknowledgment
return web.Response(text='OK')
# Send an SSE message to a client
async def send_sse_message(client_id, message):
if client_id in clients:
writer = clients[client_id]
data = json.dumps(message)
await writer(f'event: message\ndata: {data}\n\n')
```
#### Advantages and Limitations
Advantages:
- Works over standard HTTP
- Supports remote clients
- Can serve multiple clients
- Integrates with web infrastructure
Limitations:
- More complex to implement
- Requires HTTP server
- Connection management is more challenging
- Potential firewall issues
## Protocol Lifecycle
The MCP protocol follows a defined lifecycle:
```mermaid
sequenceDiagram
participant Client
participant Server
Note over Client,Server: Initialization Phase
Client->>Server: initialize request
Server->>Client: initialize response
Client->>Server: initialized notification
Note over Client,Server: Operation Phase
Client->>Server: tools/list request
Server->>Client: tools/list response
Client->>Server: tools/call request
Server->>Client: tools/call response
Note over Client,Server: Termination Phase
Client->>Server: exit notification
Note over Client,Server: Connection Closed
```
### Initialization Phase
The initialization phase establishes the connection and negotiates capabilities:
1. **initialize request**: Client sends protocol version and supported capabilities
2. **initialize response**: Server responds with its version and capabilities
3. **initialized notification**: Client acknowledges initialization
Initialize request example:
```json
{
"jsonrpc": "2.0",
"id": 0,
"method": "initialize",
"params": {
"clientInfo": {
"name": "example-client",
"version": "1.0.0"
},
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"listChanged": true,
"subscribe": true
},
"prompts": {
"listChanged": true
}
}
}
}
```
Initialize response example:
```json
{
"jsonrpc": "2.0",
"id": 0,
"result": {
"serverInfo": {
"name": "example-server",
"version": "1.0.0"
},
"capabilities": {
"tools": {
"listChanged": true
},
"resources": {
"listChanged": true,
"subscribe": true
},
"prompts": {
"listChanged": true
},
"experimental": {}
}
}
}
```
Initialized notification example:
```json
{
"jsonrpc": "2.0",
"method": "initialized",
"params": {}
}
```
### Operation Phase
During the operation phase, clients and servers exchange various requests and notifications:
1. **Feature Discovery**: Listing tools, resources, and prompts
2. **Tool Execution**: Calling tools and receiving results
3. **Resource Access**: Reading resources and subscribing to changes
4. **Prompt Usage**: Getting prompt templates
5. **Notifications**: Receiving updates about changes
### Termination Phase
The termination phase cleanly closes the connection:
1. **exit notification**: Client indicates it's closing the connection
2. **Connection closure**: Transport connection is closed
Exit notification example:
```json
{
"jsonrpc": "2.0",
"method": "exit",
"params": {}
}
```
## Message Types and Methods
MCP defines several standard message types for different operations:
### Tools Methods
| Method | Type | Description |
|--------|------|-------------|
| `tools/list` | Request/Response | List available tools |
| `tools/call` | Request/Response | Execute a tool with parameters |
| `notifications/tools/list_changed` | Notification | Notify that the tool list has changed |
Example tools/list request:
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/list"
}
```
Example tools/list response:
```json
{
"jsonrpc": "2.0",
"id": 1,
"result": {
"tools": [
{
"name": "web_scrape",
"description": "Scrape content from a URL",
"inputSchema": {
"type": "object",
"properties": {
"url": {
"type": "string",
"description": "The URL to scrape"
}
},
"required": ["url"]
}
}
]
}
}
```
### Resources Methods
| Method | Type | Description |
|--------|------|-------------|
| `resources/list` | Request/Response | List available resources |
| `resources/read` | Request/Response | Read a resource by URI |
| `resources/subscribe` | Request/Response | Subscribe to resource updates |
| `resources/unsubscribe` | Request/Response | Unsubscribe from resource updates |
| `notifications/resources/list_changed` | Notification | Notify that the resource list has changed |
| `notifications/resources/updated` | Notification | Notify that a resource has been updated |
Example resources/read request:
```json
{
"jsonrpc": "2.0",
"id": 2,
"method": "resources/read",
"params": {
"uri": "file:///path/to/file.txt"
}
}
```
Example resources/read response:
```json
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"contents": [
{
"uri": "file:///path/to/file.txt",
"text": "File content goes here",
"mimeType": "text/plain"
}
]
}
}
```
### Prompts Methods
| Method | Type | Description |
|--------|------|-------------|
| `prompts/list` | Request/Response | List available prompts |
| `prompts/get` | Request/Response | Get a prompt by name |
| `notifications/prompts/list_changed` | Notification | Notify that the prompt list has changed |
Example prompts/get request:
```json
{
"jsonrpc": "2.0",
"id": 3,
"method": "prompts/get",
"params": {
"name": "code_review",
"arguments": {
"language": "python",
"code": "def hello(): print('Hello, world!')"
}
}
}
```
Example prompts/get response:
```json
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"messages": [
{
"role": "user",
"content": {
"type": "text",
"text": "Please review this Python code:\n\ndef hello(): print('Hello, world!')"
}
}
]
}
}
```
### Logging and Progress
| Method | Type | Description |
|--------|------|-------------|
| `notifications/logging/message` | Notification | Log a message |
| `notifications/progress` | Notification | Report progress of a long-running operation |
Example logging notification:
```json
{
"jsonrpc": "2.0",
"method": "notifications/logging/message",
"params": {
"level": "info",
"message": "Operation started",
"data": {
"operation": "file_processing"
}
}
}
```
Example progress notification:
```json
{
"jsonrpc": "2.0",
"method": "notifications/progress",
"params": {
"token": "operation-123",
"value": 50
}
}
```
## Error Codes
MCP uses standard JSON-RPC error codes plus additional codes for specific errors:
| Code | Name | Description |
|------|------|-------------|
| -32700 | Parse Error | Invalid JSON |
| -32600 | Invalid Request | Request not conforming to JSON-RPC |
| -32601 | Method Not Found | Method not supported |
| -32602 | Invalid Params | Invalid parameters |
| -32603 | Internal Error | Internal server error |
| -32000 | Server Error | Server-specific error |
| -32001 | Resource Not Found | Resource URI not found |
| -32002 | Tool Not Found | Tool name not found |
| -32003 | Prompt Not Found | Prompt name not found |
| -32004 | Execution Failed | Tool execution failed |
| -32005 | Permission Denied | Operation not permitted |
## Protocol Extensions
The MCP protocol supports extensions through the "experimental" capability field:
```json
{
"capabilities": {
"experimental": {
"customFeature": {
"enabled": true,
"options": { ... }
}
}
}
}
```
Extensions should follow these guidelines:
1. Use namespaced method names (e.g., "customFeature/operation")
2. Document the extension clearly
3. Provide fallback behavior when the extension is not supported
4. Consider standardization for widely used extensions
## Troubleshooting Protocol Issues
Common protocol issues include:
### Initialization Problems
1. **Version Mismatch**: Client and server using incompatible protocol versions
- Check version in initialize request/response
- Update client or server to compatible versions
2. **Capability Negotiation Failure**: Client and server capabilities don't match
- Verify capabilities in initialize request/response
- Update client or server to support required capabilities
### Message Format Issues
1. **Invalid JSON**: Message contains malformed JSON
- Check message format before sending
- Validate JSON with a schema
2. **Missing Fields**: Required fields are missing
- Ensure all required fields are present
- Use a protocol validation library
3. **Incorrect Types**: Fields have incorrect types
- Validate field types before sending
- Use typed interfaces for messages
### Transport Issues
1. **Connection Lost**: Transport connection unexpectedly closed
- Implement reconnection logic
- Handle connection failures gracefully
2. **Message Framing**: Incorrect message framing (STDIO)
- Ensure Content-Length is correct
- Validate message framing format
3. **SSE Connection**: SSE connection issues
- Check network connectivity
- Verify SSE endpoint is accessible
### Tool Call Issues
1. **Invalid Parameters**: Tool parameters don't match schema
- Validate parameters against schema
- Provide descriptive error messages
2. **Execution Failure**: Tool execution fails
- Handle exceptions in tool implementation
- Return appropriate error responses
### Debugging Techniques
1. **Message Logging**: Log all protocol messages
- Set up logging before and after sending/receiving
- Log both raw and parsed messages
2. **Protocol Tracing**: Enable protocol tracing
- Set environment variables for trace logging
- Use MCP Inspector for visual tracing
3. **Transport Monitoring**: Monitor transport state
- Check connection status
- Log transport events
## Conclusion
Understanding the MCP communication protocols is essential for building robust MCP servers and clients. By following the standard message formats and transport mechanisms, you can ensure reliable communication between LLMs and external tools and data sources.
In the next document, we'll explore common troubleshooting techniques and solutions for MCP servers.