1. 为什么我要从零手写一个 MCP ServerMCP 这个词最近在 AI 编程圈里出现得越来越频繁但很多人第一次接触时都会卡在同一个地方知道它能给 Cursor、Claude Code 这类工具挂上外部能力却不知道一个最小的 Server 到底长什么样、怎么跑起来、怎么被客户端真正调用一次。我一开始也是这样看了不少概念文章真正动手时还是懵的。这篇就解决这个问题。我会用 fastmcp 写一个最小可运行的 MCP Server暴露两个工具函数一个返回问候语一个返回当前时间然后用 HTTP 传输模式启动最后在 Cursor 里完成一次真实调用。整个过程不需要你懂 MCP 协议的细节只要会 Python 基础就能跟下来。MCP 全称 Model Context Protocol你可以把它理解成 AI 客户端和外部工具之间的一套标准接口。以前你想让 Cursor 查个数据库、调个内部 API得写一堆适配代码有了 MCP你只要按协议暴露工具客户端就能自动发现并调用。fastmcp 是这套协议的一个 Python 实现把注册工具、启动服务这些事封装得很轻几十行就能跑起来。适合谁看正在用 Cursor 或 Claude Code、想给自己加自定义工具的开发者想理解 MCP Server 最小结构的学习者以及被各种 MCP 教程绕晕、只想先跑通一个 Hello World 的人。下面所有代码都可以直接复制路径和配置我会写清楚。2. 用 fastmcp 搭最小 Server 的前置准备与项目结构动手之前先把环境理清楚这一步做对了后面基本不会报错。fastmcp 要求 Python 3.10 及以上我实测 3.11 和 3.12 都没问题。包管理我推荐用 uv它装依赖比 pip 快很多而且能直接管理虚拟环境。如果你还没装 uv可以先装一下后面所有命令都基于它。项目结构我按标准 Python 包来组织这样后面用uv pip install -e .安装成可执行命令时不会出问题。目录长这样hello-mcp/ ├── pyproject.toml └── src/ └── hello_mcp/ ├── __init__.py └── server.pysrc布局是现在 Python 项目比较推荐的写法好处是安装后包路径清晰不会和当前目录的其他文件混淆。__init__.py里我放一个main的导出这样pyproject.toml里的脚本入口能直接指向它。先建目录mkdir -p hello-mcp/src/hello_mcp cd hello-mcp然后创建虚拟环境并激活。Windows 和 macOS/Linux 激活命令不一样我两个都写上python -m venv .venv # macOS / Linux source .venv/bin/activate # Windows PowerShell .venv\Scripts\Activate.ps1激活后命令行前面会出现(.venv)字样说明进对环境了。接下来装 uv 和 fastmcppip install uv uv pip install fastmcp2.8.0这里有个小坑fastmcp 的版本更新比较快2.8.0 之后 API 基本稳定但如果你装的是更老的版本transportstreamable-http这个参数可能不认。所以我在依赖里锁了2.8.0。装完可以用uv pip show fastmcp确认一下版本。环境准备好之后还有一个容易被忽略的点HTTP 模式下默认端口是 8000如果你本机 8000 被别的服务占了启动时会报端口占用。可以先用lsof -i :8000macOS/Linux或netstat -ano | findstr 8000Windows看一眼。我一般习惯直接改成 8765 这种不常用的端口避免冲突。3. 可复制的 fastmcp 启动脚本与 Cursor 配置片段这一节是核心代码和配置我都给全你照着贴就行。先写server.py# src/hello_mcp/server.py from fastmcp import FastMCP import datetime # 创建 MCP 服务器实例名字会显示在客户端里 mcp FastMCP(hello-mcp-server) mcp.tool(descriptionSay hello to someone) async def say_hello(name: str World) - str: 返回一句问候语。 return fHello, {name}! Welcome to MCP! mcp.tool(descriptionGet current server time) async def get_time() - str: 返回服务器当前时间。 current_time datetime.datetime.now().strftime(%Y-%m-%d %H:%M:%S) return fCurrent time: {current_time} def main(): print(Starting Hello MCP Server...) # 使用 HTTP 传输模式默认监听 127.0.0.1:8000 mcp.run(transportstreamable-http) if __name__ __main__: main()两个工具函数都用async def定义这是 fastmcp 推荐的写法即使函数体里没有真正的异步操作也没关系。mcp.tool装饰器负责把函数注册成 MCP 工具description会作为工具说明暴露给客户端Cursor 里能看到。接着写__init__.py# src/hello_mcp/__init__.py from .server import main __all__ [main]然后是pyproject.toml这个文件决定了项目怎么安装、命令怎么注册[project] name hello-mcp-server version 0.1.0 description A simple Hello World MCP Server requires-python 3.10 dependencies [ fastmcp2.8.0, ] [project.scripts] hello-mcp-server hello_mcp:main [build-system] requires [hatchling] build-backend hatchling.build [tool.hatch.build.targets.wheel] packages [src/hello_mcp][project.scripts]这一段是关键它把hello-mcp-server这个命令映射到hello_mcp包的main函数。装完之后你就能直接在命令行敲hello-mcp-server启动服务不用记python xxx.py的路径。安装并启动uv pip install -e . hello-mcp-server看到Starting Hello MCP Server...并且没有报错说明服务已经在http://127.0.0.1:8000跑起来了。MCP 的 HTTP 端点在/mcp路径下完整地址是http://127.0.0.1:8000/mcp。现在配置 Cursor。打开 Cursor 设置找到 MCP 配置入口或者直接编辑配置文件。配置片段如下{ mcpServers: { hello-mcp: { url: http://127.0.0.1:8000/mcp } } }注意这里用的是url字段而不是command因为我们是 HTTP 模式。如果你之前配过基于 stdio 的 MCP Server那用的是command加args两种模式别混。保存后 Cursor 会自动尝试连接连接成功的话在 MCP 面板里能看到hello-mcp这个服务展开后有两个工具say_hello和get_time。4. 用 curl 和 Cursor 各验证一次真实调用服务跑起来、Cursor 也配好了但怎么确认它真的能用我习惯先用 curl 验证服务端本身没问题再去 Cursor 里验证端到端调用。这样出问题时能快速定位是服务端还是客户端的问题。先看服务端。MCP 的 streamable-http 模式支持标准的 JSON-RPC 请求我们可以用 curl 发一个初始化请求探探路curl -X POST http://127.0.0.1:8000/mcp \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -d { jsonrpc: 2.0, id: 1, method: initialize, params: { protocolVersion: 2024-11-05, capabilities: {}, clientInfo: {name: curl-test, version: 1.0} } }如果服务正常你会收到一段包含serverInfo和capabilities的响应里面能看到hello-mcp-server这个名字。这一步能通说明 HTTP 端点和协议握手都没问题。接着调用工具。先列出工具列表curl -X POST http://127.0.0.1:8000/mcp \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -d { jsonrpc: 2.0, id: 2, method: tools/list, params: {} }响应里应该能看到say_hello和get_time两个工具及其描述。然后实际调用say_hellocurl -X POST http://127.0.0.1:8000/mcp \ -H Content-Type: application/json \ -H Accept: application/json, text/event-stream \ -d { jsonrpc: 2.0, id: 3, method: tools/call, params: { name: say_hello, arguments: {name: Cursor} } }返回内容里会有Hello, Cursor! Welcome to MCP!这段文本。到这一步服务端就算彻底验证通过了。现在去 Cursor 里做端到端验证。打开 Cursor 的对话窗口直接输入类似「调用 hello-mcp 的 say_hello 工具参数 name 传 TaoToken」这样的话。Cursor 会识别到可用的 MCP 工具并弹出调用确认你点允许之后它会把工具返回的结果展示出来。如果看到Hello, TaoToken! Welcome to MCP!说明整条链路通了。再试一次get_time让 Cursor 调用它获取当前时间。这个工具没有参数调用更简单。两次都成功你的第一个 MCP Server 就真正跑通了。这里补充一个实际体验Cursor 调用 MCP 工具时会有个确认弹窗这是它的安全机制不是报错。如果你希望某些工具自动执行可以在 Cursor 的 MCP 设置里调整权限但初期建议保持手动确认方便观察每次调用的输入输出。5. 跑通过程中常见的报错与排查即使步骤都对实际跑的时候还是可能撞上几个典型报错。我把踩过的和读者反馈最多的整理出来对照着排查能省不少时间。第一个是连接类报错Cursor 里显示local proxy failed或者直接连不上。这种情况九成是服务没启动或者端口不对。先在浏览器或 curl 访问http://127.0.0.1:8000/mcp如果连不上回去检查hello-mcp-server是不是还在前台运行。注意这个服务是前台进程关掉终端它就停了。另外确认 Cursor 配置里的 URL 是/mcp结尾少写这个路径会 404。第二个是401 Unauthorized。我们这个最小示例没有加认证正常不会出现。如果你在配置里手滑加了headers或者apiKey字段Cursor 会带着这些去请求而服务端不认就会返回 401。把多余的认证字段删掉即可。反过来说如果你后面要给 Server 加鉴权那 Cursor 配置里就得对应加上 header两边要一致。第三个是响应解析报错Cursor 提示reading choices或者 JSON 解析失败。这通常是因为请求头里少了Accept: application/json, text/event-stream。streamable-http 模式返回的是 SSE 流客户端必须声明接受这种类型。Cursor 新版本会自动处理但如果你用的是自己写的客户端或者旧版本就得手动加这个头。用 curl 测试时我上面已经带上了你可以对照。第四个是启动时报ModuleNotFoundError: No module named hello_mcp。这是因为没执行uv pip install -e .或者执行时不在项目根目录。-e是 editable 安装会把当前项目以开发模式装进环境改了代码不用重装。确认你在hello-mcp根目录下执行并且虚拟环境是激活状态。第五个是端口占用报Address already in use。前面提过8000 端口经常被占。改端口的话在mcp.run()里加参数mcp.run(transportstreamable-http, host127.0.0.1, port8765)改完记得 Cursor 配置里的 URL 也要同步改成http://127.0.0.1:8765/mcp两边不一致照样连不上。还有一个和 OAuth 相关的报错如果你在 Cursor 里看到OAuth字样通常是因为客户端尝试走授权流程但服务端没实现。我们这个示例不需要 OAuth检查一下配置里是不是误加了auth相关字段删掉就好。排查的核心思路就一条先用 curl 确认服务端独立可用再排查客户端配置。服务端通了、客户端配置对了中间就不会有玄学问题。6. 把 MCP Server 接到真实模型上继续验证服务端和 Cursor 都跑通之后你可能会想这套东西接到真实模型上是什么效果毕竟 Cursor 只是其中一个客户端MCP 的价值在于它能被各种支持协议的 AI 工具复用。我自己的做法是先用一个稳定的 API 入口把模型调用和 MCP 工具串起来验证一遍。TaoToken 提供了兼容主流协议的接口模型对话入口在 https://taotoken.net/api 接入文档在 https://taotoken.net/doc API Key 在 https://taotoken.net/api-keys 可以拿到。拿到 Key 之后你可以写一个简单的客户端把 MCP 工具列表喂给模型让模型决定调哪个工具、传什么参数再把工具返回结果回传给模型。这样你就有了一个最小的 Agent 循环。如果你主要用 Claude Code 做长期编码任务Coding Plan 会更合适入口在 https://taotoken.net/coding-plan 。它把模型调用和工具编排的额度打包在一起适合需要反复调用 MCP 工具的场景。想直接体验模型对话的话https://taotoken.net/models 可以快速试。回到这个 Hello World Server它虽然简单但结构是完整的工具注册、HTTP 暴露、客户端发现、真实调用四个环节都跑通了。你接下来要做的就是把say_hello换成真正有用的函数比如查数据库、调内部 API、读本地文件。fastmcp 的装饰器写法不变改的只是函数体。等你把第一个真实工具挂上去MCP 对你来说就不再是概念而是能天天用的东西了。