在 Python 中构建多功能 MCP 服务器

发布: (2025年12月20日 GMT+8 03:58)
15 min read
原文: Dev.to

Source: Dev.to

(请提供需要翻译的正文内容,我将为您翻译成简体中文并保持原有的格式。)

聊天机器人孤立时代的终结

长期以来,大型语言模型(LLM)一直生活在封闭的花园中——出色的推理引擎被限制在聊天界面后,无法访问本地文件、数据库,或在你的机器上执行代码。模型上下文协议(MCP) 完全改变了这一范式。它提供了一种标准化方式,将 AI 助手连接到实际工作发生的系统。

然而,许多开发者停留在 MCP 的 “Hello World” 阶段:仅暴露一个简单的 API 工具。虽然有用,但这仅仅触及了协议潜力的表面。一个真正健壮、易用的服务器不仅提供工具;它通过 资源(Resources) 提供 上下文,并通过 提示(Prompts) 提供结构化的工作流。

在本指南中,我们将从零构建一个完整的基于 Python 的 MCP 服务器。我们将超越简单脚本,打造一个 多工具架构,包括:

  • 数学运算能力
  • 文档访问
  • 用于会议分析的动态提示模板

我们还将采用现代的 vibe‑coding 工作流——使用 LLM 来构建 LLM 工具——并介绍使用 MCP Inspector 进行调试的关键且常被忽视的技巧。

在打开终端之前:我真的需要构建它吗?

高级工程不仅是写代码,更是要知道何时 不写。如果你只需要集成标准服务(例如 Google Drive、Slack),通常没有必要开发一个可能已经在社区生态中存在的服务器。冗余是效率的敌人。

对于简单的自动化,低代码解决方案(如 n8n)可能提供更快速的价值路径。然而,当以下情况出现时,构建自定义的 Python MCP 服务器就变得不可谈判:

原因适用情形
复杂性你需要特定的、复杂的逻辑(自定义计算、数据转换),而标准 API 无法满足。
上下文你需要将本地、专有的文档(资源)注入模型的上下文窗口。
标准化你想为团队强制执行特定的交互模式(提示),例如统一的“会议摘要”结构。

如果你的使用场景符合这些条件,是时候开始编码了。

“Vibe Coding” – 利用 LLM 辅助开发

手动编写每一行样板代码的时代已经过去。现在 不是 1999 年。为了高效构建此服务器,我们将采用 vibe‑coding 方法论——利用像 Cursor 这样集成 Claude 的 IDE,根据高质量文档生成脚手架。

先决条件

工具版本 / 备注
Python3.12 或更高
Package manageruv(快速、可靠的依赖管理)
SDKmcp(Python SDK)
FrameworkFastMCP(简化服务器创建的高级包装器)

为 AI 辅助设定上下文

  1. 索引文档 – 创建一个 llms.txt(或类似文件),其中包含:

    • 核心 MCP 规范
    • Python SDK README
    • FastMCP 使用指南
  2. 将上下文传递给 Cursor – 在 IDE 中索引这些文件,使模型能够了解您所使用的确切 SDK 版本。

这种准备工作可以让您向模型提出高层次的架构请求(例如,“创建一个带有计算器工具的服务器”),而不是与语法错误纠缠。

Source:

工具:我们服务器的基石

工具是 LLM 可以调用以执行操作的函数。我们将构建一个 计算器服务器,但其结构能够处理各种逻辑运算。

实现策略

FastMCP 让定义工具看似非常简单。关键在于 描述(docstring),因为它们会成为模型读取的 API 文档,用以决定何时调用工具。

from mcp.server.fastmcp import FastMCP
import math

# Initialize the server
mcp = FastMCP("calculator-server")

@mcp.tool()
def add(a: float, b: float) -> float:
    """Add two numbers together."""
    return a + b

@mcp.tool()
def divide(a: float, b: float) -> float:
    """Divide the first number by the second number. Includes zero checks."""
    if b == 0:
        return "Error: Cannot divide by zero"
    return a / b

