#
tokens: 16165/50000 2/25 files (page 2/3)
lines: on (toggle) GitHub
raw markdown copy reset
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 | 
```
Page 2/3FirstPrevNextLast