📚 Related Reading

← Back to Home

MCP Server 协议 Server Python SDK Pitfalls

MCPPythonSDKDeveloper ToolsPitfalls

The Problem: Why I Needed to Build a Custom MCP Server

I had a workflow pain point: every time I needed to check customer data, I'd have to open multiple dashboards in a browser and switch between them. Then I read about MCP (Model Context Protocol) — the idea that you could let AI connect to local files, databases, and tools. I thought: I'll write my own MCP Server to wrap the CRM API as an AI-callable tool.

It took me 3 days to get the first Hello World running. Along the way, I hit 5 pitfalls.

Pitfall 1: stdio Transport Protocol Misconfiguration

The first pitfall was the most basic: the official MCP Python SDK uses stdio communication by default, but I didn't know this and tried to start the Server with HTTP:

from mcp.server.fastmcp import FastMCP

mcp = FastMCP("My Server")
# This Server is in stdio mode by default!

The AI client couldn't connect, because stdio and HTTP are two incompatible transport protocols. In HTTP mode, the Server listens on a port; in stdio mode, it communicates via stdin/stdout.

**Debugging**: I read the MCP Inspector docs (npx @modelcontextprotocol/inspector lets you test stdio Servers) and confirmed the correct stdio startup method.

Solution: In stdio mode, you don't need to start an HTTP server — just run directly:

# server.py - stdio mode
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("My Server")

@mcp.tool()
def get_customer(customer_id: str) -> dict:
    """Query customer information"""
    return {"name": "John Doe", "status": "active"}

# Run directly, communicate via stdio
if __name__ == "__main__":
    mcp.run()

**Prevention**: MCP Servers default to stdio mode. If you want HTTP, you need to explicitly configure the transport parameter (mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)).

Pitfall 2: Tool Naming Violations Cause AI to Fail Calling

The Server was running, but the AI said it couldn't find the tool. After debugging, I realized it was a naming issue.

My tool definition was:

@mcp.tool()
def Get_Customer_Info(customer_id: str) -> dict:
    """Query customer information"""
    return {"name": "John Doe"}

In the MCP protocol, tool names must be lowercase letters with underscores — no camelCase allowed. The AI sends requests using snake_case, but my tool was called Get_Customer_Info, so it couldn't match.

**Debugging**: I tested with MCP Inspector. In the Tools tab, I could see the tool, but it displayed as Get_Customer_Info rather than get_customer_info.

**Solution**: Tool names must conform to the ^[a-z][a-z0-9_]*$ regex — lowercase letter start, only lowercase letters/numbers/underscores:

@mcp.tool()
def get_customer_info(customer_id: str) -> dict:
    """Query customer information (returns status and contact)"""
    return {"name": "John Doe", "phone": "+1234567890"}

# Correct! Name conforms to snake_case

Prevention: Always use snake_case for tool names — it's a hard requirement in the MCP protocol spec.

Pitfall 3: Resource Path Prefix Mishandling

The tool was callable, but then the resource had an issue.

My Server had a resource for the customer list:

@mcp.resource("customers://list")
def get_customer_list() -> str:
    """Return customer list"""
    return "John, Jane, Bob"

But the AI reported an error: Invalid resource URI: customers://list. The problem was the handling of the :// part.

**Debugging**: After reading the MCP specification, I found that resource URIs must follow RFC 3986 format: {protocol}://{authority}/{path}. The :// is the protocol separator, so customers://list was parsed as protocol=customers, authority=(empty), path=list. The empty authority caused the parse to fail.

**Solution**: MCP resource URIs must include a valid authority component — the part after :// cannot be empty:

# Wrong: customers://list — authority is empty
@mcp.resource("customers://list")

# Correct: use a path with /
@mcp.resource("customers://")
def get_customers():
    """Customer list resource endpoint"""
    return "John, Jane, Bob"

# Or use path format
@mcp.resource("customers:///list")
def get_customer_list():
    return "John, Jane, Bob"

**Prevention**: MCP resource URIs need a valid authority component. After ://, there must be at least a /. Test with MCP Inspector when unsure.

Pitfall 4: Python Version Compatibility Issues

The code ran fine locally, but on the production deployment (Ubuntu 22.04, Python 3.10) it threw:

ImportError: cannot import name 'Fault' from 'mcp.server'

My development environment was Python 3.12, but certain versions of the MCP Python SDK have Python version requirements.

**Debugging**: I ran pip show and checked the requirements. MCP SDK 0.9.0+ requires Python 3.11+, but the customer's server was running 3.10.

Solution:

# Check Python version first
python3 --version
# Output: Python 3.10.12

# Upgrade Python (Ubuntu 22.04)
sudo add-apt-repository ppa:deadsnakes/ppa
sudo apt update
sudo apt install python3.11
python3.11 --version

# Use virtual environment with correct version
python3.11 -m venv venv
source venv/bin/activate
pip install mcp==1.0.0  # Find version compatible with Python 3.11

**Prevention**: Check Python version requirements before deploying. Add requires-python = ">=3.11" in pyproject.toml to enforce it.

Pitfall 5: stdio Buffer Timeout Under Long Operations

The Server worked fine in local testing, but long AI calls would timeout:

Error: stdio read timeout after 30s

The problem: my tool queried a remote API, which normally takes 10-15 seconds. But MCP Inspector's default stdio read timeout is 30 seconds, and in production, the AI Client's timeout could be even shorter.

Debugging: I manually triggered the tool in MCP Inspector's Tools tab. It didn't timeout every time — only when the remote API was slow to respond.

Solution: There are two approaches:

1. Increase timeout configuration (client-side setting):

# Set longer timeout when client connects
mcp_client = Client(
    server,
    timeout=120  # 120 second timeout
)

2. Add async handling to the tool (reduce blocking):

import asyncio

@mcp.tool()
async def get_customer(customer_id: str) -> dict:
    """Async query customer info (with timeout control)"""
    try:
        # Use asyncio.wait_for to control timeout
        result = await asyncio.wait_for(
            query_crm_api(customer_id),
            timeout=60
        )
        return result
    except asyncio.TimeoutError:
        return {"error": "Query timeout, please retry later"}

Prevention: For MCP Server tools involving remote I/O, always use async patterns and set timeouts — avoid blocking stdio communication.

Pitfall Summary: 5-Point Configuration Checklist

ProblemCauseSolution
AI can't connect to Serverstdio vs HTTP transport confusionstdio mode runs directly, HTTP mode listens on port
Tool not foundUsed camelCase namingChange to snake_case naming
Resource URI errorAuthority component emptyAdd `/` to make path non-empty
ImportError after deployPython version incompatibleConfirm requires-python, upgrade if needed
stdio timeoutRemote I/O blocksUse async + asyncio.wait_for

MCP is a new protocol (released late 2024), documentation is still maturing, and many pitfalls require hands-on debugging. This review should help you skip these.

👉 Try it out: Deploy your MCP Server on **DigitalOcean** ($4/month, great Python environment), and use the official MCP Inspector testing method to verify your Server works correctly.

👉 **Want to build AI workflows without servers?** Try MiniMax API — integrates with MCP for intelligent automation pipelines.

🔗 Related Tech Articles

Deep dive into related technical topics:

MCP Server Python SDK Pitfalls
技术标签: mcp, python
MCP Server开发踩坑全记录
技术标签: python, mcp
MCP Server开发踩坑全记录
技术标签: python, mcp
💻 Recommended Hardware
查看推荐 →