🇺🇸 EN

用 Python 构建 MCP 服务器:完整分步教程

学习如何用 Python 和 FastMCP 构建 MCP 服务器。涵盖工具、资源、提示词、MCP Inspector 测试与部署的完整教程。

Bruce

MCPPythonClaude CodeAI AgentTutorial

AI Guides

2344  字

2026-03-05 06:00 +0000


用 Python 和 FastMCP 分步构建 MCP 服务器教程

Python 是 AI 开发者最常用的语言,而 MCP Python SDK 让构建自定义 MCP 服务器变得非常简单。借助基于装饰器的 FastMCP 框架,你只需不到 50 行代码就能从零开始搭建一个可运行的服务器。

本教程将带你从头开始构建 Python MCP 服务器。你将创建工具、资源和提示词,正确处理错误,使用 MCP Inspector 测试,连接到 Claude Code,并构建一个实用的 SQLite 数据库服务器。所有代码示例都是完整且可运行的。

什么是 MCP?为什么要构建自定义服务器

MCP(Model Context Protocol,模型上下文协议) 是一个开放标准,它让 AI 模型能够连接到外部工具、数据库和服务。可以把它想象成 AI 的 USB-C——一个通用协议,适用于 Claude Code、Cursor、VS Code Copilot 以及任何兼容 MCP 的客户端。

MCP 服务器暴露三种类型的能力:

能力作用示例
工具(Tools)让 AI 执行操作查询数据库、调用 API
资源(Resources)让 AI 读取数据配置文件、日志、系统信息
提示词(Prompts)可复用的交互模板代码审查模板、分析模板

关于 MCP 架构的深入了解,请参阅 MCP 协议详解:AI 工具的通用标准。如果你已经用 TypeScript 构建过服务器,请参阅 用 TypeScript 构建 MCP 服务器 进行对比。

本教程聚焦于 Python——非常适合数据科学工作流、ML 管道以及 Python 已经是你主要语言的脚本任务。

前置要求

依赖最低版本检查命令
Python>= 3.10python3 --version
uv(推荐)最新版uv --version
pip(替代)>= 22.0pip --version
Claude Code最新版claude --version

安装 uv(推荐)

uv 是一个快速的 Python 包管理器,能自动处理虚拟环境。MCP 官方文档将其作为默认工具。

# macOS / Linux
curl -LsSf https://astral.sh/uv/install.sh | sh

# 或者用 Homebrew
brew install uv

# 验证安装
uv --version

如果你更喜欢 pip,本教程中每个 uv 命令都会附带对应的 pip 等效命令。

项目设置

第一步:初始化项目

# 用 uv 创建项目
uv init weather-mcp-server
cd weather-mcp-server

# 添加带 CLI 工具的 MCP SDK
uv add "mcp[cli]"

用 pip 的替代方式:

mkdir weather-mcp-server
cd weather-mcp-server
python3 -m venv .venv
source .venv/bin/activate
pip install "mcp[cli]"

第二步:配置 pyproject.toml

编辑 uv init 创建的 pyproject.toml

[project]
name = "weather-mcp-server"
version = "1.0.0"
description = "MCP server providing weather data to AI assistants"
requires-python = ">=3.10"
dependencies = [
    "mcp[cli]>=1.2.0",
]

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[project.scripts]
weather-mcp-server = "weather_mcp_server.server:main"

[project.scripts] 入口点很重要——它让用户在安装包后可以直接作为命令运行你的服务器。

第三步:创建项目结构

mkdir -p src/weather_mcp_server
touch src/weather_mcp_server/__init__.py
touch src/weather_mcp_server/server.py

项目布局:

weather-mcp-server/
├── pyproject.toml
├── src/
│   └── weather_mcp_server/
│       ├── __init__.py
│       └── server.py       # 主服务器代码
└── README.md

构建你的第一个服务器:天气工具

打开 src/weather_mcp_server/server.py,编写服务器代码:

"""天气 MCP 服务器 - 为 AI 助手提供天气数据。"""

from mcp.server.fastmcp import FastMCP

