基于 Python FastMCP SDK,零基础动手搭建一个完整可用的 MCP 服务器
一、MCP 是什么
MCP(Model Context Protocol)是连接 AI 助手与外部数据源和工具的通信协议,可以理解为 AI 的 "USB 接口"------AI 通过 MCP 连接外部的工具、数据和服务。
核心设计思想
┌─────────────┐ MCP 协议 ┌─────────────┐
│ AI 客户端 │ ◄────────────► │ MCP 服务器 │
│ (Trae等) │ JSON-RPC │ (Python) │
└─────────────┘ └─────────────┘
│ │
│ ├── Tools(工具)
### MCP 三大支柱
| 能力 | 比喻 | 作用 |
|---|---|---|
| Tools | AI的"手" | 让 AI 能执行操作(计算、搜索等) |
| Resources | AI的"眼" | 让 AI 能读取数据(知识库、文件等) |
| Prompts | AI的"嘴" | 让 AI 能规范输出(代码审查、错误解释等) |
二、项目结构
核心依赖
mcp >= 1.0.0 # MCP Python SDK(FastMCP)
uvicorn >= 0.30.0 # ASGI 服务器(HTTP 模式用)
starlette >= 0.40.0 # Web 框架(HTTP 模式用)
server.py 五层架构
┌─────────────────────────────────────────────────────────────┐
│ 第 5 层:MCP 客户端 (Trae) │
│ ↕ 通过 mcp.json 配置连接 │
├─────────────────────────────────────────────────────────────┤
│ 第 4 层:MCP 协议传输层 │
│ stdio (进程管道) ←→ HTTP/SSE (网络) │
├─────────────────────────────────────────────────────────────┤
│ 第 3 层:FastMCP 调度器 │
│ 接收协议请求 → 路由到注册的函数 │
├─────────────────────────────────────────────────────────────┤
│ 第 2 层:装饰器注册层 │
│ @mcp.tool() / @mcp.resource() / @mcp.prompt() │
├─────────────────────────────────────────────────────────────┤
│ 第 1 层:业务函数 │
│ calculate() / get_time() / search_knowledge() ... │
└─────────────────────────────────────────────────────────────┘
三、核心概念:容器 + 装饰器 + docstring
3.1 快速理解
如果你学过 FastAPI,可以这样类比:
| 概念 | FastAPI | MCP | 本质 |
|---|---|---|---|
| 容器 | APIRouter |
FastMCP |
一个注册表对象 |
| 装饰器 | @router.get() |
@mcp.tool() |
往容器里注册函数 |
| 注册内容 | HTTP 路由 + 处理函数 | Tool/Resource/Prompt | 都是"能力声明" |
| 启动方式 | app.include_router() |
mcp.run() |
激活容器,开始对外服务 |
3.2 什么是 docstring?
docstring 就是 Python 函数的第一个多行注释 ,用 """ 或 ''' 包裹。它不是普通的代码注释,而是 Python 语言规范的一部分,可以通过 __doc__ 属性访问。
普通注释 用 # 开头,给人看的:
# 这是一个普通注释,Python 不会读取
docstring 用 """ 包裹,Python 会自动保存:
def get_time() -> str:
"""获取当前时间""" # 这就是 docstring,Python 会自动保存
...
你可以这样访问 docstring:
print(get_time.__doc__) # 输出: "获取当前时间"
### 3.3 docstring 对 MCP 的重要性
对 Python 来说 ,docstring 是可选的(没有也不会报错)。
对 MCP 来说 ,docstring 是极其重要的------AI 就是靠 docstring 来理解每个工具是干什么的。
看一下 server.py 中的例子:
示例:带 docstring 的工具(推荐写法)
@mcp.tool()
def calculate(expression: str) -> str:
"""
计算数学表达式 ← 工具的功能描述(AI 看这个)
Args:
expression: 数学表达式,如 "2 + 3 * 4" ← 参数说明(AI 看这个)
Returns:
计算结果 ← 返回值说明(AI 看这个)
"""
try:
result = eval(expression)
return f"结果: {result}"
except Exception as e:
return f"计算错误: {str(e)}"
这个 docstring 不是给人看的,是给 AI 看的!当 MCP 启动后,会把 docstring 转换成 JSON Schema 返回给 AI:
// AI 收到的工具列表(自动从 docstring 生成)
{
"name": "calculate",
"description": "计算数学表达式\n\nArgs:\n expression: 数学表达式,如 \"2 + 3 * 4\"\nReturns:\n 计算结果",
"inputSchema": {
"type": "object",
"properties": {
"expression": {
"type": "string",
"description": "数学表达式,如 \"2 + 3 * 4\""
}
},
"required": ["expression"]
}
}
3.4 注册流程
当 Python 导入 server.py 时,代码从上到下逐行执行:
第1步:创建空的"注册表"
mcp = FastMCP("HelloMCP")
# 内部有3个空字典: _tools={}, _resources={}, _prompts={}
# 第2步:装饰器逐个注册(自动提取 docstring 信息)
@mcp.tool()
def calculate(expression: str) -> str:
"""计算数学表达式""" # ← 这个 docstring 会被提取
...
# 等价于: mcp._tools["calculate"] = {
# "description": "计算数学表达式",
# "inputSchema": {...},
# "function": calculate
# }
装饰器做了三件事:
- 把函数存入注册表
- 解析函数签名(参数名、类型 → 生成 JSON Schema)
- 提取 docstring(描述、参数说明 → 告诉 AI 这个工具是干什么的)
3.5 对比:有 docstring vs 没有 docstring
写法一:有清晰的 docstring(推荐)
@mcp.tool()
def get_time() -> str:
"""获取当前时间
Returns:
当前日期和时间
"""
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# → AI 清楚知道这个工具是"获取时间",会精准调用
# 写法二:没有 docstring(不推荐)
@mcp.tool()
def get_time() -> str:
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# → AI 只看到名字 get_time,可能不知道它的用途
## 四、完整核心代码:server.py
以下是 server.py 的完整代码,按模块分块展示。
4.1 导入与初始化
"""
MCP Server 示例 - 学习 MCP 协议
这个服务器演示了 MCP 的核心概念:工具(Tools)、资源(Resources)、提示(Prompts)
支持 HTTP/SSE 传输,外部系统可通过 HTTP 调用
"""
from mcp.server.fastmcp import FastMCP
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.requests import Request
import socket
import uvicorn
# 自定义中间件:修复 Host header 验证问题
class HostFixMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request: Request, call_next):
# 修复 Host header 问题,确保通过验证
request.scope['server'] = ('127.0.0.1', 8080)
response = await call_next(request)
return response
# 创建 MCP 服务器实例
mcp = FastMCP("HelloMCP")
4.2 示例工具(Tools)
# ============================================
# 示例工具 (Tools) - AI 可以调用的函数
============================================
@mcp.tool()
def calculate(expression: str) -> str:
"""
计算数学表达式
Args:
expression: 数学表达式,如 "2 + 3 * 4"
Returns:
计算结果
"""
try:
result = eval(expression)
return f"结果: {result}"
except Exception as e:
return f"计算错误: {str(e)}"
@mcp.tool()
def get_time() -> str:
"""
获取当前时间
Returns:
当前日期和时间
"""
from datetime import datetime
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
@mcp.tool()
def reverse_text(text: str) -> str:
"""
反转文本
Args:
text: 要反转的文本
Returns:
反转后的文本
"""
return text[::-1]
4.3 示例资源(Resources)
============================================
# 示例资源 (Resources) - 可读取的数据
# ============================================
@mcp.resource("greeting://hello")
def get_greeting() -> str:
"""返回一个问候语资源"""
return "Hello! Welcome to MCP Server!"
@mcp.resource("info://server")
def get_server_info() -> str:
"""返回服务器信息"""
import platform
return f"""
Server Info:
- Python Version: {platform.python_version()}
- Platform: {platform.platform()}
- MCP SDK Version: 1.0.0
"""
4.4 知识库资源与工具
============================================
# 知识库资源 (Knowledge Resources) - RAG 应用
# ============================================
import os
from pathlib import Path
# 知识库目录
KNOWLEDGE_DIR = Path(__file__).parent / "knowledge"
@mcp.resource("knowledge://products")
def get_product_knowledge() -> str:
"""产品知识库"""
file_path = KNOWLEDGE_DIR / "products.md"
if file_path.exists():
return file_path.read_text(encoding="utf-8")
return "产品知识库暂无内容"
@mcp.resource("knowledge://policies")
def get_policy_knowledge() -> str:
"""政策文档知识库"""
file_path = KNOWLEDGE_DIR / "policies.md"
if file_path.exists():
return file_path.read_text(encoding="utf-8")
return "政策文档暂无内容"
@mcp.resource("knowledge://faq")
def get_faq_knowledge() -> str:
"""常见问题知识库"""
file_path = KNOWLEDGE_DIR / "faq.md"
if file_path.exists():
return file_path.read_text(encoding="utf-8")
return "FAQ 暂无内容"
# ============================================
# 知识库工具 (Knowledge Tools) - 检索
# ============================================
@mcp.tool()
def search_knowledge(keyword: str) -> str:
"""
在知识库中搜索关键词
Args:
keyword: 搜索关键词
Returns:
匹配的知识点
"""
if not KNOWLEDGE_DIR.exists():
return "知识库目录不存在"
results = []
for file_path in KNOWLEDGE_DIR.glob("*.md"):
content = file_path.read_text(encoding="utf-8")
if keyword.lower() in content.lower():
lines = content.split("\n")
matching_lines = [line for line in lines if keyword.lower() in line.lower()]
results.append(f"【{file_path.stem}】匹配到 {len(matching_lines)} 处:\n" + "\n".join(matching_lines[:10]))
if results:
return f"找到 {len(results)} 个相关知识:\n\n" + "\n\n---\n\n".join(results)
return f"未找到与 '{keyword}' 相关的知识"
4.5 文件搜索与目录树工具
============================================
# 实用工具 (Tools) - 文件搜索
# ============================================
@mcp.tool()
def search_files(directory: str, pattern: str) -> str:
"""
搜索目录下包含特定模式的文件
Args:
directory: 要搜索的目录路径
pattern: 文件名匹配模式(如 ".py", "test", "config")
Returns:
匹配的文件列表
"""
try:
matches = []
path = Path(directory)
if not path.exists():
return f"目录不存在: {directory}"
for f in path.rglob(f"*{pattern}*"):
if f.is_file():
matches.append(str(f.relative_to(path)))
if matches:
return f"找到 {len(matches)} 个匹配的文件:\n" + "\n".join(f" - {m}" for m in matches[:20])
else:
return f"在 {directory} 中没有找到包含 '{pattern}' 的文件"
except Exception as e:
return f"搜索出错: {str(e)}"
# ============================================
# 实用工具 (Tools) - 目录树
# ============================================
@mcp.tool()
def get_directory_tree() -> str:
"""返回当前目录的树状结构"""
def build_tree(path: Path, prefix: str = "", max_depth: int = 2, current_depth: int = 0) -> str:
if current_depth >= max_depth:
return ""
result = []
try:
items = sorted(path.iterdir(), key=lambda x: (not x.is_dir(), x.name))
for i, item in enumerate(items[:10]):
is_last = i == len(items[:10]) - 1
connector = "└── " if is_last else "├── "
result.append(f"{prefix}{connector}{item.name}")
if item.is_dir():
extension = " " if is_last else "│ "
result.append(build_tree(item, prefix + extension, max_depth, current_depth + 1))
except PermissionError:
pass
return "\n".join(result)
return f"当前目录结构:\n{build_tree(Path.cwd())}"
4.6 提示模板(Prompts)
⚠️ 以下代码中的 ``````````` 是 Python f-string 中的字符串内容,用于生成提示词模板,不是 Markdown 代码块。实际代码中用三反引号替换
<CODEBLOCK>和</CODEBLOCK>。
# ============================================
# 提示模板 (Prompts) - 代码解释
# ============================================
@mcp.prompt()
def explain_code_prompt(code: str, language: str = "python") -> str:
"""生成代码解释的提示模板"""
return f"""请解释以下 {language} 代码:
1. 这段代码的主要功能是什么?
2. 关键逻辑和执行流程
3. 可能的用途和应用场景
<CODEBLOCK>{language}
{code}
</CODEBLOCK>"""
@mcp.prompt()
def code_review_prompt(code: str) -> str:
"""代码审查提示模板"""
return f"""请审查以下代码并提供建议:
<CODEBLOCK>{code}
</CODEBLOCK>"""
@mcp.prompt()
def generate_tests_prompt(code: str, language: str = "python") -> str:
"""生成单元测试提示模板"""
return f"""你是一个测试工程师,负责为代码生成单元测试。
请为以下 {language} 代码生成全面的单元测试:
要求:
1. 使用 pytest 测试框架
2. 覆盖正常用例和边界用例
3. 测试函数命名清晰,表达测试意图
4. 包含必要的 setup/teardown(如需要)
<CODEBLOCK>{language}
{code}
</CODEBLOCK>
请生成完整的测试代码:"""
@mcp.prompt()
def explain_error_prompt(error_message: str, code: str = "") -> str:
"""代码错误解释提示模板"""
context = f"\n相关代码:\n<CODEBLOCK>{code}\n</CODEBLOCK>" if code else ""
return f"""你是一个耐心的编程导师,帮助解释代码错误。
错误信息:
{error_message}
{context}
请按以下格式解释:
1. 错误原因:简单清晰地解释为什么会出错
2. 解决方法:提供修复建议或正确做法
3. 预防建议:如何避免类似错误"""
@mcp.prompt()
def optimize_code_prompt(code: str, language: str = "python") -> str:
"""代码优化提示模板"""
return f"""你是一个性能优化专家,负责优化代码性能和可读性。
请优化以下 {language} 代码:
<CODEBLOCK>{language}
{code}
</CODEBLOCK>
分析维度:
1. 性能优化(时间/空间复杂度)
2. 代码可读性
3. 最佳实践
4. 潜在问题
请给出优化后的代码和优化说明:"""
4.7 启动服务器
java
mcp.run()
代码结构说明
| 部分 | 说明 |
|---|---|
HostFixMiddleware |
自定义中间件,修复 HTTP 模式下的 Host header 验证问题 |
mcp = FastMCP("HelloMCP") |
创建 MCP 服务器容器实例 |
@mcp.tool() 注册的函数 |
AI 可调用的工具函数 |
@mcp.resource() 注册的函数 |
AI 可读取的资源数据 |
@mcp.prompt() 注册的函数 |
AI 可获取的提示词模板 |
if __name__ == "__main__": |
启动入口,支持 stdio 和 HTTP 两种模式 |
启动mcp,mcp作为容器,其里面的tool、resource、prompt就可以用了,类似于fastApi的router变量和router变量对应的接口函数
五、客户端配置:mcp.json 详解
5.1 mcp.json 是什么
mcp.json 是客户端(如 Trae)的 MCP 配置文件,告诉客户端:
- 去哪里找 MCP 服务器
- 用什么方式连接
- 服务器叫什么名字
5.2 完整配置文件
{
"mcpServers": {
"helloMCP": {
"command": "python",
"args": ["C:\\path\\to\\server.py"],
"env": {},
"description": "MCP 学习示例服务器"
}
}
}
5.3 每个字段的含义
| 字段 | 含义 | 说明 |
|---|---|---|
mcpServers |
MCP 服务器列表 | 可以配置多个 MCP 服务器 |
helloMCP |
服务器名称 | 自定义名字,方便识别 |
command |
启动命令 | stdio 模式下用什么命令启动 |
args |
启动参数 | 传递给启动命令的参数(如 server.py 路径) |
env |
环境变量 | 启动时的环境变量(可留空) |
description |
描述信息 | 对这个 MCP 的简单说明 |
5.4 两种配置方式
方式一:stdio 模式(推荐,本地开发用)
{
"mcpServers": {
"helloMCP": {
"command": "python",
"args": ["/absolute/path/to/server.py"]
}
}
}
command: 用什么命令启动服务器(如python)args: 启动参数,指向 server.py 文件- 客户端自动管理:按需启动、用完关闭,不需要手动启动
方式二:HTTP 模式(远程服务用)
{
"mcpServers": {
"helloMCP-remote": {
"url": "http://example.com:9088/mcp"
}
}
}
url: 远程 MCP 服务的 HTTP 地址- 需要服务器提前启动 :
python server.py -t http -p 9088 - 客户端通过 HTTP/SSE 连接
5.5 路径问题
// 相对路径(项目级专用,不推荐)
"args": ["${workspaceFolder}/mcpProject/server.py"]
// → 切换项目后 workspaceFolder 变了 → 路径失效
// 绝对路径(推荐,跨项目可用)
"args": ["C:\\absolute\\path\\to\\server.py"]
// → 任何项目都能使用
5.6 两种配置层级
┌─────────────────────────────────────────────────┐
│ 项目级(项目根目录 .trae/mcp.json) │
│ - 只在当前项目生效 │
│ - 用 ${workspaceFolder} 或绝对路径 │
│ - 适合项目专属的 MCP │
├─────────────────────────────────────────────────┤
│ 全局级(用户设置) │
│ - 所有项目都生效 │
│ - 用绝对路径 │
│ - 适合通用的 MCP │
└─────────────────────────────────────────────────┘
5.7 配置后发生了什么?
1. 客户端读取 mcp.json
2. 发现有一个名为 "helloMCP" 的 MCP 服务器
3. 配置为 stdio 模式,command=python,args=[server.py 路径]
4. 当你第一次使用 MCP 时,客户端自动 fork 子进程
5. 通过 stdin/stdout 与 server.py 进行 JSON-RPC 通信
6. 服务器启动后,自动执行:
- tools/list → 返回所有工具
- resources/list → 返回所有资源
- prompts/list → 返回所有模板
7. 客户端拿到这些信息,AI 就知道有哪些能力可以用了
六、三大能力详解
6.1 Tools:让 AI 会做事
Tool 是 AI 可调用的函数 ,通过 @mcp.tool() 装饰器注册。
已实现的工具:
| 工具名 | 功能 | 参数 |
|---|---|---|
calculate |
计算数学表达式 | expression: str |
get_time |
获取当前时间 | 无 |
reverse_text |
文本反转 | text: str |
search_knowledge |
知识库关键词搜索 | keyword: str |
search_files |
文件名模式搜索 | directory, pattern |
get_directory_tree |
目录树结构 | 无 |
工作流程:
用户: "现在几点了?"
↓
AI 思考: 需要获取时间
↓
AI 读取工具列表:
┌──────────────────────────────────────┐
│ calculate → "计算数学表达式" │ ❌
│ get_time → "获取当前时间" │ ✅ 完美匹配
│ reverse_text → "反转文本" │ ❌
└──────────────────────────────────────┘
↓
AI 发送: tools/call → get_time
↓
MCP 执行 get_time() → "2026-10-30 16:02:23"
↓
AI 组织成自然语言回复用户
测试方法: 在对话框输入以下内容即可测试:
现在几点了?
(15 + 5) * 3 / 2
帮我搜一下 knowledge 目录下有哪些 .md 文件
注意事项: calculate 工具使用了 eval() 函数,存在安全隐患。生产环境应使用 ast.literal_eval() 或 simpleeval 库。
6.2 Resources:让 AI 能读数据
Resource 是 AI 可读取的数据源 ,通过 @mcp.resource(uri) 装饰器注册,AI 通过 URI(统一资源标识符)来读取内容。
已实现的资源:
| URI | 功能 | 关联文件 |
|---|---|---|
greeting://hello |
问候语 | 无 |
info://server |
服务器信息 | 无 |
knowledge://products |
产品知识库 | knowledge/products.md |
knowledge://policies |
政策文档 | knowledge/policies.md |
knowledge://faq |
常见问题 | knowledge/faq.md |
| URI 的含义: |
knowledge://products
↑ ↑
方案 资源标识符
(scheme) (identifier)
URI 是 MCP 协议中的资源标识符,与物理文件路径无关 ,只是 AI 和 MCP 协议之间的"资源门牌号"。
两个路径的区别:
KNOWLEDGE_DIR = Path(__file__).parent / "knowledge" # ① 物理路径(代码读文件用)
@mcp.resource("knowledge://products") # ② MCP URI(AI 识别用)
def get_product_knowledge() -> str:
file_path = KNOWLEDGE_DIR / "products.md" # ③ 物理路径(读文件用)
触发注意事项: Resources 是被动的数据源,AI 不一定会主动触发:
- ✅ 明确提示:"从产品知识库中介绍一下"
- ⚠️ 模糊提示:"介绍一下产品"(AI 可能用默认知识回答)
- ❌ 不提知识库:大概率不触发
最佳实践: 如果需要可靠的知识库检索,建议用 Tool 实现 (如 search_knowledge)
测试方法:
从产品知识库中介绍一下你们的产品
退款政策是什么?
知识库中关于 MCP 是什么的说明?
6.3 Prompts:让 AI 说好话
Prompt 是预定义的提示词模板 ,通过 @mcp.prompt() 装饰器注册,AI 可以获取模板来规范自己的输出格式。
已实现的模板:
| 模板名 | 用途 | 触发词示例 |
|---|---|---|
explain_code |
代码解释 | "解释这段代码" |
code_review |
代码审查 | "审查代码" |
generate_tests |
生成单元测试 | "写单元测试" |
explain_error |
错误解释 | "这个报错是什么意思" |
optimize_code |
代码优化 | "优化代码" |
translate_code |
代码转换 | "转成 Java" |
write_docs |
生成文档 | "写文档" |
quick_refactor |
快速重构 | "重构代码" |
security_review |
安全审查 | "检查安全" |
Prompt 的本质: 一个返回字符串的模板函数:
@mcp.prompt()
def explain_code_prompt(code: str, language: str = "python") -> str:
"""生成代码解释的提示模板"""
return f"""请解释以下 {language} 代码:
1. 主要功能是什么?
2. 关键逻辑和执行流程
3. 可能的用途和应用场景
<CODEBLOCK>{language}
{code}
</CODEBLOCK>"""
# 返回一段格式化的提示词文本,告诉 AI 应该怎么回答
三种能力的区别:
Tool: 输入 → 执行操作 → 返回结果
calculate("2+3") → "5"
Resource: URI → 读取数据 → 返回内容
knowledge://products → "产品介绍..."
Prompt: 输入参数 → 返回提示词文本
explain_code(code) → "请解释这段代码:..."
↑
返回的不是答案,而是告诉 AI "应该怎么回答"
触发注意事项: Prompt 触发概率较低,需要明确指定:
- ❌ "检查代码" → AI 不会主动找 Prompt
- ❌ "解释一下" → AI 可能直接回答
- ✅ "用 MCP 的 explain_code 模板解释" → 明确触发
测试方法:
用 MCP 的 explain_code 模板解释:
def fibonacci(n):
if n <= 1: return n
return fibonacci(n-1) + fibonacci(n-2)
调用 MCP 的 code_review 模板审查:
def calc(a, b): return a / b
七、传输层:stdio vs HTTP
7.1 两种模式对比
| 本地 stdio 模式 | 远程 HTTP 模式 | |
|---|---|---|
| 配置方式 | command + args |
url |
| 谁启动 MCP | 客户端自动启动 | 远程服务器提前启动 |
| 通信方式 | 进程管道(不走网络) | HTTP/SSE(走网络) |
| 稳定性 | ✅ 稳定(不受网络影响) | ❌ 可能断(受网络/服务器影响) |
| 适用场景 | 本地开发、单机使用 | 远程服务、多客户端共享 |
7.2 stdio 模式(默认,推荐)
{
"mcpServers": {
"helloMCP": {
"command": "python",
"args": ["/absolute/path/to/server.py"]
}
}
}
客户端 ──fork子进程──→ python server.py
↕ stdin/stdout(进程管道通信)
↕ JSON-RPC 协议
**核心优势**:本地不需要手动启动,客户端自动管理生命周期。
7.3 HTTP 模式
{
"mcpServers": {
"helloMCP-remote": {
"url": "http://example.com:9088/mcp"
}
}
}
远程服务器(提前启动好):
$ python server.py -t http -p 9088
客户端 ──HTTP/SSE──→ http://example.com:9088/mcp
核心注意:远程 MCP 必须提前启动,服务器停了就连不上。
7.4 为什么不需要手动启动?
stdio 模式下,客户端配置 mcp.json 后会自动管理 MCP 进程:
- ❌ 不需要手动启动
python server.py - ✅ 客户端按需自动 fork 子进程
- ✅ 客户端通过 stdin/stdout 双向通信
- ✅ 客户端不需要时自动终止进程
八、常见问题与解答
Q1: 手动关闭了 python server.py,客户端还会启动吗?
会!它们是两个独立进程:
- 你手动启动的进程:闲置在那里(没人给它发消息)
- 客户端启动的进程:真正在工作
手动启动那个可以关掉,不影响正常使用。
Q2: 换了项目路径,MCP 就失效了?
取决于配置方式:
${workspaceFolder}/...→ 会失效(绑定当前项目)- 绝对路径 → 不会失效(跨项目可用)
建议将 MCP 配置为全局配置,使用绝对路径。
Q3: 为什么 Resources 和 Prompts 没有自动触发?
AI 客户端的设计策略:
- ✅ 自动获取
tools/list→ 主动使用 Tools - ❌ 不主动获取
resources/list→ Resources 需明确提示 - ❌ 不主动获取
prompts/list→ Prompts 需明确指定
这是正常现象,不是代码问题。
Q4: 远程 MCP 为什么偶尔会断?
远程 HTTP 模式的不稳定因素:
- 网络问题(VPN/防火墙/超时)
- 服务器状态(重启/崩溃/连接数超限)
- SSE 长连接超时(中间层 Nginx 设置)
- 认证 Token 过期
本地 stdio 模式不会有这个问题,因为一切在进程内部通信。
Q5: eval() 安全吗?
不安全!eval() 可以执行任意 Python 代码:
eval("__import__('os').system('calc.exe')") # 会打开计算器!
生产环境应使用 ast.literal_eval() 或 simpleeval 库。
Q6: docstring 是必须的吗?
docstring对应的是工具的注释
对 Python 不是必须的,但对 MCP 极其重要:
- 有 docstring → AI 精准选择工具
- 没有 docstring → AI 只能靠函数名猜测
Q7: MCP 和 FastAPI 的关系?
两者的装饰器注册模式完全一致:
FastAPI: APIRouter + @router.get()/@router.post()
MCP: FastMCP + @mcp.tool()/@mcp.resource()/@mcp.prompt()
FastAPI 是"HTTP 能力容器",MCP 是"AI 能力容器"。
九、核心知识点总结
9.1 一句话概括
MCP = FastMCP 容器 + 三大能力(Tools/Resources/Prompts)+ 传输层(stdio/HTTP)
9.2 核心公式
MCP 服务器 = 装饰器注册 + JSON-RPC 协议 + 传输层
1. 装饰器注册:@mcp.tool() / @mcp.resource() / @mcp.prompt()
2. JSON-RPC 协议:tools/list, tools/call, resources/read, prompts/get
3. 传输层:stdio(本地)/ HTTP(远程)
### 9.3 关键实践经验
| 经验 | 说明 |
|---|---|
| docstring 必写 | AI 靠它选择工具,写得越清晰越好 |
| stdio 不需要手动启动 | 客户端自动管理进程生命周期 |
| 用绝对路径配置 | 跨项目可用,不受 workspaceFolder 限制 |
| 知识库检索用 Tool | search_knowledge 比 Resource 更可靠 |
| Prompts 需明确触发 | "用 MCP 的 xxx 模板" 比 "帮我审查" 更有效 |
9.4 三种能力的定位
┌──────────────────────────────────────────────────────┐
│ Tool: AI 的"手"(执行操作) │
│ → 触发难度低,AI 自动发现 │
├──────────────────────────────────────────────────────┤
│ Resource: AI 的"眼"(读取数据) │
│ → 触发难度中,需明确提示 │
├──────────────────────────────────────────────────────┤
│ Prompt: AI 的"嘴"(规范输出) │
│ → 触发难度高,需明确指定 │
└──────────────────────────────────────────────────────┘
9.5 学习路径建议
入门阶段(已完成)
↓
掌握 @mcp.tool() 的用法和 docstring 编写
↓
进阶阶段
↓
学习 HTTP 模式部署、多客户端共享
↓
深入阶段
↓
研究 FastMCP 源码、JSON-RPC 协议细节
↓
实战阶段
↓
开发实用 MCP(数据库操作、API 集成、企业知识库等)
```