关键洞察 – docstring("""Add two numbers together.""")不是给你看的,而是给 LLM 看的。如果描述含糊,模型可能会产生幻觉式的功能或在需要时未能调用工具。

你可以将此模式扩展到包含减法、乘法、幂运算、平方根、百分比计算等。只需使用 @mcp.tool() 包装每个函数,我们就会自动处理协议所需的 JSON‑RPC 通信。

使用 MCP Inspector 进行调试

在没有调试工具的情况下编写 MCP 服务器就像盲目飞行。MCP Inspector 让你能够在隔离环境中测试服务器, 将后端逻辑与前端客户端(Claude)解耦。

运行 Inspector

uv run mcp-inspector server.py

这会启动一个本地网页界面(通常是 http://localhost:),你可以在其中:

  • 列出工具 – 验证服务器是否真的暴露了你编写的函数。
  • 测试执行 – 手动输入参数(例如 a=10, b=2),查看原始输出或错误追踪。
  • 检查连接 – 验证传输协议(stdioHTTP)。

安全提示 – Inspector 启动时可能会生成带有安全令牌的 URL。如果你尝试通过没有该令牌的普通 localhost URL 进行连接,连接将被拒绝。务必使用终端日志中提供的具体链接,以确保已认证的访问。

使用 Inspector 严格测试边界情况——比如除以零—— 将服务器连接到真实客户端之前。

资源:为模型提供只读访问

工具让模型 执行资源让模型 读取

一个常见的错误是把 LLM 当作静态知识库。通过集成资源,你可以让模型直接、只读地访问机器上的特定数据——日志、API 文档或代码库。

实现基于文件的资源

from mcp.server.fastmcp import FastMCP
from pathlib import Path

# Initialize the server (if not already done)
mcp = FastMCP("calculator-server")

# Register a file resource
@mcp.resource()
def documentation() -> str:
    """Read the local documentation file."""
    doc_path = Path("docs/mcp_specification.md")
    return doc_path.read_text(encoding="utf-8")

现在,LLM 可以请求 documentation() 来获取你的 MCP 规范的完整文本,使其能够利用最新的、特定领域的信息进行推理。

综合示例

下面是一个 最小、可运行的示例,它将工具、资源和检查器组合在一起:

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

mcp = FastMCP("calculator-server")

# ---- Tools ----
@mcp.tool()
def add(a: float, b: float) -> float:
    """Add two numbers together."""
    return a + b

@mcp.tool()
def divide(a: float, b: float) -> float:
    """Divide the first number by the second number. Returns an error string on division by zero."""
    if b == 0:
        return "Error: Cannot divide by zero"
    return a / b

# ---- Resources ----
@mcp.resource()
def documentation() -> str:
    """Read the local MCP documentation file."""
    return Path("docs/mcp_specification.md").read_text(encoding="utf-8")

# Run the server (this line is optional; the inspector can start it)
if __name__ == "__main__":
    mcp.run()

启动调试:

uv run mcp-inspector server.py

在检查器 UI 中,你可以:

  1. 列出 adddividedocumentation 端点。
  2. 调用 add(a=5, b=7) → 返回 12
  3. 调用 divide(a=10, b=0) → 返回 "Error: Cannot divide by zero"
  4. 调用 documentation() → 返回你的规范文件的原始 Markdown 内容。

下一步

  • 添加更多工具(例如 multiplysqrtpercentage)。
  • 创建更丰富的资源(例如 JSON 配置文件、CSV 数据集)。
  • 设计提示,以协调多个工具调用(例如 “总结会议记录并计算总时长”)。
  • 在向外部客户端暴露之前确保服务器安全(TLS、身份验证令牌)。

有了 工具资源提示 的坚实基础,您的 MCP 服务器将成为 LLM 推理与现实系统之间的强大桥梁。祝编码愉快!

MCP 资源示例

import Context, Resource

# Define the path to your knowledge base
resource_path = "./docs/typescript_sdk.md"

@mcp.resource("mcp://docs/typescript-sdk")
def get_typescript_sdk() -> str:
    """Provides access to the TypeScript SDK documentation."""
    with open(resource_path, "r") as f:
        return f.read()

当您添加此代码时,MCP Inspector 将显示一个新的 Resources 选项卡。 在真实场景中(例如 Claude Desktop),用户现在可以 附加 此资源到聊天。 模型会立即获得该文件的上下文,而无需您将成千上万的文字复制‑粘贴到提示窗口中。

Strategic Value:
这使您的服务器成为一个动态库。更新本地文件后,模型的知识会立即更新。

Source:

提示层

工具是被动的(模型决定何时使用它们)。
提示是主动的——它们是标准化的、由用户发起的模板,旨在强制执行特定的工作流。

一个完美的用例是 “会议摘要”。 与其每次都输入冗长的指令,不如将该结构写进服务器。

创建动态提示模板

{{date}}, {{transcript}}) as our template.