# 创建 FastMCP 服务器实例
mcp = FastMCP("weather-server")

# 模拟天气数据(在生产环境中替换为真实 API)
WEATHER_DATA = {
    "New York": {"temp_c": 2, "condition": "Cloudy", "humidity": 65, "wind_kmh": 20},
    "San Francisco": {"temp_c": 14, "condition": "Sunny", "humidity": 70, "wind_kmh": 15},
    "London": {"temp_c": 8, "condition": "Rainy", "humidity": 85, "wind_kmh": 25},
    "Tokyo": {"temp_c": 10, "condition": "Clear", "humidity": 55, "wind_kmh": 10},
    "Sydney": {"temp_c": 25, "condition": "Sunny", "humidity": 60, "wind_kmh": 18},
    "Berlin": {"temp_c": 5, "condition": "Overcast", "humidity": 75, "wind_kmh": 22},
}


@mcp.tool()
def get_weather(city: str) -> str:
    """获取指定城市的当前天气。

    Args:
        city: 城市名称,例如 "New York"、"London"、"Tokyo"

    Returns:
        天气报告,包含温度、天气状况、湿度和风速。
    """
    data = WEATHER_DATA.get(city)
    if not data:
        return f"没有 '{city}' 的天气数据。可用城市:{', '.join(WEATHER_DATA.keys())}"

    return (
        f"{city} 的天气:\n"
        f"  温度:{data['temp_c']}°C\n"
        f"  天气:{data['condition']}\n"
        f"  湿度:{data['humidity']}%\n"
        f"  风速:{data['wind_kmh']} km/h"
    )


@mcp.tool()
def compare_weather(city1: str, city2: str) -> str:
    """比较两个城市的天气。

    Args:
        city1: 第一个城市名称
        city2: 第二个城市名称

    Returns:
        两个城市的天气并排对比,包含温差。
    """
    data1 = WEATHER_DATA.get(city1)
    data2 = WEATHER_DATA.get(city2)

    if not data1:
        return f"没有 '{city1}' 的数据。可用城市:{', '.join(WEATHER_DATA.keys())}"
    if not data2:
        return f"没有 '{city2}' 的数据。可用城市:{', '.join(WEATHER_DATA.keys())}"

    diff = data1["temp_c"] - data2["temp_c"]
    warmer = city1 if diff > 0 else city2

    return (
        f"天气对比:{city1} vs {city2}\n"
        f"{'─' * 40}\n"
        f"{city1}{data1['condition']}{data1['temp_c']}°C,"
        f"湿度 {data1['humidity']}%,风速 {data1['wind_kmh']} km/h\n"
        f"{city2}{data2['condition']}{data2['temp_c']}°C,"
        f"湿度 {data2['humidity']}%,风速 {data2['wind_kmh']} km/h\n"
        f"{'─' * 40}\n"
        f"温差:{abs(diff)}°C({warmer} 更暖)"
    )


def main():
    """服务器入口点。"""
    mcp.run()


if __name__ == "__main__":
    main()

代码中的关键概念:

  • FastMCP("weather-server") 创建服务器实例。名称用于向 MCP 客户端标识你的服务器。
  • @mcp.tool() 将一个函数注册为 MCP 工具。函数的 docstring 会成为工具描述,AI 客户端据此决定何时调用它。
  • 类型提示很重要。 SDK 读取 city: str 来生成输入 schema。AI 客户端使用此 schema 来构造正确的工具调用。
  • mcp.run() 默认以 STDIO 传输方式启动服务器——这是本地 MCP 服务器的标准方式。

这就是全部所需。没有样板 JSON-RPC 处理,没有手动 schema 定义。FastMCP 从你的函数签名和 docstring 中自动推导一切。

添加资源

资源让 AI 客户端可以从你的服务器读取数据。与工具(执行操作)不同,资源用于暴露信息。

将以下代码添加到 server.py

@mcp.resource("weather://cities")
def list_cities() -> str:
    """列出所有有天气数据的城市。

    返回支持的城市名称的 JSON 数组。
    """
    import json
    return json.dumps(list(WEATHER_DATA.keys()), indent=2)


