从 0 开发一个 MCP Server:手把手教你构建 AI 工具调用能力(2026)

AI 不够「聪明」,往往不是因为模型弱,而是缺工具——没有实时数据、无法执行操作、上下文无法持久。本文面向 AI 开发者,从 Function Calling 演进到 MCP 协议,用 Python FastMCP 手把手实现 Hello World、Tools/Resources/Prompts 三类能力、HTTP+SSE 远程传输、ChromaDB 知识库实战与 Docker 生产部署;内含 MCP vs Function Calling 对比表、七步 Runbook 与 Cursor/Claude Desktop 调试指南。

开发者通过 MCP 协议将 AI 助手连接到数据库、文件系统与外部 API 的架构示意图

目录

引言:AI 不够聪明,因为缺工具

大语言模型再强,也无法直接访问你的数据库、读取本地文件或调用第三方 API——它只有「大脑」,没有「手脚」。2023 年 OpenAI 推出 Function Calling,2024 年 Anthropic 发布 MCP(Model Context Protocol),把「AI 如何发现、选择并正确调用工具」标准化为开放协议。读完本文,你将拥有一套可运行的 MCP Server,并被 Cursor、Claude Desktop 等 Host 直接调用。

核心痛点:自研 AI 工具集成的三个困境

  1. 工具定义无法复用。 为 ChatGPT 写的 Function Calling Schema,换到 Claude Tool Use 或 Cursor Agent 就要重写;LangChain Tool 与 CrewAI Tool 格式互不兼容,N 个框架 × M 个数据源 = N×M 定制集成。
  2. 运行时发现缺失。 传统 REST API 不会告诉 AI「我现在能做什么」;硬编码工具列表在业务变更时极易过时,Agent 选错工具或参数格式错误是生产环境最高频故障。
  3. 本地开发 ≠ 生产就绪。 STDIO 子进程在笔记本合盖即断、WSL2 环境漂移、Docker 增加排障复杂度——验证通过后如何 7×24 常驻,是多数开发者卡在「Demo 到上线」之间的真实瓶颈。

一、什么是 MCP

1.1 诞生背景:Function Calling → Plugins → MCP

2023 年,OpenAI Function Calling 让模型输出结构化 JSON 调用外部函数,但每家模型格式不同。2024 年初 ChatGPT Plugins 尝试标准化,却绑定 OpenAI 生态。2024 年 11 月 Anthropic 开源 MCP,将工具集成层从模型供应商剥离,成为行业公共基础设施——2026 年 OpenAI、Google、Microsoft 均已全面采纳。

1.2 架构:Client / Server 与 Tools / Resources / Prompts

┌─────────────────────────────────┐ │ Host(宿主层) │ ← Cursor、Claude Desktop、VS Code │ ┌───────────────────────────┐ │ │ │ MCP Client(客户端) │ │ ← 与每个 Server 维护 1:1 会话 │ └───────────────────────────┘ │ └─────────────────────────────────┘ ↕ JSON-RPC 2.0 ┌─────────────────────────────────┐ │ MCP Server(服务端) │ │ Tools │ Resources │ Prompts │ └─────────────────────────────────┘ ↕ ┌─────────────────────────────────┐ │ 外部系统(DB、API、文件、向量库) │ └─────────────────────────────────┘

1.3 JSON-RPC 2.0 与传输层

MCP 底层使用 JSON-RPC 2.0,核心方法包括 initializetools/listtools/callresources/listresources/readprompts/get

{ "jsonrpc": "2.0", "method": "tools/call", "params": { "name": "fetch_url", "arguments": { "url": "https://api.example.com/status" } }, "id": 1 }

1.4 传输方式与生命周期

1.5 MCP vs Function Calling vs LangChain 对比

维度Function CallingLangChain ToolsMCP
标准化各模型厂商私有格式框架内部抽象开放协议,跨 Host 复用
工具发现静态 Schema 硬编码运行时注册,不可跨框架tools/list 动态发现
只读资源❌ 无原生支持Retrieval 链路过重✅ Resources 一等公民
Prompt 模板PromptTemplate 非标准✅ Prompts 协议级支持
Host 兼容绑定单一 API绑定 LangChain 栈Cursor / Claude / VS Code 通用

二、开发环境准备

2.1 Python vs TypeScript 选择

语言SDK适合团队上手速度
Pythonmcp + FastMCP数据/AI/后端⭐⭐⭐ 最快
TypeScript@modelcontextprotocol/sdk前端/Node/IDE 插件⭐⭐ 中等