模板 (prompt.md)

You are an executive assistant. Analyze the following meeting transcript.

**Date:** {{date}}
**Title:** {{title}}

**Transcript:**
{{transcript}}

Please provide a summary with:
1. Overview
2. Key Decisions
3. Action Items

实现方式

@mcp.prompt()
def meeting_summary(date: str, title: str, transcript: str) -> str:
    """Generates a meeting summary based on a transcript."""
    # Logic to read the template and replace placeholders
    # Returns the formatted prompt to the client
    return render_template(
        "prompt.md",
        date=date,
        title=title,
        transcript=transcript,
    )

使用提示进行调试的过程

  • list_promptsget_prompt 功能现在由现代 FastMCP 框架自动处理,消除了冗余和冲突。
  • LLM 生成的代码有时会把 modeltemperature 等参数注入到提示逻辑中。
    • 洞察: 配置(使用哪个模型、温度等)属于客户端设置,而不是 提示模板。清理代码即可去除这些幻觉。

在 Inspector(以及最终的 Claude Desktop)中测试时,这一功能会创建一个 表单式 UI:用户选择“会议摘要”,填写字段,LLM 随即收到一个完美构造的上下文包。

Source:

将服务器桥接到客户端

一旦服务器拥有 ToolsResourcesPrompts,就必须通过 claude_desktop_config.json 文件将其连接到客户端。

传输协议:stdio 与 HTTP

stdio(标准输入/输出)

客户端启动服务器进程,并通过终端流与其通信。这种方式安全、快速,非常适合本地开发。

{
  "mcpServers": {
    "my-python-server": {
      "command": "uv",
      "args": ["run", "server.py"],
      "env": {
        "PYTHONPATH": "."
      }
    }
  }
}

HTTP(SSE – 服务器发送事件)

随着规模的扩大,你可能希望通过网络暴露服务器。使用 HTTP 上的 SSE 可以将服务器的生命周期与客户端解耦,允许多个客户端连接到同一个持久的服务器实例。

关于演进的说明: 该协议现在区分了传输类型。虽然 SSE 曾是一个独立的概念,但现代实现通常使用 可流式的 HTTP 端点(例如使用 Starlette 或 FastAPI)。客户端连接到 http://localhost:8000/sse

回顾:构建 MCP 服务器

从 AI 使用者转变为 AI 工作流架构师涉及三个核心层面:

它为模型提供的内容示例
工具用于执行计算的“手”@mcp.tool 函数
资源用于阅读本地文档的“眼睛”@mcp.resource 函数
提示标准操作流程的剧本@mcp.prompt 模板

不要被“氛围编码”蒙蔽。 严谨之处在于:

  • 清晰描述你的工具
  • 深思熟虑地构建你的提示
  • 验证你的资源

建议的工作流程:

  1. stdio 开始,以保持简洁。
  2. 严格使用 Inspector 来验证逻辑。
  3. 在一切稳定后 将其整合到你的日常工作流中。

代码是容易的部分。真正的工程在于定义上下文。

Back to Blog

相关文章

阅读更多 »

仓库利用的权威指南

引言 仓库本质上只是一个 3‑D 盒子。利用率只是衡量你实际使用了该盒子多少的指标。虽然物流 c...