@mcp.resource("weather://city/{city_name}")
def get_city_details(city_name: str) -> str:
    """以 JSON 格式获取指定城市的详细天气数据。

    此资源模板接受 URL 中的任意城市名称。
    """
    import json
    data = WEATHER_DATA.get(city_name)
    if not data:
        return json.dumps({"error": f"未找到城市 '{city_name}'"})
    return json.dumps({"city": city_name, **data}, indent=2)

第二个资源使用了资源模板——URI 中的 {city_name} 是一个动态参数。当客户端请求 weather://city/Tokyo 时,FastMCP 会将 "Tokyo" 作为 city_name 传入。

资源与工具的对比:

方面资源工具
用途读取数据执行操作
类比GET 请求POST 请求
副作用可能有副作用
发现方式客户端浏览可用资源客户端查看带 schema 的工具列表

添加提示词

提示词是可复用的交互模板。它们帮助用户(和 AI 客户端)以结构化方式调用服务器的能力。

@mcp.prompt()
def travel_advisory(destination: str) -> str:
    """根据目的地的当前天气获取旅行建议。

    Args:
        destination: 你计划前往的城市
    """
    data = WEATHER_DATA.get(destination)
    if not data:
        return f"请查看 {destination} 的天气状况,并建议合适的穿着和旅行贴士。"

    return (
        f"{destination} 当前天气为 {data['condition']},"
        f"温度 {data['temp_c']}°C,湿度 {data['humidity']}%,"
        f"风速 {data['wind_kmh']} km/h。\n\n"
        f"根据这些条件,请建议:\n"
        f"1. 适合携带的衣物\n"
        f"2. 天气相关的旅行贴士\n"
        f"3. 户外活动的最佳时段"
    )


@mcp.prompt()
def weather_report(cities: str = "all") -> str:
    """生成全面的天气报告。

    Args:
        cities: 逗号分隔的城市名称,或 "all" 表示所有城市
    """
    if cities == "all":
        city_list = list(WEATHER_DATA.keys())
    else:
        city_list = [c.strip() for c in cities.split(",")]

    city_str = ", ".join(city_list)
    return (
        f"请为以下城市生成专业的天气报告:{city_str}\n\n"
        f"包含:\n"
        f"- 当前天气概况\n"
        f"- 温度趋势\n"
        f"- 旅行建议\n"
        f"- 天气预警(如有)"
    )

提示词是用户主动调用的——它们出现在客户端的提示词库中,可以手动选择。它们不会像工具那样被 AI 自动调用。

错误处理与日志最佳实践

生产级 MCP 服务器需要正确的错误处理。以下是有效的模式。

返回错误,而非抛出异常

MCP 工具应该将错误信息作为字符串返回,而不是抛出异常。这能让 AI 模型理解出了什么问题。

import httpx

@mcp.tool()
async def fetch_weather_api(city: str) -> str:
    """从外部 API 获取实时天气数据。

    Args:
        city: 要查询的城市名称
    """
    try:
        async with httpx.AsyncClient() as client:
            response = await client.get(
                "https://api.weatherapi.com/v1/current.json",
                params={"key": "YOUR_API_KEY", "q": city},
                timeout=10.0,
            )
            response.raise_for_status()
            data = response.json()

            current = data["current"]
            return (
                f"{city} 的天气:\n"
                f"  温度:{current['temp_c']}°C\n"
                f"  天气:{current['condition']['text']}\n"
                f"  湿度:{current['humidity']}%\n"
                f"  风速:{current['wind_kph']} km/h"
            )

    except httpx.TimeoutException:
        return f"错误:获取 '{city}' 天气时请求超时。请稍后重试。"
    except httpx.HTTPStatusError as e:
        return f"错误:API 为 '{city}' 返回了状态码 {e.response.status_code}。"
    except Exception as e:
        return f"错误:无法获取 '{city}' 的天气:{str(e)}"

使用 Context 对象进行日志记录

FastMCP 提供了 Context 对象,用于结构化日志记录和进度报告:

from mcp.server.fastmcp import Context

@mcp.tool()
async def analyze_weather_trends(city: str, ctx: Context) -> str:
    """分析城市的天气趋势,带进度报告。

    Args:
        city: 要分析的城市名称
    """
    # 日志消息(发送到 MCP 客户端)
    await ctx.info(f"开始分析 {city} 的天气")

    # 报告进度(对长时间运行的操作很有用)
    await ctx.report_progress(0, 3)

    await ctx.debug("正在获取当前数据...")
    data = WEATHER_DATA.get(city)
    if not data:
        await ctx.warning(f"未找到 {city} 的数据")
        return f"没有 '{city}' 的可用数据"

    await ctx.report_progress(1, 3)

    await ctx.debug("正在计算分析结果...")
    # 模拟分析工作
    analysis = f"{city} 的天气分析:{data['condition']} 天气,{data['temp_c']}°C"

    await ctx.report_progress(2, 3)

    await ctx.info(f"{city} 的分析完成")
    await ctx.report_progress(3, 3)

    return analysis

Context 对象提供以下日志方法:

方法用途
ctx.debug()详细调试信息
ctx.info()一般信息
ctx.warning()警告消息
ctx.error()错误消息
ctx.report_progress(current, total)长任务的进度更新

使用 stderr 进行调试输出

和 TypeScript MCP 服务器一样,不要用 print() 进行调试。MCP 使用 STDIO 进行通信——stdout 上的任何内容都会被解释为协议消息。

import sys

# 错误方式 - 破坏 STDIO 协议
print("Debug: processing request")

# 正确方式 - 使用 stderr,不会干扰协议
print("Debug: processing request", file=sys.stderr)

实际开发中,优先使用 Context 日志方法而非 print 语句。它们通过 MCP 协议向客户端发送结构化日志消息。

本地测试服务器

使用 MCP Inspector

MCP SDK 包含一个内置测试工具叫 MCP Inspector。它启动一个 Web 界面,让你可以交互式地测试每个工具、资源和提示词。

# 启动 Inspector
mcp dev src/weather_mcp_server/server.py

# 或者用 uv(如果不在激活的 venv 中)
uv run mcp dev src/weather_mcp_server/server.py

这会在 http://127.0.0.1:6274 打开一个 Web 界面。在那里你可以:

  1. 点击 Connect 建立与服务器的连接
  2. 浏览 Tools 标签 — 查看所有注册的工具及其 schema
  3. 测试工具 — 填入参数并点击 Execute
  4. 浏览 Resources — 列出并读取所有注册的资源
  5. 测试 Prompts — 选择一个提示词并提供参数

Inspector 是开发过程中的主要调试工具。用它来验证:

  • 工具参数是否正确定义
  • 返回值格式是否正确
  • 错误处理是否按预期工作

直接运行服务器

快速测试(不使用 Inspector):

# 以 stdio 传输方式运行(默认)
mcp run src/weather_mcp_server/server.py

连接到 Claude Code

服务器在 Inspector 中测试通过后,将其连接到 Claude Code。

注册服务器

# 使用 uv(推荐 - 自动处理虚拟环境)
claude mcp add weather-server -- uv run --directory /absolute/path/to/weather-mcp-server mcp run src/weather_mcp_server/server.py

# 或者使用 pyproject.toml 中定义的脚本入口点
claude mcp add weather-server -- uv run --directory /absolute/path/to/weather-mcp-server weather-mcp-server

如果用 pip 安装到虚拟环境中:

claude mcp add weather-server /absolute/path/to/weather-mcp-server/.venv/bin/python -m weather_mcp_server.server

验证和测试

# 检查服务器是否已注册
claude mcp list

现在打开 Claude Code 进行测试:

> 东京的天气怎么样?

> 比较一下纽约和伦敦的天气

> 列出天气服务器中所有可用的城市

Claude Code 会自动发现你的工具并调用它们。有关 Claude Code 中 MCP 配置的更多详情,请参阅 Claude Code MCP 设置:将 AI 连接到任何外部服务