本文以 Python + FastMCP 为主(代码最少、与 AI 栈一致);TypeScript 版逻辑完全对称。

2.2 环境搭建

mkdir my-mcp-server && cd my-mcp-server python3 -m venv .venv source .venv/bin/activate # Windows: .venv\Scripts\activate pip install "mcp[cli]" httpx pydantic chromadb

2.3 推荐项目结构

my-mcp-server/ ├── server.py # 主入口(FastMCP 实例) ├── tools/ │ ├── calculator.py │ ├── filesystem.py │ └── http_client.py ├── resources/ │ └── docs.py ├── prompts/ │ └── code_review.py ├── tests/ │ └── test_tools.py ├── Dockerfile └── pyproject.toml

2.4 调试工具链

三、Hello World:第一个 MCP Server

3.1 FastMCP say_hello

# server.py from mcp.server.fastmcp import FastMCP mcp = FastMCP("demo-server") @mcp.tool() def say_hello(name: str = "World") -> str: """向指定对象打招呼。""" return f"Hello, {name}! MCP Server 运行正常。" if __name__ == "__main__": mcp.run()

3.2 用 MCP Inspector 运行

# 终端 1:启动 Server(STDIO 模式) python server.py # 终端 2:启动 Inspector npx @modelcontextprotocol/inspector python server.py

浏览器打开 Inspector UI,点击 ConnectList Tools → 调用 say_hello,输入 {"name": "VPSMAC"} 验证返回。

3.3 Cursor / Claude Desktop 配置

// .cursor/mcp.json { "mcpServers": { "demo-server": { "command": "/path/to/my-mcp-server/.venv/bin/python", "args": ["/path/to/my-mcp-server/server.py"] } } }
// claude_desktop_config.json { "mcpServers": { "demo-server": { "command": "/path/to/my-mcp-server/.venv/bin/python", "args": ["/path/to/my-mcp-server/server.py"] } } }

重启 Cursor / Claude Desktop,在 Agent 对话中输入「用 say_hello 向 VPSMAC 问好」验证工具调用。

四、Tools:让 AI 真正「动手」

4.1 Tool 基本结构

FastMCP 用装饰器 @mcp.tool() 注册;函数 docstring 成为工具描述,参数类型自动转为 JSON Schema。

4.2 Pydantic 输入校验

from pydantic import BaseModel, Field class CalcInput(BaseModel): expression: str = Field(description="数学表达式,如 2+3*4") @mcp.tool() def calculator(input: CalcInput) -> str: """安全计算器,仅支持四则运算。""" allowed = set("0123456789+-*/(). ") if not all(c in allowed for c in input.expression): raise ValueError("表达式含非法字符") return str(eval(input.expression)) # 生产环境请用 ast 解析

4.3 五个实用工具

import os, sqlite3, httpx from datetime import datetime, timezone @mcp.tool() def read_file(path: str) -> str: """读取本地文本文件内容(限制在 ALLOWED_DIR 内)。""" base = os.environ.get("ALLOWED_DIR", "/data") full = os.path.realpath(os.path.join(base, path)) if not full.startswith(os.path.realpath(base)): raise PermissionError("路径越界") with open(full, "r", encoding="utf-8") as f: return f.read() @mcp.tool() async def fetch_url(url: str, timeout: int = 10) -> str: """异步 HTTP GET,返回响应体文本。""" async with httpx.AsyncClient(timeout=timeout) as client: resp = await client.get(url) resp.raise_for_status() return resp.text[:50000] @mcp.tool() def query_sqlite(sql: str) -> str: """执行只读 SQLite 查询(SELECT only)。""" if not sql.strip().upper().startswith("SELECT"): raise ValueError("仅允许 SELECT 语句") conn = sqlite3.connect(os.environ["DB_PATH"]) rows = conn.execute(sql).fetchall() conn.close() return str(rows[:100]) @mcp.tool() def current_time(tz: str = "UTC") -> str: """返回当前 ISO 8601 时间戳。""" return datetime.now(timezone.utc).isoformat()

4.4 错误处理最佳实践

五、Resources:为 AI 提供只读上下文

5.1 Resource vs Tool

