diff --git a/api-reference/client-api/connection-management.mdx b/api-reference/client-api/connection-management.mdx index 844f896..2101fb0 100644 --- a/api-reference/client-api/connection-management.mdx +++ b/api-reference/client-api/connection-management.mdx @@ -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. + + +```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" +) +``` + + +## 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. + + +```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) +``` + + +### SSE transport + +Server-Sent Events transport is suitable for scenarios where the server pushes data to the client. + + +```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) +``` + + +### Stdio transport + +For local server processes, use the stdio transport to communicate via standard input/output. + + +```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) +``` + + +## Connection lifecycle + +### Event handling + +Set up event handlers to manage the connection lifecycle. + + +```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}") +``` + + +### Connection states + +Monitor and manage connection states throughout your application's lifecycle. + + +```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() +``` + + +## Error handling + +Implement robust error handling for connection-related issues. + + +```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 +``` + + +## Best practices + + + + Always close connections when they're no longer needed to free up resources + + + Implement comprehensive error handling for all connection states + + + Set up appropriate event handlers before establishing connections + + + Use try/finally blocks to ensure proper cleanup of resources + + + +## Connection examples + +Here are some common connection patterns: + + + + + ```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) + ``` + + + + + + ```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) + ``` + + + + +## Troubleshooting + + + + - Verify the server is running and accessible + - Check if the port is correct and available + - Ensure firewalls aren't blocking the connection + + + + - Verify credentials if required + - Check if environment variables are properly set + - Ensure transport configuration is correct + + + + - Implement reconnection logic + - Check network stability + - Monitor server health + + + +## 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 | \ No newline at end of file diff --git a/api-reference/client-api/resource-consumption.mdx b/api-reference/client-api/resource-consumption.mdx index 39388bf..81a7a1b 100644 --- a/api-reference/client-api/resource-consumption.mdx +++ b/api-reference/client-api/resource-consumption.mdx @@ -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: + + + +```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']})") +``` + + + +## Reading resources + +Once you have a resource URI, you can read its contents: + + + +```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)) +``` + + + +## Resource templates + +Some servers provide resource templates that can be used to construct resource URIs dynamically: + + + +```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']}") +``` + + + +## Subscribing to updates + +You can subscribe to receive notifications when resources change: + + + +```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" + } +}) +``` + + + +## Handling pagination + +When listing resources, servers may paginate results. Here's how to handle pagination: + + + +```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 +``` + + + +## Error handling + +When working with resources, handle potential errors appropriately: + + + +```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}") +``` + + + +## Best practices + +When consuming resources from MCP servers: + + + + Always unsubscribe from resources when you're done with them + + + Implement proper error handling for all resource operations + + + Check MIME types before processing resource contents + + + Always handle pagination when listing resources + + + +## Type safety + +To ensure type safety when working with resources: + + + +```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"] +``` + + \ No newline at end of file diff --git a/api-reference/client-api/tool-invocation.mdx b/api-reference/client-api/tool-invocation.mdx index a76a659..477a87f 100644 --- a/api-reference/client-api/tool-invocation.mdx +++ b/api-reference/client-api/tool-invocation.mdx @@ -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. + + +```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}") +``` + + +## Tool invocation + +To invoke a tool, you'll use the `tools/call` method with the tool name and any required arguments. + + +```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) +``` + + +## Progress tracking + +For long-running tools, you can track progress using progress notifications. + + +```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 +) +``` + + +## Error handling + +Tool invocations may fail for various reasons. Always handle potential errors appropriately. + + +```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) +``` + + +## Complete example + +Here's a complete example showing how to discover and use tools with proper error handling. + + +```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) +``` + + +## Best practices + +When working with tools: + + + + Always check tool availability before invoking + + + Implement comprehensive error handling + + + Use progress callbacks for long-running operations + + + Validate arguments against the tool's input schema + + + +## 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 | \ No newline at end of file diff --git a/api-reference/error-handling.mdx b/api-reference/error-handling.mdx index b5932b7..3a8d864 100644 --- a/api-reference/error-handling.mdx +++ b/api-reference/error-handling.mdx @@ -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 + + + A number indicating the error type + + + A concise, single-sentence description + + + Optional object with additional details + + + +## 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 + + + + 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"] + } + } + } + ``` + + + + Errors when accessing resources: + + ```json + { + "jsonrpc": "2.0", + "id": 2, + "error": { + "code": -32601, + "message": "Resource not found", + "data": { + "uri": "file:///nonexistent.txt" + } + } + } + ``` + + + + Errors during tool execution: + + ```json + { + "jsonrpc": "2.0", + "id": 3, + "error": { + "code": -32602, + "message": "Invalid tool parameters", + "data": { + "tool": "query", + "invalidParams": ["sql"] + } + } + } + ``` + + + +## Error handling best practices + + + + Choose the most specific error code that applies to the situation. Prefer standard JSON-RPC codes when applicable. + + + Error messages should be: + - Concise (single sentence) + - Descriptive + - Action-oriented when possible + + + 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"] + } + } + } + ``` + + + 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(); + } + } + ``` + + + +## Client-side error handling + +Recommendations for MCP clients: + + + + 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) + ) + ]); + ``` + + + 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); + } + } + } + ``` + + + +## Server-side error handling + +Best practices for MCP servers: + + + + When possible, continue operating with reduced functionality rather than failing completely + + + Always clean up resources when errors occur, especially for long-running operations + + + Log errors with appropriate context for debugging while keeping sensitive information secure + + + Implement rate limiting and return appropriate error responses when limits are exceeded + + + +## 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 + } +} +``` \ No newline at end of file diff --git a/api-reference/message-types.mdx b/api-reference/message-types.mdx index fe9cff8..a5d2387 100644 --- a/api-reference/message-types.mdx +++ b/api-reference/message-types.mdx @@ -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: + + + + Messages that expect a response, identified by a unique ID + + + Messages sent in reply to requests, containing results or errors + + + One-way messages that don't expect a response + + + +## Message structure + +All messages in MCP follow the JSON-RPC 2.0 specification and include specific required fields. + + + + ```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 + + + + + ```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) + + + + + ```json + { + "jsonrpc": "2.0", + "method": "notifications/initialized", + "params": { + // Optional notification parameters + } + } + ``` + Required fields: + - `jsonrpc`: Must be "2.0" + - `method`: The name of the notification + + + +## 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. + + + + Add a progress token in the request: + ```json + { + "jsonrpc": "2.0", + "id": 1, + "method": "tools/call", + "params": { + "_meta": { + "progressToken": "abc123" + } + } + } + ``` + + + The server sends progress updates as notifications: + ```json + { + "jsonrpc": "2.0", + "method": "notifications/progress", + "params": { + "progressToken": "abc123", + "progress": 50, + "total": 100 + } + } + ``` + + + +## Message content types + +MCP supports different content types for messages exchanged between the client and server. + + +```json Text content +{ + "type": "text", + "text": "This is a text message" +} +``` + +```json Image content +{ + "type": "image", + "data": "base64-encoded-image-data", + "mimeType": "image/png" +} +``` + + +## 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: + + + + 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" + } + ] + } + } + ``` + + + + 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" + } + } + ``` + + + +## Best practices + +When working with MCP messages: + + + + Always include appropriate error codes and meaningful error messages + + + Send regular progress updates for long-running operations + + + Validate message content against the appropriate schemas + + + Clean up resources when connections are closed + + \ No newline at end of file diff --git a/api-reference/protocol-overview.mdx b/api-reference/protocol-overview.mdx index b58d90a..a650ab5 100644 --- a/api-reference/protocol-overview.mdx +++ b/api-reference/protocol-overview.mdx @@ -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 + + + + Connect AI models to any data source or tool through a unified protocol + + + Control what data and capabilities are exposed to AI systems + + + Combine multiple servers together for enhanced functionality + + + Build integrations that work consistently across AI applications + + + +# Core concepts + +MCP defines several key primitives that form the foundation of the protocol: + + + + Data that can be loaded as context for the AI model. Resources have URIs and can contain text or binary data. + + + Functions that the AI can invoke to perform actions, like writing files or calling APIs. + + + Pre-defined prompt templates that help guide AI interactions in specific ways. + + + Allows servers to request completions from the AI model through the client. + + + +# Protocol flow + +## Connection and initialization + +When a client connects to an MCP server, they go through the following initialization sequence: + + + + The client establishes a connection to the server using one of the supported transports. + + + The client sends an initialize request containing its capabilities and protocol version. + + + The server responds with its own capabilities and supported protocol version. + + + The client sends an initialized notification to indicate setup is complete. + + + +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: + + + + - 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 + + + + - Bidirectional communication over WebSocket protocol + - Client and server negotiate using 'mcp' subprotocol + - Can connect to remote servers + - State persists until disconnect + + + + - Server pushes messages via SSE + - Client sends messages via HTTP POST + - Supports cross-origin restrictions + - State persists until SSE connection closes + + \ No newline at end of file diff --git a/api-reference/python/class-reference.mdx b/api-reference/python/class-reference.mdx index 43739a4..1ebd5bc 100644 --- a/api-reference/python/class-reference.mdx +++ b/api-reference/python/class-reference.mdx @@ -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) +``` \ No newline at end of file diff --git a/api-reference/python/examples.mdx b/api-reference/python/examples.mdx deleted file mode 100644 index 94c751c..0000000 --- a/api-reference/python/examples.mdx +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: "Python Examples" -description: "Example Python implementations" ---- - -# Python Examples - -Example Python implementations diff --git a/api-reference/python/type-hints.mdx b/api-reference/python/type-hints.mdx index d037e47..1b76006 100644 --- a/api-reference/python/type-hints.mdx +++ b/api-reference/python/type-hints.mdx @@ -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 + }) +``` \ No newline at end of file diff --git a/api-reference/server-api/event-handling.mdx b/api-reference/server-api/event-handling.mdx index dab1bc3..50c87b6 100644 --- a/api-reference/server-api/event-handling.mdx +++ b/api-reference/server-api/event-handling.mdx @@ -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. + + +```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"} + } + } + ) + ] + ) +``` + + +## 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. + + +```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}") +``` + + +## Connection lifecycle + +Your server can handle connection lifecycle events through callbacks. This is useful for setup and cleanup operations. + + +```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 +``` + + +## Error handling + +Properly handle errors in your event handlers to provide meaningful feedback to clients. + + +```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)} + ) +``` + + +## Progress reporting + +For long-running operations, you can report progress back to the client. + + +```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"} +``` + + +## State management + +While MCP servers are generally stateless, you might need to maintain some state between requests. Here's how to manage state safely: + + +```typescript state.ts +class ExampleServer { + private subscriptions: Set = 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() +``` + + +## Best practices + +When implementing event handlers in your MCP server: + + + + Always validate request parameters before processing + + + Use appropriate error codes and provide helpful error messages + + + Handle long-running operations asynchronously and report progress + + + Clean up resources and state when connections are closed + + + +## Debugging tips + +To help debug your event handlers: + +1. Enable logging in your server: + + +```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 {} +``` + + +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. \ No newline at end of file diff --git a/api-reference/server-api/resource-management.mdx b/api-reference/server-api/resource-management.mdx index 34de1ab..ffaca01 100644 --- a/api-reference/server-api/resource-management.mdx +++ b/api-reference/server-api/resource-management.mdx @@ -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 + + + +```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" + } + ] + } +``` + + + +## 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) + + + +```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 + ] + } +``` + + + +## Resource templates + +Resource templates allow servers to define patterns for dynamic resources using URI templates (RFC 6570). + + + +```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" + } + ] + } +``` + + + +## Resource updates + +MCP supports notifying clients about resource changes through subscriptions and notifications. + + + +```typescript updates.ts +import { SubscribeRequestSchema, UnsubscribeRequestSchema } from "@modelcontextprotocol/sdk/types"; + +// Track subscribed resources +const subscriptions = new Set(); + +// 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} + ) +``` + + + +## Pagination + +For servers with many resources, MCP supports pagination using cursors. + + + +```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 + } +``` + + + +## Best practices + +When implementing resource management: + + + + Use meaningful URI schemes and hierarchical paths for resource organization + + + Return appropriate error codes for missing or inaccessible resources + + + Always specify accurate MIME types for resource contents + + + Implement proper cleanup for subscriptions when connections close + + + +## 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 \ No newline at end of file diff --git a/api-reference/server-api/tool-implementation.mdx b/api-reference/server-api/tool-implementation.mdx index 0ede2a0..dc6e403 100644 --- a/api-reference/server-api/tool-implementation.mdx +++ b/api-reference/server-api/tool-implementation.mdx @@ -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. + + +```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; + }; +} +``` + +```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": {} + }) +``` + + +## Basic implementation + +Here's a simple example of implementing a tool that adds two numbers: + + +```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") +``` + + +## Progress reporting + +For long-running operations, tools can report progress back to the client: + + +```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") +``` + + +## Error handling + +Tools should handle errors gracefully and return appropriate error information: + + +```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 + } + ) +``` + + +## Best practices + +When implementing tools, follow these guidelines: + + + + Provide detailed descriptions that help AI models understand when and how to use the tool + + + Use JSON Schema to define and validate input parameters thoroughly + + + Send regular progress updates for operations that take more than a few seconds + + + Return specific error codes and helpful error messages + + + +### 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: + + +```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"] +} +``` + + +## Testing tools + +Here's how to test your tool implementations: + + +```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 + }) +``` + \ No newline at end of file diff --git a/api-reference/typescript/examples.mdx b/api-reference/typescript/examples.mdx deleted file mode 100644 index 7b5b7b5..0000000 --- a/api-reference/typescript/examples.mdx +++ /dev/null @@ -1,8 +0,0 @@ ---- -title: "TypeScript Examples" -description: "Example TypeScript implementations" ---- - -# TypeScript Examples - -Example TypeScript implementations diff --git a/docs/guides/claude.mdx b/docs/guides/claude.mdx index 8c862f0..4cfc314 100644 --- a/docs/guides/claude.mdx +++ b/docs/guides/claude.mdx @@ -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 diff --git a/mint.json b/mint.json index 6fce8e4..7551cc2 100644 --- a/mint.json +++ b/mint.json @@ -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" ] } ],