AI-Chain

把 Python 函式变成 AI 能力:FastMCP 如何缩短 MCP Server 的实作距离

分享:
把 Python 函式变成 AI 能力:FastMCP 如何缩短 MCP Server 的实作距离

把 Python 函式变成 AI 能力:FastMCP 如何缩短 MCP Server 的实作距离

我认为,Model Context Protocol(MCP)真正难的地方,从来不是把一个 API endpoint 写出来,而是让模型、工具、资料与互动流程能以一致的方式被发现、描述、验证与呼叫。当团队开始把内部搜寻、资料库查询、档案操作或第三方服务交给 AI agent 使用,最先遇到的通常不是模型不够聪明,而是每个工具都用不同的包装方式:有的靠自订 JSON,有的塞在 prompt,有的直接把业务逻辑绑死在某个聊天产品。

FastMCP 是我这次挑出的实作型开源专案。它不是一份 MCP 伺服器清单,也不是只讲概念的教学集合,而是一套以 Python 为核心的 MCP application framework,涵盖 server、client 与互动式应用程式。它最吸引人的地方,是把「让普通 Python 函式成为模型可呼叫的能力」缩短成几个清楚的步骤,同时保留往正式部署演进时需要的传输、验证与组合空间。

先讲结论:FastMCP 解决的是协定落差,不是替你完成治理

如果你的团队已经有 Python 函式、资料服务或内部 API,想快速做出一个能被 MCP client 使用的工具层,FastMCP 很值得先试。它透过 @mcp.tool 把函式注册成工具,依照函式签名与型别注记产生输入 schema,再把执行结果交回 client;同一个 server 也能用 @mcp.resource 提供只读资料,或用 @mcp.prompt 暴露可重用的讯息模板。

但我不会把它描述成「加一个 decorator 就完成 AI production」。真正上线仍然要处理权限、秘密管理、输入验证、网路边界、日志、速率限制与工具副作用。FastMCP 降低的是 MCP application 的起始成本,让工程团队可以先把协定边界做对,再针对风险补上治理。

为什么 MCP Server 的第一版常常做得太重?

传统做法很容易从框架、路由、资料模型、错误格式与部署设定全部开始设计。这在一般服务开发不一定错,但对 MCP 工具来说,第一个问题通常只是:「模型能不能可靠地呼叫这个能力?」如果还没验证使用者场景,就先建立一整套客制协定,最后往往得到一个只有自己家 client 能理解的整合层。

FastMCP 的设计把第一个可验证任务放在很前面:建立 FastMCP 实例、用 decorator 登录能力、启动 server。这个路径很适合把现有 Python 程式逐步包装,而不是一次重写整个后端。从架构角度看,它将几个层次分开:

  • Tool 是可执行能力,例如查询资料、呼叫 API 或计算结果。
  • Resource 是 client 可以读取的资料或档案,也可以由 URI template 动态产生。
  • Prompt 是可重用、可参数化的讯息模板,用来引导一致的模型互动。
  • Transport 决定 client 如何连到 server,例如本机 stdio 或远端 HTTP。

这个分层很重要。若把所有东西都做成 tool,模型可能会把原本只需要读取的资料操作成具有副作用的动作;若把所有提示都硬编码在 client,server 端的领域知识又难以重用。

从一个可验证的 Tool 开始

官方 quickstart 的最小路径很直白。我会先准备 Python 3.10 以上的环境,再安装 FastMCP。专案的 pyproject.toml 将最低 Python 版本设为 3.10,并以 Apache License 2.0 发布。使用 uv 时,可以在自己的专案中执行:

uv add fastmcp

接着建立 my_server.py:

from fastmcp import FastMCP
mcp = FastMCP("My MCP Server")
@mcp.tool
def greet(name: str) -> str:
    """Return a greeting for a person."""
    return f"Hello, {name}!"
if __name__ == "__main__":
    mcp.run()

