首页 / ai-tutorial / 从零构建 MCP 服务器:让 Claude 拥有你的自定义工具集 4 次阅读
从零构建 MCP 服务器:让 Claude 拥有你的自定义工具集 — AI 实战教程
AI 实战教程 中级

从零构建 MCP 服务器:
让 Claude 拥有你的自定义工具集

⏱ 预计 45 分钟 📋 7 个步骤 🗓 2026 年 2 月 28 日

MCP(Model Context Protocol)是 Anthropic 于 2024 年底推出的开放协议,它让 Claude 能像插 USB 设备一样即插即用地连接外部工具和数据。本文将手把手带你用 Python(FastMCP)构建一个真实可用的 MCP 服务器——一个能查询 Hacker News 热点和执行 Shell 命令的开发者助手,并接入 Claude Desktop 和 Claude Code。全程代码可直接运行,不需要任何服务器或云资源。

你将学到
  • MCP 协议的核心架构:Host / Client / Server 三角关系
  • 用 Python FastMCP 快速定义工具(Tools)和资源(Resources)
  • 将 MCP 服务器接入 Claude Desktop 与 Claude Code
  • 调试技巧:用 MCP Inspector 检查工具调用
  • 进阶能力:带进度条的长任务、错误处理最佳实践

准备工作

  • Python 3.10+(建议 3.12)
  • uv(Python 包管理器,比 pip 快 10 倍)
  • Claude DesktopClaude Code(v0.2.0+)
  • 预计学习时间:45 分钟 | 难度:中级(需要基础 Python 能力)
# 安装 uv(若尚未安装)
curl -LsSf https://astral.sh/uv/install.sh | sh

核心概念

MCP 三角架构:Host、Client、Server 的关系

MCP 的设计围绕三个角色展开:

Host(宿主)
Claude Desktop 或 Claude Code,负责发起连接、协调 AI 推理
Client(客户端)
Host 内置,通过 JSON-RPC 2.0 与 Server 通信,处理协议细节
Server(服务器)
你来构建,暴露工具、资源、提示模板,供 Claude 按需调用

MCP 的三大原语

  • Tools(工具):Claude 可以主动调用的函数,如搜索、执行命令、写文件
  • Resources(资源):Claude 可以被动读取的数据,如文件内容、数据库查询结果
  • Prompts(提示):预设的提示模板,打包可复用的工作流
💡 MCP vs 传统 API 的本质区别:传统 API 由固定代码调用,MCP 工具由 Claude 根据上下文自主决定何时调用哪个工具。你只需定义好工具的描述(docstring),Claude 会自己判断调用时机。

实战步骤

MCP 服务器开发完整流程:4 个步骤
01

初始化项目

用 uv 创建隔离的 Python 项目,避免污染全局环境:

# 创建项目目录
uv init dev-assistant-mcp
cd dev-assistant-mcp

# 安装 MCP SDK(含 FastMCP)
uv add "mcp[cli]" httpx

# 验证安装
uv run python -c "import mcp; print(mcp.__version__)"
💡 为什么用 uv? uv 的 run 命令自动激活虚拟环境,在 Claude Desktop 配置中无需手动 activate,直接用绝对路径调用即可。
02

创建第一个工具(Hello World)

新建 server.py,先写最简单的工具验证环境正常:

# server.py
from mcp.server.fastmcp import FastMCP

# 初始化服务器,名称会显示在 Claude 工具列表里
mcp = FastMCP("dev-assistant")

@mcp.tool()
def greet(name: str) -> str:
    """向指定用户打招呼(测试用工具)"""
    return f"你好,{name}!我是你的开发助手 MCP 服务器。"

if __name__ == "__main__":
    mcp.run()

用 MCP Inspector 测试(强烈建议,能节省大量后续调试时间):

npx @modelcontextprotocol/inspector uv run server.py

浏览器打开 http://localhost:5173,点击 Tools → greet → Run Tool,看到响应说明服务器正常。

03

添加 Hacker News 查询工具

这才是真正有用的工具——实时查询 HN 热点,并发请求加速:

# server.py(续接步骤2)
import httpx
import json
import asyncio

HN_API = "https://hacker-news.firebaseio.com/v0"

