🚀 MCP基础完全上手指南:让Claude像开挂一样调用外部工具

如果能让Claude直接读取你的GitHub仓库、操作Notion文档、查询数据库,那该有多爽?但现实是,每次接入一个新服务,你都要写一堆工具定义、测试API、处理异常...简直是体力活。

MCP(Model Context Protocol)就是来解决这个痛点的。 它把集成外部服务的脏活累活全部打包到标准化的服务端,你只需要连上去就能用。就像你不需要自己实现HTTP协议一样,你也不需要从零开始写GitHub的100个API工具。

这篇文章会带你从零开始理解MCP的工作原理,并亲手搭建一个完整的文档管理系统。看完你就知道怎么让Claude成为你的得力助手。

一、MCP到底解决了什么问题?

传统方式的痛点

假设你要做一个聊天界面,用户可以问Claude:"我的GitHub上有哪些开着的PR?"

++没有MCP时你需要做什么:++

  1. 研究GitHub API文档,找到获取PR的接口
  2. 为每个API端点写工具定义(JSON Schema)
  3. 实现工具函数,处理认证、请求、错误
  4. 测试每个工具,确保Claude能正确调用
  5. 维护这一大堆代码

GitHub的功能极其庞大------仓库、PR、Issue、Projects...你得写几十上百个工具定义。更要命的是,GitHub API一更新,你的代码就得跟着改。

MCP的解决方案

MCP把工具定义和执行逻辑都搬到了专门的MCP Server上。 你的应用只需要连接这个Server,就能获得所有工具,不用自己写一行集成代码。

arduino 复制代码
传统方式:你的应用 → 写100个工具 → 调用GitHub API
MCP方式:你的应用 → MCP Client → MCP Server(已经实现好的100个工具) → GitHub API

关键优势:

  • 工具定义已经写好 --- 别人帮你测试过的高质量工具
  • 维护成本转移 --- API变更由MCP Server作者处理
  • 标准化接口 --- 所有MCP Server用统一的协议通信

二、MCP的核心架构:三个角色,一套流程

架构概览

arduino 复制代码
用户提问
  ↓
你的应用(MCP Client)
  ↓
MCP Server(工具集合)
  ↓
外部服务(GitHub/Notion/数据库...)

MCP Client 是你的应用代码,负责和MCP Server通信。

MCP Server 是独立的服务进程,封装了某个领域的工具(比如GitHub专属Server、AWS专属Server)。

通信协议 支持多种传输方式:标准输入输出(stdio)、HTTP、WebSocket等。最常见的是本地运行时用stdio。

一次完整的对话流程

以"我有哪些仓库?"这个问题为例:

  1. 用户发起提问 → 你的应用收到请求
  2. 获取可用工具 → 应用通过Client向Server发送ListToolsRequest
  3. Server返回工具列表 → 比如get_reposlist_issues
  4. 发送给Claude → 应用把问题+工具列表一起发给Claude API
  5. Claude决策 → "我需要调用get_repos工具"
  6. 执行工具 → 应用通过Client发送CallToolRequest给Server
  7. Server调用GitHub API → 获取真实数据
  8. 结果回流 → Server返回CallToolResult,应用再发给Claude
  9. Claude生成回答 → "你有3个仓库:repo1、repo2、repo3"
  10. 返回用户 → 应用展示最终答案

这个流程看起来步骤很多,但每个组件职责清晰 。MCP Client帮你抽象掉了和Server通信的复杂性,你只需要调用简单的list_tools()call_tool()方法。

三、动手实战:搭建一个文档管理Server

理论讲完,现在写代码。我们要实现一个内存文档管理系统,支持读取和编辑文档。

3.1 初始化MCP Server

使用官方Python SDK,创建Server只需一行:

python 复制代码
from mcp.server.fastmcp import FastMCP

mcp = FastMCP("DocumentMCP", log_level="ERROR")

准备一些测试文档:

python 复制代码
docs = {
    "deposition.md": "This deposition covers the testimony of Angela Smith, P.E.",
    "report.pdf": "The report details the state of a 20m condenser tower.",
    "financials.docx": "These financials outline the project's budget",
    "outlook.pdf": "This document presents the projected future performance",
    "plan.md": "The plan outlines the steps for implementation.",
    "spec.txt": "Technical requirements for the equipment"
}

3.2 定义第一个工具:读取文档

装饰器定义工具,SDK会自动生成Claude能理解的JSON Schema:

python 复制代码
from pydantic import Field

@mcp.tool(
    name="read_doc_contents",
    description="Read the contents of a document and return it as a string."
)
def read_document(
    doc_id: str = Field(description="Id of the document to read")
):
    if doc_id not in docs:
        raise ValueError(f"Doc with id {doc_id} not found")
    
    return docs[doc_id]

关键点:

  • @mcp.tool装饰器自动注册工具
  • Field提供参数描述,帮助Claude理解每个参数的用途
  • 类型提示(str)提供自动校验
  • 返回值直接返回字符串,SDK处理序列化

3.3 定义第二个工具:编辑文档

实现简单的查找替换功能:

python 复制代码
@mcp.tool(
    name="edit_document",
    description="Edit a document by replacing a string with a new string."
)
def edit_document(
    doc_id: str = Field(description="Id of the document to edit"),
    old_str: str = Field(description="Text to replace. Must match exactly."),
    new_str: str = Field(description="New text to insert.")
):
    if doc_id not in docs:
        raise ValueError(f"Doc with id {doc_id} not found")
    
    docs[doc_id] = docs[doc_id].replace(old_str, new_str)
    return f"Successfully replaced '{old_str}' with '{new_str}' in {doc_id}"

对比传统方式: 如果不用MCP,你需要手写这样的JSON Schema:

python 复制代码
{
  "name": "edit_document",
  "description": "Edit a document...",
  "input_schema": {
    "type": "object",
    "properties": {
      "doc_id": {"type": "string", "description": "..."},
      "old_str": {"type": "string", "description": "..."},
      "new_str": {"type": "string", "description": "..."}
    },
    "required": ["doc_id", "old_str", "new_str"]
  }
}

用SDK后,这些全部自动生成。

四、调试神器:MCP Inspector

写完代码后怎么测试?MCP SDK内置了一个浏览器调试工具

启动Inspector

bash 复制代码
mcp dev mcp_server.py

访问http://127.0.0.1:6274,你会看到一个Web界面。

测试工具

  1. 点击Connect按钮启动Server
  2. 进入Tools 标签,点击List Tools
  3. 选择read_doc_contents工具
  4. 输入doc_idreport.pdf
  5. 点击Run Tool
  6. 查看返回结果:"The report details the state of a 20m condenser tower."

测试工具链

可以连续测试多个工具:

  1. 先用edit_document修改文档
  2. 再用read_doc_contents验证修改是否生效

Inspector会保持Server状态,所以修改会持久化(在内存中)。

开发建议: 在写复杂工具时,先在Inspector里测试边界条件(比如传入不存在的doc_id),确保错误处理正确。

五、实现MCP Client:连接Server的桥梁

Server写完了,现在需要一个Client来使用这些工具。

5.1 Client架构

Client包含两部分:

  • ClientSession --- SDK提供的底层连接对象
  • 自定义Client类 --- 封装Session,简化使用

为什么要封装?因为Session需要手动管理资源(连接的打开和关闭),封装后可以用Python的async with自动处理。

5.2 实现核心方法

列出所有工具

python 复制代码
async def list_tools(self) -> list[types.Tool]:
    result = await self.session().list_tools()
    return result.tools

这个方法返回Server提供的所有工具定义,你会把这些工具发给Claude。

执行工具

python 复制代码
async def call_tool(
    self, tool_name: str, tool_input: dict
) -> types.CallToolResult | None:
    return await self.session().call_tool(tool_name, tool_input)

当Claude决定调用某个工具时,你用这个方法转发请求给Server。

5.3 测试Client

Client文件通常包含一个测试入口:

python 复制代码
if __name__ == "__main__":
    import asyncio
    
    async def test():
        async with MCPClient() as client:
            tools = await client.list_tools()
            print(f"Found {len(tools)} tools:")
            for tool in tools:
                print(f"  - {tool.name}: {tool.description}")
    
    asyncio.run(test())

运行:

bash 复制代码
uv run mcp_client.py

你应该看到:

bash 复制代码
Found 2 tools:
  - read_doc_contents: Read the contents of a document...
  - edit_document: Edit a document by replacing a string...

六、Resources:给应用提供数据

Tools是给Claude用的,Resources是给你的应用代码用的。典型场景是UI自动补全或注入上下文。

使用场景

假设你要实现一个@文档名的提及功能:

  • 输入@时,自动补全显示所有可用文档
  • 选择文档后,自动把文档内容注入到发给Claude的Prompt里

这需要两个操作:

  1. 获取文档列表 --- 用于自动补全
  2. 获取单个文档内容 --- 用于注入上下文

定义Resources

静态Resource:列出所有文档

python 复制代码
@mcp.resource(
    "docs://documents",
    mime_type="application/json"
)
def list_docs() -> list[str]:
    return list(docs.keys())

URI是固定的docs://documents,返回文档ID列表。

模板Resource:获取单个文档

python 复制代码
@mcp.resource(
    "docs://documents/{doc_id}",
    mime_type="text/plain"
)
def fetch_doc(doc_id: str) -> str:
    if doc_id not in docs:
        raise ValueError(f"Doc with id {doc_id} not found")
    return docs[doc_id]

URI包含参数{doc_id},SDK会自动解析并传给函数。

Client端读取Resource

python 复制代码
import json
from pydantic import AnyUrl

async def read_resource(self, uri: str) -> Any:
    result = await self.session().read_resource(AnyUrl(uri))
    resource = result.contents[0]
    
    if isinstance(resource, types.TextResourceContents):
        if resource.mimeType == "application/json":
            return json.loads(resource.text)
    
    return resource.text

工作流程:

  1. 应用代码调用client.read_resource("docs://documents")
  2. Client发送ReadResourceRequest给Server
  3. Server执行list_docs()函数
  4. 返回["deposition.md", "report.pdf", ...]
  5. 应用用这个列表填充自动补全UI

七、Prompts:预定义的工作流

Prompts是给用户用的高质量模板。用户触发后,直接发送精心设计的指令给Claude。

为什么需要Prompts?

用户当然可以自己输入"把report.pdf转成markdown格式",但效果可能不理想。作为MCP Server作者,你可以提供一个经过反复测试的优化Prompt,处理各种边缘情况。

这样用户只需要点一个按钮或输入/format report.pdf,就能获得最佳结果。

实现Format命令

python 复制代码
from mcp.server.fastmcp import base

@mcp.prompt(
    name="format",
    description="Rewrites the contents of a document in Markdown format."
)
def format_document(
    doc_id: str = Field(description="Id of the document to format")
) -> list[base.Message]:
    prompt = f"""Your goal is to reformat a document to be written with markdown syntax.

The id of the document you need to reformat is:
<document_id>
{doc_id}
</document_id>

Add headers, bullet points, tables, etc as necessary. Use the 'edit_document' tool to modify the document. After reformatting, return a summary of changes made.
"""
    
    return [base.UserMessage(prompt)]

关键设计:

  • Prompt清晰说明任务目标
  • 用XML标签包裹变量,避免混淆
  • 指示Claude使用哪些工具完成任务
  • 返回的是消息列表,支持多轮对话(可以包含UserMessage和AssistantMessage)

Client端调用Prompt

python 复制代码
async def list_prompts(self) -> list[types.Prompt]:
    result = await self.session().list_prompts()
    return result.prompts

async def get_prompt(self, prompt_name: str, args: dict[str, str]):
    result = await self.session().get_prompt(prompt_name, args)
    return result.messages

用户输入/format plan.md时:

  1. 应用识别/format命令
  2. 调用client.get_prompt("format", {"doc_id": "plan.md"})
  3. 获得填充好变量的完整Prompt
  4. 把Prompt发给Claude API
  5. Claude自动调用read_doc_contentsedit_document完成任务