修改代码后重启

修改服务器代码后,需要重启:

claude mcp remove weather-server
claude mcp add weather-server -- uv run --directory /absolute/path/to/weather-mcp-server mcp run src/weather_mcp_server/server.py

实战示例:SQLite 数据库服务器

让我们构建一个你在生产中会实际用到的东西——一个让 AI 助手查询 SQLite 数据库的 MCP 服务器。这是自定义 MCP 服务器最常见的用例之一。

创建新文件 src/weather_mcp_server/db_server.py

"""SQLite MCP 服务器 - 让 AI 助手以只读方式访问 SQLite 数据库。"""

import sqlite3
import json
from pathlib import Path
from contextlib import closing

from mcp.server.fastmcp import FastMCP, Context

# 创建服务器
mcp = FastMCP("sqlite-server")

# 数据库路径 - 根据你的项目配置
DB_PATH = Path("./data/example.db")


def get_connection() -> sqlite3.Connection:
    """创建带安全设置的数据库连接。"""
    conn = sqlite3.connect(str(DB_PATH))
    conn.row_factory = sqlite3.Row  # 以字典形式返回行
    conn.execute("PRAGMA journal_mode=WAL")  # 更好的并发读取性能
    return conn


def init_sample_db():
    """创建示例数据库用于演示。"""
    DB_PATH.parent.mkdir(parents=True, exist_ok=True)
    conn = sqlite3.connect(str(DB_PATH))
    cursor = conn.cursor()

    cursor.executescript("""
        CREATE TABLE IF NOT EXISTS users (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            email TEXT UNIQUE NOT NULL,
            role TEXT DEFAULT 'user',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        );

        CREATE TABLE IF NOT EXISTS projects (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            name TEXT NOT NULL,
            description TEXT,
            owner_id INTEGER REFERENCES users(id),
            status TEXT DEFAULT 'active',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        );

        CREATE TABLE IF NOT EXISTS tasks (
            id INTEGER PRIMARY KEY AUTOINCREMENT,
            title TEXT NOT NULL,
            project_id INTEGER REFERENCES projects(id),
            assignee_id INTEGER REFERENCES users(id),
            status TEXT DEFAULT 'todo',
            priority TEXT DEFAULT 'medium',
            created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
        );

        -- 如果表为空则插入示例数据
        INSERT OR IGNORE INTO users (id, name, email, role) VALUES
            (1, 'Alice Chen', '[email protected]', 'admin'),
            (2, 'Bob Smith', '[email protected]', 'developer'),
            (3, 'Carol Wu', '[email protected]', 'developer'),
            (4, 'Dave Brown', '[email protected]', 'designer');

        INSERT OR IGNORE INTO projects (id, name, description, owner_id, status) VALUES
            (1, 'MCP Server', 'Build custom MCP servers', 1, 'active'),
            (2, 'Web Dashboard', 'Admin dashboard redesign', 4, 'active'),
            (3, 'API Migration', 'Migrate REST to GraphQL', 2, 'completed');

        INSERT OR IGNORE INTO tasks (id, title, project_id, assignee_id, status, priority) VALUES
            (1, 'Set up project structure', 1, 1, 'done', 'high'),
            (2, 'Implement weather tool', 1, 2, 'in_progress', 'high'),
            (3, 'Write unit tests', 1, 3, 'todo', 'medium'),
            (4, 'Design dashboard layout', 2, 4, 'in_progress', 'high'),
            (5, 'Implement auth flow', 2, 2, 'todo', 'high'),
            (6, 'Write API docs', 3, 3, 'done', 'low'),
            (7, 'Performance optimization', 1, 2, 'todo', 'medium');
    """)

    conn.commit()
    conn.close()


