added specs
This commit is contained in:
parent
9681ec94e6
commit
21a38ebe74
15 changed files with 3532 additions and 60 deletions
|
|
@ -1,8 +1,355 @@
|
|||
---
|
||||
title: "Connection Management"
|
||||
description: "Manage MCP client connections"
|
||||
title: 'Connection Management'
|
||||
description: 'Learn how to establish and manage MCP client connections'
|
||||
---
|
||||
|
||||
# Connection Management
|
||||
## Overview
|
||||
|
||||
Manage MCP client connections
|
||||
The MCP client API provides flexible connection management options with support for different transport types. This guide covers establishing connections, handling connection events, and managing connection lifecycles.
|
||||
|
||||
## Creating a client
|
||||
|
||||
First, create an MCP client instance with your application's information.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
import { Client } from "@modelcontextprotocol/sdk";
|
||||
|
||||
const client = new Client({
|
||||
name: "my-application",
|
||||
version: "1.0.0"
|
||||
});
|
||||
```
|
||||
|
||||
```python Python
|
||||
from mcp import Client
|
||||
|
||||
client = Client(
|
||||
name="my-application",
|
||||
version="1.0.0"
|
||||
)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Transport types
|
||||
|
||||
MCP supports multiple transport types for different use cases. Choose the appropriate transport based on your needs.
|
||||
|
||||
### WebSocket transport
|
||||
|
||||
Use WebSocket transport for real-time bidirectional communication.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
import { WebSocketClientTransport } from "@modelcontextprotocol/sdk";
|
||||
|
||||
const transport = new WebSocketClientTransport(
|
||||
new URL("ws://localhost:3000")
|
||||
);
|
||||
|
||||
await client.connect(transport);
|
||||
```
|
||||
|
||||
```python Python
|
||||
from mcp.transports import WebSocketTransport
|
||||
|
||||
transport = WebSocketTransport(
|
||||
url="ws://localhost:3000"
|
||||
)
|
||||
|
||||
await client.connect(transport)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### SSE transport
|
||||
|
||||
Server-Sent Events transport is suitable for scenarios where the server pushes data to the client.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
import { SSEClientTransport } from "@modelcontextprotocol/sdk";
|
||||
|
||||
const transport = new SSEClientTransport(
|
||||
new URL("http://localhost:3000/events")
|
||||
);
|
||||
|
||||
await client.connect(transport);
|
||||
```
|
||||
|
||||
```python Python
|
||||
from mcp.transports import SSETransport
|
||||
|
||||
transport = SSETransport(
|
||||
url="http://localhost:3000/events"
|
||||
)
|
||||
|
||||
await client.connect(transport)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Stdio transport
|
||||
|
||||
For local server processes, use the stdio transport to communicate via standard input/output.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
import { StdioClientTransport } from "@modelcontextprotocol/sdk";
|
||||
|
||||
const transport = new StdioClientTransport({
|
||||
command: "/path/to/server",
|
||||
args: ["--arg1", "--arg2"],
|
||||
env: { "KEY": "value" }
|
||||
});
|
||||
|
||||
await client.connect(transport);
|
||||
```
|
||||
|
||||
```python Python
|
||||
from mcp.transports import StdioTransport
|
||||
|
||||
transport = StdioTransport(
|
||||
command="/path/to/server",
|
||||
args=["--arg1", "--arg2"],
|
||||
env={"KEY": "value"}
|
||||
)
|
||||
|
||||
await client.connect(transport)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Connection lifecycle
|
||||
|
||||
### Event handling
|
||||
|
||||
Set up event handlers to manage the connection lifecycle.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
client.onclose = () => {
|
||||
console.log("Connection closed");
|
||||
};
|
||||
|
||||
client.onerror = (error) => {
|
||||
console.error("Connection error:", error);
|
||||
};
|
||||
|
||||
// Transport-specific events
|
||||
transport.onclose = () => {
|
||||
console.log("Transport closed");
|
||||
};
|
||||
|
||||
transport.onerror = (error) => {
|
||||
console.error("Transport error:", error);
|
||||
};
|
||||
```
|
||||
|
||||
```python Python
|
||||
@client.on_close
|
||||
async def handle_close():
|
||||
print("Connection closed")
|
||||
|
||||
@client.on_error
|
||||
async def handle_error(error):
|
||||
print(f"Connection error: {error}")
|
||||
|
||||
# Transport-specific events
|
||||
@transport.on_close
|
||||
async def handle_transport_close():
|
||||
print("Transport closed")
|
||||
|
||||
@transport.on_error
|
||||
async def handle_transport_error(error):
|
||||
print(f"Transport error: {error}")
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
### Connection states
|
||||
|
||||
Monitor and manage connection states throughout your application's lifecycle.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
// Check if connected
|
||||
if (client.transport) {
|
||||
console.log("Client is connected");
|
||||
}
|
||||
|
||||
// Get server capabilities after connection
|
||||
const capabilities = client.getServerCapabilities();
|
||||
const serverVersion = client.getServerVersion();
|
||||
|
||||
// Clean up connection
|
||||
await client.close();
|
||||
```
|
||||
|
||||
```python Python
|
||||
# Check if connected
|
||||
if client.transport:
|
||||
print("Client is connected")
|
||||
|
||||
# Get server capabilities after connection
|
||||
capabilities = client.get_server_capabilities()
|
||||
server_version = client.get_server_version()
|
||||
|
||||
# Clean up connection
|
||||
await client.close()
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Error handling
|
||||
|
||||
Implement robust error handling for connection-related issues.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
try {
|
||||
await client.connect(transport);
|
||||
} catch (error) {
|
||||
if (error.code === ErrorCode.ConnectionClosed) {
|
||||
// Handle connection closure
|
||||
} else if (error.code === ErrorCode.InvalidRequest) {
|
||||
// Handle invalid request
|
||||
} else {
|
||||
// Handle other errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```python Python
|
||||
try:
|
||||
await client.connect(transport)
|
||||
except ConnectionClosedError:
|
||||
# Handle connection closure
|
||||
pass
|
||||
except InvalidRequestError:
|
||||
# Handle invalid request
|
||||
pass
|
||||
except McpError as e:
|
||||
# Handle other errors
|
||||
pass
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Best practices
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Connection management" icon="plug">
|
||||
Always close connections when they're no longer needed to free up resources
|
||||
</Card>
|
||||
<Card title="Error handling" icon="shield-check">
|
||||
Implement comprehensive error handling for all connection states
|
||||
</Card>
|
||||
<Card title="Event handling" icon="bell">
|
||||
Set up appropriate event handlers before establishing connections
|
||||
</Card>
|
||||
<Card title="Resource cleanup" icon="broom">
|
||||
Use try/finally blocks to ensure proper cleanup of resources
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Connection examples
|
||||
|
||||
Here are some common connection patterns:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Basic connection with retry">
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
async function connectWithRetry(
|
||||
client: Client,
|
||||
transport: Transport,
|
||||
maxAttempts = 3
|
||||
) {
|
||||
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
||||
try {
|
||||
await client.connect(transport);
|
||||
return;
|
||||
} catch (error) {
|
||||
if (attempt === maxAttempts) throw error;
|
||||
await new Promise(resolve => setTimeout(resolve, 1000 * attempt));
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```python Python
|
||||
async def connect_with_retry(
|
||||
client: Client,
|
||||
transport: Transport,
|
||||
max_attempts: int = 3
|
||||
):
|
||||
for attempt in range(max_attempts):
|
||||
try:
|
||||
await client.connect(transport)
|
||||
return
|
||||
except Exception as e:
|
||||
if attempt == max_attempts - 1:
|
||||
raise
|
||||
await asyncio.sleep(attempt + 1)
|
||||
```
|
||||
</CodeGroup>
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Connection with timeout">
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
async function connectWithTimeout(
|
||||
client: Client,
|
||||
transport: Transport,
|
||||
timeoutMs = 5000
|
||||
) {
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => reject(new Error("Connection timeout")), timeoutMs);
|
||||
});
|
||||
|
||||
await Promise.race([
|
||||
client.connect(transport),
|
||||
timeoutPromise
|
||||
]);
|
||||
}
|
||||
```
|
||||
|
||||
```python Python
|
||||
async def connect_with_timeout(
|
||||
client: Client,
|
||||
transport: Transport,
|
||||
timeout: float = 5.0
|
||||
):
|
||||
async with asyncio.timeout(timeout):
|
||||
await client.connect(transport)
|
||||
```
|
||||
</CodeGroup>
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Connection refused">
|
||||
- Verify the server is running and accessible
|
||||
- Check if the port is correct and available
|
||||
- Ensure firewalls aren't blocking the connection
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Authentication failures">
|
||||
- Verify credentials if required
|
||||
- Check if environment variables are properly set
|
||||
- Ensure transport configuration is correct
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Connection drops">
|
||||
- Implement reconnection logic
|
||||
- Check network stability
|
||||
- Monitor server health
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Transport selection guide
|
||||
|
||||
Choose the appropriate transport based on your use case:
|
||||
|
||||
| Transport | Use Case | Advantages | Considerations |
|
||||
|-----------|----------|------------|----------------|
|
||||
| WebSocket | Real-time bidirectional | Full-duplex, low latency | Requires WebSocket server |
|
||||
| SSE | Server push updates | Simple, firewall-friendly | One-way server push |
|
||||
| Stdio | Local process communication | Simple, reliable | Limited to local processes |
|
||||
|
|
@ -1,8 +1,418 @@
|
|||
---
|
||||
title: "Resource Consumption"
|
||||
description: "Work with MCP resources from clients"
|
||||
title: 'Resource Consumption'
|
||||
description: 'Learn how to consume resources from MCP servers'
|
||||
---
|
||||
|
||||
# Resource Consumption
|
||||
MCP servers can expose resources that clients can access. This guide explains how to list available resources, read their contents, and subscribe to resource updates using both Python and TypeScript.
|
||||
|
||||
Work with MCP resources from clients
|
||||
## Listing resources
|
||||
|
||||
To get a list of available resources from an MCP server:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript list_resources.ts
|
||||
import { Client } from "@modelcontextprotocol/sdk";
|
||||
|
||||
const client = new Client({
|
||||
name: "my-client",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
// Connect to the server
|
||||
await client.connect(transport);
|
||||
|
||||
// List available resources
|
||||
const result = await client.request(
|
||||
{
|
||||
method: "resources/list"
|
||||
},
|
||||
ListResourcesResultSchema
|
||||
);
|
||||
|
||||
// Available resources
|
||||
for (const resource of result.resources) {
|
||||
console.log(`Found resource: ${resource.name} (${resource.uri})`);
|
||||
}
|
||||
```
|
||||
|
||||
```python list_resources.py
|
||||
from modelcontextprotocol.client import Client
|
||||
|
||||
client = Client(
|
||||
name="my-client",
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# Connect to the server
|
||||
await client.connect(transport)
|
||||
|
||||
# List available resources
|
||||
result = await client.request({
|
||||
"method": "resources/list"
|
||||
})
|
||||
|
||||
# Available resources
|
||||
for resource in result["resources"]:
|
||||
print(f"Found resource: {resource['name']} ({resource['uri']})")
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Reading resources
|
||||
|
||||
Once you have a resource URI, you can read its contents:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript read_resource.ts
|
||||
// Read a specific resource
|
||||
const result = await client.request(
|
||||
{
|
||||
method: "resources/read",
|
||||
params: {
|
||||
uri: "file:///example.txt"
|
||||
}
|
||||
},
|
||||
ReadResourceResultSchema
|
||||
);
|
||||
|
||||
// Process the contents
|
||||
for (const content of result.contents) {
|
||||
if ("text" in content) {
|
||||
console.log("Text content:", content.text);
|
||||
} else if ("blob" in content) {
|
||||
// Handle binary data (base64 encoded)
|
||||
const binaryData = Buffer.from(content.blob, "base64");
|
||||
console.log("Binary content length:", binaryData.length);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```python read_resource.py
|
||||
# Read a specific resource
|
||||
result = await client.request({
|
||||
"method": "resources/read",
|
||||
"params": {
|
||||
"uri": "file:///example.txt"
|
||||
}
|
||||
})
|
||||
|
||||
# Process the contents
|
||||
for content in result["contents"]:
|
||||
if "text" in content:
|
||||
print("Text content:", content["text"])
|
||||
elif "blob" in content:
|
||||
# Handle binary data (base64 encoded)
|
||||
import base64
|
||||
binary_data = base64.b64decode(content["blob"])
|
||||
print("Binary content length:", len(binary_data))
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Resource templates
|
||||
|
||||
Some servers provide resource templates that can be used to construct resource URIs dynamically:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript list_templates.ts
|
||||
const result = await client.request(
|
||||
{
|
||||
method: "resources/templates/list"
|
||||
},
|
||||
ListResourceTemplatesResultSchema
|
||||
);
|
||||
|
||||
for (const template of result.resourceTemplates) {
|
||||
console.log(`Template: ${template.name}`);
|
||||
console.log(`URI template: ${template.uriTemplate}`);
|
||||
}
|
||||
```
|
||||
|
||||
```python list_templates.py
|
||||
result = await client.request({
|
||||
"method": "resources/templates/list"
|
||||
})
|
||||
|
||||
for template in result["resourceTemplates"]:
|
||||
print(f"Template: {template['name']}")
|
||||
print(f"URI template: {template['uriTemplate']}")
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Subscribing to updates
|
||||
|
||||
You can subscribe to receive notifications when resources change:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript subscribe_resource.ts
|
||||
// Subscribe to changes
|
||||
await client.request(
|
||||
{
|
||||
method: "resources/subscribe",
|
||||
params: {
|
||||
uri: "file:///example.txt"
|
||||
}
|
||||
},
|
||||
EmptyResultSchema
|
||||
);
|
||||
|
||||
// Set up notification handler
|
||||
client.setNotificationHandler(
|
||||
ResourceUpdatedNotificationSchema,
|
||||
async (notification) => {
|
||||
console.log(`Resource updated: ${notification.params.uri}`);
|
||||
|
||||
// Re-read the resource
|
||||
const result = await client.request(
|
||||
{
|
||||
method: "resources/read",
|
||||
params: {
|
||||
uri: notification.params.uri
|
||||
}
|
||||
},
|
||||
ReadResourceResultSchema
|
||||
);
|
||||
|
||||
// Process updated contents...
|
||||
}
|
||||
);
|
||||
|
||||
// Later, unsubscribe when done
|
||||
await client.request(
|
||||
{
|
||||
method: "resources/unsubscribe",
|
||||
params: {
|
||||
uri: "file:///example.txt"
|
||||
}
|
||||
},
|
||||
EmptyResultSchema
|
||||
);
|
||||
```
|
||||
|
||||
```python subscribe_resource.py
|
||||
# Subscribe to changes
|
||||
await client.request({
|
||||
"method": "resources/subscribe",
|
||||
"params": {
|
||||
"uri": "file:///example.txt"
|
||||
}
|
||||
})
|
||||
|
||||
# Set up notification handler
|
||||
@client.notification_handler("notifications/resources/updated")
|
||||
async def handle_update(notification):
|
||||
print(f"Resource updated: {notification['params']['uri']}")
|
||||
|
||||
# Re-read the resource
|
||||
result = await client.request({
|
||||
"method": "resources/read",
|
||||
"params": {
|
||||
"uri": notification["params"]["uri"]
|
||||
}
|
||||
})
|
||||
|
||||
# Process updated contents...
|
||||
|
||||
# Later, unsubscribe when done
|
||||
await client.request({
|
||||
"method": "resources/unsubscribe",
|
||||
"params": {
|
||||
"uri": "file:///example.txt"
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Handling pagination
|
||||
|
||||
When listing resources, servers may paginate results. Here's how to handle pagination:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript pagination.ts
|
||||
let cursor: string | undefined = undefined;
|
||||
|
||||
do {
|
||||
const result = await client.request(
|
||||
{
|
||||
method: "resources/list",
|
||||
params: { cursor }
|
||||
},
|
||||
ListResourcesResultSchema
|
||||
);
|
||||
|
||||
// Process current page of resources
|
||||
for (const resource of result.resources) {
|
||||
console.log(`Resource: ${resource.name}`);
|
||||
}
|
||||
|
||||
// Update cursor for next page
|
||||
cursor = result.nextCursor;
|
||||
} while (cursor !== undefined);
|
||||
```
|
||||
|
||||
```python pagination.py
|
||||
cursor = None
|
||||
|
||||
while True:
|
||||
result = await client.request({
|
||||
"method": "resources/list",
|
||||
"params": { "cursor": cursor }
|
||||
})
|
||||
|
||||
# Process current page of resources
|
||||
for resource in result["resources"]:
|
||||
print(f"Resource: {resource['name']}")
|
||||
|
||||
# Update cursor for next page
|
||||
cursor = result.get("nextCursor")
|
||||
if cursor is None:
|
||||
break
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Error handling
|
||||
|
||||
When working with resources, handle potential errors appropriately:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript error_handling.ts
|
||||
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk";
|
||||
|
||||
try {
|
||||
const result = await client.request(
|
||||
{
|
||||
method: "resources/read",
|
||||
params: {
|
||||
uri: "file:///nonexistent.txt"
|
||||
}
|
||||
},
|
||||
ReadResourceResultSchema
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof McpError) {
|
||||
switch (error.code) {
|
||||
case ErrorCode.InvalidRequest:
|
||||
console.error("Invalid resource URI");
|
||||
break;
|
||||
case ErrorCode.MethodNotFound:
|
||||
console.error("Server doesn't support resource reading");
|
||||
break;
|
||||
default:
|
||||
console.error(`Resource error: ${error.message}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```python error_handling.py
|
||||
from modelcontextprotocol.errors import McpError, ErrorCode
|
||||
|
||||
try:
|
||||
result = await client.request({
|
||||
"method": "resources/read",
|
||||
"params": {
|
||||
"uri": "file:///nonexistent.txt"
|
||||
}
|
||||
})
|
||||
except McpError as error:
|
||||
if error.code == ErrorCode.INVALID_REQUEST:
|
||||
print("Invalid resource URI")
|
||||
elif error.code == ErrorCode.METHOD_NOT_FOUND:
|
||||
print("Server doesn't support resource reading")
|
||||
else:
|
||||
print(f"Resource error: {error.message}")
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Best practices
|
||||
|
||||
When consuming resources from MCP servers:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Resource cleanup" icon="broom">
|
||||
Always unsubscribe from resources when you're done with them
|
||||
</Card>
|
||||
<Card title="Error handling" icon="shield">
|
||||
Implement proper error handling for all resource operations
|
||||
</Card>
|
||||
<Card title="Content types" icon="file">
|
||||
Check MIME types before processing resource contents
|
||||
</Card>
|
||||
<Card title="Pagination" icon="list">
|
||||
Always handle pagination when listing resources
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Type safety
|
||||
|
||||
To ensure type safety when working with resources:
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript type_safety.ts
|
||||
import {
|
||||
Resource,
|
||||
ResourceContents,
|
||||
TextResourceContents,
|
||||
BlobResourceContents
|
||||
} from "@modelcontextprotocol/sdk";
|
||||
|
||||
function isTextContent(
|
||||
content: ResourceContents
|
||||
): content is TextResourceContents {
|
||||
return "text" in content;
|
||||
}
|
||||
|
||||
function isBlobContent(
|
||||
content: ResourceContents
|
||||
): content is BlobResourceContents {
|
||||
return "blob" in content;
|
||||
}
|
||||
|
||||
// Use type guards when processing contents
|
||||
for (const content of result.contents) {
|
||||
if (isTextContent(content)) {
|
||||
// TypeScript knows this is text content
|
||||
const text: string = content.text;
|
||||
} else if (isBlobContent(content)) {
|
||||
// TypeScript knows this is binary content
|
||||
const blob: string = content.blob;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```python type_safety.py
|
||||
from typing import TypedDict, Union, Literal
|
||||
|
||||
class TextContent(TypedDict):
|
||||
type: Literal["text"]
|
||||
text: str
|
||||
uri: str
|
||||
mimeType: str
|
||||
|
||||
class BlobContent(TypedDict):
|
||||
type: Literal["blob"]
|
||||
blob: str
|
||||
uri: str
|
||||
mimeType: str
|
||||
|
||||
ResourceContent = Union[TextContent, BlobContent]
|
||||
|
||||
def process_content(content: ResourceContent) -> None:
|
||||
if content["type"] == "text":
|
||||
text: str = content["text"]
|
||||
else:
|
||||
blob: str = content["blob"]
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
|
@ -1,8 +1,329 @@
|
|||
---
|
||||
title: "Tool Invocation"
|
||||
description: "Invoke MCP tools from clients"
|
||||
title: 'Tool Invocation'
|
||||
description: 'Learn how to invoke tools through the MCP client API'
|
||||
---
|
||||
|
||||
# Tool Invocation
|
||||
MCP allows clients to invoke tools exposed by servers. Tools are discrete operations that servers can perform on behalf of clients, similar to function calls.
|
||||
|
||||
Invoke MCP tools from clients
|
||||
## Getting available tools
|
||||
|
||||
Before invoking tools, you'll need to get a list of available tools from the server.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
import { Client } from "@modelcontextprotocol/sdk/client";
|
||||
import { ListToolsResultSchema } from "@modelcontextprotocol/sdk/types";
|
||||
|
||||
// Assuming client is already connected
|
||||
const result = await client.request(
|
||||
{
|
||||
method: "tools/list"
|
||||
},
|
||||
ListToolsResultSchema
|
||||
);
|
||||
|
||||
// Tools are now available in result.tools
|
||||
for (const tool of result.tools) {
|
||||
console.log(`Found tool: ${tool.name}`);
|
||||
console.log(`Description: ${tool.description}`);
|
||||
}
|
||||
```
|
||||
|
||||
```python Python
|
||||
from mcp.client import Client
|
||||
from mcp.types import ListToolsRequest
|
||||
|
||||
# Assuming client is already connected
|
||||
result = await client.request(
|
||||
ListToolsRequest(method="tools/list"),
|
||||
ListToolsResultSchema
|
||||
)
|
||||
|
||||
# Tools are now available in result.tools
|
||||
for tool in result.tools:
|
||||
print(f"Found tool: {tool.name}")
|
||||
print(f"Description: {tool.description}")
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Tool invocation
|
||||
|
||||
To invoke a tool, you'll use the `tools/call` method with the tool name and any required arguments.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
import { CallToolRequest, CallToolResultSchema } from "@modelcontextprotocol/sdk/types";
|
||||
|
||||
const result = await client.request(
|
||||
{
|
||||
method: "tools/call",
|
||||
params: {
|
||||
name: "example_tool",
|
||||
arguments: {
|
||||
param1: "value1",
|
||||
param2: 42
|
||||
}
|
||||
}
|
||||
},
|
||||
CallToolResultSchema
|
||||
);
|
||||
|
||||
// Tool result is available in result.toolResult
|
||||
console.log("Tool result:", result.toolResult);
|
||||
```
|
||||
|
||||
```python Python
|
||||
from mcp.types import CallToolRequest, CallToolResult
|
||||
|
||||
result = await client.request(
|
||||
CallToolRequest(
|
||||
method="tools/call",
|
||||
params={
|
||||
"name": "example_tool",
|
||||
"arguments": {
|
||||
"param1": "value1",
|
||||
"param2": 42
|
||||
}
|
||||
}
|
||||
),
|
||||
CallToolResult
|
||||
)
|
||||
|
||||
# Tool result is available in result.tool_result
|
||||
print("Tool result:", result.tool_result)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Progress tracking
|
||||
|
||||
For long-running tools, you can track progress using progress notifications.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
const result = await client.request(
|
||||
{
|
||||
method: "tools/call",
|
||||
params: {
|
||||
name: "long_running_tool",
|
||||
arguments: {
|
||||
duration: 10
|
||||
}
|
||||
}
|
||||
},
|
||||
CallToolResultSchema,
|
||||
(progress) => {
|
||||
console.log(`Progress: ${progress.progress}/${progress.total ?? '?'}`);
|
||||
}
|
||||
);
|
||||
```
|
||||
|
||||
```python Python
|
||||
def progress_callback(progress):
|
||||
print(f"Progress: {progress.progress}/{progress.total or '?'}")
|
||||
|
||||
result = await client.request(
|
||||
CallToolRequest(
|
||||
method="tools/call",
|
||||
params={
|
||||
"name": "long_running_tool",
|
||||
"arguments": {
|
||||
"duration": 10
|
||||
}
|
||||
}
|
||||
),
|
||||
CallToolResult,
|
||||
on_progress=progress_callback
|
||||
)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Error handling
|
||||
|
||||
Tool invocations may fail for various reasons. Always handle potential errors appropriately.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
try {
|
||||
const result = await client.request(
|
||||
{
|
||||
method: "tools/call",
|
||||
params: {
|
||||
name: "risky_tool",
|
||||
arguments: {
|
||||
important_param: "value"
|
||||
}
|
||||
}
|
||||
},
|
||||
CallToolResultSchema
|
||||
);
|
||||
} catch (error) {
|
||||
if (error instanceof McpError) {
|
||||
console.error(`Tool error (${error.code}): ${error.message}`);
|
||||
if (error.data) {
|
||||
console.error("Additional error data:", error.data);
|
||||
}
|
||||
} else {
|
||||
console.error("Unexpected error:", error);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```python Python
|
||||
from mcp.errors import McpError
|
||||
|
||||
try:
|
||||
result = await client.request(
|
||||
CallToolRequest(
|
||||
method="tools/call",
|
||||
params={
|
||||
"name": "risky_tool",
|
||||
"arguments": {
|
||||
"important_param": "value"
|
||||
}
|
||||
}
|
||||
),
|
||||
CallToolResult
|
||||
)
|
||||
except McpError as e:
|
||||
print(f"Tool error ({e.code}): {e.message}")
|
||||
if e.data:
|
||||
print("Additional error data:", e.data)
|
||||
except Exception as e:
|
||||
print("Unexpected error:", e)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Complete example
|
||||
|
||||
Here's a complete example showing how to discover and use tools with proper error handling.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript TypeScript
|
||||
import { Client } from "@modelcontextprotocol/sdk/client";
|
||||
import {
|
||||
ListToolsResultSchema,
|
||||
CallToolResultSchema,
|
||||
McpError
|
||||
} from "@modelcontextprotocol/sdk/types";
|
||||
|
||||
async function invokeTools(client: Client) {
|
||||
try {
|
||||
// Get available tools
|
||||
const toolsList = await client.request(
|
||||
{ method: "tools/list" },
|
||||
ListToolsResultSchema
|
||||
);
|
||||
|
||||
// Find a specific tool
|
||||
const tool = toolsList.tools.find(t => t.name === "example_tool");
|
||||
if (!tool) {
|
||||
console.error("Tool not found");
|
||||
return;
|
||||
}
|
||||
|
||||
// Invoke the tool with progress tracking
|
||||
const result = await client.request(
|
||||
{
|
||||
method: "tools/call",
|
||||
params: {
|
||||
name: tool.name,
|
||||
arguments: {
|
||||
// Tool-specific arguments
|
||||
param1: "value1"
|
||||
}
|
||||
}
|
||||
},
|
||||
CallToolResultSchema,
|
||||
(progress) => {
|
||||
console.log(
|
||||
`Progress: ${progress.progress}/${progress.total ?? '?'}`
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
console.log("Tool result:", result.toolResult);
|
||||
} catch (error) {
|
||||
if (error instanceof McpError) {
|
||||
console.error(`Tool error (${error.code}): ${error.message}`);
|
||||
} else {
|
||||
console.error("Unexpected error:", error);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```python Python
|
||||
from mcp.client import Client
|
||||
from mcp.types import ListToolsRequest, CallToolRequest
|
||||
from mcp.errors import McpError
|
||||
|
||||
async def invoke_tools(client: Client):
|
||||
try:
|
||||
# Get available tools
|
||||
tools_list = await client.request(
|
||||
ListToolsRequest(method="tools/list"),
|
||||
ListToolsResultSchema
|
||||
)
|
||||
|
||||
# Find a specific tool
|
||||
tool = next(
|
||||
(t for t in tools_list.tools if t.name == "example_tool"),
|
||||
None
|
||||
)
|
||||
if not tool:
|
||||
print("Tool not found")
|
||||
return
|
||||
|
||||
# Invoke the tool with progress tracking
|
||||
result = await client.request(
|
||||
CallToolRequest(
|
||||
method="tools/call",
|
||||
params={
|
||||
"name": tool.name,
|
||||
"arguments": {
|
||||
# Tool-specific arguments
|
||||
"param1": "value1"
|
||||
}
|
||||
}
|
||||
),
|
||||
CallToolResult,
|
||||
on_progress=lambda p: print(
|
||||
f"Progress: {p.progress}/{p.total or '?'}"
|
||||
)
|
||||
)
|
||||
|
||||
print("Tool result:", result.tool_result)
|
||||
except McpError as e:
|
||||
print(f"Tool error ({e.code}): {e.message}")
|
||||
except Exception as e:
|
||||
print("Unexpected error:", e)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Best practices
|
||||
|
||||
When working with tools:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Validation" icon="check-circle">
|
||||
Always check tool availability before invoking
|
||||
</Card>
|
||||
<Card title="Error handling" icon="shield-check">
|
||||
Implement comprehensive error handling
|
||||
</Card>
|
||||
<Card title="Progress" icon="bars-progress">
|
||||
Use progress callbacks for long-running operations
|
||||
</Card>
|
||||
<Card title="Arguments" icon="input-text">
|
||||
Validate arguments against the tool's input schema
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Common errors
|
||||
|
||||
| Error Code | Description | Resolution |
|
||||
|------------|-------------|------------|
|
||||
| -32601 | Method not found | Verify the tool name exists |
|
||||
| -32602 | Invalid params | Check the tool's input schema |
|
||||
| -32603 | Internal error | Server-side error, check server logs |
|
||||
| -1 | Connection closed | Check connection status |
|
||||
|
|
@ -1,8 +1,241 @@
|
|||
---
|
||||
title: "Error Handling"
|
||||
description: "Handle errors in MCP implementations"
|
||||
title: 'Error Handling'
|
||||
description: 'How to handle and respond to errors in the Model Context Protocol'
|
||||
---
|
||||
|
||||
# Error Handling
|
||||
MCP follows JSON-RPC 2.0's error handling patterns while adding specific error codes and best practices for MCP implementations.
|
||||
|
||||
Handle errors in MCP implementations
|
||||
## Error response structure
|
||||
|
||||
When an error occurs, servers must respond with an error object instead of a result:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"error": {
|
||||
"code": -32601,
|
||||
"message": "Method not found",
|
||||
"data": {
|
||||
// Optional additional error details
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Required fields
|
||||
<CardGroup cols={3}>
|
||||
<Card title="code" icon="hashtag">
|
||||
A number indicating the error type
|
||||
</Card>
|
||||
<Card title="message" icon="message-dots">
|
||||
A concise, single-sentence description
|
||||
</Card>
|
||||
<Card title="data" icon="circle-info">
|
||||
Optional object with additional details
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Standard error codes
|
||||
|
||||
MCP uses both standard JSON-RPC error codes and MCP-specific codes.
|
||||
|
||||
### JSON-RPC standard codes
|
||||
|
||||
| Code | Name | Description | Example |
|
||||
|------|------|-------------|----------|
|
||||
| -32700 | Parse error | Invalid JSON received | Malformed JSON in request |
|
||||
| -32600 | Invalid request | Request object invalid | Missing required fields |
|
||||
| -32601 | Method not found | Method doesn't exist | Calling undefined endpoint |
|
||||
| -32602 | Invalid params | Invalid method parameters | Wrong parameter types |
|
||||
| -32603 | Internal error | Internal JSON-RPC error | Unexpected server error |
|
||||
|
||||
### MCP-specific codes
|
||||
|
||||
| Code | Name | Description |
|
||||
|------|------|-------------|
|
||||
| -1 | Connection closed | Connection was terminated |
|
||||
| Additional codes TBD | | |
|
||||
|
||||
## Common error scenarios
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Initialization errors">
|
||||
Errors that can occur during the connection initialization phase:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"error": {
|
||||
"code": -32600,
|
||||
"message": "Unsupported protocol version",
|
||||
"data": {
|
||||
"requestedVersion": "2023-01-01",
|
||||
"supportedVersions": ["2024-10-07"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Resource errors">
|
||||
Errors when accessing resources:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"error": {
|
||||
"code": -32601,
|
||||
"message": "Resource not found",
|
||||
"data": {
|
||||
"uri": "file:///nonexistent.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Tool invocation errors">
|
||||
Errors during tool execution:
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 3,
|
||||
"error": {
|
||||
"code": -32602,
|
||||
"message": "Invalid tool parameters",
|
||||
"data": {
|
||||
"tool": "query",
|
||||
"invalidParams": ["sql"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Error handling best practices
|
||||
|
||||
<Steps>
|
||||
<Step title="Use appropriate error codes">
|
||||
Choose the most specific error code that applies to the situation. Prefer standard JSON-RPC codes when applicable.
|
||||
</Step>
|
||||
<Step title="Write clear messages">
|
||||
Error messages should be:
|
||||
- Concise (single sentence)
|
||||
- Descriptive
|
||||
- Action-oriented when possible
|
||||
</Step>
|
||||
<Step title="Include helpful data">
|
||||
Add relevant details in the `data` field to help diagnose and resolve the issue:
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": -32602,
|
||||
"message": "Invalid resource URI format",
|
||||
"data": {
|
||||
"uri": "invalid://uri",
|
||||
"expectedFormat": "file:///path/to/resource",
|
||||
"validSchemes": ["file", "http", "https"]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
<Step title="Handle connection errors">
|
||||
Implement proper cleanup and recovery for connection-related errors:
|
||||
```typescript
|
||||
try {
|
||||
await client.connect(transport);
|
||||
} catch (error) {
|
||||
if (error.code === -1) {
|
||||
// Handle connection closed
|
||||
await cleanup();
|
||||
await reconnect();
|
||||
}
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Client-side error handling
|
||||
|
||||
Recommendations for MCP clients:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Request timeout handling">
|
||||
Implement timeouts for requests and handle them gracefully:
|
||||
```typescript
|
||||
const response = await Promise.race([
|
||||
client.request(request),
|
||||
new Promise((_, reject) =>
|
||||
setTimeout(() => reject(new Error('Timeout')), 5000)
|
||||
)
|
||||
]);
|
||||
```
|
||||
</Card>
|
||||
<Card title="Reconnection logic">
|
||||
Implement exponential backoff for reconnection attempts:
|
||||
```typescript
|
||||
async function reconnect(attempts = 0) {
|
||||
try {
|
||||
await client.connect(transport);
|
||||
} catch (error) {
|
||||
if (attempts < maxAttempts) {
|
||||
await delay(Math.min(1000 * Math.pow(2, attempts), 30000));
|
||||
await reconnect(attempts + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Server-side error handling
|
||||
|
||||
Best practices for MCP servers:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Graceful degradation">
|
||||
When possible, continue operating with reduced functionality rather than failing completely
|
||||
</Card>
|
||||
<Card title="Resource cleanup">
|
||||
Always clean up resources when errors occur, especially for long-running operations
|
||||
</Card>
|
||||
<Card title="Error logging">
|
||||
Log errors with appropriate context for debugging while keeping sensitive information secure
|
||||
</Card>
|
||||
<Card title="Rate limiting">
|
||||
Implement rate limiting and return appropriate error responses when limits are exceeded
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Type-safe error handling
|
||||
|
||||
When using TypeScript with MCP:
|
||||
|
||||
```typescript
|
||||
import { McpError, ErrorCode } from "@modelcontextprotocol/sdk";
|
||||
|
||||
try {
|
||||
await server.handleRequest(request);
|
||||
} catch (error) {
|
||||
if (error instanceof McpError) {
|
||||
switch (error.code) {
|
||||
case ErrorCode.ConnectionClosed:
|
||||
// Handle connection closed
|
||||
break;
|
||||
case ErrorCode.MethodNotFound:
|
||||
// Handle unknown method
|
||||
break;
|
||||
default:
|
||||
// Handle other MCP errors
|
||||
}
|
||||
} else {
|
||||
// Handle unexpected errors
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -1,8 +1,267 @@
|
|||
---
|
||||
title: "Message Types"
|
||||
description: "Complete reference of MCP message formats"
|
||||
title: 'Message Types'
|
||||
description: 'Understanding the different types of messages in the Model Context Protocol'
|
||||
---
|
||||
|
||||
# Message Types
|
||||
The Model Context Protocol (MCP) uses JSON-RPC 2.0 as its base protocol and defines several message types for communication between clients and servers.
|
||||
|
||||
Complete reference of MCP message formats
|
||||
## Core message types
|
||||
|
||||
MCP uses three core types of messages for communication:
|
||||
|
||||
<CardGroup cols={3}>
|
||||
<Card title="Requests" icon="arrow-right">
|
||||
Messages that expect a response, identified by a unique ID
|
||||
</Card>
|
||||
<Card title="Responses" icon="arrow-left">
|
||||
Messages sent in reply to requests, containing results or errors
|
||||
</Card>
|
||||
<Card title="Notifications" icon="bell">
|
||||
One-way messages that don't expect a response
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Message structure
|
||||
|
||||
All messages in MCP follow the JSON-RPC 2.0 specification and include specific required fields.
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Request structure">
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "resources/list",
|
||||
"params": {
|
||||
// Optional method parameters
|
||||
}
|
||||
}
|
||||
```
|
||||
Required fields:
|
||||
- `jsonrpc`: Must be "2.0"
|
||||
- `id`: A unique identifier for the request
|
||||
- `method`: The name of the method to invoke
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Response structure">
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
// Method-specific result data
|
||||
}
|
||||
}
|
||||
```
|
||||
Required fields:
|
||||
- `jsonrpc`: Must be "2.0"
|
||||
- `id`: The same ID as the corresponding request
|
||||
- `result`: The result data (or `error` for error responses)
|
||||
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Notification structure">
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/initialized",
|
||||
"params": {
|
||||
// Optional notification parameters
|
||||
}
|
||||
}
|
||||
```
|
||||
Required fields:
|
||||
- `jsonrpc`: Must be "2.0"
|
||||
- `method`: The name of the notification
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Error handling
|
||||
|
||||
When a request cannot be fulfilled, the server responds with an error object instead of a result.
|
||||
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"error": {
|
||||
"code": -32601,
|
||||
"message": "Method not found",
|
||||
"data": {
|
||||
// Optional additional error information
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Standard error codes
|
||||
|
||||
| Code | Message | Description |
|
||||
|------|---------|-------------|
|
||||
| -32700 | Parse error | Invalid JSON received |
|
||||
| -32600 | Invalid request | Request object invalid |
|
||||
| -32601 | Method not found | Requested method doesn't exist |
|
||||
| -32602 | Invalid params | Method parameters invalid |
|
||||
| -32603 | Internal error | Internal JSON-RPC error |
|
||||
|
||||
## Progress tracking
|
||||
|
||||
MCP supports progress tracking for long-running operations through a combination of request parameters and notifications.
|
||||
|
||||
<Steps>
|
||||
<Step title="Include progress token">
|
||||
Add a progress token in the request:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"_meta": {
|
||||
"progressToken": "abc123"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
<Step title="Receive progress notifications">
|
||||
The server sends progress updates as notifications:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/progress",
|
||||
"params": {
|
||||
"progressToken": "abc123",
|
||||
"progress": 50,
|
||||
"total": 100
|
||||
}
|
||||
}
|
||||
```
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
## Message content types
|
||||
|
||||
MCP supports different content types for messages exchanged between the client and server.
|
||||
|
||||
<CodeGroup>
|
||||
```json Text content
|
||||
{
|
||||
"type": "text",
|
||||
"text": "This is a text message"
|
||||
}
|
||||
```
|
||||
|
||||
```json Image content
|
||||
{
|
||||
"type": "image",
|
||||
"data": "base64-encoded-image-data",
|
||||
"mimeType": "image/png"
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Special considerations
|
||||
|
||||
When implementing MCP message handling, keep in mind:
|
||||
|
||||
- All messages must be encoded in UTF-8
|
||||
- Binary data must be base64 encoded
|
||||
- Message IDs should be unique within a session
|
||||
- Error messages should be concise and single-sentence
|
||||
- Progress notifications are optional and may not be supported by all servers
|
||||
|
||||
## Message flow examples
|
||||
|
||||
Here are some common message flow patterns in MCP:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Resource listing">
|
||||
1. Client sends request:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"method": "resources/list"
|
||||
}
|
||||
```
|
||||
|
||||
2. Server responds with resources:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 1,
|
||||
"result": {
|
||||
"resources": [
|
||||
{
|
||||
"uri": "file:///example.txt",
|
||||
"name": "Example File",
|
||||
"mimeType": "text/plain"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Tool invocation with progress">
|
||||
1. Client sends tool request:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"method": "tools/call",
|
||||
"params": {
|
||||
"name": "longRunningTool",
|
||||
"_meta": {
|
||||
"progressToken": "token123"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
2. Server sends progress notifications:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "notifications/progress",
|
||||
"params": {
|
||||
"progressToken": "token123",
|
||||
"progress": 50,
|
||||
"total": 100
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
3. Server sends final response:
|
||||
```json
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": 2,
|
||||
"result": {
|
||||
"toolResult": "Operation completed successfully"
|
||||
}
|
||||
}
|
||||
```
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
||||
## Best practices
|
||||
|
||||
When working with MCP messages:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Error handling" icon="shield-check">
|
||||
Always include appropriate error codes and meaningful error messages
|
||||
</Card>
|
||||
<Card title="Progress updates" icon="bars-progress">
|
||||
Send regular progress updates for long-running operations
|
||||
</Card>
|
||||
<Card title="Content validation" icon="check-double">
|
||||
Validate message content against the appropriate schemas
|
||||
</Card>
|
||||
<Card title="Resource management" icon="database">
|
||||
Clean up resources when connections are closed
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
|
@ -1,8 +1,92 @@
|
|||
---
|
||||
title: "Protocol Overview"
|
||||
description: "Understanding the Model Context Protocol"
|
||||
description: "Learn about the core concepts and architecture of the Model Context Protocol (MCP)"
|
||||
---
|
||||
|
||||
# Protocol Overview
|
||||
The Model Context Protocol (MCP) is a standardized protocol that enables AI applications to securely access external data sources and tools. MCP follows a client-server architecture where AI applications act as clients that can connect to one or more MCP servers.
|
||||
|
||||
Understanding the Model Context Protocol
|
||||
## Key features
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Flexible integration" icon="puzzle-piece">
|
||||
Connect AI models to any data source or tool through a unified protocol
|
||||
</Card>
|
||||
<Card title="Secure access" icon="shield-check">
|
||||
Control what data and capabilities are exposed to AI systems
|
||||
</Card>
|
||||
<Card title="Composable design" icon="cubes">
|
||||
Combine multiple servers together for enhanced functionality
|
||||
</Card>
|
||||
<Card title="Standard interface" icon="code">
|
||||
Build integrations that work consistently across AI applications
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
# Core concepts
|
||||
|
||||
MCP defines several key primitives that form the foundation of the protocol:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Resources" icon="book">
|
||||
Data that can be loaded as context for the AI model. Resources have URIs and can contain text or binary data.
|
||||
</Card>
|
||||
<Card title="Tools" icon="screwdriver-wrench">
|
||||
Functions that the AI can invoke to perform actions, like writing files or calling APIs.
|
||||
</Card>
|
||||
<Card title="Prompts" icon="message">
|
||||
Pre-defined prompt templates that help guide AI interactions in specific ways.
|
||||
</Card>
|
||||
<Card title="Sampling" icon="rotate">
|
||||
Allows servers to request completions from the AI model through the client.
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
# Protocol flow
|
||||
|
||||
## Connection and initialization
|
||||
|
||||
When a client connects to an MCP server, they go through the following initialization sequence:
|
||||
|
||||
<Steps>
|
||||
<Step title="Client connects">
|
||||
The client establishes a connection to the server using one of the supported transports.
|
||||
</Step>
|
||||
<Step title="Initialize request">
|
||||
The client sends an initialize request containing its capabilities and protocol version.
|
||||
</Step>
|
||||
<Step title="Server response">
|
||||
The server responds with its own capabilities and supported protocol version.
|
||||
</Step>
|
||||
<Step title="Initialized notification">
|
||||
The client sends an initialized notification to indicate setup is complete.
|
||||
</Step>
|
||||
</Steps>
|
||||
|
||||
After initialization, the client can begin using the server's resources, tools, and other capabilities.
|
||||
|
||||
## Transport options
|
||||
|
||||
MCP supports multiple transport mechanisms for client-server communication:
|
||||
|
||||
<AccordionGroup>
|
||||
<Accordion title="Standard input/output (stdio)">
|
||||
- Server process reads from stdin and writes to stdout
|
||||
- Messages are newline-terminated JSON
|
||||
- Client manages server process lifetime
|
||||
- State is lost when process exits
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="WebSocket">
|
||||
- Bidirectional communication over WebSocket protocol
|
||||
- Client and server negotiate using 'mcp' subprotocol
|
||||
- Can connect to remote servers
|
||||
- State persists until disconnect
|
||||
</Accordion>
|
||||
|
||||
<Accordion title="Server-Sent Events (SSE)">
|
||||
- Server pushes messages via SSE
|
||||
- Client sends messages via HTTP POST
|
||||
- Supports cross-origin restrictions
|
||||
- State persists until SSE connection closes
|
||||
</Accordion>
|
||||
</AccordionGroup>
|
||||
|
|
@ -3,6 +3,338 @@ title: "Python Class Reference"
|
|||
description: "Complete Python SDK class documentation"
|
||||
---
|
||||
|
||||
# Python Class Reference
|
||||
## Server Class
|
||||
|
||||
Complete Python SDK class documentation
|
||||
The main class for implementing MCP servers in Python.
|
||||
|
||||
```python
|
||||
from mcp_python.server import Server
|
||||
```
|
||||
|
||||
### Constructor
|
||||
|
||||
```python
|
||||
Server(name: str, version: str = "1.0.0")
|
||||
```
|
||||
|
||||
Creates a new MCP server instance.
|
||||
|
||||
**Parameters:**
|
||||
- `name` (str): Name of the server
|
||||
- `version` (str, optional): Version of the server. Defaults to "1.0.0"
|
||||
|
||||
### Decorators
|
||||
|
||||
#### Resource Handling
|
||||
|
||||
##### @server.list_resources()
|
||||
```python
|
||||
@server.list_resources()
|
||||
async def list_resources() -> List[Resource]:
|
||||
"""Lists available resources."""
|
||||
```
|
||||
|
||||
**Returns:** List of `Resource` objects containing:
|
||||
- `uri` (str): Resource identifier
|
||||
- `name` (str): Human-readable name
|
||||
- `description` (str, optional): Resource description
|
||||
- `mimeType` (str, optional): MIME type of the resource
|
||||
|
||||
##### @server.read_resource()
|
||||
```python
|
||||
@server.read_resource()
|
||||
async def read_resource(uri: str) -> Union[str, bytes]:
|
||||
"""Reads content of a specific resource."""
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `uri` (str): Resource URI to read
|
||||
|
||||
**Returns:** Content of the resource as either string or bytes
|
||||
|
||||
##### @server.subscribe_resource()
|
||||
```python
|
||||
@server.subscribe_resource()
|
||||
async def subscribe_resource(uri: str) -> None:
|
||||
"""Handles resource subscription requests."""
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `uri` (str): Resource URI to subscribe to
|
||||
|
||||
##### @server.unsubscribe_resource()
|
||||
```python
|
||||
@server.unsubscribe_resource()
|
||||
async def unsubscribe_resource(uri: str) -> None:
|
||||
"""Handles resource unsubscribe requests."""
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `uri` (str): Resource URI to unsubscribe from
|
||||
|
||||
#### Tool Support
|
||||
|
||||
##### @server.list_tools()
|
||||
```python
|
||||
@server.list_tools()
|
||||
async def list_tools() -> List[Tool]:
|
||||
"""Lists available tools."""
|
||||
```
|
||||
|
||||
**Returns:** List of `Tool` objects containing:
|
||||
- `name` (str): Tool identifier
|
||||
- `description` (str): Human-readable description
|
||||
- `parameters` (dict): JSON Schema defining tool parameters
|
||||
|
||||
##### @server.call_tool()
|
||||
```python
|
||||
@server.call_tool()
|
||||
async def call_tool(name: str, **kwargs) -> Any:
|
||||
"""Handles tool execution requests."""
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `name` (str): Tool identifier
|
||||
- `**kwargs`: Tool-specific parameters
|
||||
|
||||
**Returns:** Tool execution result (any JSON-serializable value)
|
||||
|
||||
#### Prompt Management
|
||||
|
||||
##### @server.list_prompts()
|
||||
```python
|
||||
@server.list_prompts()
|
||||
async def list_prompts() -> List[Prompt]:
|
||||
"""Lists available prompts."""
|
||||
```
|
||||
|
||||
**Returns:** List of `Prompt` objects containing:
|
||||
- `name` (str): Prompt identifier
|
||||
- `description` (str, optional): Human-readable description
|
||||
- `parameters` (dict, optional): JSON Schema defining prompt parameters
|
||||
|
||||
##### @server.get_prompt()
|
||||
```python
|
||||
@server.get_prompt()
|
||||
async def get_prompt(name: str, arguments: dict) -> PromptResponse:
|
||||
"""Retrieves specific prompt templates."""
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `name` (str): Prompt identifier
|
||||
- `arguments` (dict): Prompt parameters
|
||||
|
||||
**Returns:** `PromptResponse` object containing:
|
||||
- `messages` (List[Message]): List of prompt messages
|
||||
- `description` (str, optional): Description of the prompt response
|
||||
|
||||
### Session Methods
|
||||
|
||||
Methods available on the server session object:
|
||||
|
||||
#### send_resource_updated()
|
||||
```python
|
||||
async def send_resource_updated(uri: str) -> None:
|
||||
"""Notifies clients that a resource has been updated."""
|
||||
```
|
||||
|
||||
#### send_progress_notification()
|
||||
```python
|
||||
async def send_progress_notification(
|
||||
token: str,
|
||||
progress: int,
|
||||
total: Optional[int] = None
|
||||
) -> None:
|
||||
"""Sends progress updates for long-running operations."""
|
||||
```
|
||||
|
||||
#### send_log_message()
|
||||
```python
|
||||
async def send_log_message(
|
||||
level: str,
|
||||
data: Any,
|
||||
logger: Optional[str] = None
|
||||
) -> None:
|
||||
"""Sends log messages to clients."""
|
||||
```
|
||||
|
||||
## Data Classes
|
||||
|
||||
### Resource
|
||||
```python
|
||||
@dataclass
|
||||
class Resource:
|
||||
uri: str
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
mimeType: Optional[str] = None
|
||||
```
|
||||
|
||||
### Tool
|
||||
```python
|
||||
@dataclass
|
||||
class Tool:
|
||||
name: str
|
||||
description: str
|
||||
parameters: dict
|
||||
```
|
||||
|
||||
### Prompt
|
||||
```python
|
||||
@dataclass
|
||||
class Prompt:
|
||||
name: str
|
||||
description: Optional[str] = None
|
||||
parameters: Optional[dict] = None
|
||||
```
|
||||
|
||||
### Message
|
||||
```python
|
||||
@dataclass
|
||||
class Message:
|
||||
role: Literal["user", "assistant"]
|
||||
content: Union[TextContent, ImageContent]
|
||||
```
|
||||
|
||||
### PromptResponse
|
||||
```python
|
||||
@dataclass
|
||||
class PromptResponse:
|
||||
messages: List[Message]
|
||||
description: Optional[str] = None
|
||||
```
|
||||
|
||||
## Example Usage
|
||||
|
||||
Here's a complete example demonstrating the main features:
|
||||
|
||||
```python
|
||||
from mcp_python.server import Server
|
||||
import anyio
|
||||
|
||||
server = Server("example-server")
|
||||
|
||||
@server.list_resources()
|
||||
async def list_resources():
|
||||
return [
|
||||
{
|
||||
"uri": "example://resource1",
|
||||
"name": "Example Resource 1",
|
||||
"description": "An example resource"
|
||||
}
|
||||
]
|
||||
|
||||
@server.read_resource()
|
||||
async def read_resource(uri):
|
||||
if uri == "example://resource1":
|
||||
return "Example resource content"
|
||||
raise ValueError(f"Unknown resource: {uri}")
|
||||
|
||||
@server.list_tools()
|
||||
async def list_tools():
|
||||
return [
|
||||
{
|
||||
"name": "example-tool",
|
||||
"description": "An example tool",
|
||||
"parameters": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"param1": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@server.call_tool()
|
||||
async def call_tool(name, **kwargs):
|
||||
if name == "example-tool":
|
||||
return f"Tool executed with params: {kwargs}"
|
||||
raise ValueError(f"Unknown tool: {name}")
|
||||
|
||||
async def main():
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
await server.run(
|
||||
read_stream,
|
||||
write_stream,
|
||||
server.create_initialization_options()
|
||||
)
|
||||
|
||||
if __name__ == "__main__":
|
||||
anyio.run(main)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Error Handling**: Always implement proper error handling in your server methods:
|
||||
```python
|
||||
@server.read_resource()
|
||||
async def read_resource(uri):
|
||||
try:
|
||||
return await fetch_resource(uri)
|
||||
except ResourceNotFoundError as e:
|
||||
raise ValueError(f"Resource not found: {uri}") from e
|
||||
```
|
||||
|
||||
2. **Type Hints**: Use typing hints for better code clarity:
|
||||
```python
|
||||
@server.list_resources()
|
||||
async def list_resources() -> List[Resource]:
|
||||
resources: List[Resource] = []
|
||||
return resources
|
||||
```
|
||||
|
||||
3. **Progress Updates**: For long-running operations, send progress updates:
|
||||
```python
|
||||
@server.call_tool()
|
||||
async def call_tool(name: str, session: ServerSession, **kwargs):
|
||||
for i in range(100):
|
||||
await session.send_progress_notification(
|
||||
"operation-1",
|
||||
progress=i,
|
||||
total=100
|
||||
)
|
||||
```
|
||||
|
||||
4. **Resource Updates**: Notify clients when resources change:
|
||||
```python
|
||||
@server.call_tool()
|
||||
async def call_tool(name: str, session: ServerSession, **kwargs):
|
||||
# ... perform update ...
|
||||
await session.send_resource_updated("example://resource1")
|
||||
```
|
||||
|
||||
## Transport Options
|
||||
|
||||
### stdio Transport
|
||||
```python
|
||||
async with stdio_server() as (read_stream, write_stream):
|
||||
await server.run(read_stream, write_stream)
|
||||
```
|
||||
|
||||
### SSE Transport
|
||||
```python
|
||||
from mcp_python.transport import SSETransport
|
||||
|
||||
app = FastAPI()
|
||||
transport = SSETransport()
|
||||
|
||||
@app.get("/sse")
|
||||
async def sse_endpoint():
|
||||
return await transport.handle_sse()
|
||||
|
||||
@app.post("/message")
|
||||
async def message_endpoint(request: Request):
|
||||
return await transport.handle_message(request)
|
||||
```
|
||||
|
||||
### WebSocket Transport
|
||||
```python
|
||||
from mcp_python.transport import WebSocketTransport
|
||||
|
||||
app = FastAPI()
|
||||
transport = WebSocketTransport()
|
||||
|
||||
@app.websocket("/ws")
|
||||
async def websocket_endpoint(websocket: WebSocket):
|
||||
await transport.handle_websocket(websocket)
|
||||
```
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
title: "Python Examples"
|
||||
description: "Example Python implementations"
|
||||
---
|
||||
|
||||
# Python Examples
|
||||
|
||||
Example Python implementations
|
||||
|
|
@ -3,6 +3,357 @@ title: "Python Type Hints"
|
|||
description: "Python type system documentation"
|
||||
---
|
||||
|
||||
# Python Type Hints
|
||||
## Core Types
|
||||
|
||||
Python type system documentation
|
||||
### Resource Types
|
||||
|
||||
```python
|
||||
from typing import TypedDict, Optional, Literal, Union, List
|
||||
|
||||
class Resource(TypedDict):
|
||||
"""Represents a resource that can be accessed through MCP."""
|
||||
uri: str
|
||||
name: str
|
||||
description: Optional[str]
|
||||
mimeType: Optional[str]
|
||||
|
||||
class ResourceTemplate(TypedDict):
|
||||
"""Template for constructing resource URIs."""
|
||||
uriTemplate: str
|
||||
name: str
|
||||
description: Optional[str]
|
||||
mimeType: Optional[str]
|
||||
|
||||
class ResourceContents(TypedDict):
|
||||
"""Contents of a specific resource."""
|
||||
uri: str
|
||||
mimeType: Optional[str]
|
||||
|
||||
class TextResourceContents(ResourceContents):
|
||||
"""Resource contents in text format."""
|
||||
text: str
|
||||
|
||||
class BlobResourceContents(ResourceContents):
|
||||
"""Resource contents in binary format."""
|
||||
blob: bytes # Base64 encoded
|
||||
```
|
||||
|
||||
### Message Types
|
||||
|
||||
```python
|
||||
class TextContent(TypedDict):
|
||||
"""Text content in a message."""
|
||||
type: Literal["text"]
|
||||
text: str
|
||||
|
||||
class ImageContent(TypedDict):
|
||||
"""Image content in a message."""
|
||||
type: Literal["image"]
|
||||
data: str # Base64 encoded
|
||||
mimeType: str
|
||||
|
||||
MessageContent = Union[TextContent, ImageContent]
|
||||
|
||||
class Message(TypedDict):
|
||||
"""A message in an LLM conversation."""
|
||||
role: Literal["user", "assistant"]
|
||||
content: MessageContent
|
||||
```
|
||||
|
||||
### Tool Types
|
||||
|
||||
```python
|
||||
class ToolParameter(TypedDict):
|
||||
"""Parameter definition for a tool."""
|
||||
type: str
|
||||
description: Optional[str]
|
||||
required: Optional[bool]
|
||||
|
||||
class ToolSchema(TypedDict):
|
||||
"""JSON Schema for tool parameters."""
|
||||
type: Literal["object"]
|
||||
properties: dict[str, ToolParameter]
|
||||
required: Optional[List[str]]
|
||||
|
||||
class Tool(TypedDict):
|
||||
"""Definition of a tool that can be called through MCP."""
|
||||
name: str
|
||||
description: str
|
||||
inputSchema: ToolSchema
|
||||
```
|
||||
|
||||
### Prompt Types
|
||||
|
||||
```python
|
||||
class PromptArgument(TypedDict):
|
||||
"""Argument definition for a prompt template."""
|
||||
name: str
|
||||
description: Optional[str]
|
||||
required: Optional[bool]
|
||||
|
||||
class Prompt(TypedDict):
|
||||
"""A prompt template that can be used through MCP."""
|
||||
name: str
|
||||
description: Optional[str]
|
||||
arguments: Optional[List[PromptArgument]]
|
||||
|
||||
class PromptResponse(TypedDict):
|
||||
"""Response to a prompt request."""
|
||||
description: Optional[str]
|
||||
messages: List[Message]
|
||||
```
|
||||
|
||||
### Progress Types
|
||||
|
||||
```python
|
||||
class Progress(TypedDict):
|
||||
"""Progress information for long-running operations."""
|
||||
progress: int
|
||||
total: Optional[int]
|
||||
```
|
||||
|
||||
### Logging Types
|
||||
|
||||
```python
|
||||
LogLevel = Literal["debug", "info", "warning", "error"]
|
||||
|
||||
class LogMessage(TypedDict):
|
||||
"""A log message sent through MCP."""
|
||||
level: LogLevel
|
||||
logger: Optional[str]
|
||||
data: Any # JSON-serializable value
|
||||
```
|
||||
|
||||
## Request/Response Types
|
||||
|
||||
### Resource Requests
|
||||
|
||||
```python
|
||||
class ListResourcesRequest(TypedDict):
|
||||
"""Request to list available resources."""
|
||||
cursor: Optional[str]
|
||||
|
||||
class ListResourcesResponse(TypedDict):
|
||||
"""Response containing available resources."""
|
||||
resources: List[Resource]
|
||||
nextCursor: Optional[str]
|
||||
|
||||
class ReadResourceRequest(TypedDict):
|
||||
"""Request to read a specific resource."""
|
||||
uri: str
|
||||
|
||||
class ReadResourceResponse(TypedDict):
|
||||
"""Response containing resource contents."""
|
||||
contents: List[Union[TextResourceContents, BlobResourceContents]]
|
||||
|
||||
class SubscribeRequest(TypedDict):
|
||||
"""Request to subscribe to resource updates."""
|
||||
uri: str
|
||||
|
||||
class UnsubscribeRequest(TypedDict):
|
||||
"""Request to unsubscribe from resource updates."""
|
||||
uri: str
|
||||
```
|
||||
|
||||
### Tool Requests
|
||||
|
||||
```python
|
||||
class ListToolsRequest(TypedDict):
|
||||
"""Request to list available tools."""
|
||||
cursor: Optional[str]
|
||||
|
||||
class ListToolsResponse(TypedDict):
|
||||
"""Response containing available tools."""
|
||||
tools: List[Tool]
|
||||
nextCursor: Optional[str]
|
||||
|
||||
class CallToolRequest(TypedDict):
|
||||
"""Request to call a specific tool."""
|
||||
name: str
|
||||
arguments: Optional[dict]
|
||||
|
||||
class CallToolResponse(TypedDict):
|
||||
"""Response from a tool call."""
|
||||
toolResult: Any # JSON-serializable value
|
||||
```
|
||||
|
||||
### Prompt Requests
|
||||
|
||||
```python
|
||||
class ListPromptsRequest(TypedDict):
|
||||
"""Request to list available prompts."""
|
||||
cursor: Optional[str]
|
||||
|
||||
class ListPromptsResponse(TypedDict):
|
||||
"""Response containing available prompts."""
|
||||
prompts: List[Prompt]
|
||||
nextCursor: Optional[str]
|
||||
|
||||
class GetPromptRequest(TypedDict):
|
||||
"""Request to get a specific prompt."""
|
||||
name: str
|
||||
arguments: Optional[dict[str, str]]
|
||||
```
|
||||
|
||||
## Protocol Types
|
||||
|
||||
### Implementation Info
|
||||
|
||||
```python
|
||||
class Implementation(TypedDict):
|
||||
"""Information about an MCP implementation."""
|
||||
name: str
|
||||
version: str
|
||||
```
|
||||
|
||||
### Capabilities
|
||||
|
||||
```python
|
||||
class ResourceCapabilities(TypedDict):
|
||||
"""Capabilities related to resources."""
|
||||
subscribe: Optional[bool]
|
||||
listChanged: Optional[bool]
|
||||
|
||||
class PromptCapabilities(TypedDict):
|
||||
"""Capabilities related to prompts."""
|
||||
listChanged: Optional[bool]
|
||||
|
||||
class ToolCapabilities(TypedDict):
|
||||
"""Capabilities related to tools."""
|
||||
listChanged: Optional[bool]
|
||||
|
||||
class ServerCapabilities(TypedDict):
|
||||
"""Complete set of server capabilities."""
|
||||
experimental: Optional[dict]
|
||||
logging: Optional[dict]
|
||||
prompts: Optional[PromptCapabilities]
|
||||
resources: Optional[ResourceCapabilities]
|
||||
tools: Optional[ToolCapabilities]
|
||||
```
|
||||
|
||||
## Transport Types
|
||||
|
||||
### Stream Types
|
||||
|
||||
```python
|
||||
class ReadStream(Protocol):
|
||||
"""Protocol for reading from a transport."""
|
||||
async def read(self) -> bytes: ...
|
||||
|
||||
class WriteStream(Protocol):
|
||||
"""Protocol for writing to a transport."""
|
||||
async def write(self, data: bytes) -> None: ...
|
||||
```
|
||||
|
||||
### Session Types
|
||||
|
||||
```python
|
||||
class ServerSession(Protocol):
|
||||
"""Protocol for server-side session operations."""
|
||||
async def send_resource_updated(self, uri: str) -> None: ...
|
||||
async def send_progress_notification(
|
||||
self, token: str, progress: int, total: Optional[int] = None
|
||||
) -> None: ...
|
||||
async def send_log_message(
|
||||
self, level: LogLevel, data: Any, logger: Optional[str] = None
|
||||
) -> None: ...
|
||||
```
|
||||
|
||||
## Usage Examples
|
||||
|
||||
### Type Usage in Handlers
|
||||
|
||||
```python
|
||||
from mcp_python.server import Server
|
||||
from mcp_python.types import Resource, Tool, PromptResponse, Message
|
||||
|
||||
server = Server("example")
|
||||
|
||||
@server.list_resources()
|
||||
async def list_resources() -> List[Resource]:
|
||||
return [
|
||||
{
|
||||
"uri": "example://test",
|
||||
"name": "Test Resource",
|
||||
"description": "A test resource",
|
||||
"mimeType": "text/plain"
|
||||
}
|
||||
]
|
||||
|
||||
@server.list_tools()
|
||||
async def list_tools() -> List[Tool]:
|
||||
return [
|
||||
{
|
||||
"name": "test-tool",
|
||||
"description": "A test tool",
|
||||
"inputSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"param": {
|
||||
"type": "string",
|
||||
"description": "A parameter"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@server.get_prompt()
|
||||
async def get_prompt(name: str, arguments: dict) -> PromptResponse:
|
||||
return {
|
||||
"description": "A test prompt",
|
||||
"messages": [
|
||||
{
|
||||
"role": "user",
|
||||
"content": {
|
||||
"type": "text",
|
||||
"text": f"Test prompt with args: {arguments}"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
### Type Checking
|
||||
|
||||
```python
|
||||
from typing import TypeGuard
|
||||
|
||||
def is_text_content(content: MessageContent) -> TypeGuard[TextContent]:
|
||||
"""Type guard for text content."""
|
||||
return content["type"] == "text"
|
||||
|
||||
def process_message(message: Message) -> str:
|
||||
content = message["content"]
|
||||
if is_text_content(content):
|
||||
return content["text"]
|
||||
return f"Image message with type: {content['mimeType']}"
|
||||
```
|
||||
|
||||
## Additional Notes
|
||||
|
||||
- All types are compatible with standard Python type checkers (mypy, pyright)
|
||||
- TypedDict is used extensively to ensure proper structure of JSON-like objects
|
||||
- Literal types are used to restrict string values to specific options
|
||||
- Optional fields use the Optional type hint to indicate nullable values
|
||||
- Protocol classes define interface contracts for pluggable components
|
||||
|
||||
For runtime type checking, you can use the following pattern:
|
||||
|
||||
```python
|
||||
from typing import cast
|
||||
from typing_extensions import assert_type
|
||||
|
||||
def validate_resource(data: dict) -> Resource:
|
||||
"""Validate and convert a dict to a Resource type."""
|
||||
required_fields = {"uri", "name"}
|
||||
if not all(field in data for field in required_fields):
|
||||
raise ValueError("Missing required fields")
|
||||
|
||||
return cast(Resource, {
|
||||
"uri": str(data["uri"]),
|
||||
"name": str(data["name"]),
|
||||
"description": str(data.get("description")) if "description" in data else None,
|
||||
"mimeType": str(data.get("mimeType")) if "mimeType" in data else None
|
||||
})
|
||||
```
|
||||
|
|
@ -1,8 +1,330 @@
|
|||
---
|
||||
title: "Event Handling"
|
||||
description: "Handle events in your MCP server"
|
||||
title: 'Event Handling'
|
||||
description: 'Handle events and manage state in your MCP server'
|
||||
---
|
||||
|
||||
# Event Handling
|
||||
MCP servers process various events including requests, notifications, and connection lifecycle events. This guide explains how to implement event handlers effectively in your server.
|
||||
|
||||
Handle events in your MCP server
|
||||
## Request handlers
|
||||
|
||||
Request handlers process incoming requests from clients and must return a response. Each handler is associated with a specific method name.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript server.ts
|
||||
import { Server } from "@modelcontextprotocol/sdk/server";
|
||||
import { ListToolsRequestSchema, ListToolsResultSchema } from "@modelcontextprotocol/sdk/types";
|
||||
|
||||
const server = new Server({
|
||||
name: "example-server",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
server.setRequestHandler(ListToolsRequestSchema, async (request) => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "example-tool",
|
||||
description: "An example tool",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
message: { type: "string" },
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
```python server.py
|
||||
from mcp import Server
|
||||
from mcp.types import ListToolsRequest, ListToolsResult, Tool
|
||||
|
||||
server = Server(name="example-server", version="1.0.0")
|
||||
|
||||
@server.request_handler("tools/list")
|
||||
async def handle_list_tools(request: ListToolsRequest) -> ListToolsResult:
|
||||
return ListToolsResult(
|
||||
tools=[
|
||||
Tool(
|
||||
name="example-tool",
|
||||
description="An example tool",
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {"type": "string"}
|
||||
}
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Notification handlers
|
||||
|
||||
Notification handlers process one-way messages that don't require responses. They're useful for handling events like resource updates or logging messages.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript notifications.ts
|
||||
import { ResourceUpdatedNotificationSchema } from "@modelcontextprotocol/sdk/types";
|
||||
|
||||
server.setNotificationHandler(ResourceUpdatedNotificationSchema, async (notification) => {
|
||||
const { uri } = notification.params;
|
||||
console.log(`Resource updated: ${uri}`);
|
||||
});
|
||||
```
|
||||
|
||||
```python notifications.py
|
||||
from mcp.types import ResourceUpdatedNotification
|
||||
|
||||
@server.notification_handler("notifications/resources/updated")
|
||||
async def handle_resource_updated(notification: ResourceUpdatedNotification):
|
||||
uri = notification.params.uri
|
||||
print(f"Resource updated: {uri}")
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Connection lifecycle
|
||||
|
||||
Your server can handle connection lifecycle events through callbacks. This is useful for setup and cleanup operations.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript lifecycle.ts
|
||||
const server = new Server({
|
||||
name: "example-server",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
server.oninitialized = () => {
|
||||
console.log("Client has completed initialization");
|
||||
};
|
||||
|
||||
server.onclose = () => {
|
||||
console.log("Connection closed");
|
||||
// Perform cleanup
|
||||
};
|
||||
```
|
||||
|
||||
```python lifecycle.py
|
||||
server = Server(name="example-server", version="1.0.0")
|
||||
|
||||
@server.on_initialized
|
||||
async def handle_initialized():
|
||||
print("Client has completed initialization")
|
||||
|
||||
@server.on_close
|
||||
async def handle_close():
|
||||
print("Connection closed")
|
||||
# Perform cleanup
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Error handling
|
||||
|
||||
Properly handle errors in your event handlers to provide meaningful feedback to clients.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript errors.ts
|
||||
import { ErrorCode, McpError } from "@modelcontextprotocol/sdk/types";
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
try {
|
||||
// Attempt to call tool
|
||||
const result = await callTool(request.params);
|
||||
return { toolResult: result };
|
||||
} catch (error) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
"Failed to execute tool",
|
||||
{ details: error.message }
|
||||
);
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
```python errors.py
|
||||
from mcp.types import CallToolRequest, McpError, ErrorCode
|
||||
|
||||
@server.request_handler("tools/call")
|
||||
async def handle_call_tool(request: CallToolRequest):
|
||||
try:
|
||||
# Attempt to call tool
|
||||
result = await call_tool(request.params)
|
||||
return {"toolResult": result}
|
||||
except Exception as e:
|
||||
raise McpError(
|
||||
code=ErrorCode.INVALID_PARAMS,
|
||||
message="Failed to execute tool",
|
||||
data={"details": str(e)}
|
||||
)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Progress reporting
|
||||
|
||||
For long-running operations, you can report progress back to the client.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript progress.ts
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
const progressToken = request.params._meta?.progressToken;
|
||||
|
||||
if (progressToken) {
|
||||
await server.notification({
|
||||
method: "notifications/progress",
|
||||
params: {
|
||||
progressToken,
|
||||
progress: 50,
|
||||
total: 100
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Complete the operation
|
||||
return { toolResult: "Operation completed" };
|
||||
});
|
||||
```
|
||||
|
||||
```python progress.py
|
||||
@server.request_handler("tools/call")
|
||||
async def handle_call_tool(request: CallToolRequest):
|
||||
progress_token = request.params._meta.progress_token if request.params._meta else None
|
||||
|
||||
if progress_token:
|
||||
await server.notify(
|
||||
"notifications/progress",
|
||||
{
|
||||
"progressToken": progress_token,
|
||||
"progress": 50,
|
||||
"total": 100
|
||||
}
|
||||
)
|
||||
|
||||
# Complete the operation
|
||||
return {"toolResult": "Operation completed"}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## State management
|
||||
|
||||
While MCP servers are generally stateless, you might need to maintain some state between requests. Here's how to manage state safely:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript state.ts
|
||||
class ExampleServer {
|
||||
private subscriptions: Set<string> = new Set();
|
||||
private server: Server;
|
||||
|
||||
constructor() {
|
||||
this.server = new Server({
|
||||
name: "example-server",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
this.server.setRequestHandler(SubscribeRequestSchema, async (request) => {
|
||||
const { uri } = request.params;
|
||||
this.subscriptions.add(uri);
|
||||
return {};
|
||||
});
|
||||
|
||||
this.server.setRequestHandler(UnsubscribeRequestSchema, async (request) => {
|
||||
const { uri } = request.params;
|
||||
this.subscriptions.delete(uri);
|
||||
return {};
|
||||
});
|
||||
|
||||
this.server.onclose = () => {
|
||||
this.subscriptions.clear();
|
||||
};
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```python state.py
|
||||
class ExampleServer:
|
||||
def __init__(self):
|
||||
self.subscriptions = set()
|
||||
self.server = Server(name="example-server", version="1.0.0")
|
||||
|
||||
@self.server.request_handler("resources/subscribe")
|
||||
async def handle_subscribe(request: SubscribeRequest):
|
||||
self.subscriptions.add(request.params.uri)
|
||||
return {}
|
||||
|
||||
@self.server.request_handler("resources/unsubscribe")
|
||||
async def handle_unsubscribe(request: UnsubscribeRequest):
|
||||
self.subscriptions.remove(request.params.uri)
|
||||
return {}
|
||||
|
||||
@self.server.on_close
|
||||
async def handle_close():
|
||||
self.subscriptions.clear()
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Best practices
|
||||
|
||||
When implementing event handlers in your MCP server:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Validation" icon="shield-check">
|
||||
Always validate request parameters before processing
|
||||
</Card>
|
||||
<Card title="Error handling" icon="triangle-exclamation">
|
||||
Use appropriate error codes and provide helpful error messages
|
||||
</Card>
|
||||
<Card title="Async operations" icon="spinner">
|
||||
Handle long-running operations asynchronously and report progress
|
||||
</Card>
|
||||
<Card title="Resource cleanup" icon="broom">
|
||||
Clean up resources and state when connections are closed
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Debugging tips
|
||||
|
||||
To help debug your event handlers:
|
||||
|
||||
1. Enable logging in your server:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript logging.ts
|
||||
server.setRequestHandler(SetLevelRequestSchema, async (request) => {
|
||||
const { level } = request.params;
|
||||
|
||||
server.notification({
|
||||
method: "notifications/message",
|
||||
params: {
|
||||
level: "debug",
|
||||
data: `Setting log level to: ${level}`
|
||||
}
|
||||
});
|
||||
|
||||
return {};
|
||||
});
|
||||
```
|
||||
|
||||
```python logging.py
|
||||
@server.request_handler("logging/setLevel")
|
||||
async def handle_set_level(request: SetLevelRequest):
|
||||
level = request.params.level
|
||||
|
||||
await server.notify(
|
||||
"notifications/message",
|
||||
{
|
||||
"level": "debug",
|
||||
"data": f"Setting log level to: {level}"
|
||||
}
|
||||
)
|
||||
|
||||
return {}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
2. Use the progress reporting mechanism to debug long-running operations
|
||||
3. Implement comprehensive error handling with detailed error messages
|
||||
4. Add request/response logging for debugging complex interactions
|
||||
|
||||
Remember that logging and debugging information should be appropriate for the execution environment and should never expose sensitive information.
|
||||
|
|
@ -1,8 +1,385 @@
|
|||
---
|
||||
title: "Resource Management"
|
||||
description: "Manage resources in your MCP server"
|
||||
title: 'Resource Management'
|
||||
description: 'How to implement resource management in your MCP server'
|
||||
---
|
||||
|
||||
# Resource Management
|
||||
Resources in MCP are any type of content that can be loaded as context for an LLM. This guide explains how to implement resource management in your MCP server.
|
||||
|
||||
Manage resources in your MCP server
|
||||
## Overview
|
||||
|
||||
Resources are identified by URIs and can represent:
|
||||
- Files on the local filesystem
|
||||
- Remote data (e.g., database records, API responses)
|
||||
- Generated content
|
||||
- Dynamic system state
|
||||
|
||||
## Basic resource implementation
|
||||
|
||||
To implement resources in your MCP server, you need to handle two main requests:
|
||||
- `resources/list`: Lists available resources
|
||||
- `resources/read`: Reads the content of a specific resource
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript server.ts
|
||||
import { Server } from "@modelcontextprotocol/sdk/server";
|
||||
import {
|
||||
ListResourcesRequestSchema,
|
||||
ReadResourceRequestSchema
|
||||
} from "@modelcontextprotocol/sdk/types";
|
||||
|
||||
const server = new Server({
|
||||
name: "example-server",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
// Handle resource listing
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
return {
|
||||
resources: [
|
||||
{
|
||||
uri: "file:///example.txt",
|
||||
name: "Example File",
|
||||
mimeType: "text/plain",
|
||||
description: "An example text file resource"
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
// Handle resource reading
|
||||
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||
const uri = request.params.uri;
|
||||
|
||||
// Read and return the resource content
|
||||
return {
|
||||
contents: [
|
||||
{
|
||||
uri: uri,
|
||||
mimeType: "text/plain",
|
||||
text: "Example content"
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
```python server.py
|
||||
from mcp import Server
|
||||
from mcp.types import Resource, ResourceContents
|
||||
|
||||
server = Server(
|
||||
name="example-server",
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# Handle resource listing
|
||||
@server.request_handler("resources/list")
|
||||
async def list_resources(request):
|
||||
return {
|
||||
"resources": [
|
||||
{
|
||||
"uri": "file:///example.txt",
|
||||
"name": "Example File",
|
||||
"mimeType": "text/plain",
|
||||
"description": "An example text file resource"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
# Handle resource reading
|
||||
@server.request_handler("resources/read")
|
||||
async def read_resource(request):
|
||||
uri = request.params["uri"]
|
||||
|
||||
# Read and return the resource content
|
||||
return {
|
||||
"contents": [
|
||||
{
|
||||
"uri": uri,
|
||||
"mimeType": "text/plain",
|
||||
"text": "Example content"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Resource content types
|
||||
|
||||
MCP supports two types of resource contents:
|
||||
- Text content: For plain text and other text-based formats
|
||||
- Binary content: For images and other binary data (encoded as base64)
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript content-types.ts
|
||||
// Text content example
|
||||
const textContent = {
|
||||
uri: "file:///document.txt",
|
||||
mimeType: "text/plain",
|
||||
text: "This is the text content"
|
||||
};
|
||||
|
||||
// Binary content example
|
||||
const binaryContent = {
|
||||
uri: "file:///image.png",
|
||||
mimeType: "image/png",
|
||||
blob: Buffer.from("...").toString("base64")
|
||||
};
|
||||
|
||||
server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
|
||||
return {
|
||||
contents: [
|
||||
// Return either text or binary content based on the resource
|
||||
request.params.uri.endsWith(".txt") ? textContent : binaryContent
|
||||
]
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
```python content-types.py
|
||||
import base64
|
||||
|
||||
# Text content example
|
||||
text_content = {
|
||||
"uri": "file:///document.txt",
|
||||
"mimeType": "text/plain",
|
||||
"text": "This is the text content"
|
||||
}
|
||||
|
||||
# Binary content example
|
||||
binary_content = {
|
||||
"uri": "file:///image.png",
|
||||
"mimeType": "image/png",
|
||||
"blob": base64.b64encode(b"...").decode()
|
||||
}
|
||||
|
||||
@server.request_handler("resources/read")
|
||||
async def read_resource(request):
|
||||
return {
|
||||
"contents": [
|
||||
# Return either text or binary content based on the resource
|
||||
text_content if request.params["uri"].endswith(".txt") else binary_content
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Resource templates
|
||||
|
||||
Resource templates allow servers to define patterns for dynamic resources using URI templates (RFC 6570).
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript templates.ts
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async () => {
|
||||
return {
|
||||
resources: [
|
||||
// Static resources
|
||||
{
|
||||
uri: "file:///static.txt",
|
||||
name: "Static File"
|
||||
}
|
||||
],
|
||||
resourceTemplates: [
|
||||
// Dynamic resources
|
||||
{
|
||||
uriTemplate: "user://{userId}/profile",
|
||||
name: "User Profile",
|
||||
description: "Access user profile data",
|
||||
mimeType: "application/json"
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
```python templates.py
|
||||
@server.request_handler("resources/list")
|
||||
async def list_resources(request):
|
||||
return {
|
||||
"resources": [
|
||||
# Static resources
|
||||
{
|
||||
"uri": "file:///static.txt",
|
||||
"name": "Static File"
|
||||
}
|
||||
],
|
||||
"resourceTemplates": [
|
||||
# Dynamic resources
|
||||
{
|
||||
"uriTemplate": "user://{userId}/profile",
|
||||
"name": "User Profile",
|
||||
"description": "Access user profile data",
|
||||
"mimeType": "application/json"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Resource updates
|
||||
|
||||
MCP supports notifying clients about resource changes through subscriptions and notifications.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript updates.ts
|
||||
import { SubscribeRequestSchema, UnsubscribeRequestSchema } from "@modelcontextprotocol/sdk/types";
|
||||
|
||||
// Track subscribed resources
|
||||
const subscriptions = new Set<string>();
|
||||
|
||||
// Handle subscription requests
|
||||
server.setRequestHandler(SubscribeRequestSchema, async (request) => {
|
||||
const uri = request.params.uri;
|
||||
subscriptions.add(uri);
|
||||
return {};
|
||||
});
|
||||
|
||||
// Handle unsubscribe requests
|
||||
server.setRequestHandler(UnsubscribeRequestSchema, async (request) => {
|
||||
const uri = request.params.uri;
|
||||
subscriptions.delete(uri);
|
||||
return {};
|
||||
});
|
||||
|
||||
// Notify about resource updates
|
||||
function notifyResourceUpdate(uri: string) {
|
||||
if (subscriptions.has(uri)) {
|
||||
server.notification({
|
||||
method: "notifications/resources/updated",
|
||||
params: { uri }
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```python updates.py
|
||||
# Track subscribed resources
|
||||
subscriptions = set()
|
||||
|
||||
# Handle subscription requests
|
||||
@server.request_handler("resources/subscribe")
|
||||
async def subscribe(request):
|
||||
uri = request.params["uri"]
|
||||
subscriptions.add(uri)
|
||||
return {}
|
||||
|
||||
# Handle unsubscribe requests
|
||||
@server.request_handler("resources/unsubscribe")
|
||||
async def unsubscribe(request):
|
||||
uri = request.params["uri"]
|
||||
subscriptions.remove(uri)
|
||||
return {}
|
||||
|
||||
# Notify about resource updates
|
||||
async def notify_resource_update(uri: str):
|
||||
if uri in subscriptions:
|
||||
await server.notification(
|
||||
method="notifications/resources/updated",
|
||||
params={"uri": uri}
|
||||
)
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Pagination
|
||||
|
||||
For servers with many resources, MCP supports pagination using cursors.
|
||||
|
||||
<CodeGroup>
|
||||
|
||||
```typescript pagination.ts
|
||||
const PAGE_SIZE = 10;
|
||||
|
||||
server.setRequestHandler(ListResourcesRequestSchema, async (request) => {
|
||||
const cursor = request.params?.cursor;
|
||||
let startIndex = 0;
|
||||
|
||||
if (cursor) {
|
||||
startIndex = parseInt(Buffer.from(cursor, 'base64').toString());
|
||||
}
|
||||
|
||||
const resources = getAllResources();
|
||||
const pageResources = resources.slice(startIndex, startIndex + PAGE_SIZE);
|
||||
|
||||
// Calculate next cursor if there are more resources
|
||||
const nextCursor = startIndex + PAGE_SIZE < resources.length
|
||||
? Buffer.from(String(startIndex + PAGE_SIZE)).toString('base64')
|
||||
: undefined;
|
||||
|
||||
return {
|
||||
resources: pageResources,
|
||||
nextCursor
|
||||
};
|
||||
});
|
||||
```
|
||||
|
||||
```python pagination.py
|
||||
PAGE_SIZE = 10
|
||||
|
||||
@server.request_handler("resources/list")
|
||||
async def list_resources(request):
|
||||
cursor = request.params.get("cursor")
|
||||
start_index = 0
|
||||
|
||||
if cursor:
|
||||
start_index = int(base64.b64decode(cursor))
|
||||
|
||||
resources = get_all_resources()
|
||||
page_resources = resources[start_index:start_index + PAGE_SIZE]
|
||||
|
||||
# Calculate next cursor if there are more resources
|
||||
next_cursor = (base64.b64encode(str(start_index + PAGE_SIZE).encode()).decode()
|
||||
if start_index + PAGE_SIZE < len(resources) else None)
|
||||
|
||||
return {
|
||||
"resources": page_resources,
|
||||
"nextCursor": next_cursor
|
||||
}
|
||||
```
|
||||
|
||||
</CodeGroup>
|
||||
|
||||
## Best practices
|
||||
|
||||
When implementing resource management:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="URI design" icon="link">
|
||||
Use meaningful URI schemes and hierarchical paths for resource organization
|
||||
</Card>
|
||||
<Card title="Error handling" icon="triangle-exclamation">
|
||||
Return appropriate error codes for missing or inaccessible resources
|
||||
</Card>
|
||||
<Card title="Content types" icon="file-code">
|
||||
Always specify accurate MIME types for resource contents
|
||||
</Card>
|
||||
<Card title="Resource cleanup" icon="broom">
|
||||
Implement proper cleanup for subscriptions when connections close
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
## Resource security
|
||||
|
||||
When exposing resources:
|
||||
|
||||
- Validate all URI parameters and resource paths
|
||||
- Implement appropriate access controls
|
||||
- Sanitize and validate all resource content
|
||||
- Consider rate limiting for resource-intensive operations
|
||||
- Use secure transport mechanisms (e.g., HTTPS) for remote resources
|
||||
|
||||
## Common pitfalls
|
||||
|
||||
- Failing to handle URI encoding/decoding properly
|
||||
- Not cleaning up subscriptions when connections close
|
||||
- Returning inconsistent MIME types
|
||||
- Missing error handling for invalid resource requests
|
||||
- Not implementing pagination for large resource collections
|
||||
- Memory leaks from unmanaged resource subscriptions
|
||||
|
|
@ -1,8 +1,462 @@
|
|||
---
|
||||
title: "Tool Implementation"
|
||||
description: "Implement tools in your MCP server"
|
||||
title: 'Tool Implementation'
|
||||
description: 'Learn how to implement tools in your MCP server'
|
||||
---
|
||||
|
||||
# Tool Implementation
|
||||
Tools are one of the core features of MCP, allowing servers to expose functionality that can be invoked by clients or AI models. This guide explains how to implement tools in your MCP server.
|
||||
|
||||
Implement tools in your MCP server
|
||||
## Tool definition
|
||||
|
||||
Tools are defined by implementing the `Tool` interface, which describes the tool's name, description, and input schema.
|
||||
|
||||
<CodeGroup>
|
||||
```typescript Tool interface in TypeScript
|
||||
interface Tool {
|
||||
// The name of the tool
|
||||
name: string;
|
||||
|
||||
// A human-readable description
|
||||
description?: string;
|
||||
|
||||
// JSON Schema defining the expected parameters
|
||||
inputSchema: {
|
||||
type: "object";
|
||||
properties?: Record<string, unknown>;
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
```python Tool interface in Python
|
||||
@dataclass
|
||||
class Tool:
|
||||
# The name of the tool
|
||||
name: str
|
||||
|
||||
# A human-readable description
|
||||
description: Optional[str] = None
|
||||
|
||||
# JSON Schema defining the expected parameters
|
||||
input_schema: Dict[str, Any] = field(default_factory=lambda: {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
})
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Basic implementation
|
||||
|
||||
Here's a simple example of implementing a tool that adds two numbers:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript calculator.ts
|
||||
import { Server } from "@modelcontextprotocol/sdk/server";
|
||||
import { ListToolsRequestSchema, CallToolRequestSchema } from "@modelcontextprotocol/sdk/types";
|
||||
|
||||
const server = new Server({
|
||||
name: "calculator-server",
|
||||
version: "1.0.0",
|
||||
});
|
||||
|
||||
// Define and register available tools
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "add",
|
||||
description: "Add two numbers together",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
a: { type: "number", description: "First number" },
|
||||
b: { type: "number", description: "Second number" }
|
||||
},
|
||||
required: ["a", "b"]
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
// Implement tool execution
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
if (request.params.name === "add") {
|
||||
const a = request.params.arguments?.a as number;
|
||||
const b = request.params.arguments?.b as number;
|
||||
|
||||
return {
|
||||
toolResult: a + b
|
||||
};
|
||||
}
|
||||
|
||||
throw new Error("Tool not found");
|
||||
});
|
||||
```
|
||||
|
||||
```python calculator.py
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Any
|
||||
from mcp_sdk import Server, Tool
|
||||
|
||||
server = Server(
|
||||
name="calculator-server",
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# Define the tool
|
||||
add_tool = Tool(
|
||||
name="add",
|
||||
description="Add two numbers together",
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"a": {"type": "number", "description": "First number"},
|
||||
"b": {"type": "number", "description": "Second number"}
|
||||
},
|
||||
"required": ["a", "b"]
|
||||
}
|
||||
)
|
||||
|
||||
@server.handle_list_tools
|
||||
async def handle_list_tools(request):
|
||||
return {
|
||||
"tools": [add_tool]
|
||||
}
|
||||
|
||||
@server.handle_call_tool
|
||||
async def handle_call_tool(request):
|
||||
if request.params.name == "add":
|
||||
a = request.params.arguments["a"]
|
||||
b = request.params.arguments["b"]
|
||||
return {
|
||||
"toolResult": a + b
|
||||
}
|
||||
|
||||
raise ValueError("Tool not found")
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Progress reporting
|
||||
|
||||
For long-running operations, tools can report progress back to the client:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript long-operation.ts
|
||||
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
||||
return {
|
||||
tools: [
|
||||
{
|
||||
name: "longOperation",
|
||||
description: "A time-consuming operation with progress updates",
|
||||
inputSchema: {
|
||||
type: "object",
|
||||
properties: {
|
||||
duration: {
|
||||
type: "number",
|
||||
description: "Duration in seconds"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
});
|
||||
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
if (request.params.name === "longOperation") {
|
||||
const duration = request.params.arguments?.duration as number ?? 10;
|
||||
const steps = 10;
|
||||
const stepDuration = duration / steps;
|
||||
const progressToken = request.params._meta?.progressToken;
|
||||
|
||||
for (let i = 1; i <= steps; i++) {
|
||||
await new Promise(resolve => setTimeout(resolve, stepDuration * 1000));
|
||||
|
||||
if (progressToken) {
|
||||
server.notification({
|
||||
method: "notifications/progress",
|
||||
params: {
|
||||
progress: i,
|
||||
total: steps,
|
||||
progressToken
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return { toolResult: "Operation completed" };
|
||||
}
|
||||
|
||||
throw new Error("Tool not found");
|
||||
});
|
||||
```
|
||||
|
||||
```python long_operation.py
|
||||
import asyncio
|
||||
from mcp_sdk import Server, Tool
|
||||
|
||||
long_operation_tool = Tool(
|
||||
name="longOperation",
|
||||
description="A time-consuming operation with progress updates",
|
||||
input_schema={
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"duration": {
|
||||
"type": "number",
|
||||
"description": "Duration in seconds"
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
@server.handle_list_tools
|
||||
async def handle_list_tools(request):
|
||||
return {
|
||||
"tools": [long_operation_tool]
|
||||
}
|
||||
|
||||
@server.handle_call_tool
|
||||
async def handle_call_tool(request):
|
||||
if request.params.name == "longOperation":
|
||||
duration = request.params.arguments.get("duration", 10)
|
||||
steps = 10
|
||||
step_duration = duration / steps
|
||||
progress_token = request.params._meta.get("progressToken")
|
||||
|
||||
for i in range(1, steps + 1):
|
||||
await asyncio.sleep(step_duration)
|
||||
|
||||
if progress_token:
|
||||
await server.notification({
|
||||
"method": "notifications/progress",
|
||||
"params": {
|
||||
"progress": i,
|
||||
"total": steps,
|
||||
"progressToken": progress_token
|
||||
}
|
||||
})
|
||||
|
||||
return {"toolResult": "Operation completed"}
|
||||
|
||||
raise ValueError("Tool not found")
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Error handling
|
||||
|
||||
Tools should handle errors gracefully and return appropriate error information:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript error-handling.ts
|
||||
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
||||
if (request.params.name === "riskyOperation") {
|
||||
try {
|
||||
// Attempt the operation
|
||||
const result = await performRiskyOperation(request.params.arguments);
|
||||
return { toolResult: result };
|
||||
} catch (error) {
|
||||
if (error instanceof ValidationError) {
|
||||
throw new McpError(
|
||||
ErrorCode.InvalidParams,
|
||||
"Invalid parameters provided",
|
||||
error.details
|
||||
);
|
||||
}
|
||||
|
||||
if (error instanceof OperationError) {
|
||||
throw new McpError(
|
||||
ErrorCode.InternalError,
|
||||
"Operation failed",
|
||||
{
|
||||
reason: error.message,
|
||||
retryable: error.isRetryable
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
```python error_handling.py
|
||||
from mcp_sdk import McpError, ErrorCode
|
||||
|
||||
@server.handle_call_tool
|
||||
async def handle_call_tool(request):
|
||||
if request.params.name == "riskyOperation":
|
||||
try:
|
||||
# Attempt the operation
|
||||
result = await perform_risky_operation(request.params.arguments)
|
||||
return {"toolResult": result}
|
||||
except ValidationError as e:
|
||||
raise McpError(
|
||||
code=ErrorCode.INVALID_PARAMS,
|
||||
message="Invalid parameters provided",
|
||||
data=e.details
|
||||
)
|
||||
except OperationError as e:
|
||||
raise McpError(
|
||||
code=ErrorCode.INTERNAL_ERROR,
|
||||
message="Operation failed",
|
||||
data={
|
||||
"reason": str(e),
|
||||
"retryable": e.is_retryable
|
||||
}
|
||||
)
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Best practices
|
||||
|
||||
When implementing tools, follow these guidelines:
|
||||
|
||||
<CardGroup cols={2}>
|
||||
<Card title="Clear descriptions" icon="book">
|
||||
Provide detailed descriptions that help AI models understand when and how to use the tool
|
||||
</Card>
|
||||
<Card title="Input validation" icon="shield-check">
|
||||
Use JSON Schema to define and validate input parameters thoroughly
|
||||
</Card>
|
||||
<Card title="Progress updates" icon="bars-progress">
|
||||
Send regular progress updates for operations that take more than a few seconds
|
||||
</Card>
|
||||
<Card title="Error handling" icon="triangle-exclamation">
|
||||
Return specific error codes and helpful error messages
|
||||
</Card>
|
||||
</CardGroup>
|
||||
|
||||
### Tool naming conventions
|
||||
|
||||
- Use camelCase for tool names in TypeScript/JavaScript
|
||||
- Use snake_case for tool names in Python
|
||||
- Keep names concise but descriptive
|
||||
- Use verbs for action-oriented tools (e.g., `convertCurrency`, `fetchWeather`)
|
||||
- Use nouns for data-oriented tools (e.g., `userProfile`, `systemStatus`)
|
||||
|
||||
### Input schema best practices
|
||||
|
||||
- Be explicit about required vs optional parameters
|
||||
- Include descriptions for all parameters
|
||||
- Use appropriate JSON Schema types and formats
|
||||
- Consider adding validation constraints (min, max, pattern, etc.)
|
||||
|
||||
Example of a well-defined input schema:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript schema-example.ts
|
||||
const toolSchema = {
|
||||
type: "object",
|
||||
properties: {
|
||||
query: {
|
||||
type: "string",
|
||||
description: "Search query string",
|
||||
minLength: 1,
|
||||
maxLength: 100
|
||||
},
|
||||
limit: {
|
||||
type: "integer",
|
||||
description: "Maximum number of results",
|
||||
minimum: 1,
|
||||
maximum: 100,
|
||||
default: 10
|
||||
},
|
||||
includeArchived: {
|
||||
type: "boolean",
|
||||
description: "Include archived items in results",
|
||||
default: false
|
||||
}
|
||||
},
|
||||
required: ["query"]
|
||||
};
|
||||
```
|
||||
|
||||
```python schema_example.py
|
||||
tool_schema = {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"query": {
|
||||
"type": "string",
|
||||
"description": "Search query string",
|
||||
"minLength": 1,
|
||||
"maxLength": 100
|
||||
},
|
||||
"limit": {
|
||||
"type": "integer",
|
||||
"description": "Maximum number of results",
|
||||
"minimum": 1,
|
||||
"maximum": 100,
|
||||
"default": 10
|
||||
},
|
||||
"include_archived": {
|
||||
"type": "boolean",
|
||||
"description": "Include archived items in results",
|
||||
"default": False
|
||||
}
|
||||
},
|
||||
"required": ["query"]
|
||||
}
|
||||
```
|
||||
</CodeGroup>
|
||||
|
||||
## Testing tools
|
||||
|
||||
Here's how to test your tool implementations:
|
||||
|
||||
<CodeGroup>
|
||||
```typescript tool-test.ts
|
||||
import { TestServer } from "@modelcontextprotocol/sdk/testing";
|
||||
|
||||
describe("Calculator tools", () => {
|
||||
let server: TestServer;
|
||||
|
||||
beforeEach(() => {
|
||||
server = new TestServer();
|
||||
// Initialize your tools
|
||||
});
|
||||
|
||||
it("should add two numbers", async () => {
|
||||
const response = await server.callTool("add", {
|
||||
a: 5,
|
||||
b: 3
|
||||
});
|
||||
|
||||
expect(response.toolResult).toBe(8);
|
||||
});
|
||||
|
||||
it("should handle invalid inputs", async () => {
|
||||
await expect(
|
||||
server.callTool("add", {
|
||||
a: "not a number",
|
||||
b: 3
|
||||
})
|
||||
).rejects.toThrow();
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
```python tool_test.py
|
||||
import pytest
|
||||
from mcp_sdk.testing import TestServer
|
||||
|
||||
@pytest.fixture
|
||||
async def server():
|
||||
server = TestServer()
|
||||
# Initialize your tools
|
||||
return server
|
||||
|
||||
async def test_add_numbers(server):
|
||||
response = await server.call_tool("add", {
|
||||
"a": 5,
|
||||
"b": 3
|
||||
})
|
||||
assert response.tool_result == 8
|
||||
|
||||
async def test_invalid_input(server):
|
||||
with pytest.raises(ValueError):
|
||||
await server.call_tool("add", {
|
||||
"a": "not a number",
|
||||
"b": 3
|
||||
})
|
||||
```
|
||||
</CodeGroup>
|
||||
|
|
@ -1,8 +0,0 @@
|
|||
---
|
||||
title: "TypeScript Examples"
|
||||
description: "Example TypeScript implementations"
|
||||
---
|
||||
|
||||
# TypeScript Examples
|
||||
|
||||
Example TypeScript implementations
|
||||
|
|
@ -17,7 +17,7 @@ And it does this all securely - Claude can only access what you explicitly allow
|
|||
## Prerequisites
|
||||
|
||||
- macOS (Windows/Linux guides coming soon)
|
||||
- Node.js v16 or higher (`node --version` to check)
|
||||
- Node.js v18 or higher (`node --version` to check)
|
||||
- Git (`git --version` to check)
|
||||
- [Claude desktop app](https://claude.ai/download) installed
|
||||
|
||||
|
|
|
|||
|
|
@ -96,16 +96,14 @@
|
|||
"group": "TypeScript SDK",
|
||||
"pages": [
|
||||
"api-reference/typescript/class-reference",
|
||||
"api-reference/typescript/type-definitions",
|
||||
"api-reference/typescript/examples"
|
||||
"api-reference/typescript/type-definitions"
|
||||
]
|
||||
},
|
||||
{
|
||||
"group": "Python SDK",
|
||||
"pages": [
|
||||
"api-reference/python/class-reference",
|
||||
"api-reference/python/type-hints",
|
||||
"api-reference/python/examples"
|
||||
"api-reference/python/type-hints"
|
||||
]
|
||||
}
|
||||
],
|
||||
|
|
|
|||
Loading…
Reference in a new issue