This is page 2 of 3. Use http://codebase.md/surya-madhav/mcp?lines=true&page={x} to view the full context. # Directory Structure ``` ├── .DS_Store ├── .gitignore ├── docs │ ├── 00-important-official-mcp-documentation.md │ ├── 00-important-python-mcp-sdk.md │ ├── 01-introduction-to-mcp.md │ ├── 02-mcp-core-concepts.md │ ├── 03-building-mcp-servers-python.md │ ├── 04-connecting-to-mcp-servers.md │ ├── 05-communication-protocols.md │ ├── 06-troubleshooting-guide.md │ ├── 07-extending-the-repo.md │ └── 08-advanced-mcp-features.md ├── frontend │ ├── app.py │ ├── pages │ │ ├── 01_My_Active_Servers.py │ │ ├── 02_Settings.py │ │ └── 03_Documentation.py │ └── utils.py ├── LICENSE ├── README.md ├── requirements.txt ├── run.bat ├── run.sh ├── server.py └── tools ├── __init__.py ├── crawl4ai_scraper.py ├── ddg_search.py └── web_scrape.py ``` # Files -------------------------------------------------------------------------------- /docs/04-connecting-to-mcp-servers.md: -------------------------------------------------------------------------------- ```markdown 1 | # Connecting to MCP Servers 2 | 3 | This document explains the different methods for connecting to Model Context Protocol (MCP) servers. Whether you're using Claude Desktop, a custom client, or programmatic access, this guide will help you establish and manage connections to MCP servers. 4 | 5 | ## Overview of MCP Clients 6 | 7 | Before diving into implementation details, it's important to understand what an MCP client does: 8 | 9 | 1. **Discovers** MCP servers (through configuration or discovery mechanisms) 10 | 2. **Establishes** connections to servers using appropriate transport methods 11 | 3. **Negotiates** capabilities through protocol initialization 12 | 4. **Lists** available tools, resources, and prompts 13 | 5. **Facilitates** tool execution, resource retrieval, and prompt application 14 | 6. **Handles** errors, timeouts, and reconnection 15 | 16 | ```mermaid 17 | flowchart LR 18 | Client[MCP Client] 19 | Server1[MCP Server 1] 20 | Server2[MCP Server 2] 21 | LLM[LLM] 22 | User[User] 23 | 24 | User <--> Client 25 | Client <--> Server1 26 | Client <--> Server2 27 | Client <--> LLM 28 | ``` 29 | 30 | ## Client Types 31 | 32 | There are several ways to connect to MCP servers: 33 | 34 | 1. **Integrated Clients**: Built into applications like Claude Desktop 35 | 2. **Standalone Clients**: Dedicated applications for MCP interaction (like our Streamlit UI) 36 | 3. **SDK Clients**: Using MCP SDKs for programmatic access 37 | 4. **Development Tools**: Tools like MCP Inspector for testing and development 38 | 39 | ## Using Claude Desktop 40 | 41 | [Claude Desktop](https://claude.ai/download) is an integrated client that can connect to MCP servers through configuration. 42 | 43 | ### Configuration Setup 44 | 45 | To configure Claude Desktop to use MCP servers: 46 | 47 | 1. Locate the configuration file: 48 | - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` 49 | - Windows: `%APPDATA%\Claude\claude_desktop_config.json` 50 | 51 | 2. Create or edit the file to include your MCP servers: 52 | 53 | ```json 54 | { 55 | "mcpServers": { 56 | "web-tools": { 57 | "command": "python", 58 | "args": ["/absolute/path/to/server.py"] 59 | }, 60 | "database-tools": { 61 | "command": "npx", 62 | "args": ["-y", "@modelcontextprotocol/server-postgres", "postgres://user:pass@localhost/db"] 63 | } 64 | } 65 | } 66 | ``` 67 | 68 | Each server configuration includes: 69 | - A unique name (e.g., "web-tools") 70 | - The command to run the server 71 | - Arguments to pass to the command 72 | - Optional environment variables 73 | 74 | ### Starting Servers 75 | 76 | After configuring Claude Desktop: 77 | 78 | 1. Restart the application 79 | 2. Claude will automatically start configured servers 80 | 3. You'll see the MCP tools icon in the interface 81 | 4. You can now use the servers in conversations 82 | 83 | ### Using MCP Features in Claude 84 | 85 | With MCP servers configured, you can: 86 | 87 | 1. **Use tools**: Ask Claude to perform actions using server tools 88 | 2. **Access resources**: Request information from resources 89 | 3. **Apply prompts**: Use the prompts menu for standardized interactions 90 | 91 | ## Using the Streamlit UI 92 | 93 | The Streamlit UI included in this repository provides a graphical interface for interacting with MCP servers. 94 | 95 | ### Running the UI 96 | 97 | ```bash 98 | streamlit run streamlit_app.py 99 | ``` 100 | 101 | This will open a web browser with the UI. 102 | 103 | ### Connecting to Servers 104 | 105 | 1. Enter the path to your Claude Desktop config file 106 | 2. Click "Load Servers" to see all configured servers 107 | 3. Select a server tab and click "Connect" 108 | 4. The UI will display tools, resources, and prompts 109 | 110 | ### Using Tools 111 | 112 | 1. Select a tool tab 113 | 2. Fill in the required parameters 114 | 3. Click "Execute" to run the tool 115 | 4. View the results in the UI 116 | 117 | ## Programmatic Access with Python 118 | 119 | For programmatic access, you can use the MCP Python SDK. 120 | 121 | ### Basic Client Example 122 | 123 | ```python 124 | import asyncio 125 | from mcp import ClientSession, StdioServerParameters 126 | from mcp.client.stdio import stdio_client 127 | 128 | async def connect_to_server(): 129 | # Set up server parameters 130 | server_params = StdioServerParameters( 131 | command="python", 132 | args=["server.py"] 133 | ) 134 | 135 | # Connect to the server 136 | async with stdio_client(server_params) as (read, write): 137 | async with ClientSession(read, write) as session: 138 | # Initialize the connection 139 | await session.initialize() 140 | 141 | # List tools 142 | tools_result = await session.list_tools() 143 | print(f"Available tools: {[tool.name for tool in tools_result.tools]}") 144 | 145 | # Call a tool 146 | result = await session.call_tool("web_scrape", {"url": "example.com"}) 147 | print(f"Result: {result.content[0].text if result.content else 'No content'}") 148 | 149 | # Run the async function 150 | if __name__ == "__main__": 151 | asyncio.run(connect_to_server()) 152 | ``` 153 | 154 | ### Tool Execution 155 | 156 | To call a tool programmatically: 157 | 158 | ```python 159 | # Call a tool with parameters 160 | result = await session.call_tool("tool_name", { 161 | "param1": "value1", 162 | "param2": 42 163 | }) 164 | 165 | # Process the result 166 | if hasattr(result, 'content') and result.content: 167 | for item in result.content: 168 | if hasattr(item, 'text'): 169 | print(item.text) 170 | ``` 171 | 172 | ### Resource Access 173 | 174 | To access resources programmatically: 175 | 176 | ```python 177 | # List available resources 178 | resources_result = await session.list_resources() 179 | for resource in resources_result.resources: 180 | print(f"Resource: {resource.name} ({resource.uri})") 181 | 182 | # Read a resource 183 | result = await session.read_resource("resource://uri") 184 | content, mime_type = result.contents[0].text, result.contents[0].mimeType 185 | print(f"Content ({mime_type}): {content[:100]}...") 186 | ``` 187 | 188 | ### Prompt Usage 189 | 190 | To use prompts programmatically: 191 | 192 | ```python 193 | # List available prompts 194 | prompts_result = await session.list_prompts() 195 | for prompt in prompts_result.prompts: 196 | print(f"Prompt: {prompt.name}") 197 | 198 | # Get a prompt 199 | result = await session.get_prompt("prompt_name", {"arg1": "value1"}) 200 | for message in result.messages: 201 | print(f"{message.role}: {message.content.text}") 202 | ``` 203 | 204 | ## Transport Methods 205 | 206 | MCP supports different transport methods for client-server communication. 207 | 208 | ### STDIO Transport 209 | 210 | Standard Input/Output (STDIO) transport is the simplest method: 211 | 212 | ```python 213 | # STDIO server parameters 214 | server_params = StdioServerParameters( 215 | command="python", # Command to run the server 216 | args=["server.py"], # Arguments 217 | env={"ENV_VAR": "value"} # Optional environment variables 218 | ) 219 | 220 | # Connect using STDIO 221 | async with stdio_client(server_params) as (read, write): 222 | # Use the connection... 223 | ``` 224 | 225 | STDIO transport: 226 | - Is simple to set up 227 | - Works well for local processes 228 | - Doesn't require network configuration 229 | - Automatically terminates when the process ends 230 | 231 | ### SSE Transport 232 | 233 | Server-Sent Events (SSE) transport is used for web-based connections: 234 | 235 | ```python 236 | from mcp.client.sse import sse_client 237 | 238 | # Connect to an SSE server 239 | async with sse_client("http://localhost:5000") as (read, write): 240 | async with ClientSession(read, write) as session: 241 | # Use the session... 242 | ``` 243 | 244 | SSE transport: 245 | - Supports remote connections 246 | - Works over standard HTTP 247 | - Can be used with web servers 248 | - Supports multiple clients per server 249 | 250 | ## Connection Lifecycle 251 | 252 | Understanding the connection lifecycle is important for robust implementations: 253 | 254 | ```mermaid 255 | sequenceDiagram 256 | participant Client 257 | participant Server 258 | 259 | Client->>Server: initialize request 260 | Server->>Client: initialize response (capabilities) 261 | Client->>Server: initialized notification 262 | 263 | Note over Client,Server: Connection Ready 264 | 265 | loop Normal Operation 266 | Client->>Server: Requests (list_tools, call_tool, etc.) 267 | Server->>Client: Responses 268 | end 269 | 270 | Note over Client,Server: Termination 271 | 272 | Client->>Server: exit notification 273 | Client->>Server: Close connection 274 | ``` 275 | 276 | ### Initialization 277 | 278 | When a connection is established: 279 | 280 | 1. Client sends `initialize` request with supported capabilities 281 | 2. Server responds with its capabilities 282 | 3. Client sends `initialized` notification 283 | 4. Normal operation begins 284 | 285 | ### Normal Operation 286 | 287 | During normal operation: 288 | 289 | 1. Client sends requests (e.g., `list_tools`, `call_tool`) 290 | 2. Server processes requests and sends responses 291 | 3. Server may send notifications (e.g., `resources/list_changed`) 292 | 293 | ### Termination 294 | 295 | When ending a connection: 296 | 297 | 1. Client sends `exit` notification 298 | 2. Client closes the connection 299 | 3. Server cleans up resources 300 | 301 | ## Error Handling 302 | 303 | Robust error handling is essential for MCP clients: 304 | 305 | ```python 306 | try: 307 | result = await session.call_tool("tool_name", params) 308 | except Exception as e: 309 | print(f"Error calling tool: {str(e)}") 310 | 311 | # Check for specific error types 312 | if isinstance(e, mcp.McpProtocolError): 313 | print(f"Protocol error: {e.code}") 314 | elif isinstance(e, mcp.McpTimeoutError): 315 | print("Request timed out") 316 | elif isinstance(e, mcp.McpConnectionError): 317 | print("Connection lost") 318 | ``` 319 | 320 | Common error scenarios: 321 | 322 | 1. **Connection Failures**: Server not found or refused connection 323 | 2. **Initialization Errors**: Protocol incompatibility or capability mismatch 324 | 3. **Request Errors**: Invalid parameters or tool not found 325 | 4. **Execution Errors**: Tool execution failed or timed out 326 | 5. **Connection Loss**: Server terminated unexpectedly 327 | 328 | ## Building Your Own Client 329 | 330 | To build a custom MCP client, follow these steps: 331 | 332 | ### 1. Set Up Transport 333 | 334 | Choose a transport method and establish a connection: 335 | 336 | ```python 337 | import asyncio 338 | from mcp.client.stdio import stdio_client 339 | from mcp import ClientSession 340 | 341 | # Set up server parameters 342 | server_params = StdioServerParameters( 343 | command="python", 344 | args=["server.py"] 345 | ) 346 | 347 | # Establish connection 348 | async with stdio_client(server_params) as (read, write): 349 | # Create session and use it... 350 | ``` 351 | 352 | ### 2. Create a Session 353 | 354 | The `ClientSession` manages the protocol interaction: 355 | 356 | ```python 357 | async with ClientSession(read, write) as session: 358 | # Initialize the connection 359 | await session.initialize() 360 | 361 | # Now you can use the session 362 | ``` 363 | 364 | ### 3. Implement Feature Discovery 365 | 366 | List available features from the server: 367 | 368 | ```python 369 | # List tools 370 | tools_result = await session.list_tools() 371 | tools = tools_result.tools if hasattr(tools_result, 'tools') else [] 372 | 373 | # List resources 374 | resources_result = await session.list_resources() 375 | resources = resources_result.resources if hasattr(resources_result, 'resources') else [] 376 | 377 | # List prompts 378 | prompts_result = await session.list_prompts() 379 | prompts = prompts_result.prompts if hasattr(prompts_result, 'prompts') else [] 380 | ``` 381 | 382 | ### 4. Implement Tool Execution 383 | 384 | Create a function to call tools: 385 | 386 | ```python 387 | async def call_tool(session, tool_name, tool_args): 388 | try: 389 | result = await session.call_tool(tool_name, arguments=tool_args) 390 | 391 | # Format the result 392 | if hasattr(result, 'content') and result.content: 393 | content_text = [] 394 | for item in result.content: 395 | if hasattr(item, 'text'): 396 | content_text.append(item.text) 397 | return "\n".join(content_text) 398 | return "Tool executed, but no text content was returned." 399 | except Exception as e: 400 | return f"Error calling tool: {str(e)}" 401 | ``` 402 | 403 | ### 5. Implement Resource Access 404 | 405 | Create a function to read resources: 406 | 407 | ```python 408 | async def read_resource(session, resource_uri): 409 | try: 410 | result = await session.read_resource(resource_uri) 411 | 412 | # Format the result 413 | content_items = [] 414 | for content in result.contents: 415 | if hasattr(content, 'text'): 416 | content_items.append(content.text) 417 | elif hasattr(content, 'blob'): 418 | content_items.append(f"[Binary data: {len(content.blob)} bytes]") 419 | 420 | return "\n".join(content_items) 421 | except Exception as e: 422 | return f"Error reading resource: {str(e)}" 423 | ``` 424 | 425 | ### 6. Implement User Interface 426 | 427 | Create a user interface appropriate for your application: 428 | 429 | - Command-line interface 430 | - Web UI (like our Streamlit example) 431 | - GUI application 432 | - Integration with existing tools 433 | 434 | ## Example: Command-Line Client 435 | 436 | Here's a simple command-line client example: 437 | 438 | ```python 439 | import asyncio 440 | import argparse 441 | import json 442 | from mcp import ClientSession, StdioServerParameters 443 | from mcp.client.stdio import stdio_client 444 | 445 | async def main(args): 446 | server_params = StdioServerParameters( 447 | command=args.command, 448 | args=args.args 449 | ) 450 | 451 | async with stdio_client(server_params) as (read, write): 452 | async with ClientSession(read, write) as session: 453 | await session.initialize() 454 | 455 | if args.action == "list-tools": 456 | tools_result = await session.list_tools() 457 | tools = tools_result.tools if hasattr(tools_result, 'tools') else [] 458 | print(json.dumps([{ 459 | "name": tool.name, 460 | "description": tool.description 461 | } for tool in tools], indent=2)) 462 | 463 | elif args.action == "call-tool": 464 | tool_args = json.loads(args.params) 465 | result = await session.call_tool(args.tool, arguments=tool_args) 466 | if hasattr(result, 'content') and result.content: 467 | for item in result.content: 468 | if hasattr(item, 'text'): 469 | print(item.text) 470 | 471 | elif args.action == "list-resources": 472 | resources_result = await session.list_resources() 473 | resources = resources_result.resources if hasattr(resources_result, 'resources') else [] 474 | print(json.dumps([{ 475 | "name": resource.name, 476 | "uri": resource.uri 477 | } for resource in resources], indent=2)) 478 | 479 | elif args.action == "read-resource": 480 | result = await session.read_resource(args.uri) 481 | for content in result.contents: 482 | if hasattr(content, 'text'): 483 | print(content.text) 484 | 485 | if __name__ == "__main__": 486 | parser = argparse.ArgumentParser(description="MCP Command Line Client") 487 | parser.add_argument("--command", required=True, help="Server command") 488 | parser.add_argument("--args", nargs="*", default=[], help="Server arguments") 489 | 490 | subparsers = parser.add_subparsers(dest="action", required=True) 491 | 492 | list_tools_parser = subparsers.add_parser("list-tools") 493 | 494 | call_tool_parser = subparsers.add_parser("call-tool") 495 | call_tool_parser.add_argument("--tool", required=True, help="Tool name") 496 | call_tool_parser.add_argument("--params", required=True, help="Tool parameters (JSON)") 497 | 498 | list_resources_parser = subparsers.add_parser("list-resources") 499 | 500 | read_resource_parser = subparsers.add_parser("read-resource") 501 | read_resource_parser.add_argument("--uri", required=True, help="Resource URI") 502 | 503 | args = parser.parse_args() 504 | asyncio.run(main(args)) 505 | ``` 506 | 507 | ## Integration with LLMs 508 | 509 | To integrate MCP clients with LLMs like Claude: 510 | 511 | 1. **Tool Registration**: Register MCP tools with the LLM system 512 | 2. **Resource Loading**: Provide a way to load resources into LLM context 513 | 3. **Permission Handling**: Implement approval flows for tool execution 514 | 4. **Result Processing**: Process and present tool results to the LLM 515 | 516 | Example integration with Anthropic Claude: 517 | 518 | ```python 519 | import anthropic 520 | from mcp import ClientSession, StdioServerParameters 521 | from mcp.client.stdio import stdio_client 522 | 523 | async def process_claude_query(client, query): 524 | # Connect to MCP server 525 | server_params = StdioServerParameters( 526 | command="python", 527 | args=["server.py"] 528 | ) 529 | 530 | async with stdio_client(server_params) as (read, write): 531 | async with ClientSession(read, write) as session: 532 | # Initialize 533 | await session.initialize() 534 | 535 | # Get available tools 536 | tools_result = await session.list_tools() 537 | tools = [] 538 | for tool in tools_result.tools: 539 | tools.append({ 540 | "name": tool.name, 541 | "description": tool.description, 542 | "input_schema": tool.inputSchema 543 | }) 544 | 545 | # Initial Claude query 546 | messages = [{"role": "user", "content": query}] 547 | response = client.messages.create( 548 | model="claude-3-opus-20240229", 549 | max_tokens=1000, 550 | messages=messages, 551 | tools=tools 552 | ) 553 | 554 | # Process tool calls 555 | for content in response.content: 556 | if content.type == "tool_use": 557 | # Execute the tool 558 | tool_name = content.name 559 | tool_args = content.input 560 | 561 | # Call MCP tool 562 | result = await session.call_tool(tool_name, arguments=tool_args) 563 | 564 | # Format result 565 | result_text = "" 566 | if hasattr(result, 'content') and result.content: 567 | for item in result.content: 568 | if hasattr(item, 'text'): 569 | result_text += item.text 570 | 571 | # Send result back to Claude 572 | messages.append({ 573 | "role": "assistant", 574 | "content": [content] 575 | }) 576 | messages.append({ 577 | "role": "user", 578 | "content": [ 579 | { 580 | "type": "tool_result", 581 | "tool_use_id": content.id, 582 | "content": result_text 583 | } 584 | ] 585 | }) 586 | 587 | # Get final response 588 | final_response = client.messages.create( 589 | model="claude-3-opus-20240229", 590 | max_tokens=1000, 591 | messages=messages 592 | ) 593 | 594 | return final_response.content[0].text 595 | 596 | # If no tool calls, return initial response 597 | return response.content[0].text 598 | ``` 599 | 600 | ## Troubleshooting Connection Issues 601 | 602 | ### Common Problems and Solutions 603 | 604 | 1. **Server Not Found**: 605 | - Check that the command path is correct 606 | - Verify the server file exists 607 | - Check that Python or Node.js is properly installed 608 | 609 | 2. **Connection Refused**: 610 | - For SSE, verify the port is available 611 | - Check for firewall or network issues 612 | - Ensure the server is running 613 | 614 | 3. **Protocol Errors**: 615 | - Verify MCP versions are compatible 616 | - Check for syntax errors in tool schemas 617 | - Ensure tools are properly registered 618 | 619 | 4. **Tool Execution Failures**: 620 | - Validate input parameters 621 | - Check for runtime errors in tool implementation 622 | - Verify external dependencies are available 623 | 624 | 5. **Node.js Environment Issues**: 625 | - Ensure Node.js is properly installed 626 | - Check for proper paths to node, npm, and npx 627 | - Verify global packages are accessible 628 | 629 | ### Debugging Techniques 630 | 631 | 1. **Logging**: 632 | - Enable debug logging in your client 633 | - Check server logs for errors 634 | - Use the MCP Inspector for detailed message logs 635 | 636 | 2. **Environment Variables**: 637 | - Set `MCP_DEBUG=1` for verbose logging 638 | - Use appropriate environment variables for servers 639 | 640 | 3. **Manual Testing**: 641 | - Test servers directly with the MCP Inspector 642 | - Try simple tools first to isolate issues 643 | - Verify transport works with echo tools 644 | 645 | ## Conclusion 646 | 647 | Connecting to MCP servers opens up powerful capabilities for extending LLMs with custom tools and data sources. Whether using existing clients like Claude Desktop, building custom integrations, or developing your own applications, the MCP protocol provides a standardized way to enhance LLM interactions. 648 | 649 | In the next document, we'll explore the communication protocols used by MCP in more detail. 650 | ``` -------------------------------------------------------------------------------- /docs/06-troubleshooting-guide.md: -------------------------------------------------------------------------------- ```markdown 1 | # MCP Troubleshooting Guide 2 | 3 | This comprehensive guide addresses common issues encountered when working with Model Context Protocol (MCP) servers and clients. It provides step-by-step solutions, diagnostic techniques, and best practices for resolving problems efficiently. 4 | 5 | ## Environment Setup Issues 6 | 7 | ### Python Environment Problems 8 | 9 | #### Missing Dependencies 10 | 11 | **Symptoms:** 12 | - Import errors when running server code 13 | - "Module not found" errors 14 | - Unexpected version conflicts 15 | 16 | **Solutions:** 17 | 1. Verify all dependencies are installed: 18 | ```bash 19 | pip install -r requirements.txt 20 | ``` 21 | 22 | 2. Check for version conflicts: 23 | ```bash 24 | pip list 25 | ``` 26 | 27 | 3. Consider using a virtual environment: 28 | ```bash 29 | python -m venv venv 30 | source venv/bin/activate # On Windows: venv\Scripts\activate 31 | pip install -r requirements.txt 32 | ``` 33 | 34 | 4. Try using `uv` for faster, more reliable installation: 35 | ```bash 36 | uv pip install -r requirements.txt 37 | ``` 38 | 39 | #### Incompatible Python Version 40 | 41 | **Symptoms:** 42 | - Syntax errors in valid code 43 | - Feature not found errors 44 | - Type hint errors 45 | 46 | **Solutions:** 47 | 1. Check your Python version: 48 | ```bash 49 | python --version 50 | ``` 51 | 52 | 2. Ensure you're using Python 3.10 or higher (required for MCP): 53 | ```bash 54 | # Install or update Python if needed 55 | # Then create a new virtual environment with the correct version 56 | python3.10 -m venv venv 57 | ``` 58 | 59 | ### Node.js Environment Problems 60 | 61 | #### Missing or Inaccessible Node.js 62 | 63 | **Symptoms:** 64 | - "Command not found: npx" errors 65 | - "npx is not recognized as an internal or external command" 66 | - Node.js servers fail to start 67 | 68 | **Solutions:** 69 | 1. Verify Node.js installation: 70 | ```bash 71 | node --version 72 | npm --version 73 | npx --version 74 | ``` 75 | 76 | 2. Install Node.js if needed (from [nodejs.org](https://nodejs.org/)) 77 | 78 | 3. Check PATH environment variable: 79 | ```bash 80 | # On Unix-like systems 81 | echo $PATH 82 | 83 | # On Windows 84 | echo %PATH% 85 | ``` 86 | 87 | 4. Find the location of Node.js binaries: 88 | ```bash 89 | # On Unix-like systems 90 | which node 91 | which npm 92 | which npx 93 | 94 | # On Windows 95 | where node 96 | where npm 97 | where npx 98 | ``` 99 | 100 | 5. Add the Node.js bin directory to your PATH if needed 101 | 102 | #### NPM Package Issues 103 | 104 | **Symptoms:** 105 | - NPM packages fail to install 106 | - "Error: Cannot find module" when using npx 107 | - Permission errors during installation 108 | 109 | **Solutions:** 110 | 1. Clear npm cache: 111 | ```bash 112 | npm cache clean --force 113 | ``` 114 | 115 | 2. Try installing packages globally: 116 | ```bash 117 | npm install -g @modelcontextprotocol/server-name 118 | ``` 119 | 120 | 3. Check npm permissions: 121 | ```bash 122 | # Fix ownership issues on Unix-like systems 123 | sudo chown -R $(whoami) ~/.npm 124 | ``` 125 | 126 | 4. Use npx with explicit paths: 127 | ```bash 128 | npx --no-install @modelcontextprotocol/server-name 129 | ``` 130 | 131 | ## Server Connection Issues 132 | 133 | ### STDIO Connection Problems 134 | 135 | #### Process Launch Failures 136 | 137 | **Symptoms:** 138 | - "No such file or directory" errors 139 | - "Cannot execute binary file" errors 140 | - Process exits immediately 141 | 142 | **Solutions:** 143 | 1. Check that the command exists and is executable: 144 | ```bash 145 | # For Python servers 146 | which python 147 | 148 | # For Node.js servers 149 | which node 150 | ``` 151 | 152 | 2. Verify file paths are correct: 153 | ```bash 154 | # Check if file exists 155 | ls -l /path/to/server.py 156 | ``` 157 | 158 | 3. Use absolute paths in configuration: 159 | ```json 160 | { 161 | "command": "/usr/bin/python", 162 | "args": ["/absolute/path/to/server.py"] 163 | } 164 | ``` 165 | 166 | 4. Check file permissions: 167 | ```bash 168 | # Make script executable if needed 169 | chmod +x /path/to/server.py 170 | ``` 171 | 172 | #### STDIO Protocol Errors 173 | 174 | **Symptoms:** 175 | - "Unexpected message format" errors 176 | - "Invalid JSON" errors 177 | - Connection dropped after initialization 178 | 179 | **Solutions:** 180 | 1. Avoid mixing regular print statements with MCP protocol: 181 | ```python 182 | # Bad: writes to stdout, interfering with protocol 183 | print("Debug info") 184 | 185 | # Good: writes to stderr, doesn't interfere 186 | import sys 187 | print("Debug info", file=sys.stderr) 188 | ``` 189 | 190 | 2. Enable protocol logging for debugging: 191 | ```python 192 | import logging 193 | logging.basicConfig(level=logging.DEBUG) 194 | ``` 195 | 196 | 3. Check for blocked I/O operations 197 | 198 | ### SSE Connection Problems 199 | 200 | #### HTTP Server Issues 201 | 202 | **Symptoms:** 203 | - "Connection refused" errors 204 | - Timeout errors 205 | - SSE connection fails 206 | 207 | **Solutions:** 208 | 1. Verify server is running on the correct host/port: 209 | ```bash 210 | # Check if something is listening on the port 211 | netstat -tuln | grep 5000 212 | ``` 213 | 214 | 2. Check for firewall or network issues: 215 | ```bash 216 | # Test connection to server 217 | curl http://localhost:5000/ 218 | ``` 219 | 220 | 3. Ensure CORS is properly configured (for web clients): 221 | ```python 222 | # Example CORS headers in aiohttp 223 | response.headers.update({ 224 | 'Access-Control-Allow-Origin': '*', 225 | 'Access-Control-Allow-Methods': 'GET, POST, OPTIONS', 226 | 'Access-Control-Allow-Headers': 'Content-Type' 227 | }) 228 | ``` 229 | 230 | #### SSE Message Problems 231 | 232 | **Symptoms:** 233 | - Messages not received 234 | - "Invalid SSE format" errors 235 | - Connection closes unexpectedly 236 | 237 | **Solutions:** 238 | 1. Check SSE message format: 239 | ``` 240 | event: message 241 | data: {"jsonrpc":"2.0","id":1,"result":{...}} 242 | 243 | ``` 244 | (Note the double newline at the end) 245 | 246 | 2. Verify content-type header: 247 | ``` 248 | Content-Type: text/event-stream 249 | ``` 250 | 251 | 3. Ensure Keep-Alive is properly configured: 252 | ``` 253 | Connection: keep-alive 254 | Cache-Control: no-cache 255 | ``` 256 | 257 | ## Claude Desktop Integration Issues 258 | 259 | ### Configuration Problems 260 | 261 | #### Configuration File Issues 262 | 263 | **Symptoms:** 264 | - MCP servers don't appear in Claude 265 | - "Failed to start server" errors 266 | - No MCP icon in Claude interface 267 | 268 | **Solutions:** 269 | 1. Check configuration file location: 270 | - macOS: `~/Library/Application Support/Claude/claude_desktop_config.json` 271 | - Windows: `%APPDATA%\Claude\claude_desktop_config.json` 272 | 273 | 2. Verify JSON syntax is valid: 274 | ```json 275 | { 276 | "mcpServers": { 277 | "web-tools": { 278 | "command": "python", 279 | "args": ["/absolute/path/to/server.py"] 280 | } 281 | } 282 | } 283 | ``` 284 | 285 | 3. Create the file if it doesn't exist: 286 | ```bash 287 | # Create directory if needed 288 | mkdir -p ~/Library/Application\ Support/Claude/ 289 | 290 | # Create basic config file 291 | echo '{"mcpServers":{}}' > ~/Library/Application\ Support/Claude/claude_desktop_config.json 292 | ``` 293 | 294 | 4. Check file permissions: 295 | ```bash 296 | # Ensure user can read/write the file 297 | chmod 600 ~/Library/Application\ Support/Claude/claude_desktop_config.json 298 | ``` 299 | 300 | #### Server Path Issues 301 | 302 | **Symptoms:** 303 | - "Command not found" errors 304 | - "No such file or directory" errors 305 | 306 | **Solutions:** 307 | 1. Use absolute paths in configuration: 308 | ```json 309 | { 310 | "mcpServers": { 311 | "web-tools": { 312 | "command": "/usr/bin/python", 313 | "args": ["/Users/username/Documents/Personal/MCP/server.py"] 314 | } 315 | } 316 | } 317 | ``` 318 | 319 | 2. Avoid using environment variables or relative paths: 320 | ```json 321 | // Bad: using relative path 322 | "args": ["./server.py"] 323 | 324 | // Good: using absolute path 325 | "args": ["/Users/username/Documents/Personal/MCP/server.py"] 326 | ``` 327 | 328 | 3. Escape backslashes properly on Windows: 329 | ```json 330 | "args": ["C:\\Users\\username\\Documents\\Personal\\MCP\\server.py"] 331 | ``` 332 | 333 | ### Tool Execution Problems 334 | 335 | #### Permission Denials 336 | 337 | **Symptoms:** 338 | - "Permission denied" errors 339 | - Tools fail silently 340 | - Claude cannot access files or resources 341 | 342 | **Solutions:** 343 | 1. Check file and directory permissions: 344 | ```bash 345 | ls -la /path/to/files/ 346 | ``` 347 | 348 | 2. Run Claude Desktop with appropriate permissions 349 | 350 | 3. Check for sandboxing restrictions 351 | 352 | #### Command Execution Failures 353 | 354 | **Symptoms:** 355 | - Tools fail but not due to permission issues 356 | - Timeouts during tool execution 357 | - Tool returns error message 358 | 359 | **Solutions:** 360 | 1. Check logs for detailed error messages: 361 | ```bash 362 | # View Claude Desktop MCP logs 363 | tail -f ~/Library/Logs/Claude/mcp*.log 364 | ``` 365 | 366 | 2. Test tools directly outside of Claude: 367 | ```bash 368 | # Run the server directly and test with MCP Inspector 369 | npx @modelcontextprotocol/inspector python server.py 370 | ``` 371 | 372 | 3. Implement better error handling in tools 373 | 374 | ## Streamlit UI Issues 375 | 376 | ### Connection Problems 377 | 378 | #### Config File Access 379 | 380 | **Symptoms:** 381 | - "File not found" errors 382 | - Cannot load servers from config file 383 | - Permission errors 384 | 385 | **Solutions:** 386 | 1. Verify the config file path is correct 387 | 2. Check file permissions 388 | 3. Use the pre-filled default path if available 389 | 390 | #### Server Command Execution 391 | 392 | **Symptoms:** 393 | - "Command not found" errors 394 | - Node.js/Python not found 395 | - Server fails to start 396 | 397 | **Solutions:** 398 | 1. Check environment detection in the UI: 399 | ```python 400 | # Are Node.js and other tools detected? 401 | node_installed = bool(find_executable('node')) 402 | ``` 403 | 404 | 2. Add logging to track command execution: 405 | ```python 406 | print(f"Trying to execute: {command} {' '.join(args)}") 407 | ``` 408 | 409 | 3. Use full paths to executables 410 | 411 | ### UI Display Issues 412 | 413 | #### Tool Schema Problems 414 | 415 | **Symptoms:** 416 | - Tool parameters not displayed correctly 417 | - Input fields missing 418 | - Form submission fails 419 | 420 | **Solutions:** 421 | 1. Check tool schema format: 422 | ```python 423 | # Ensure schema has proper structure 424 | @mcp.tool() 425 | def my_tool(param1: str, param2: int = 0) -> str: 426 | """ 427 | Tool description. 428 | 429 | Args: 430 | param1: Description of param1 431 | param2: Description of param2 (default: 0) 432 | 433 | Returns: 434 | Result description 435 | """ 436 | # Implementation 437 | ``` 438 | 439 | 2. Verify all required schema fields are present 440 | 3. Check for type conversion issues 441 | 442 | #### Tool Execution Display 443 | 444 | **Symptoms:** 445 | - Results not displayed 446 | - Format issues in results 447 | - Truncated output 448 | 449 | **Solutions:** 450 | 1. Check error handling in result processing: 451 | ```python 452 | try: 453 | result = asyncio.run(call_tool(command, args, tool_name, tool_inputs)) 454 | st.subheader("Result") 455 | st.write(result) 456 | except Exception as e: 457 | st.error(f"Error: {str(e)}") 458 | ``` 459 | 460 | 2. Improve content type handling: 461 | ```python 462 | # Process different content types 463 | for item in result.content: 464 | if hasattr(item, 'text'): 465 | st.write(item.text) 466 | elif hasattr(item, 'blob'): 467 | st.write("Binary data: use appropriate display method") 468 | ``` 469 | 470 | 3. Add pagination for large results 471 | 472 | ## Tool-Specific Issues 473 | 474 | ### Web Scraping Tool Problems 475 | 476 | #### URL Formatting Issues 477 | 478 | **Symptoms:** 479 | - "Invalid URL" errors 480 | - Requests to wrong domain 481 | - URL protocol issues 482 | 483 | **Solutions:** 484 | 1. Ensure proper URL formatting: 485 | ```python 486 | # Add protocol if missing 487 | if not url.startswith(('http://', 'https://')): 488 | url = 'https://' + url 489 | ``` 490 | 491 | 2. Handle URL encoding properly: 492 | ```python 493 | from urllib.parse import quote_plus 494 | 495 | # Encode URL components 496 | safe_url = quote_plus(url) 497 | ``` 498 | 499 | 3. Validate URLs before processing: 500 | ```python 501 | import re 502 | 503 | # Simple URL validation 504 | if not re.match(r'^(https?://)?[a-zA-Z0-9][-a-zA-Z0-9.]*\.[a-zA-Z]{2,}(/.*)?$', url): 505 | raise ValueError("Invalid URL format") 506 | ``` 507 | 508 | #### HTTP Request Failures 509 | 510 | **Symptoms:** 511 | - Timeouts 512 | - Rate limiting errors 513 | - Connection refused errors 514 | 515 | **Solutions:** 516 | 1. Implement proper error handling: 517 | ```python 518 | try: 519 | async with httpx.AsyncClient() as client: 520 | response = await client.get(url, timeout=30.0) 521 | response.raise_for_status() 522 | return response.text 523 | except httpx.HTTPStatusError as e: 524 | return f"Error: HTTP status error - {e.response.status_code}" 525 | except httpx.RequestError as e: 526 | return f"Error: Request failed - {str(e)}" 527 | ``` 528 | 529 | 2. Add retries for transient errors: 530 | ```python 531 | for attempt in range(3): 532 | try: 533 | async with httpx.AsyncClient() as client: 534 | response = await client.get(url, timeout=30.0) 535 | response.raise_for_status() 536 | return response.text 537 | except (httpx.HTTPStatusError, httpx.RequestError) as e: 538 | if attempt == 2: # Last attempt 539 | raise 540 | await asyncio.sleep(1) # Wait before retry 541 | ``` 542 | 543 | 3. Add user-agent headers: 544 | ```python 545 | headers = { 546 | "User-Agent": "MCP-WebScraper/1.0", 547 | "Accept": "text/html,application/xhtml+xml,application/xml" 548 | } 549 | response = await client.get(url, headers=headers, timeout=30.0) 550 | ``` 551 | 552 | #### Content Processing Issues 553 | 554 | **Symptoms:** 555 | - Empty or malformed content 556 | - Encoding issues 557 | - Content too large 558 | 559 | **Solutions:** 560 | 1. Handle different content types: 561 | ```python 562 | if "application/json" in response.headers.get("content-type", ""): 563 | return json.dumps(response.json(), indent=2) 564 | elif "text/html" in response.headers.get("content-type", ""): 565 | # Extract main content 566 | soup = BeautifulSoup(response.text, 'html.parser') 567 | # Remove scripts, styles, etc. 568 | for script in soup(["script", "style", "meta", "noscript"]): 569 | script.extract() 570 | return soup.get_text() 571 | else: 572 | return response.text 573 | ``` 574 | 575 | 2. Handle encoding properly: 576 | ```python 577 | # Detect encoding 578 | encoding = response.encoding 579 | # Fix common encoding issues 580 | if not encoding or encoding == 'ISO-8859-1': 581 | encoding = 'utf-8' 582 | text = response.content.decode(encoding, errors='replace') 583 | ``` 584 | 585 | 3. Implement content size limits: 586 | ```python 587 | # Limit content size 588 | max_size = 100 * 1024 # 100 KB 589 | if len(response.content) > max_size: 590 | return response.content[:max_size].decode('utf-8', errors='replace') + "\n[Content truncated...]" 591 | ``` 592 | 593 | ## Protocol and Message Issues 594 | 595 | ### JSON-RPC Issues 596 | 597 | #### Invalid Message Format 598 | 599 | **Symptoms:** 600 | - "Invalid request" errors 601 | - "Parse error" errors 602 | - Unexpected protocol errors 603 | 604 | **Solutions:** 605 | 1. Validate JSON-RPC message structure: 606 | ```python 607 | def validate_jsonrpc_message(message): 608 | if "jsonrpc" not in message or message["jsonrpc"] != "2.0": 609 | raise ValueError("Invalid jsonrpc version") 610 | 611 | if "method" in message: 612 | if "id" in message: 613 | # It's a request 614 | if "params" in message and not isinstance(message["params"], (dict, list)): 615 | raise ValueError("Params must be object or array") 616 | else: 617 | # It's a notification 618 | pass 619 | elif "id" in message: 620 | # It's a response 621 | if "result" not in message and "error" not in message: 622 | raise ValueError("Response must have result or error") 623 | if "error" in message and "result" in message: 624 | raise ValueError("Response cannot have both result and error") 625 | else: 626 | raise ValueError("Invalid message format") 627 | ``` 628 | 629 | 2. Use proper JSON-RPC libraries: 630 | ```python 631 | from jsonrpcserver import method, async_dispatch 632 | from jsonrpcclient import request, parse 633 | ``` 634 | 635 | 3. Check for JSON encoding/decoding issues: 636 | ```python 637 | try: 638 | json_str = json.dumps(message) 639 | decoded = json.loads(json_str) 640 | # Compare decoded with original to check for precision loss 641 | except Exception as e: 642 | print(f"JSON error: {str(e)}") 643 | ``` 644 | 645 | #### Method Not Found 646 | 647 | **Symptoms:** 648 | - "Method not found" errors 649 | - Methods available but not accessible 650 | - Methods incorrectly implemented 651 | 652 | **Solutions:** 653 | 1. Check method registration: 654 | ```python 655 | # For FastMCP, ensure methods are properly decorated 656 | @mcp.tool() 657 | def my_tool(): 658 | pass 659 | 660 | # For low-level API, ensure methods are registered 661 | server.setRequestHandler("tools/call", handle_tool_call) 662 | ``` 663 | 664 | 2. Verify method names exactly match specifications: 665 | ``` 666 | tools/list 667 | tools/call 668 | resources/list 669 | resources/read 670 | prompts/list 671 | prompts/get 672 | ``` 673 | 674 | 3. Check capability negotiation: 675 | ```python 676 | # Ensure capabilities are properly declared 677 | server = FastMCP( 678 | "MyServer", 679 | capabilities={ 680 | "tools": { 681 | "listChanged": True 682 | } 683 | } 684 | ) 685 | ``` 686 | 687 | ### Error Handling Issues 688 | 689 | #### Unhandled Exceptions 690 | 691 | **Symptoms:** 692 | - Crashes during operation 693 | - Unexpected termination 694 | - Missing error responses 695 | 696 | **Solutions:** 697 | 1. Wrap operations in try-except blocks: 698 | ```python 699 | @mcp.tool() 700 | async def risky_operation(param: str) -> str: 701 | try: 702 | # Potentially dangerous operation 703 | result = await perform_operation(param) 704 | return result 705 | except Exception as e: 706 | # Log the error 707 | logging.error(f"Error in risky_operation: {str(e)}") 708 | # Return a friendly error message 709 | return f"Operation failed: {str(e)}" 710 | ``` 711 | 712 | 2. Use context managers for resource cleanup: 713 | ```python 714 | @mcp.tool() 715 | async def file_operation(path: str) -> str: 716 | try: 717 | async with aiofiles.open(path, "r") as f: 718 | content = await f.read() 719 | return content 720 | except FileNotFoundError: 721 | return f"File not found: {path}" 722 | except PermissionError: 723 | return f"Permission denied: {path}" 724 | except Exception as e: 725 | return f"Error reading file: {str(e)}" 726 | ``` 727 | 728 | 3. Implement proper error responses: 729 | ```python 730 | # Return error in tool result 731 | return { 732 | "isError": True, 733 | "content": [ 734 | { 735 | "type": "text", 736 | "text": f"Error: {str(e)}" 737 | } 738 | ] 739 | } 740 | ``` 741 | 742 | #### Error Response Format 743 | 744 | **Symptoms:** 745 | - Clients can't parse error responses 746 | - Errors not displayed properly 747 | - Missing error details 748 | 749 | **Solutions:** 750 | 1. Use standard error codes: 751 | ```python 752 | # JSON-RPC standard error codes 753 | PARSE_ERROR = -32700 754 | INVALID_REQUEST = -32600 755 | METHOD_NOT_FOUND = -32601 756 | INVALID_PARAMS = -32602 757 | INTERNAL_ERROR = -32603 758 | 759 | # MCP-specific error codes 760 | RESOURCE_NOT_FOUND = -32001 761 | TOOL_NOT_FOUND = -32002 762 | PROMPT_NOT_FOUND = -32003 763 | EXECUTION_FAILED = -32004 764 | ``` 765 | 766 | 2. Include helpful error messages: 767 | ```python 768 | raise McpError( 769 | code=INVALID_PARAMS, 770 | message="Invalid parameters", 771 | data={ 772 | "details": "Parameter 'url' must be a valid URL", 773 | "parameter": "url" 774 | } 775 | ) 776 | ``` 777 | 778 | 3. Log detailed errors but return simplified messages: 779 | ```python 780 | try: 781 | # Operation 782 | except Exception as e: 783 | # Log detailed error 784 | logging.error(f"Detailed error: {str(e)}", exc_info=True) 785 | # Return simplified error to client 786 | raise McpError( 787 | code=INTERNAL_ERROR, 788 | message="Operation failed" 789 | ) 790 | ``` 791 | 792 | ## Advanced Troubleshooting Techniques 793 | 794 | ### Logging and Monitoring 795 | 796 | #### Setting Up Comprehensive Logging 797 | 798 | **Approach:** 799 | 1. Configure logging at different levels: 800 | ```python 801 | import logging 802 | 803 | # Set up file handler 804 | file_handler = logging.FileHandler("mcp_server.log") 805 | file_handler.setLevel(logging.DEBUG) 806 | 807 | # Set up console handler 808 | console_handler = logging.StreamHandler() 809 | console_handler.setLevel(logging.INFO) 810 | 811 | # Set up formatter 812 | formatter = logging.Formatter('%(asctime)s - %(name)s - %(levelname)s - %(message)s') 813 | file_handler.setFormatter(formatter) 814 | console_handler.setFormatter(formatter) 815 | 816 | # Configure logger 817 | logger = logging.getLogger("mcp") 818 | logger.setLevel(logging.DEBUG) 819 | logger.addHandler(file_handler) 820 | logger.addHandler(console_handler) 821 | ``` 822 | 823 | 2. Log at appropriate levels: 824 | ```python 825 | logger.debug("Detailed debug info") 826 | logger.info("General operational info") 827 | logger.warning("Warning - something unexpected") 828 | logger.error("Error - operation failed") 829 | logger.critical("Critical - system failure") 830 | ``` 831 | 832 | 3. Use structured logging for better analysis: 833 | ```python 834 | import json 835 | 836 | def log_structured(level, message, **kwargs): 837 | log_data = { 838 | "message": message, 839 | **kwargs 840 | } 841 | log_str = json.dumps(log_data) 842 | 843 | if level == "debug": 844 | logger.debug(log_str) 845 | elif level == "info": 846 | logger.info(log_str) 847 | # etc. 848 | 849 | # Usage 850 | log_structured("info", "Tool called", tool="web_scrape", url="example.com") 851 | ``` 852 | 853 | #### Protocol Tracing 854 | 855 | **Approach:** 856 | 1. Set up protocol tracing: 857 | ```python 858 | # Enable detailed protocol tracing 859 | os.environ["MCP_TRACE"] = "1" 860 | ``` 861 | 862 | 2. Log all protocol messages: 863 | ```python 864 | async def log_protocol_message(direction, message): 865 | log_structured( 866 | "debug", 867 | f"MCP {direction}", 868 | message=message, 869 | timestamp=datetime.now().isoformat() 870 | ) 871 | 872 | # Intercept all messages 873 | original_send = protocol.send 874 | 875 | async def logged_send(message): 876 | await log_protocol_message("SEND", message) 877 | return await original_send(message) 878 | 879 | protocol.send = logged_send 880 | ``` 881 | 882 | 3. Use MCP Inspector for visual tracing 883 | 884 | ### Performance Diagnosis 885 | 886 | #### Identifying Bottlenecks 887 | 888 | **Approach:** 889 | 1. Time operations: 890 | ```python 891 | import time 892 | 893 | @mcp.tool() 894 | async def slow_operation(param: str) -> str: 895 | start_time = time.time() 896 | 897 | # Operation 898 | result = await perform_operation(param) 899 | 900 | elapsed_time = time.time() - start_time 901 | logger.info(f"Operation took {elapsed_time:.3f} seconds") 902 | 903 | return result 904 | ``` 905 | 906 | 2. Profile code: 907 | ```python 908 | import cProfile 909 | import pstats 910 | 911 | def profile_function(func, *args, **kwargs): 912 | profiler = cProfile.Profile() 913 | profiler.enable() 914 | result = func(*args, **kwargs) 915 | profiler.disable() 916 | 917 | stats = pstats.Stats(profiler).sort_stats("cumtime") 918 | stats.print_stats(20) # Print top 20 items 919 | 920 | return result 921 | ``` 922 | 923 | 3. Monitor resource usage: 924 | ```python 925 | import psutil 926 | 927 | def log_resource_usage(): 928 | process = psutil.Process() 929 | memory_info = process.memory_info() 930 | cpu_percent = process.cpu_percent(interval=1) 931 | 932 | logger.info(f"Memory usage: {memory_info.rss / 1024 / 1024:.2f} MB") 933 | logger.info(f"CPU usage: {cpu_percent:.2f}%") 934 | ``` 935 | 936 | #### Optimizing Performance 937 | 938 | **Approach:** 939 | 1. Use connection pooling: 940 | ```python 941 | # Create a shared HTTP client 942 | http_client = httpx.AsyncClient() 943 | 944 | @mcp.tool() 945 | async def fetch_url(url: str) -> str: 946 | # Use shared client instead of creating a new one each time 947 | response = await http_client.get(url) 948 | return response.text 949 | 950 | # Clean up on shutdown 951 | @lifespan.cleanup 952 | async def close_http_client(): 953 | await http_client.aclose() 954 | ``` 955 | 956 | 2. Implement caching: 957 | ```python 958 | # Simple in-memory cache 959 | cache = {} 960 | cache_ttl = {} 961 | 962 | async def cached_fetch(url, ttl=300): 963 | now = time.time() 964 | 965 | # Check if in cache and not expired 966 | if url in cache and now < cache_ttl.get(url, 0): 967 | return cache[url] 968 | 969 | # Fetch and cache 970 | response = await http_client.get(url) 971 | result = response.text 972 | 973 | cache[url] = result 974 | cache_ttl[url] = now + ttl 975 | 976 | return result 977 | ``` 978 | 979 | 3. Use async operations effectively: 980 | ```python 981 | # Run operations in parallel 982 | async def fetch_multiple(urls): 983 | tasks = [http_client.get(url) for url in urls] 984 | responses = await asyncio.gather(*tasks) 985 | return [response.text for response in responses] 986 | ``` 987 | 988 | ### Debugging Complex Servers 989 | 990 | #### Interactive Debugging 991 | 992 | **Approach:** 993 | 1. Set up Python debugger: 994 | ```python 995 | import pdb 996 | 997 | @mcp.tool() 998 | def debug_tool(param: str) -> str: 999 | # Set breakpoint 1000 | pdb.set_trace() 1001 | # Rest of function 1002 | ``` 1003 | 1004 | 2. Use remote debugging for production: 1005 | ```python 1006 | from debugpy import listen, wait_for_client 1007 | 1008 | # Set up remote debugger 1009 | listen(("0.0.0.0", 5678)) 1010 | wait_for_client() # Wait for the debugger to attach 1011 | ``` 1012 | 1013 | 3. Use logging-based debugging: 1014 | ```python 1015 | def trace_function(func): 1016 | def wrapper(*args, **kwargs): 1017 | arg_str = ", ".join([ 1018 | *[repr(arg) for arg in args], 1019 | *[f"{k}={repr(v)}" for k, v in kwargs.items()] 1020 | ]) 1021 | logger.debug(f"CALL: {func.__name__}({arg_str})") 1022 | 1023 | try: 1024 | result = func(*args, **kwargs) 1025 | logger.debug(f"RETURN: {func.__name__} -> {repr(result)}") 1026 | return result 1027 | except Exception as e: 1028 | logger.debug(f"EXCEPTION: {func.__name__} -> {str(e)}") 1029 | raise 1030 | 1031 | return wrapper 1032 | ``` 1033 | 1034 | #### Reproducing Issues 1035 | 1036 | **Approach:** 1037 | 1. Create minimal test cases: 1038 | ```python 1039 | # test_web_scrape.py 1040 | import asyncio 1041 | from server import mcp 1042 | 1043 | async def test_web_scrape(): 1044 | # Get tool function 1045 | web_scrape = mcp._tools["web_scrape"] 1046 | 1047 | # Test with different inputs 1048 | result1 = await web_scrape("example.com") 1049 | print(f"Result 1: {result1[:100]}...") 1050 | 1051 | result2 = await web_scrape("invalid^^url") 1052 | print(f"Result 2: {result2}") 1053 | 1054 | # Add more test cases 1055 | 1056 | if __name__ == "__main__": 1057 | asyncio.run(test_web_scrape()) 1058 | ``` 1059 | 1060 | 2. Record and replay protocol sessions: 1061 | ```python 1062 | # Record session 1063 | async def record_session(file_path): 1064 | messages = [] 1065 | 1066 | # Intercept messages 1067 | original_send = protocol.send 1068 | original_receive = protocol.receive 1069 | 1070 | async def logged_send(message): 1071 | messages.append({"direction": "send", "message": message}) 1072 | return await original_send(message) 1073 | 1074 | async def logged_receive(): 1075 | message = await original_receive() 1076 | messages.append({"direction": "receive", "message": message}) 1077 | return message 1078 | 1079 | protocol.send = logged_send 1080 | protocol.receive = logged_receive 1081 | 1082 | # Run session 1083 | # ... 1084 | 1085 | # Save recorded session 1086 | with open(file_path, "w") as f: 1087 | json.dump(messages, f, indent=2) 1088 | ``` 1089 | 1090 | 3. Use request/response mocking: 1091 | ```python 1092 | # Mock HTTP responses 1093 | class MockResponse: 1094 | def __init__(self, text, status_code=200): 1095 | self.text = text 1096 | self.status_code = status_code 1097 | 1098 | def raise_for_status(self): 1099 | if self.status_code >= 400: 1100 | raise httpx.HTTPStatusError(f"HTTP Error: {self.status_code}", request=None, response=self) 1101 | 1102 | # Replace httpx client get method 1103 | async def mock_get(url, **kwargs): 1104 | if url == "https://example.com": 1105 | return MockResponse("<html><body>Example content</body></html>") 1106 | elif url == "https://error.example.com": 1107 | return MockResponse("Error", status_code=500) 1108 | else: 1109 | raise httpx.RequestError(f"Connection error: {url}") 1110 | 1111 | # Apply mock 1112 | httpx.AsyncClient.get = mock_get 1113 | ``` 1114 | 1115 | ## Conclusion 1116 | 1117 | Troubleshooting MCP servers requires a systematic approach and understanding of the various components involved. By following the guidelines in this document, you should be able to diagnose and resolve most common issues. 1118 | 1119 | Remember these key principles: 1120 | 1121 | 1. **Start simple**: Check the basics first (environment, commands, paths) 1122 | 2. **Use logging**: Enable detailed logging to understand what's happening 1123 | 3. **Test incrementally**: Test individual components before full integration 1124 | 4. **Check documentation**: Consult MCP documentation for specifications 1125 | 5. **Use tools**: Leverage MCP Inspector and other debugging tools 1126 | 1127 | The next document will explain how to extend this repository with new tools. 1128 | ```