@mcp.tool()
async def get_hn_top_stories(limit: int = 5) -> str:
    """
    获取 Hacker News 当前热门文章列表。

    Args:
        limit: 返回数量,默认 5,最多 20

    Returns:
        格式化的热门文章列表(标题、URL、分数、评论数)
    """
    limit = min(limit, 20)

    async with httpx.AsyncClient(timeout=10.0) as client:
        resp = await client.get(f"{HN_API}/topstories.json")
        story_ids = resp.json()[:limit]

        # 并发获取文章详情
        tasks = [client.get(f"{HN_API}/item/{sid}.json") for sid in story_ids]
        responses = await asyncio.gather(*tasks)

    stories = []
    for r in responses:
        s = r.json()
        stories.append({
            "title": s.get("title", "无标题"),
            "url":   s.get("url", f"https://news.ycombinator.com/item?id={s['id']}"),
            "score": s.get("score", 0),
            "comments": s.get("descendants", 0),
        })

    lines = [f"Hacker News 今日热门(前 {limit} 条):\n"]
    for i, s in enumerate(stories, 1):
        lines.append(f"{i}. {s['title']}")
        lines.append(f"   🔗 {s['url']}")
        lines.append(f"   ⬆️ {s['score']} 分 | 💬 {s['comments']} 评论\n")

    return "\n".join(lines)
💡 async def vs def:涉及网络请求时用 async def + httpx.AsyncClient,可以并发执行多个请求,速度比串行快 3-5 倍。FastMCP 两种风格都支持。
04

添加 Shell 命令执行工具

让 Claude 能在你的机器上执行安全的 Shell 命令(带白名单保护):

# server.py(续)
import subprocess
import shlex

# 允许的命令白名单(只读/安全操作)
ALLOWED_COMMANDS = {
    "ls", "cat", "echo", "pwd", "git",
    "python3", "node", "bun", "grep", "find"
}

@mcp.tool()
def run_shell(command: str, working_dir: str = ".") -> str:
    """
    在本地执行 Shell 命令(仅限白名单命令)。

    Args:
        command: 要执行的 shell 命令
        working_dir: 工作目录,默认当前目录
    """
    parts = shlex.split(command)
    if not parts:
        return "错误:命令为空"

    cmd_name = parts[0]
    if cmd_name not in ALLOWED_COMMANDS:
        return f"错误:'{cmd_name}' 不在允许列表。允许:{', '.join(sorted(ALLOWED_COMMANDS))}"

    try:
        result = subprocess.run(
            parts,
            capture_output=True,
            text=True,
            timeout=30,
            cwd=working_dir,
        )
        output = result.stdout
        if result.returncode != 0:
            output += f"\n[退出码 {result.returncode}]\n{result.stderr}"
        return output or "(命令执行成功,无输出)"

    except subprocess.TimeoutExpired:
        return "错误:命令执行超时(>30秒)"
    except Exception as e:
        return f"错误:{type(e).__name__}: {e}"
⚠️ 安全提示:给 AI 执行 Shell 命令的能力时务必设白名单。此处故意排除了 rmcurlchmod 等危险命令,生产环境请根据需要调整。
05

添加资源(Resources)

资源让 Claude 能"读取"你的数据,适合只读场景:

Tools vs Resources 对比:主动调用 vs 被动读取
# server.py(续)
import os
from pathlib import Path

@mcp.resource("project://readme")
def get_project_readme() -> str:
    """当前目录的 README 文件内容"""
    readme = Path("README.md")
    if readme.exists():
        return readme.read_text(encoding="utf-8")
    return "(当前目录没有 README.md)"

@mcp.resource("system://info")
def get_system_info() -> str:
    """获取系统基本信息(OS、Python 版本、当前目录)"""
    import platform
    info = {
        "os":     platform.system(),
        "python": platform.python_version(),
        "cwd":    os.getcwd(),
        "home":   str(Path.home()),
    }
    return json.dumps(info, ensure_ascii=False, indent=2)
06

接入 Claude Desktop

找到配置文件位置:

# macOS
open ~/Library/Application\ Support/Claude/

# Windows(在资源管理器地址栏输入)
%APPDATA%\Claude\

编辑 claude_desktop_config.json(不存在则新建):

{
  "mcpServers": {
    "dev-assistant": {
      "command": "uv",
      "args": [
        "run",
        "--project",
        "/Users/yourname/dev-assistant-mcp",
        "python",
        "/Users/yourname/dev-assistant-mcp/server.py"
      ]
    }
  }
}

/Users/yourname 替换为你的实际路径,然后完全重启 Claude Desktop。在聊天框下方看到工具图标 🔨 表示连接成功。

07

接入 Claude Code

Claude Code 使用项目级配置文件:

# 在你的工作目录执行
mkdir -p .claude
cat > .claude/settings.json << 'EOF'
{
  "mcpServers": {
    "dev-assistant": {
      "command": "uv",
      "args": [
        "run",
        "--project",
        "/Users/yourname/dev-assistant-mcp",
        "python",
        "/Users/yourname/dev-assistant-mcp/server.py"
      ]
    }
  }
}
EOF