@mcp.tool()
def query_database(sql: str) -> str:
    """对数据库执行只读 SQL 查询。

    仅允许 SELECT 语句。查询在只读事务中运行以防止任何数据修改。

    Args:
        sql: 要执行的 SELECT SQL 查询

    Returns:
        格式化的 JSON 字符串形式的查询结果,或错误消息。
    """
    # 安全性:仅允许 SELECT 语句
    normalized = sql.strip().upper()
    if not normalized.startswith("SELECT"):
        return "错误:仅允许 SELECT 查询。此服务器提供只读访问。"

    # 阻止危险关键字(包括子查询中的)
    dangerous_keywords = ["INSERT", "UPDATE", "DELETE", "DROP", "ALTER", "CREATE", "EXEC"]
    for keyword in dangerous_keywords:
        if keyword in normalized:
            return f"错误:不允许 '{keyword}' 语句。"

    try:
        with closing(get_connection()) as conn:
            cursor = conn.execute(sql)
            columns = [description[0] for description in cursor.description]
            rows = [dict(zip(columns, row)) for row in cursor.fetchall()]

            return json.dumps({
                "columns": columns,
                "row_count": len(rows),
                "rows": rows,
            }, indent=2, default=str)

    except sqlite3.OperationalError as e:
        return f"SQL 错误:{str(e)}"
    except Exception as e:
        return f"执行查询时出错:{str(e)}"


@mcp.tool()
def list_tables() -> str:
    """列出数据库中所有表及其列定义。

    Returns:
        每个表及其列的格式化描述。
    """
    try:
        with closing(get_connection()) as conn:
            # 获取所有表名
            cursor = conn.execute(
                "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name"
            )
            tables = [row[0] for row in cursor.fetchall()]

            result = []
            for table in tables:
                # 获取每个表的列信息
                cursor = conn.execute(f"PRAGMA table_info({table})")
                columns = []
                for col in cursor.fetchall():
                    col_name = col[1]
                    col_type = col[2]
                    not_null = "NOT NULL" if col[3] else ""
                    pk = "PRIMARY KEY" if col[5] else ""
                    columns.append(f"    {col_name} {col_type} {pk} {not_null}".strip())

                # 获取行数
                cursor = conn.execute(f"SELECT COUNT(*) FROM {table}")
                row_count = cursor.fetchone()[0]

                result.append(
                    f"表:{table}{row_count} 行)\n" +
                    "\n".join(columns)
                )

            return "\n\n".join(result)

    except Exception as e:
        return f"列出表时出错:{str(e)}"


@mcp.tool()
async def describe_table(table_name: str, ctx: Context) -> str:
    """获取指定表的详细 schema 和示例数据。

    Args:
        table_name: 要描述的表名

    Returns:
        表 schema、示例行和基本统计信息。
    """
    await ctx.info(f"正在描述表:{table_name}")

    try:
        with closing(get_connection()) as conn:
            # 验证表是否存在
            cursor = conn.execute(
                "SELECT name FROM sqlite_master WHERE type='table' AND name=?",
                (table_name,)
            )
            if not cursor.fetchone():
                return f"错误:表 '{table_name}' 不存在。"

            # 获取 schema
            cursor = conn.execute(f"PRAGMA table_info({table_name})")
            columns = cursor.fetchall()

            schema_lines = [f"'{table_name}' 的 Schema:"]
            for col in columns:
                schema_lines.append(
                    f"  {col[1]} ({col[2]})"
                    + (" PRIMARY KEY" if col[5] else "")
                    + (" NOT NULL" if col[3] else "")
                    + (f" DEFAULT {col[4]}" if col[4] else "")
                )

            # 获取行数
            cursor = conn.execute(f"SELECT COUNT(*) FROM {table_name}")
            count = cursor.fetchone()[0]

            # 获取示例行(前 5 行)
            cursor = conn.execute(f"SELECT * FROM {table_name} LIMIT 5")
            col_names = [desc[0] for desc in cursor.description]
            sample_rows = [dict(zip(col_names, row)) for row in cursor.fetchall()]

            result = "\n".join(schema_lines)
            result += f"\n\n总行数:{count}"
            result += f"\n\n示例数据(前 5 行):\n{json.dumps(sample_rows, indent=2, default=str)}"

            return result

    except Exception as e:
        return f"描述表时出错:{str(e)}"