这段程式的重点不在问候语,而在于函式签名成为协定的一部分。name: str 提供输入型别资讯,docstring 则能成为工具描述的一部分。实务上我会把型别注记写完整,把会影响模型选择的限制放进 docstring,并避免让一个 tool 同时包住太多不相关的副作用。

启动后,stdio 是适合本机 client 的预设路径。如果希望以 HTTP 提供远端连线,官方 quickstart 示范的是:

if __name__ == "__main__":
    mcp.run(transport="http", port=8000)

这不代表把 stdio 改成 http 就完成了远端服务。HTTP 会把认证、反向代理、TLS、网路存取控制与观测性问题一起带进来,所以我会先在本机确认工具契约,再把 HTTP 当成部署阶段的明确决策。

Tool、Resource、Prompt:三种能力不要混在一起

FastMCP 的另一个价值,是让 MCP 的语义能直接对应到 Python 程式码。

Tool:让模型采取行动

Tool 适合需要执行的能力,例如查询订单、计算报表或呼叫一个外部服务。FastMCP 会依照函式名称、docstring 与型别注记建立工具描述和输入 schema,并在收到呼叫时进行参数处理。

@mcp.tool
def add(a: int, b: int) -> int:
    """Add two integers."""
    return a + b

我会把 tool 视为一个需要被授权的动作,而不是「任何函式都可以直接公开」。涉及删除、付款、寄信或修改资料的 tool,应该另外加入明确的权限检查、幂等设计与人工确认。框架能帮你描述与注册能力,但不会替你判断某个使用者是否有权使用它。

Resource:让模型读取上下文

Resource 比较接近只读资料入口,可以回传文字、JSON、档案或由 URI template 产生的动态内容。例子如下:

import json
@mcp.resource("data://config")
def get_config() -> str:
    return json.dumps({
        "theme": "dark",
        "features": ["tools", "resources"],
    })

当需求是「让模型读取目前设定」或「取得某个可定位的资料来源」,我会优先考虑 resource,而不是新增一个看起来像查询动作的 tool。这样 client 和人类读者都更容易理解这个入口是否有副作用。

Prompt:让互动流程可重用

Prompt 适合把领域团队反复使用的讯息模板集中管理。例如,依照主题产生一个请求说明:

from fastmcp.prompts import Message
@mcp.prompt
def ask_about_topic(topic: str) -> str:
    return f"Can you explain the concept of '{topic}'?"
@mcp.prompt
def code_request(language: str, task: str) -> list[Message]:
    return [
        Message(f"Write a {language} function for: {task}"),
        Message("I will help you write it.", role="assistant"),
    ]

Prompt 不是权限系统,也不是把所有商业规则藏起来的地方。它的价值是让 client 能发现一个可重用的互动入口。若一个流程需要真正取得资料或改变状态,仍要把动作放在适合治理的 tool,并在 server 端做验证。

我会怎么安排实际导入

第一步:先定义一个小而清楚的能力

不要从「把整个资料库交给 agent」开始。先挑一个输入和输出都能明确描述的工作,例如查询某个专案的部署状态、依 ID 取得一笔文件,或计算一个不会改变资料的结果。第一版越小,越容易观察模型到底如何选择工具,以及错误讯息是否足够。

第二步:用型别与 docstring 固定契约

Python 的型别注记不是装饰品。它会影响 schema 和 client 对工具的理解。我会明确写出可接受的值域、单位、时区、分页行为与错误条件;对复杂输入则建立清楚的资料模型,而不是接受一个没有结构的字串。这能降低模型传错参数的机率,也让人类开发者更快看懂。

第三步:在本机用 stdio 验证发现和呼叫

官方文件的 quickstart 不是只提供 hello world,它完整走过 server、tool、client 和视觉化结果的概念。我的验证顺序会是:先确认 server 能启动,再确认 client 能列出工具,最后用固定输入呼叫一次并检查结果。若这三步都过不了,先不要急着接模型,因为问题多半在协定描述、启动命令或输入 schema。

