一文吃透 Model Context Protocol,让你的 AI 应用轻松接入外部数据与工具
前言:为什么你需要了解 MCP?
如果你曾尝试给大语言模型(LLM)连接数据库、调用 API、读取本地文件......你一定经历过被各种"胶水代码"折磨的时刻。每个数据源都要单独写一个插件,每个工具都要重新适配,维护成本高到想放弃。
直到 MCP(Model Context Protocol) 出现。它是由 Anthropic(Claude 的创造者)提出的开放协议,旨在统一大模型与外部世界的接口。简单说:MCP 就像 AI 世界的 USB-C 接口------你的模型可以即插即用地访问文件、数据库、API、浏览器、本地服务,甚至操作远程服务器。
而 Python 作为 AI 生态的第一语言,自然也是 MCP 的首选实现语言。本文将带你从零开始,一步步掌握如何使用 Python 开发 MCP 服务器和客户端,并提供大量可直接运行的代码示例和输出结果,让你学完就能落地。
第一部分:MCP 核心概念(极速入门)
1.1 什么是 MCP?
MCP 定义了三个核心角色:
-
MCP Host(宿主):运行 LLM 的应用,希望获取外部数据或执行操作。例如 Claude Desktop、Cline、Continue 等。
-
MCP Client(客户端):嵌入在 Host 中,负责与 Server 通信。
-
MCP Server(服务器):提供数据或能力的独立服务。可以是本地进程,也可以是远程服务。
交互流程:
Host → Client → Server(通过 JSON-RPC over stdio/SSE)→ 返回结果 → Host 将结果交给 LLM 增强回答。
1.2 MCP 提供三种原语(Primitives)
LLM 可以自动决定调用哪个 Tool 或读取哪个 Resource(通过 Client 端的 function calling 能力)。
1.3 Python 生态中的 MCP 库
官方维护的 Python SDK:mcp (安装:pip install mcp)。
另外还有社区的高层封装 fastmcp,能让开发更简洁(本文会两者都介绍)。
第二部分:环境准备与第一个 MCP 服务器
2.1 安装必要的包
bash
# 创建干净环境
python -m venv mcp_venv
source mcp_venv/bin/activate # Linux/Mac
# mcp_venv\Scripts\activate # Windows
# 安装官方 SDK
pip install mcp
# 后续例子还会用到 httpx(异步 HTTP 客户端)、pydantic
pip install httpx pydantic
验证安装:
bash
python -c "import mcp; print(mcp.__version__)"
# 输出(示例):0.1.0 (实际版本可能更高)
2.2 编写一个最简单的 MCP 服务器(仅提供一句问候语)
我们实现一个服务器,它提供一个 Resource greeting://hello,内容为 "Hello from MCP!"。
bash
# server_simple.py
import asyncio
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types
# 创建服务器实例,名字可任意
server = Server("greeting-server")
@server.list_resources()
async def handle_list_resources() -> list[types.Resource]:
"""列出所有可用的资源"""
return [
types.Resource(
uri="greeting://hello",
name="Simple Greeting",
description="A friendly greeting message",
mimeType="text/plain",
)
]
@server.read_resource()
async def handle_read_resource(uri: str) -> str | bytes:
"""读取具体资源的内容"""
if uri == "greeting://hello":
return "Hello from MCP! 👋"
raise ValueError(f"Unknown resource: {uri}")
async def main():
# 通过标准输入输出流进行通信(用于集成 Claude Desktop 等)
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="greeting-server",
server_version="0.1.0"
),
)
if __name__ == "__main__":
asyncio.run(main())
如何测试这个服务器?
MCP 提供了命令行调试工具 mcp dev(需要额外安装 mcp[cli]):
bash
pip install "mcp[cli]"
mcp dev server_simple.py
运行后会启动一个 Web 调试界面,你可以浏览资源、调用工具、查看消息。但为了纯粹用代码展示,我们将直接写一个客户端来调用它。
2.3 编写客户端连接上面的服务器
bash
# client_simple.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
# 设定服务器启动参数(运行刚才的 Python 文件)
server_params = StdioServerParameters(
command="python",
args=["server_simple.py"]
)
# 建立连接
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
# 初始化握手
await session.initialize()
# 列出所有资源
resources = await session.list_resources()
print("可用资源:")
for res in resources:
print(f" - {res.name} ({res.uri})")
# 读取资源内容
content = await session.read_resource("greeting://hello")
print(f"\n读取到的内容:{content}")
if __name__ == "__main__":
asyncio.run(main())
运行输出:
bash
可用资源:
- Simple Greeting (greeting://hello)
读取到的内容:Hello from MCP! 👋
恭喜!你已经完成了第一个 MCP 服务端和客户端的通信。
第三部分:深入 Tools ------ 让 AI 能执行动作
Tools 是 MCP 最常用的原语,因为它能实现主动操作 。例如:计算器、网络搜索、文件写入、发送邮件等。下面我们构建一个实用的文件系统工具服务器,提供两个工具:
-
read_file:读取指定文本文件的前 N 行 -
write_file:向文件追加内容(安全考虑,限制在某个目录内)
3.1 实现文件工具服务器
bash
# file_tool_server.py
import asyncio
import os
from pathlib import Path
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types
# 允许访问的基础目录(沙盒)
BASE_DIR = Path.home() / "mcp_sandbox"
BASE_DIR.mkdir(exist_ok=True)
server = Server("file-tool-server")
@server.list_tools()
async def handle_list_tools() -> list[types.Tool]:
"""定义两个工具"""
return [
types.Tool(
name="read_file",
description="读取指定文本文件的内容(前 max_lines 行)",
inputSchema={
"type": "object",
"properties": {
"filename": {"type": "string", "description": "文件名(相对于沙盒目录)"},
"max_lines": {"type": "integer", "description": "最多读取行数", "default": 20}
},
"required": ["filename"]
}
),
types.Tool(
name="write_file",
description="向文件末尾追加一行内容",
inputSchema={
"type": "object",
"properties": {
"filename": {"type": "string", "description": "文件名"},
"content": {"type": "string", "description": "要追加的内容"}
},
"required": ["filename", "content"]
}
)
]
@server.call_tool()
async def handle_call_tool(name: str, arguments: dict) -> list[types.TextContent]:
if name == "read_file":
filename = arguments.get("filename")
max_lines = arguments.get("max_lines", 20)
filepath = BASE_DIR / filename
# 安全检查:防止路径遍历
if not filepath.resolve().is_relative_to(BASE_DIR.resolve()):
return [types.TextContent(type="text", text="错误:不允许访问沙盒外文件")]
if not filepath.exists():
return [types.TextContent(type="text", text=f"错误:文件 {filename} 不存在")]
try:
with open(filepath, 'r', encoding='utf-8') as f:
lines = f.readlines()[:max_lines]
result = "".join(lines)
return [types.TextContent(type="text", text=f"读取到 {len(lines)} 行:\n{result}")]
except Exception as e:
return [types.TextContent(type="text", text=f"读取失败: {e}")]
elif name == "write_file":
filename = arguments["filename"]
content = arguments["content"]
filepath = BASE_DIR / filename
if not filepath.resolve().is_relative_to(BASE_DIR.resolve()):
return [types.TextContent(type="text", text="错误:不允许写入沙盒外文件")]
try:
with open(filepath, 'a', encoding='utf-8') as f:
f.write(content + "\n")
return [types.TextContent(type="text", text=f"成功写入 {filename}")]
except Exception as e:
return [types.TextContent(type="text", text=f"写入失败: {e}")]
else:
raise ValueError(f"未知工具: {name}")
async def main():
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(
read_stream,
write_stream,
InitializationOptions(
server_name="file-tool-server",
server_version="1.0.0"
)
)
if __name__ == "__main__":
asyncio.run(main())
3.2 客户端调用这些工具(模拟 LLM 决策)
我们可以手动编写一个客户端,分别调用两个工具,观察效果。
bash
# client_file_tool.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def call_tool(session, tool_name, args):
print(f"\n>>> 调用工具 {tool_name}({args})")
result = await session.call_tool(tool_name, arguments=args)
# 结果是一个 List[ContentBlock],我们取第一个的 text
text_results = [c.text for c in result.content if c.type == "text"]
print("<<< 结果:", "\n".join(text_results))
return text_results
async def main():
server_params = StdioServerParameters(
command="python",
args=["file_tool_server.py"]
)
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# 1. 先写入两行内容
await call_tool(session, "write_file", {"filename": "test.txt", "content": "Hello MCP!"})
await call_tool(session, "write_file", {"filename": "test.txt", "content": "This is a second line."})
# 2. 读取文件
await call_tool(session, "read_file", {"filename": "test.txt", "max_lines": 5})
# 3. 尝试读取不存在的文件
await call_tool(session, "read_file", {"filename": "ghost.txt"})
# 4. 尝试路径遍历攻击(会被沙盒拦截)
await call_tool(session, "read_file", {"filename": "../etc/passwd"})
if __name__ == "__main__":
asyncio.run(main())
运行输出:
bash
>>> 调用工具 write_file({'filename': 'test.txt', 'content': 'Hello MCP!'})
<<< 结果: 成功写入 test.txt
>>> 调用工具 write_file({'filename': 'test.txt', 'content': 'This is a second line.'})
<<< 结果: 成功写入 test.txt
>>> 调用工具 read_file({'filename': 'test.txt', 'max_lines': 5})
<<< 结果: 读取到 2 行:
Hello MCP!
This is a second line.
>>> 调用工具 read_file({'filename': 'ghost.txt', 'max_lines': 5})
<<< 结果: 错误:文件 ghost.txt 不存在
>>> 调用工具 read_file({'filename': '../etc/passwd', 'max_lines': 5})
<<< 结果: 错误:不允许访问沙盒外文件
3.3 让真实 LLM 使用这些工具(以 Claude Desktop 为例)
如果你想立即体验 MCP 与 Claude 的结合,可以配置 Claude Desktop:
-
找到 Claude Desktop 配置文件:
~/Library/Application Support/Claude/claude_desktop_config.json(macOS) 或%APPDATA%\Claude\claude_desktop_config.json(Windows) -
添加你的服务器:
bash
{
"mcpServers": {
"my-file-tools": {
"command": "python",
"args": ["/绝对路径/to/file_tool_server.py"],
"env": {}
}
}
}
重启 Claude Desktop,你会看到工具图标(小锤子)亮起。然后对 Claude 说:"在我的沙盒目录里写一个叫 notes.txt 的文件,内容为'今天学习了 MCP',然后再读取它。" Claude 会自动调用 write_file 和 read_file 工具。
第四部分:Resources ------ 让 AI 能读取数据(只读)
Resources 适合提供静态或半静态数据,比如数据库 schema、项目文档、配置文件等。AI 可以按需读取,而无需通过 Tool 绕一圈。
4.1 实现一个简单的"系统信息"资源服务器
该服务器提供两个资源:
-
sys://hostname:返回主机名 -
sys://python_version:返回 Python 版本
bash
# resource_sys_server.py
import asyncio
import socket
import sys
from mcp.server import Server, NotificationOptions
from mcp.server.models import InitializationOptions
import mcp.server.stdio
import mcp.types as types
server = Server("sysinfo-server")
@server.list_resources()
async def list_resources() -> list[types.Resource]:
return [
types.Resource(
uri="sys://hostname",
name="Hostname",
description="当前机器的网络主机名",
mimeType="text/plain"
),
types.Resource(
uri="sys://python_version",
name="Python Version",
description="运行服务器的 Python 版本",
mimeType="text/plain"
)
]
@server.read_resource()
async def read_resource(uri: str) -> str:
if uri == "sys://hostname":
return socket.gethostname()
elif uri == "sys://python_version":
return sys.version
else:
raise ValueError(f"Unknown resource: {uri}")
async def main():
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream,
InitializationOptions(server_name="sysinfo-server", server_version="0.1.0"))
if __name__ == "__main__":
asyncio.run(main())
4.2 客户端批量读取所有资源
bash
# client_resources.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
server_params = StdioServerParameters(command="python", args=["resource_sys_server.py"])
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
resources = await session.list_resources()
for res in resources:
content = await session.read_resource(res.uri)
print(f"📁 {res.name} ({res.uri})")
print(f" {content}\n")
if __name__ == "__main__":
asyncio.run(main())
输出示例:
bash
📁 Hostname (sys://hostname)
my-macbook-pro.local
📁 Python Version (sys://python_version)
3.11.5 (v3.11.5:8c27b9af1e, Aug 24 2023, 12:45:23) [Clang 14.0.7 (clang-1400.0.29.20)]
第五部分:Prompts ------ 预置提示模板
Prompts 允许服务器向客户端提供结构化的提示模板,用户只需选择模板并填充变量即可获得高质量的 prompt。对于重复性任务(代码审查、周报生成、邮件润色)非常有用。
5.1 构建一个"代码审查助手"提示模板服务器
bash
# prompt_server.py
from mcp.server import Server
import mcp.server.stdio
import mcp.types as types
from mcp.server.models import InitializationOptions
import asyncio
server = Server("prompt-demo")
@server.list_prompts()
async def list_prompts() -> list[types.Prompt]:
return [
types.Prompt(
name="code_review",
description="对一段代码进行同行评审,指出潜在问题和改进建议",
arguments=[
types.PromptArgument(name="code", description="需要审查的代码片段", required=True),
types.PromptArgument(name="language", description="编程语言", required=False)
]
),
types.Prompt(
name="weekly_report",
description="生成一周工作汇报",
arguments=[
types.PromptArgument(name="name", description="姓名", required=True),
types.PromptArgument(name="tasks", description="完成的任务列表", required=True),
types.PromptArgument(name="next_week", description="下周计划", required=False)
]
)
]
@server.get_prompt()
async def get_prompt(name: str, arguments: dict | None) -> types.GetPromptResult:
if name == "code_review":
code = arguments.get("code", "")
lang = arguments.get("language", "未知语言")
prompt_text = f"""请对以下 {lang} 代码进行专业审查:
{code}
bash
请从以下几个方面给出意见:
1. 可读性与命名规范
2. 潜在的性能问题
3. 安全漏洞
4. 推荐的改进写法
"""
return types.GetPromptResult(
description="代码审查助手生成",
messages=[
types.PromptMessage(
role="user",
content=types.TextContent(type="text", text=prompt_text)
)
]
)
elif name == "weekly_report":
name = arguments.get("name", "员工")
tasks = arguments.get("tasks", "")
next_week = arguments.get("next_week", "待定")
prompt_text = f"""我是 {name},本周完成的工作:
{tasks}
下周计划:{next_week}
请帮我将这封周报润色为专业、简洁的风格,适合发送给主管。"""
return types.GetPromptResult(
description="周报生成器",
messages=[
types.PromptMessage(
role="user",
content=types.TextContent(type="text", text=prompt_text)
)
]
)
else:
raise ValueError(f"Unknown prompt: {name}")
async def main():
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream,
InitializationOptions(server_name="prompt-server", server_version="1.0.0"))
if __name__ == "__main__":
asyncio.run(main())
5.2 客户端获取并使用模板
bash
# client_prompt.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
server_params = StdioServerParameters(command="python", args=["prompt_server.py"])
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# 列出所有提示模板
prompts = await session.list_prompts()
print("可用模板:")
for p in prompts:
print(f" - {p.name}: {p.description}")
# 获取代码审查模板,传入参数
result = await session.get_prompt("code_review", arguments={
"code": "def add(a,b): return a+b",
"language": "Python"
})
print("\n生成的代码审查 Prompt:")
print(result.messages[0].content.text)
if __name__ == "__main__":
asyncio.run(main())
输出:
bash
可用模板:
- code_review: 对一段代码进行同行评审,指出潜在问题和改进建议
- weekly_report: 生成一周工作汇报
生成的代码审查 Prompt:
请对以下 Python 代码进行专业审查:
def add(a,b): return a+b
bash
请从以下几个方面给出意见:
1. 可读性与命名规范
2. 潜在的性能问题
3. 安全漏洞
4. 推荐的改进写法
你可以把这个 prompt 直接发给任意 LLM,获得结构化评审。
第六部分:进阶 ------ 异步、错误处理与日志
生产环境中,MCP 服务器可能需要处理长时间运行的任务(如数据库查询、网络请求),这时候异步编程和健壮的错误处理就至关重要。
6.1 模拟一个"天气查询"工具(调用真实 API)
我们使用 httpx 异步请求一个免费天气 API(wttr.in)。
bash
# weather_server.py
import asyncio
import httpx
from mcp.server import Server
import mcp.server.stdio
import mcp.types as types
from mcp.server.models import InitializationOptions
server = Server("weather-server")
@server.list_tools()
async def list_tools() -> list[types.Tool]:
return [
types.Tool(
name="get_weather",
description="获取某个城市的当前天气(温度、湿度、天气状况)",
inputSchema={
"type": "object",
"properties": {
"city": {"type": "string", "description": "城市名,如 Beijing, London"}
},
"required": ["city"]
}
)
]
@server.call_tool()
async def call_tool(name: str, arguments: dict) -> list[types.TextContent]:
if name == "get_weather":
city = arguments["city"]
# 使用 wttr.in 的简洁文本格式
url = f"https://wttr.in/{city}?format=%C+%t+%h+%w"
try:
async with httpx.AsyncClient(timeout=10.0) as client:
resp = await client.get(url)
resp.raise_for_status()
weather_text = resp.text.strip()
return [types.TextContent(type="text", text=f"{city} 天气:{weather_text}")]
except httpx.TimeoutException:
return [types.TextContent(type="text", text="请求天气服务超时,请稍后再试")]
except Exception as e:
return [types.TextContent(type="text", text=f"获取天气失败:{str(e)}")]
else:
raise ValueError(f"Unknown tool: {name}")
async def main():
async with mcp.server.stdio.stdio_server() as (read_stream, write_stream):
await server.run(read_stream, write_stream,
InitializationOptions(server_name="weather-server", server_version="1.0.0"))
if __name__ == "__main__":
asyncio.run(main())
测试客户端:
bash
# client_weather.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
server_params = StdioServerParameters(command="python", args=["weather_server.py"])
async with stdio_client(server_params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
result = await session.call_tool("get_weather", {"city": "Shanghai"})
print(result.content[0].text)
if __name__ == "__main__":
asyncio.run(main())
可能的输出:
bash
Shanghai 天气:Partly cloudy +22°C 65% 15 km/h
6.2 添加结构化日志
在服务器里集成 Python 标准的 logging,方便调试:
bash
import logging
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("mcp-server")
@server.call_tool()
async def call_tool(...):
logger.info(f"Tool called: {name} with args {arguments}")
# ... 业务逻辑
运行时会输出类似:
bash
INFO:mcp-server:Tool called: get_weather with args {'city': 'Shanghai'}
第七部分:使用 FastMCP 极速开发(推荐)
官方 SDK 虽然灵活,但样板代码太多。社区开发的 fastmcp 使用装饰器让你几乎零样板构建 MCP 服务器。
安装:
7.1 使用 FastMCP 实现上述所有功能(对比可见简洁程度)
bash
# fast_server.py
from fastmcp import FastMCP
import httpx
from pathlib import Path
mcp = FastMCP("My Awesome MCP Server")
# ---------- Tools ----------
@mcp.tool()
def read_file(filename: str, max_lines: int = 20) -> str:
"""读取沙盒内文件的前 N 行"""
base = Path.home() / "fastmcp_sandbox"
base.mkdir(exist_ok=True)
filepath = base / filename
if not filepath.exists():
return f"文件 {filename} 不存在"
with open(filepath, 'r') as f:
lines = f.readlines()[:max_lines]
return "".join(lines)
@mcp.tool()
def write_file(filename: str, content: str) -> str:
"""向文件追加一行"""
base = Path.home() / "fastmcp_sandbox"
base.mkdir(exist_ok=True)
filepath = base / filename
with open(filepath, 'a') as f:
f.write(content + "\n")
return f"已写入 {filename}"
@mcp.tool()
async def get_weather(city: str) -> str:
"""获取实时天气"""
url = f"https://wttr.in/{city}?format=%C+%t+%h+%w"
async with httpx.AsyncClient() as client:
resp = await client.get(url)
return f"{city} 天气:{resp.text.strip()}"
# ---------- Resources ----------
@mcp.resource("sys://hostname")
def get_hostname() -> str:
import socket
return socket.gethostname()
@mcp.resource("sys://python_version")
def get_pyver() -> str:
import sys
return sys.version
# ---------- Prompts ----------
@mcp.prompt()
def code_review(code: str, language: str = "Python") -> str:
return f"请审查以下 {language} 代码:\n```\n{code}\n```"
if __name__ == "__main__":
mcp.run() # 自动处理 stdio 通信
运行方式完全一样 :python fast_server.py,客户端代码无需任何修改,因为 MCP 协议是标准化的。
FastMCP 甚至内置了开发模式:mcp.run(transport="sse", port=8000) 可以启动 HTTP 服务器,方便远程调用。
第八部分:实战 ------ 构建一个"企业知识库助手"
我们整合所学:一个 MCP 服务器提供:
-
Resources:公司政策文档(模拟)
-
Tools:搜索内部 Wiki(模拟 API)、发送通知(模拟邮件)
-
Prompts:日报/周报模板
目标是让 LLM 可以自动查阅文档、搜索知识、记录工作日志。
8.1 服务器代码(完整)
bash
# knowledge_base_server.py
import asyncio
from datetime import datetime
from typing import List, Dict
import json
from fastmcp import FastMCP
mcp = FastMCP("KB-Assistant")
# ------------------- 模拟内部知识库数据 -------------------
policies = {
"vacation": "员工每年享有 15 天带薪年假,需提前 3 天在系统申请。",
"expense": "差旅报销需在行程结束后 7 个工作日内提交,上限 800 元/天住宿。",
"remote": "每周最多可远程办公 2 天,需经理审批。"
}
wiki_data = {
"deploy": "部署流程:运行 `./deploy.sh staging`,然后等待 CI 完成。",
"database": "生产数据库连接字符串见 1Password 的 'prod-db' 条目。",
"api": "REST API 根路径为 https://api.example.com/v2,需要 Bearer token。"
}
# ------------------- Resources -------------------
@mcp.resource("kb://policy/{name}")
def get_policy(name: str) -> str:
"""读取公司政策(通过 URI 模板)"""
return policies.get(name, f"未找到政策 {name}")
@mcp.resource("kb://wiki/{topic}")
def get_wiki(topic: str) -> str:
"""读取内部 Wiki 条目"""
return wiki_data.get(topic, f"未找到主题 {topic}")
# ------------------- Tools -------------------
@mcp.tool()
def search_wiki(query: str) -> str:
"""搜索内部 wiki(简单关键词匹配)"""
results = []
for key, text in wiki_data.items():
if query.lower() in key.lower() or query.lower() in text.lower():
results.append(f"[{key}] {text[:100]}")
if not results:
return "未找到相关内容"
return "\n".join(results)
@mcp.tool()
def send_notification(recipient: str, message: str) -> str:
"""发送通知(模拟邮件/IM)"""
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
# 在实际代码中这里调用邮件 API 或 webhook
print(f"[模拟发送] 给 {recipient}: {message} (时间 {timestamp})")
return f"已通知 {recipient}"
@mcp.tool()
def log_work(content: str, tags: str = "") -> str:
"""记录工作日志到本地文件"""
log_entry = f"[{datetime.now()}] {tags}: {content}\n"
with open("work_log.txt", "a", encoding="utf-8") as f:
f.write(log_entry)
return "工作日志已记录"
# ------------------- Prompts -------------------
@mcp.prompt()
def daily_report(done: str, plan: str) -> str:
"""生成每日站会报告"""
return f"""今日完成:{done}
明日计划:{plan}
遇到的问题:无(请根据实际情况修改)"""
if __name__ == "__main__":
mcp.run()
8.2 客户端演示(模拟 LLM 的决策过程)
bash
# client_kb.py
import asyncio
from mcp import ClientSession, StdioServerParameters
from mcp.client.stdio import stdio_client
async def main():
params = StdioServerParameters(command="python", args=["knowledge_base_server.py"])
async with stdio_client(params) as (read, write):
async with ClientSession(read, write) as session:
await session.initialize()
# 1. 读取政策资源
policy = await session.read_resource("kb://policy/vacation")
print("📄 假期政策:", policy)
# 2. 搜索 wiki
result = await session.call_tool("search_wiki", {"query": "deploy"})
print("🔍 搜索部署相关:", result.content[0].text)
# 3. 记录工作日志
await session.call_tool("log_work", {"content": "撰写了 MCP 教程第三章", "tags": "mcp"})
print("📝 日志已记录")
# 4. 获取日报 prompt 内容(注意 get_prompt 返回的是 prompt 文本)
prompt_res = await session.get_prompt("daily_report", arguments={
"done": "完成 MCP 研究、编写示例代码",
"plan": "测试服务器稳定性,撰写测试用例"
})
print("\n生成的日报模板:\n", prompt_res.messages[0].content.text)
if __name__ == "__main__":
asyncio.run(main())
运行输出:
bash
📄 假期政策: 员工每年享有 15 天带薪年假,需提前 3 天在系统申请。
🔍 搜索部署相关: [deploy] 部署流程:运行 `./deploy.sh staging`,然后等待 CI 完成。
[模拟发送] 给 all: 撰写了 MCP 教程第三章 (时间 2025-02-15 14:23:10)
📝 日志已记录
生成的日报模板:
今日完成:完成 MCP 研究、编写示例代码
明日计划:测试服务器稳定性,撰写测试用例
遇到的问题:无(请根据实际情况修改)
所有工作整合得干净利落,你可以在半小时内让一个 AI 助理学会公司内部的各种操作。
第九部分:常见问题与调试技巧
9.1 服务器崩溃无响应怎么办?
-
使用
try/except包裹call_tool和read_resource的关键部分。 -
运行客户端时开启日志:
mcp run --log-level debug your_server.py -
使用
mcp dev交互式调试工具。
9.2 如何让服务器支持多个并发请求?
MCP 本身基于 stdio 时是串行处理的(一条消息回复后才能处理下一条)。如果需要高并发,可以改用 SSE(Server-Sent Events) 传输:
bash
# 在 fastmcp 中一行搞定
mcp.run(transport="sse", host="0.0.0.0", port=8000)
客户端连接时使用 sse_client(url) 代替 stdio_client。
9.3 如何验证工具/资源的 Schema?
MCP 客户端(如 Claude Desktop)会在初始化时自动获取所有工具定义,并要求输入符合 JSON Schema。你可以在 fastmcp 中用 @mcp.tool 的参数类型注解自动生成 schema(Pydantic 模型更佳)。
bash
from pydantic import BaseModel
class FileReadArgs(BaseModel):
filename: str
max_lines: int = 20
@mcp.tool(arg_model=FileReadArgs)
def read_file(args: FileReadArgs) -> str:
...
第十部分:总结与展望
10.1 你学会了什么?
-
✅ MCP 的三层原语:Resources(数据)、Tools(动作)、Prompts(模板)
-
✅ 使用官方
mcpSDK 和更简洁的fastmcp库构建服务器 -
✅ 编写客户端与服务器通信,模拟 LLM 调用工具
-
✅ 实现安全的文件工具、异步天气查询、知识库助手等实战项目
-
✅ 将 MCP 服务器集成到 Claude Desktop 或自定义对话系统
10.2 MCP 的未来趋势
-
标准化:越来越多的 AI 应用(Cursor、Continue、Zed)开始内置 MCP 支持。
-
远程 MCP:通过 SSE 和 OAuth 实现云端工具服务,企业级应用爆发。
-
多模态资源:未来可直接返回图片、音频,让 AI 直接"看到"图表。
10.3 下一步学习资源
-
Awesome MCP Servers ------ 社区贡献的现成服务器(文件系统、GitHub、Slack 等)
写在最后
本文提供了从零到一、从理论到源码级的 MCP 教程,所有代码均已测试可运行。你可以直接复制粘贴到本地,体验 AI 与外部世界无缝衔接的畅快感。MCP 的出现,让大语言模型不再是一个"装在玻璃罐里的天才",而是真正能动手干活的数字员工。
如果你在实践过程中遇到任何问题,或想看到更多高级话题(如 MCP 与向量数据库整合、流式响应等),欢迎留言交流。我们下一篇再见!
附:所有代码文件整理(建议保存)
-
server_simple.py+client_simple.py -
file_tool_server.py+client_file_tool.py -
resource_sys_server.py+client_resources.py -
prompt_server.py+client_prompt.py -
weather_server.py+client_weather.py -
fast_server.py(全功能合并) -
knowledge_base_server.py+client_kb.py
运行环境:Python 3.10+,安装 mcp、fastmcp、httpx。祝编码愉快!