@mcp.resource("db://schema")
def get_full_schema() -> str:
    """以资源形式获取完整的数据库 schema。

    这对于需要在编写查询前了解数据库结构的 AI 助手很有用。
    """
    try:
        with closing(get_connection()) as conn:
            cursor = conn.execute(
                "SELECT sql FROM sqlite_master WHERE type='table' AND sql IS NOT NULL"
            )
            statements = [row[0] for row in cursor.fetchall()]
            return "\n\n".join(statements)
    except Exception as e:
        return f"错误:{str(e)}"


@mcp.prompt()
def data_analysis(question: str) -> str:
    """根据数据库 schema 生成数据分析提示词。

    Args:
        question: 你想回答的数据问题
    """
    schema = get_full_schema()
    return (
        f"你可以访问一个具有以下 schema 的 SQLite 数据库:\n\n"
        f"{schema}\n\n"
        f"用户想知道:{question}\n\n"
        f"请使用 query_database 工具编写并执行合适的 SQL 查询,"
        f"然后清楚地解释结果。"
    )


def main():
    """初始化示例数据库并启动服务器。"""
    init_sample_db()
    mcp.run()


if __name__ == "__main__":
    main()

测试数据库服务器

# 使用 MCP Inspector 测试
mcp dev src/weather_mcp_server/db_server.py

连接到 Claude Code:

claude mcp add sqlite-server -- uv run --directory /absolute/path/to/weather-mcp-server python src/weather_mcp_server/db_server.py

现在你可以用自然语言向 Claude Code 提问关于数据的问题:

> 数据库里有哪些表?

> 显示所有活跃的项目及其负责人

> 每个开发者有多少任务?按状态分组

> 找出所有未完成的高优先级任务

AI 会编写 SQL 查询,调用你的 query_database 工具,并呈现结果——全部自动完成。

这个模式适用于任何 SQLite 数据库。将 DB_PATH 指向你项目的数据库文件,Claude Code 就能立即获得查询能力。关于通过 MCP 暴露数据库的安全最佳实践,请参阅 MCP 安全指南

部署选项

本地 STDIO(默认)

本地开发的标准传输方式。服务器作为子进程运行,通过 stdin/stdout 通信。

# 这是调用 mcp.run() 时的默认方式
mcp.run()

# 显式指定 stdio 传输
mcp.run(transport="stdio")

这是 Claude Code 和大多数本地客户端使用的方式。无需网络配置。

SSE 用于远程访问

Server-Sent Events 传输方式支持通过 HTTP 进行远程连接:

mcp.run(transport="sse", host="0.0.0.0", port=8000)

从 Claude Code 通过网络连接:

claude mcp add remote-weather --url http://your-server:8000/sse

Streamable HTTP(最新)

最新的传输选项——支持流式的双向 HTTP:

mcp.run(transport="streamable-http", host="0.0.0.0", port=8000)

Streamable HTTP 是生产环境远程部署的推荐传输方式。它支持双向通信,并能通过标准 HTTP 代理和负载均衡器工作。

Docker 部署

对于生产服务器,Docker 提供隔离性和可重现性:

FROM python:3.12-slim

WORKDIR /app

# 安装 uv 以快速解析依赖
RUN pip install uv

# 复制项目文件
COPY pyproject.toml .
COPY src/ ./src/

# 安装依赖
RUN uv pip install --system -e .

# 以 streamable-http 传输方式运行用于远程访问
EXPOSE 8000
CMD ["python", "-m", "weather_mcp_server.server"]

修改服务器以在生产环境中使用适当的传输方式:

import os

def main():
    transport = os.environ.get("MCP_TRANSPORT", "stdio")
    if transport == "stdio":
        mcp.run()
    else:
        mcp.run(
            transport="streamable-http",
            host="0.0.0.0",
            port=int(os.environ.get("MCP_PORT", "8000")),
        )

发布到 MCP 生态系统

当你的服务器准备好供他人使用时,可以发布它。

发布到 PyPI

# 构建包
uv build

# 发布到 PyPI
uv publish

发布后,任何人都可以使用你的服务器:

# 用户使用 uvx(uv 的工具运行器)安装并运行
claude mcp add weather-server -- uvx weather-mcp-server

# 或者用 pip
pip install weather-mcp-server
claude mcp add weather-server weather-mcp-server

与社区分享

将你的服务器提交到 awesome-mcp-servers 仓库和 MCP 官方服务器注册表。查看 Claude Code 最佳 MCP 服务器 了解文档规范的 MCP 服务器列表是什么样的。

常见错误与解决方案

症状原因解决方案
服务器无法启动Python < 3.10升级到 Python 3.10+
ModuleNotFoundError: mcpSDK 未安装在正确的环境中运行 uv add "mcp[cli]" 或激活 venv
工具未被发现函数参数缺少类型提示添加类型提示:def my_tool(name: str)
print() 破坏协议stdout 被用于调试使用 print(..., file=sys.stderr)ctx.info()
服务器静默崩溃工具中有未处理的异常用 try/except 包装并返回错误字符串
资源未找到URI scheme 错误检查 URI 完全匹配,例如 weather://cities
连接被拒绝服务器未运行或路径错误claude mcp list 验证并检查路径
代码修改后工具过时服务器需要重启claude mcp remove 再重新 claude mcp add

常见问题

构建 MCP 服务器需要什么版本的 Python?

需要 Python 3.10 或更高版本。MCP Python SDK 官方支持 Python 3.10、3.11、3.12 和 3.13。SDK 使用了类型提示和 match 语句等现代 Python 特性,这些需要 3.10+。

FastMCP 是什么?它和 MCP Python SDK 是什么关系?

FastMCP 是官方 MCP Python SDK(PyPI 上的 mcp 包)中包含的高级框架。它提供了基于装饰器的 API 来构建 MCP 服务器——你可以用 @mcp.tool()@mcp.resource()@mcp.prompt() 来注册能力。这是构建 Python MCP 服务器的推荐方式。

可以用 pip 代替 uv 来安装 MCP Python SDK 吗?

可以。使用 pip install "mcp[cli]" 安装包含 CLI 工具的完整包,或者 pip install mcp 只安装库。不过推荐使用 uv,因为它速度更快,能自动处理虚拟环境,而且是官方 MCP 文档使用的工具。

在连接 Claude Code 之前如何测试 Python MCP 服务器?

运行 mcp dev server.py 启动 MCP Inspector,它是一个位于 http://127.0.0.1:6274 的 Web 测试界面。你可以交互式地测试每个工具、资源和提示词。测试通过后,用 claude mcp add 连接到 Claude Code。

用 Python 和 TypeScript 构建 MCP 服务器有什么区别?

两种语言都有官方 SDK。Python 使用基于装饰器的注册方式(@mcp.tool()),而 TypeScript 使用方法调用(server.registerTool())。Python 更适合数据科学、ML 管道和脚本任务。TypeScript 的 SDK 稍微更成熟。协议是完全相同的——任一语言构建的服务器都可以与任何 MCP 客户端配合使用。

总结

用 Python 构建 MCP 服务器遵循一个清晰的工作流:

  1. 设置uv init + uv add "mcp[cli]" + 创建 pyproject.toml
  2. 构建工具 — 使用 @mcp.tool() 配合类型参数和 docstring
  3. 添加资源 — 使用 @mcp.resource() 提供只读数据访问
  4. 添加提示词 — 使用 @mcp.prompt() 创建可复用模板
  5. 测试mcp dev server.py 启动 MCP Inspector
  6. 连接claude mcp add 将服务器注册到 Claude Code
  7. 部署 — 本地用 STDIO,远程用 streamable-http,生产用 Docker
  8. 发布uv build + uv publish 到 PyPI

MCP 生态系统正在快速增长——仅 Python SDK 每月下载量就达数百万次。你构建的每个服务器都能立即与 Claude Code、Cursor、VS Code Copilot 以及未来任何兼容 MCP 的工具一起使用。现在就开始构建吧。

相关文章

Comments

Join the discussion — requires a GitHub account