八、三大原语的使用决策

MCP提供了三种机制,分别服务于不同的控制主体:

Tools:模型控制

谁决定? Claude自主决定何时调用

适用场景:

  • 给Claude提供计算能力(执行代码、数学运算)
  • 让Claude访问实时数据(查询数据库、调用API)
  • 赋予Claude操作能力(创建Issue、发送邮件)

示例: 用户问"3的平方根是多少?",Claude自动调用execute_javascript工具计算。

Resources:应用控制

谁决定? 你的应用代码决定何时获取

适用场景:

  • UI自动补全(获取候选列表)
  • 注入上下文(把文档内容加到Prompt里)
  • 预加载数据(应用启动时获取配置)

示例: 用户输入@,应用调用Resource获取文档列表,显示在下拉菜单。

Prompts:用户控制

谁决定? 用户通过按钮、命令触发

适用场景:

  • 预定义工作流(一键生成报告、批量处理)
  • 优化过的指令(复杂任务的最佳Prompt)
  • 快捷操作(斜杠命令、工具栏按钮)

示例: 用户点击"格式化文档"按钮,应用获取format Prompt并发送给Claude。

九、完整流程串联:从提问到响应

现在把所有部分组合起来,看一个完整的交互流程。

场景1:读取文档

用户输入: "report.pdf里写了什么?"

  1. 应用调用 client.list_tools() → 获取 [read_doc_contents, edit_document]

  2. 应用发送请求到Claude API:

json 复制代码
   {
     "messages": [{"role": "user", "content": "report.pdf里写了什么?"}],
     "tools": [{...read_doc_contents定义...}, {...edit_document定义...}]
   }
  1. Claude返回:
json 复制代码
   {
     "stop_reason": "tool_use",
     "content": [{
       "type": "tool_use",
       "name": "read_doc_contents",
       "input": {"doc_id": "report.pdf"}
     }]
   }
  1. 应用调用 client.call_tool("read_doc_contents", {"doc_id": "report.pdf"}) → Server返回 "The report details the state of a 20m condenser tower."

  2. 应用把工具结果发回Claude:

json 复制代码
    {
     "messages": [...之前的消息..., {
       "role": "user",
       "content": [{
         "type": "tool_result",
         "tool_use_id": "...",
         "content": "The report details..."
       }]
     }]
   }
  1. Claude返回最终答案: "报告详细说明了一个20米冷凝塔的状态。"

场景2:使用Resource注入上下文

用户输入: @report.pdf "总结这个文档"

  1. 用户输入@后,应用调用 client.read_resource("docs://documents") → 获取 ["deposition.md", "report.pdf", "financials.docx", ...]

  2. 显示自动补全,用户选择 report.pdf

  3. 应用调用 client.read_resource("docs://documents/report.pdf") → 获取文档内容

  4. 应用构造Prompt并发送给Claude: { "messages": [{ "role": "user", "content": "这是report.pdf的内容:\n\nThe report details...\n\n请总结这个文档。" }] }

  5. Claude直接根据提供的内容生成总结,无需调用工具

场景3:触发Prompt工作流

用户输入: /format plan.md

  1. 应用识别/命令,调用 client.list_prompts() → 获取 [{name: "format", description: "Rewrites..."}]

  2. 用户选择format命令,输入doc_id为plan.md

  3. 应用调用 client.get_prompt("format", {"doc_id": "plan.md"}) → 获取填充好的Prompt消息

  4. 应用把Prompt作为初始消息发送给Claude:

json 复制代码
   {
     "messages": [{
       "role": "user",
       "content": "Your goal is to reformat...<document_id>plan.md</document_id>..."
     }],
     "tools": [...所有可用工具...]
   }
  1. Claude自动执行:
    • 调用 read_doc_contents 获取plan.md内容
    • 分析内容,决定如何格式化
    • 调用 edit_document 多次,添加markdown语法
    • 返回格式化完成的总结

十、关键技术细节

1. 错误处理