能力ToolResource
操作类型可写、可执行只读
寻址按名称 + 参数按 URI(file://db://
典型场景发请求、改数据读文档、拉配置、看日志

5.2 URI 寻址与资源类型

MCP Resource 通过 URI 标识:file:///docs/api.mdconfig://app/settingsnote://kb/001。Host 通过 resources/list 发现,resources/read 拉取内容。

5.3 静态与动态资源代码

@mcp.resource("file://docs/{name}") def get_doc(name: str) -> str: """读取 docs/ 目录下的 Markdown 文档。""" path = f"docs/{name}.md" with open(path, "r", encoding="utf-8") as f: return f.read() @mcp.resource("config://app/settings") def app_settings() -> str: """返回当前应用配置的 JSON 快照。""" import json return json.dumps({"env": os.environ.get("APP_ENV", "dev"), "version": "1.2.0"}, indent=2)

5.4 文件系统实战

结合 ALLOWED_DIR 白名单,暴露项目 README、API 文档、CHANGELOG 为 Resource,Agent 在 Code Review 时可自动拉取上下文,无需用户手动粘贴。

六、Prompts:预置多轮对话模板

6.1 MCP Prompt 定义

Prompt 是带参数的多轮消息模板,Host 通过 prompts/get 获取后直接注入对话,保证 Code Review、PR 摘要等场景的一致性。

6.2 code_review_prompt 示例

@mcp.prompt() def code_review_prompt(language: str, focus: str = "security") -> str: """生成 Code Review 系统提示,聚焦指定语言与安全/性能维度。""" return f"""你是一位资深 {language} 工程师。请对以下代码进行 Review, 重点关注:{focus}。 输出格式: 1. 严重问题(必须修复) 2. 改进建议(可选) 3. 总体评分(1-10)""" @mcp.prompt() def pr_summary(repo: str, branch: str) -> str: """PR 变更摘要模板。""" return f"请总结 {repo} 仓库 {branch} 分支相对于 main 的变更要点、风险与测试建议。"

6.3 多轮对话

Prompt 返回的消息列表可包含 system + user 角色,Host 将其作为对话起点;Agent 随后可调用 Tools 拉取实际 diff、再基于 Prompt 框架输出结构化 Review。

七、HTTP 传输:从本地到云端

7.1 STDIO vs HTTP+SSE 对比

传输启动方式网络适用
STDIOHost spawn 子进程本地 onlyCursor、Claude Desktop
HTTP + SSE独立 HTTP 服务跨网络团队共享、云部署、多 Host 接入
Streamable HTTP单端点双向流跨网络2025+ 推荐远程模式

7.2 Streamable HTTP 代码

# server_http.py from mcp.server.fastmcp import FastMCP mcp = FastMCP("demo-server", host="0.0.0.0", port=8080) @mcp.tool() def say_hello(name: str = "World") -> str: return f"Hello, {name}!" if __name__ == "__main__": mcp.run(transport="streamable-http")
// Cursor 远程 MCP 配置示例 { "mcpServers": { "remote-demo": { "url": "https://mcp.example.com/mcp", "headers": { "Authorization": "Bearer YOUR_TOKEN" } } } }

7.3 认证与安全

八、调试与测试

8.1 MCP Inspector 深度用法

Inspector 支持查看 JSON-RPC 原始报文、模拟 initialize 握手、批量测试 tools/call。开发阶段建议「Inspector 通过 → 再接入 Cursor」的两段式验收。

8.2 pytest 单元测试

# tests/test_tools.py import pytest from server import calculator, current_time def test_calculator_basic(): assert calculator(CalcInput(expression="2+3")) == "5" def test_calculator_rejects_injection(): with pytest.raises(ValueError): calculator(CalcInput(expression="__import__('os')")) def test_current_time_iso(): result = current_time() assert "T" in result and result.endswith("+00:00")

8.3 常见错误排查

现象原因解决
Cursor 看不到工具mcp.json 路径错误或 Python 未激活 venv检查 command 绝对路径,重启 Cursor
tools/call 超时HTTP 工具无 timeout、DB 慢查询设置 httpx timeout=10,SQL LIMIT
JSON-RPC parse errorstdout 混入 print 调试输出日志写 stderr,禁止 print 到 stdout
Permission denied文件路径越界 ALLOWED_DIR检查 realpath 白名单逻辑
Inspector 连接失败端口占用或 transport 不匹配确认 STDIO vs HTTP 模式一致

九、生产部署

9.1 Dockerfile

FROM python:3.12-slim WORKDIR /app COPY pyproject.toml server.py tools/ resources/ prompts/ ./ RUN pip install --no-cache-dir "mcp[cli]" httpx pydantic chromadb ENV ALLOWED_DIR=/data DB_PATH=/data/app.db EXPOSE 8080 CMD ["python", "server.py"]

9.2 部署平台选型

平台优势注意
Railway / Render零配置 PaaS,适合 HTTP+SSE冷启动延迟,需健康检查
AWS ECS / Fly.io弹性扩展、全球边缘需自行配置 TLS 与 Secret Manager
通用 VPS完全控制、成本可控需 systemd/launchd 托管进程

9.3 监控与版本管理

十、知识库实战:ChromaDB + 语义搜索

10.1 架构:Embedding + 向量检索

将个人笔记/团队文档索引为 MCP Resource 与 Tool,Agent 通过语义搜索获取相关片段,再基于 Prompt 生成回答——这是 RAG 与 MCP 结合的典型模式。

10.2 ChromaDB 索引与搜索

import chromadb from chromadb.utils import embedding_functions ef = embedding_functions.DefaultEmbeddingFunction() client = chromadb.PersistentClient(path="./chroma_db") collection = client.get_or_create_collection("notes", embedding_function=ef) @mcp.tool() def index_note(note_id: str, content: str) -> str: """将笔记写入向量库。""" collection.upsert(ids=[note_id], documents=[content]) return f"Indexed note {note_id}" @mcp.tool() def semantic_search(query: str, top_k: int = 5) -> str: """语义搜索笔记,返回最相关片段。""" results = collection.query(query_texts=[query], n_results=top_k) return str(results["documents"]) @mcp.resource("note://kb/{note_id}") def read_note(note_id: str) -> str: """按 ID 读取完整笔记。""" doc = collection.get(ids=[note_id]) return doc["documents"][0] if doc["documents"] else "Not found"

10.3 Qdrant 替代方案

生产环境数据量 > 100 万向量时,可切换 Qdrant 托管服务;MCP Server 层接口不变,仅替换底层 vector store 实现。

10.4 Cursor 演示场景

在 Cursor Agent 中:「搜索关于 MCP HTTP 传输的笔记」→ 调用 semantic_search → 读取 Top-1 Resource → 基于 code_review_prompt 输出结构化摘要。三步完成 RAG,无需用户手动 @ 文件。

十一、生态展望

11.1 推荐官方与社区 Server

11.2 2026 趋势

11.3 学习路径

  1. 阅读 modelcontextprotocol.io 规范 → 跑通 Hello World
  2. 实现 3 个 Tools + 1 个 Resource → Inspector 验收
  3. 接入 Cursor → 构建 ChromaDB 知识库 Server
  4. 切换 HTTP+SSE → Docker 部署 → 监控告警
  5. 贡献社区 Server 或企业内部 Server 治理

七步 Runbook:从 0 到生产级 MCP Server

步骤 1 — 环境初始化

创建 venv,pip install "mcp[cli]",初始化项目目录与 ALLOWED_DIR 白名单。

步骤 2 — Hello World 验收

编写 say_hellonpx @modelcontextprotocol/inspector 验证 tools/list 与 tools/call。

步骤 3 — 扩展 Tools/Resources/Prompts

按业务需求实现 5 类工具、URI 资源与 Code Review Prompt;Pydantic 校验所有输入。

步骤 4 — 接入 Cursor / Claude Desktop

配置 mcp.json / claude_desktop_config.json,重启 Host,Agent 对话验证端到端调用。

步骤 5 — 单元测试与错误排查

pytest 覆盖核心工具;对照常见错误表修复 stdout 污染与超时问题。

步骤 6 — HTTP+SSE 云端传输

切换 streamable-http,配置 Bearer Token,部署到 Railway/Render/AWS。

步骤 7 — 监控与 launchd 常驻

接入 Prometheus + Sentry;Mac 云节点用 launchd 托管 STDIO 子进程,设置资源上限与日志轮转。

可引用技术要点(2026)

结语与配套资源

从 Function Calling 到 MCP,AI 工具集成终于有了「USB-C 时刻」——写一次 Server,所有兼容 Host 即插即用。本文覆盖了从 Hello World 到 ChromaDB 知识库、从 STDIO 到 HTTP+SSE 的完整路径;下一步,你可以基于业务需求扩展 Tools,或组合官方 filesystem/github/postgres Server 构建企业级 Agent 工具链。

配套资源:

在笔记本或 WSL2 上跑 STDIO MCP Server 可以完成验证,但合盖断线、环境漂移与无 Apple 工具链会让生产级 Agent 难以 7×24 稳定运行。Docker 方案虽灵活,却增加抽象层与排障复杂度,HTTP+SSE 远程模式还需额外维护 TLS 与进程守护。若你需要 Cursor、Claude Desktop 与 MCP Server 长期同机常驻、原生 macOS 与 launchd 托管,租赁 VPSMAC 的 Mac 云节点通常是更省心、更适合 AI 自动化生产环境的选择——工具层写一次,模型随意换,节点始终在线。