MCP 协议实战:给你的 AI 装上真正的手和眼
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9 MCP 协议实战:给你的 AI 装上真正的手和眼
AI 最大的局限之一:它只能用你喂给它的信息。
你问它今天的新闻,它不知道。你让它查你的数据库,它做不到。你希望它操作你的文件系统,它只能靠猜。
MCP(Model Context Protocol)就是为了解决这个问题而生的——给 AI 装上手和眼,让它能真正触及外部世界。
MCP 是什么
MCP 是 Anthropic 提出的开放协议,定义了 AI 模型与外部工具/数据源之间的通信标准。
简单理解:它是一套规范,让任何人都能写一个”工具服务器”,AI 客户端(如 Claude Code)可以发现这些服务器并调用里面的工具。
Claude Code ←→ MCP 协议 ←→ MCP 服务器(你写的)
├── 读取文件
├── 查询数据库
├── 调用 API
└── 执行命令
MCP 之前,每个 AI 工具都要自己定义工具调用格式,彼此不兼容。MCP 之后,写一次服务器,所有支持 MCP 的客户端都能用。
核心概念:三种能力
MCP 服务器可以暴露三种能力:
Tools(工具)
AI 可以主动调用的函数。类似 function calling,但标准化了。
{
"name": "search_posts",
"description": "搜索博客文章",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" }
}
}
}
Resources(资源)
AI 可以读取的数据。文件、数据库记录、API 响应都可以包装成 Resource。
{
"uri": "file:///data/posts.json",
"mimeType": "application/json",
"description": "所有博客文章的元数据"
}
Prompts(提示模板)
预置的对话模板,用户可以通过斜杠命令触发。
这三者可以单独使用,也可以混合——一个 MCP 服务器通常同时提供 Tools 和 Resources。
动手:搭一个最小 MCP 服务器
用 Python 官方 SDK(mcp),实现一个能查询本地 markdown 文章的工具服务器:
安装
pip install mcp
代码
# blog_mcp_server.py
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import json
import os
from pathlib import Path
app = Server("blog-tools")
POSTS_DIR = Path("./src/content/blog")
@app.list_tools()
async def list_tools():
return [
Tool(
name="search_posts",
description="在博客文章中搜索关键词,返回匹配的文章标题和摘要",
inputSchema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "搜索关键词"
}
},
"required": ["query"]
}
),
Tool(
name="get_post",
description="读取指定 slug 的文章全文",
inputSchema={
"type": "object",
"properties": {
"slug": {
"type": "string",
"description": "文章的 slug(文件名不含 .md)"
}
},
"required": ["slug"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name == "search_posts":
query = arguments["query"].lower()
results = []
for f in POSTS_DIR.glob("*.md"):
content = f.read_text(encoding="utf-8")
if query in content.lower():
# 提取标题(frontmatter 第一行 title:)
title = next((l.split(":", 1)[1].strip().strip("'\"")
for l in content.splitlines()
if l.startswith("title:")), f.stem)
results.append({"slug": f.stem, "title": title})
return [TextContent(type="text", text=json.dumps(results, ensure_ascii=False))]
elif name == "get_post":
slug = arguments["slug"]
post_file = POSTS_DIR / f"{slug}.md"
if not post_file.exists():
return [TextContent(type="text", text=f"文章 {slug} 不存在")]
return [TextContent(type="text", text=post_file.read_text(encoding="utf-8"))]
if __name__ == "__main__":
import asyncio
asyncio.run(stdio_server(app))
注册到 Claude Code
在 ~/.claude/claude_desktop_config.json(或 Claude Code 的 MCP 配置文件)里添加:
{
"mcpServers": {
"blog-tools": {
"command": "python",
"args": ["/path/to/blog_mcp_server.py"]
}
}
}
重启 Claude Code,现在你可以直接说:
“搜索所有关于 Agent 的文章” “读一下 one-person-ai-company 这篇文章”
Claude 会自动调用你的工具,而不是凭印象回答。
真实用例:我在用的 MCP 工具
context7 — 实时文档查询
这是我用得最多的 MCP 工具。当 Claude Code 需要某个库的最新 API 时,context7 直接从源头拉文档,不靠训练数据里的旧知识。
# Claude.md 里配置
使用 context7 获取最新文档,避免用过时的 API
效果:Astro 5 的新语法、Vercel 的最新配置,全部实时准确。
文件系统工具
Claude Code 内置的文件读写工具本质上就是 MCP 工具——这也是为什么它能直接编辑你的代码而不只是生成代码片段。
自定义飞书工具
我给系统里的 Agent 写了飞书 MCP 工具,让 Claude 能直接发消息到飞书群、读取最近的对话记录。
核心工具:
send_feishu_message(chat_id, text)— 发消息get_recent_messages(chat_id, count)— 读最近 N 条create_task(title, assignee)— 创建飞书任务
这让”AI 回复飞书消息”变成了可靠的工作流,而不是脆弱的字符串拼接。
设计好 MCP 工具的原则
用了一段时间之后,几点心得:
1. 工具描述要像写给人看的
AI 通过 description 字段决定什么时候用这个工具。描述不清楚,工具就会被误用或不被使用。
❌ "description": "查询数据"
✅ "description": "按关键词全文搜索博客文章,返回匹配文章的 slug 和标题列表"
2. 一个工具做一件事
不要写”万能工具”:do_everything(action, params)。
每个工具的参数应该类型明确、数量少、意图单一。
3. 返回结构化数据,不要返回散文
AI 能更可靠地处理 JSON 数组,而不是”找到 3 篇文章,分别是……”这种自然语言描述。
4. 错误信息要有意义
# ❌
return [TextContent(type="text", text="Error")]
# ✅
return [TextContent(type="text", text=json.dumps({
"error": "post_not_found",
"slug": slug,
"available_slugs": [f.stem for f in POSTS_DIR.glob("*.md")][:5]
}))]
当工具出错时,好的错误信息能让 AI 自动修正,而不是卡住。
MCP vs 直接 function calling
有人会问:直接用 OpenAI 的 function calling 不就够了,为什么要 MCP?
| 维度 | Function Calling | MCP |
|---|---|---|
| 标准化 | 厂商各自定义 | 统一协议 |
| 复用性 | 绑定特定模型/SDK | 任意支持 MCP 的客户端都能用 |
| 服务发现 | 需要在代码里硬编码 | 客户端自动发现服务器能力 |
| 资源支持 | 仅 Tools | Tools + Resources + Prompts |
| 生态 | 各厂商各自为政 | 快速增长的统一生态 |
MCP 的核心价值是可移植性:今天写的工具服务器,未来换了 AI 客户端照样能用。
下一步
MCP 生态正在快速增长。如果你用 Claude Code,现在就可以开始:
- 找几个高质量的社区 MCP 服务器(mcp.so 有索引)
- 为你最常用的内部工具写一个 MCP 服务器
- 把常用的 MCP 工具注册进 Claude Code 的配置
当 AI 能真正触及你的数据和工具,那种”终于有个靠谱助手”的感觉才会真正到来。
系列文章:Claude Code 实战 · 相关阅读:Claude Code 的记忆架构
What MCP Is
MCP stands for Model Context Protocol. It’s an open protocol published by Anthropic that defines a standard way for AI models to interact with external tools, data sources, and services.
The core problem it solves: AI models are isolated by default. They can reason about text, but they can’t read files, query databases, call APIs, or interact with anything outside their context window — unless you build an explicit bridge.
MCP is that bridge, standardized.
Before MCP, every tool integration was custom. You’d write function definitions in whatever format the model expected, handle the execution yourself, and pass results back. This worked, but each integration was its own project.
With MCP, tool integrations follow a common protocol. A server you build for one MCP-compatible client works with any MCP-compatible client. The integration is reusable and composable.
Three Capabilities
MCP exposes three types of capabilities:
Tools
Functions the model can call. The model receives a description of the tool, decides when to call it, sends a request, and receives a result. Tools are the most commonly used capability — they’re how you give the model “hands.”
Examples: search the web, read a file, send a Feishu message, run a SQL query, get the current time.
Resources
Data sources the model can read. Unlike tools, resources are passive — they expose content that the model can pull when needed. Think of them as a structured way to make external data available in context.
Examples: a project’s documentation, a database schema, a configuration file, a list of available endpoints.
Prompts
Reusable prompt templates that can be invoked by name. Less commonly used than Tools or Resources, but useful for standardizing complex prompt structures across sessions.
A Minimal MCP Server in Python
Here’s a working MCP server that exposes a single tool: searching blog posts by keyword.
from mcp.server import Server
from mcp.server.stdio import stdio_server
from mcp.types import Tool, TextContent
import json
import os
import re
app = Server("blog-search")
# Load blog posts at startup
BLOG_DIR = "/path/to/your/content/blog"
def load_posts():
posts = []
for filename in os.listdir(BLOG_DIR):
if filename.endswith(".md"):
filepath = os.path.join(BLOG_DIR, filename)
with open(filepath, "r") as f:
content = f.read()
posts.append({"filename": filename, "content": content})
return posts
POSTS = load_posts()
@app.list_tools()
async def list_tools():
return [
Tool(
name="search_blog",
description="Search blog posts by keyword. Returns matching post filenames and relevant excerpts.",
inputSchema={
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The keyword or phrase to search for"
}
},
"required": ["query"]
}
)
]
@app.call_tool()
async def call_tool(name: str, arguments: dict):
if name != "search_blog":
raise ValueError(f"Unknown tool: {name}")
query = arguments["query"].lower()
results = []
for post in POSTS:
if query in post["content"].lower():
# Extract a short excerpt around the match
idx = post["content"].lower().find(query)
start = max(0, idx - 100)
end = min(len(post["content"]), idx + 200)
excerpt = post["content"][start:end].strip()
results.append({
"file": post["filename"],
"excerpt": f"...{excerpt}..."
})
if not results:
return [TextContent(type="text", text="No matching posts found.")]
output = json.dumps(results, indent=2, ensure_ascii=False)
return [TextContent(type="text", text=output)]
async def main():
async with stdio_server() as streams:
await app.run(streams[0], streams[1], app.create_initialization_options())
if __name__ == "__main__":
import asyncio
asyncio.run(main())
This server communicates over stdio. Claude Desktop (or any MCP-compatible client) starts the server as a subprocess and communicates with it via stdin/stdout.
Registering in claude_desktop_config.json
Once your server is written, register it in Claude Desktop’s configuration:
{
"mcpServers": {
"blog-search": {
"command": "python",
"args": ["/path/to/your/blog_search_server.py"],
"env": {}
}
}
}
The config file is at:
- macOS:
~/Library/Application Support/Claude/claude_desktop_config.json - Windows:
%APPDATA%\Claude\claude_desktop_config.json
Restart Claude Desktop after editing. The tools will appear in the model’s available toolset for that session.
For development, I run the server with extra logging enabled and watch the output in a separate terminal. Debugging MCP servers is much easier when you can see exactly what’s being sent and received.
Tools I Actually Use
context7
Fetches up-to-date documentation for libraries. When I’m working with a framework I haven’t used recently, instead of hoping the model’s training data is current, I call context7 to pull the actual docs. It’s the single most useful MCP tool I’ve added.
filesystem tools
Read, write, and list files outside the default working directory. Useful for agents that need to reach across project boundaries — for example, a coordinator that needs to read from a sibling service’s directory.
Custom Feishu tools
Tools I built myself for sending messages, reading inbox, and querying group membership in Feishu (Lark). These are what the Liaison agent uses to bridge AI reasoning with the messaging platform. The MCP interface means I can invoke them from Claude Desktop during development and from the agent’s runtime in production, with the same tool definitions.
Principles for Designing Good MCP Tools
One tool, one clear job. Don’t build a do_everything tool that takes a mode parameter. Build search_posts, create_post, and update_post as separate tools. The model reasons better about focused tools.
Descriptions are load-bearing. The tool description is what the model reads to decide whether to call the tool. Write it like documentation for a developer who doesn’t know your system — precise about what the tool does, what it returns, and what it won’t do.
Return structured data, not prose. JSON or structured text is easier for the model to parse and reason about than a narrative response. Save prose for human-facing output.
Fail loudly. Return clear error messages when something goes wrong. “File not found at /path/to/file” is infinitely more useful than an empty result or a generic error string. The model can adapt its behavior based on informative errors; it can’t do much with silence.
Keep side effects explicit. If a tool modifies state — writes a file, sends a message, updates a record — say so clearly in the description. The model needs to know the difference between read-only and write tools to plan correctly.
MCP vs Function Calling
| MCP | Function Calling | |
|---|---|---|
| Protocol | Open standard | Provider-specific |
| Transport | stdio or HTTP/SSE | API request/response |
| Server lifecycle | Persistent subprocess | Stateless per-call |
| Tool discovery | Dynamic (list_tools) | Static (defined in API call) |
| Client compatibility | Any MCP client | Specific provider’s SDK |
| Development overhead | Higher (server setup) | Lower (just define schema) |
| Reusability | High (server is portable) | Low (tied to one integration) |
Function calling is faster to prototype. MCP is better for tools you’ll use repeatedly across different contexts and clients.
In practice, I use function calling for one-off integrations and MCP for anything that’s become a permanent part of the workflow.
Next Steps
If you want to go further:
- MCP specification: modelcontextprotocol.io — the full protocol docs
- Python SDK:
pip install mcp— the official Anthropic SDK for building servers - Inspector: the MCP inspector tool lets you test a server interactively without spinning up a full client
- Claude Desktop as a test harness: once a server is registered, you can test it by asking Claude to use the tool in a normal conversation
The pattern I’d recommend: pick one thing you repeatedly wish Claude could access — a local database, an internal API, a set of project files — and build the smallest possible MCP server that exposes it. Get that working. Then extend it.
The value compounds. Each tool you add makes the model more capable in that context, and the MCP standard means each tool is reusable across future projects.
相关文章
Claude Code Hooks:让 AI 编程有迹可循、可审计、可回滚
Claude Code 的 Hooks 系统让你能在 AI 执行操作的前后注入自定义逻辑——自动备份、风险拦截、操作日志、通知推送。这是从"信任 AI"到"验证 AI"的关键一步。
Claude Code 的记忆架构:如何让 AI 真正记住你的项目
大多数人用 Claude Code 的方式是错的——每次对话都从零开始,反复解释背景。本文分享我在真实多 Agent 系统中摸索出的五层记忆架构,让 AI 持续积累项目知识。
Claude Code 15 个实用技巧:从够用到真正好用
大多数人只用到了 Claude Code 20% 的能力。这 15 个技巧覆盖从上下文管理、工作流加速到防坑实践——每一条都来自真实使用中的摸索。
这篇文章对你有帮助吗?
分享这篇文章
引用此文
讨论
这篇文章让你感觉
喜欢这篇文章?
订阅 RSS,第一时间收到新文章推送
私人笔记
仅保存在本地浏览器讨论
评论加载中...