在 Claude Code 中输入 /mcp 确认服务器已连接,或直接问:"用 dev-assistant 查一下 HN 今天最热的 5 条新闻"


效果验证

# 方法1:MCP Inspector(推荐,最直观)
npx @modelcontextprotocol/inspector uv run server.py
# 浏览器打开 http://localhost:5173,逐一测试工具

在 Claude 中测试的预期输出:

Hacker News 今日热门(前 3 条):

1. Show HN: I built a local MCP server in 50 lines
   🔗 https://github.com/...
   ⬆️ 847 分 | 💬 234 评论

2. Anthropic releases MCP 2.0 specification
   🔗 https://anthropic.com/...
   ⬆️ 621 分 | 💬 189 评论

常见问题

MCP 开发核心要点:stdout 规范、调试、docstring、安全
问题原因解决方案
Claude Desktop 看不到工具 配置文件路径错误或 JSON 格式有误 使用绝对路径,用 JSON 校验器验证格式
uv: command not found uv 未安装或未在 PATH 中 重新运行安装脚本,或用绝对路径 /Users/xxx/.cargo/bin/uv
工具调用无响应 stdout 被 print() 输出污染 调试信息改用 print(..., file=sys.stderr)
ConnectionRefusedError 服务器进程启动失败 查看 ~/Library/Logs/Claude/ 下的错误日志
工具描述不显示 docstring 缺失或格式错误 确保每个工具有完整的三引号 docstring
⚠️ 调试黄金法则:永远不要在 MCP 服务器的 stdout 里输出非 JSON-RPC 内容,否则会破坏协议通信。所有调试信息必须写 sys.stderr
import sys
print("调试:工具被调用", file=sys.stderr)  # ✅ 正确
print("调试:工具被调用")                    # ❌ 破坏协议

进阶技巧

1. 进度通知(长任务必备)

from mcp.server.fastmcp import Context

@mcp.tool()
async def batch_process(ctx: Context, items: list[str]) -> str:
    """批量处理任务,带进度反馈"""
    results = []
    total = len(items)

    for i, item in enumerate(items):
        await ctx.report_progress(i, total)  # 实时进度更新
        results.append(f"处理完成:{item}")

    return "\n".join(results)

2. 通过配置文件安全注入 API Key

{
  "mcpServers": {
    "dev-assistant": {
      "command": "uv",
      "args": ["run", "python", "/path/server.py"],
      "env": {
        "MY_API_KEY": "sk-xxx",
        "GITHUB_TOKEN": "ghp_xxx"
      }
    }
  }
}

在服务器代码中通过 os.environ.get("MY_API_KEY") 读取,永远不要把密钥硬编码进 server.py

3. 动态资源 URI

@mcp.resource("file://{path}")
def read_file(path: str) -> str:
    """读取指定路径的文件(URI 中的 {path} 自动映射为参数)"""
    file_path = Path(path)
    if not file_path.exists():
        raise FileNotFoundError(f"文件不存在:{path}")
    return file_path.read_text(encoding="utf-8")

4. TypeScript 版本(Node.js 生态首选)

// server.ts
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";

const server = new McpServer({ name: "dev-assistant", version: "1.0.0" });

server.tool(
  "greet",
  { name: z.string().describe("用户名") },
  async ({ name }) => ({
    content: [{ type: "text", text: `你好,${name}!` }],
  })
);

const transport = new StdioServerTransport();
await server.connect(transport);

总结

本文构建了一个包含 4 个工具(greet、HN 热点、Shell 命令、系统信息)和 2 个资源(README、系统信息)的完整 MCP 服务器,并成功接入 Claude Desktop 和 Claude Code。

  • MCP 用 JSON-RPC 2.0 通信,工具的 docstring 直接决定 Claude 的调用决策
  • FastMCP 的装饰器模式极大简化了服务器开发,无需处理底层协议细节
  • 调试首选 MCP Inspector,生产环境必须用 stderr 记录日志
  • 白名单 + 超时 + 错误处理是 Shell 类工具的安全三要素

下一步可以探索:构建连接 SQLite 数据库的 MCP 服务器、用 HTTP 传输部署远程 MCP 服务器(支持多用户)、或将 MCP 与 Claude Code Hooks 结合实现更深度的 IDE 集成。

MCP Claude Python FastMCP AI 工具开发 LLM 集成
选择栏目
今日简报 播客电台 AI 实战教程 关于我
栏目
全球AI日报国内AI日报全球金融日报国内金融日报全球大新闻日报国内大新闻日报Claude Code 玩法日报OpenClaw 动态日报GitHub 热门项目日报AI工具实战AI应用开发编程实战工作流自动化AI原理图解AI Agent开发
我的收藏