Tools和Resources中抛出的异常会被自动捕获并返回给Client:

json 复制代码
@mcp.tool(name="read_doc")
def read_document(doc_id: str):
    if doc_id not in docs:
        raise ValueError(f"Doc with id {doc_id} not found")
    return docs[doc_id]

Claude会收到错误信息,并可能重试或换个策略。

2. 类型安全

使用Pydantic的Field可以提供运行时校验:

json 复制代码
from pydantic import Field, validator

@mcp.tool(name="edit_doc")
def edit_document(
    doc_id: str = Field(description="...", min_length=1),
    old_str: str = Field(description="...", min_length=1),
    new_str: str = Field(description="...")
):
    # min_length会自动校验,不满足条件会拒绝请求
    ...

3. 异步支持

所有Client方法都是异步的,需要在async函数中调用:

json 复制代码
async def main():
    async with MCPClient() as client:
        tools = await client.list_tools()
        result = await client.call_tool("read_doc", {"doc_id": "report.pdf"})

4. 资源清理

使用async with确保Session正确关闭:

json 复制代码
class MCPClient:
    async def __aenter__(self):
        # 创建并启动Session
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        # 关闭Session,释放资源
        pass

十一、常见问题

Q1: MCP和直接调用API有什么区别?

直接调用API: 你需要自己写工具定义、处理认证、管理请求。

使用MCP: 工具定义已经写好,认证由Server处理,你只需要连接并使用。

类比: 就像你不会自己实现HTTP协议去访问网站,而是用浏览器或requests库。MCP就是外部服务集成的"标准库"。

Q2: MCP Server谁来写?

通常由服务提供商官方社区开发者编写。比如AWS可能会发布官方MCP Server,包含所有AWS服务的工具。也有很多开源MCP Server可以直接用。

Q3: 一个应用可以连接多个MCP Server吗?

可以。你的Client可以同时连接GitHub Server、Notion Server、AWS Server等,Claude可以跨Server调用工具。

Q4: MCP支持哪些编程语言?

官方SDK支持Python和TypeScript。其他语言可以实现MCP协议(基于JSON-RPC 2.0)。

Q5: 性能如何?

MCP基于本地进程通信(stdio)或网络协议,延迟很低。工具调用的耗时主要取决于实际API(比如GitHub API的响应时间)。


十二、总结:MCP的价值

MCP的核心价值在于标准化和复用

  1. 降低集成成本 --- 不用为每个服务从零开始写工具
  2. 提高工具质量 --- 使用经过测试的成熟实现
  3. 简化维护负担 --- API变更由Server作者处理
  4. 加速开发速度 --- 专注业务逻辑,而非集成细节

如果你要构建AI应用,并且需要让Claude访问外部服务,MCP是目前最优雅的解决方案。它不会限制你的灵活性,反而通过标准化让你的应用更易扩展。

相关资源:

相关推荐
撒币使我快乐1 小时前
Generate Cursor Rules指令消失后的替代方案
ai编程·cursor
有意义2 小时前
Vibe Coding:人机共生时代的开发革命 —— 从概念到 Chrome 扩展实战
前端·ai编程·vibecoding
用户4099322502124 小时前
Vue浅响应式如何解决深层响应式的性能问题?适用场景有哪些?
前端·ai编程·trae
后端小肥肠4 小时前
别再找提示词了!n8n+Coze+Sora2:扔个链接,AI自动反推,爆款视频直存本地!
aigc·agent·coze
xhxxx8 小时前
《从代码规范到智能体开发:构建面向未来的工程思维》
agent·ai编程
AI袋鼠帝8 小时前
Cursor可以删了?美团悄悄上线了个更香的平替~
aigc·ai编程
CodeLiving8 小时前
MCP学习三——MCP相关概念
人工智能·mcp
AI袋鼠帝8 小时前
豆包也开始抢程序员饭碗了,一个月只要9块9。。
aigc·ai编程
程序员X小鹿18 小时前
限时免费!字节 TRAE SOLO 正式上线,无需邀请码!新增 TRAE Coder(附实测体验)
ai编程·trae