我也实际用 FastMCP 的公开 API 做了最小 smoke test:注册一个 add tool 和一个 data://status resource,列出两者名称,再呼叫 add(2, 3)。实际结果是 tool 清单包含 add、resource 清单包含 data://status,计算结果为 5。这个测试很小,却能验证 decorator 注册和公开列举 API 都能正常工作。

第四步:只有在需求成立时才切换 HTTP

本机桌面 client 或开发环境适合 stdio,因为它不必先暴露网路服务。当多个使用者或服务需要共用同一个 MCP Server,才考虑 HTTP。此时要把认证方式、租户隔离、请求逾时、重试、日志脱敏与流量上限列入设计,而不是把传输参数当成纯技术细节。

第五步:把工具治理放在 server 边界

模型输入永远是不可信的。即使 FastMCP 会依照型别处理参数,业务规则仍需要自己验证,例如使用者只能读取所属专案、查询条件必须限制范围、外部 URL 不可任意存取。对会改变状态的操作,我会记录呼叫者、输入摘要、结果状态与 trace id,并为重试设计幂等键。

FastMCP 的限制与容易误判的地方

第一,MCP 是能力交换协定,不是完整的 agent framework。它可以让模型发现和呼叫 tools、读取 resources、取得 prompts,但不会替你决定何时呼叫、如何规划长流程、如何评估答案品质。若团队要做多步骤 agent,还需要另一层 orchestration 或 application logic。

第二,Python 的低摩擦不等于低风险。把既有函式加上 decorator 很方便,也可能把原本只在内部呼叫的高权限函式快速暴露出去。导入前应该建立公开能力清单,逐一标记读取、写入、外部网路与敏感资料风险。

第三,HTTP 部署的复杂度会显著高于本机 stdio。你仍要处理网路可靠性、认证、授权和可观测性;如果只是想让本机 coding assistant 呼叫几个工具,直接采用远端部署反而可能增加不必要的攻击面。

第四,工具描述品质会直接影响模型选择。FastMCP 能从签名与 docstring 产生结构,但它不能替你写出精准的名称、边界和错误说明。两个功能相近的 tool 若描述模糊,模型可能选错;所以 schema review 应该和一般 API review 一样正式。

哪些团队适合先试?

我会推荐以下几类团队先建立小型 PoC:

  • 已经以 Python 维护内部 API、资料处理函式或自动化脚本,希望用一致协定接到 AI client。
  • 想把搜寻、查询、档案读取等能力提供给多个 MCP client,而不想为每个 client 重做一套 adapter。
  • 需要在本机先验证工具契约,再视需求演进到 HTTP 服务的开发团队。
  • 正在建立 AI agent 平台,希望把「工具实作」与「agent 的规划和对话逻辑」拆成不同层次。

相反地,如果团队目前还没有明确的工具使用场景,只是因为 MCP 很热门就想把所有内部系统接上去,我会建议先做能力盘点和风险分级。框架再好,也不能替没有边界的整合需求收敛范围。

我的判断:先把能力边界做小,FastMCP 才能放大价值

FastMCP 的优势不是把 MCP 复杂度假装不存在,而是把第一个正确实作的距离缩短。Python 函式、型别、docstring、tool/resource/prompt 分层和 stdio quickstart,让团队可以很快得到一个可列举、可呼叫、可测试的能力入口。接下来要不要改用 HTTP、要不要接多个 client、要不要加入授权与观测性,则可以依照真实需求逐步增加。

我会把它放在 AI application 的「能力层」,而不是把它当成完整产品平台。最实用的导入策略,是先挑一个低风险、可重复验证的 read-only 工作,确认 client 能发现并正确呼叫,再把工具契约、权限与日志纳入正式工程流程。这样做,FastMCP 才不是另一个需要维护的 wrapper,而是把既有 Python 能力转成可重用 AI 介面的清楚边界。


参考资料