☰
MCP 一文搞懂:从 Host、Client 到 Server,FastMCP 手把手跑通
2026/10/8 17:52:17 网站建设 项目流程

1. 从一次 Agent 工具接入的崩溃说起

如果你正在做 Agent 或者 AI 应用,大概率经历过这样的场景:一开始只让模型调一个天气接口,注册个 Function Calling 就完事了。后来需求像滚雪球一样加进来——查 MySQL、搜 Elasticsearch、操作 GitHub、读本地文件、调内部订单系统。这时候你会发现代码里开始出现一堆if model == "openai" ... elif model == "anthropic" ...和if tool == "mysql" ... elif tool == "github" ...的分支判断,模型越多、工具越多,集成复杂度呈乘法增长。

MCP(Model Context Protocol)就是来解决这个问题的。它给 AI 应用和外部工具之间定义了一套统一协议,你可以把它理解成 AI 世界里的 USB-C:Agent 不再直连各种 SDK,而是通过 MCP Client 连到 MCP Server,Server 后面接数据库、浏览器、GitHub 还是公司内部 API,Agent 完全不用关心。同样,Server 也不关心前面是 GPT、Claude 还是 Gemini。

这篇文章面向初次接触 MCP 的开发者,用 Host、Client、Server 三个角色把协议全貌拆开,然后以 FastMCP 为例,手把手跑通一条最小可运行链路:写一个同时包含 Tool、Resource、Prompt 的 Server,配好 Host 侧的mcp_config.json,用 Client 发一次真实请求,最后把常见的报错逐条排掉。全程本地可复现,不需要任何远程服务。

我试过把 MCP 当成"又一个 Function Calling 封装"来理解,结果卡了很久。真正想通的关键是:Function Calling 是模型怎么表达"我要调用工具",MCP 是 Host 怎么标准化地找到并调用这个工具。前者靠近 LLM,后者靠近工具生态,两者是上下游关系,不是替代关系。

2. Host、Client、Server 三个角色到底谁管什么

很多人第一次看 MCP 架构,最容易混淆的就是 Host 和 Client 的区别。实际上 MCP 的核心可以拆成三个角色,职责边界非常清晰。

2.1 Host:真正的"大脑"和总调度中心

Host 是承载 LLM、用户会话、Agent Loop 和 MCP Client 的应用程序。一个 AI IDE、桌面助手,或者你自己写的 Agent,都可以成为 Host。它负责接收用户 Query、保存聊天上下文、调用 LLM、把 MCP Server 暴露的 Tool 转换成模型能理解的 Tool Schema、判断模型返回的是普通文本还是工具调用、调用对应的 MCP Client、把 Tool Result 再交还给 LLM,同时还要管权限、确认弹窗、安全策略和多个 Server 的生命周期。

所以千万别把 Host 理解成一个简单转发器。真正的 Agent Loop 发生在 Host:用户问题进来,Host 交给 LLM,LLM 决定是否调用工具,如果调用就走 MCP Client → MCP Server → Tool Result,再回到 LLM 生成最终答案。这个循环里 Host 是唯一的调度者。

2.2 Client:Host 与某个 Server 之间的协议适配层

Client 的职责更底层。它负责建立与 MCP Server 的连接、处理传输层、发送 MCP 请求、接收 Response、做 JSON-RPC 编解码、做 Tool/Resource/Prompt 能力发现,以及调用tools/call、resources/read、prompts/get,还要管理超时和连接关闭。

一个 Host 通常不是只有一个 MCP Client,而是一个 Server 对应一个逻辑 Client Connection。比如 Host 下面挂着 filesystem client、mysql client、github client,各自连各自的 Server。这种一对一的映射关系是理解 MCP 连接模型的关键。

2.3 Server:真正提供能力的一侧

MCP Server 是把已有系统能力包装成 MCP 标准接口的适配器。它可以连接数据库、REST API、RPC、文件系统、浏览器、Shell、企业知识库、Git、Kubernetes、云平台、内部微服务。Server 不需要知道用户是谁,也不必知道前面具体用哪个 LLM,它只需要遵守协议:你问我有哪些工具,我返回tools/list;你调用某个工具,我处理tools/call;你读取资源,我处理resources/read。

2.4 三种原语的职责划分

Server 最值得先掌握的是三种核心原语,FastMCP 里对应@mcp.tool、@mcp.resource、@mcp.prompt。它们的职责划分如下:

原语谁主要控制用来干什么
ToolModel执行动作,比如查天气、创建订单、发邮件
ResourceApplication提供上下文数据,比如读配置、读文件、读数据库 schema
PromptUser提供可复用提示模板,比如代码审查、写测试

Tool 是 Action,Resource 是读取,Prompt 是模板,这三个概念一定不要混。Tool 由模型决定调用,Resource 由应用读取并放进上下文,Prompt 更接近用户主动选择的模板。

3. 用 FastMCP 写一个最小可运行 Server

理论看得再多不如自己写一次。这一节我们用 FastMCP 写一个同时包含 Tool、Resource、Prompt 的 Server,然后配好 Host 侧的mcp_config.json,把整条链路跑起来。

3.1 环境准备

建议用 Python 3.10+ 配合 uv。创建项目:

mkdir mcp-demo cd mcp-demo uv init uv add fastmcp

如果不用 uv,直接pip install fastmcp也可以。项目结构很简单,一个server.py加一个pyproject.toml就够了。

3.2 完整 Server 代码

新建server.py,把三种原语都写进去:

from fastmcp import FastMCP mcp = FastMCP("DemoServer") @mcp.tool def add(a: int, b: int) -> int: """计算两个整数之和""" return a + b @mcp.resource("config://app") def get_app_config() -> str: """读取应用配置""" return '{"app_name":"MCP Demo","env":"dev"}' @mcp.resource("users://{user_id}") def get_user(user_id: str) -> str: """根据用户 ID 获取用户信息""" return f"user_id={user_id}, name=user_{user_id}" @mcp.prompt def code_review(code: str) -> str: """生成代码审查提示词""" return f"请检查以下代码的 Bug、安全和性能问题:\n\n{code}" if __name__ == "__main__": mcp.run()

@mcp.tool会把普通 Python 函数注册成 MCP Tool,FastMCP 根据函数名、类型注解、Docstring 和参数默认值自动生成 Tool Schema。@mcp.resource("users://{user_id}")是一个 Resource Template,Client 通过resources/templates/list发现它,然后读取users://10001这样的具体 URI。@mcp.prompt不执行外部动作,只生成一段参数化的 Prompt。

3.3 Host 侧配置:mcp_config.json

Server 写完以后,Host 怎么知道有哪些 MCP Server?这就是 Host Configuration 要解决的问题。需要强调:mcp_config.json并不是 MCP Core Protocol 强制规定的唯一格式,不同 Host 的文件名和字段可能不同,但{"mcpServers": {}}这个结构已经成为非常常见的工程约定。

最基础的 Python 启动方式:

{ "mcpServers": { "demo": { "command": "python", "args": ["/absolute/path/mcp-demo/server.py"] } } }

实际项目里更推荐用 uv,因为一个 MCP Server 通常有自己独立的 Python 版本、依赖和启动命令,uv 能把这些环境问题封装起来:

{ "mcpServers": { "demo": { "command": "uv", "args": [ "run", "--directory", "/absolute/path/mcp-demo", "python", "server.py" ], "env": { "LOG_LEVEL": "INFO" } } } }

这里有个经常踩的坑:不要想当然地认为 stdio 子进程一定继承 Host 的全部环境变量。stdio 子进程使用受控的环境变量集合,需要的敏感变量应明确通过env传入。比如 Server 要访问外部天气 API,就得在配置里写"WEATHER_API_KEY": "${WEATHER_API_KEY}",Server 里用os.environ["WEATHER_API_KEY"]读取。

3.4 三件套对照:Base URL、Key、Model ID

如果你打算把 MCP Server 接到远程模型服务上,配置里必须写全三件套:Base URL、API Key、Model ID。以 TaoToken 为例,Base URL 用https://taotoken.net/api,Key 在控制台的 API Keys 页面生成,Model ID 按你实际要用的模型填。这三者缺一不可,少任何一个都会在请求阶段报错。很多新手只填了 Key 忘了 Base URL,结果请求打到默认地址上,报 401 或者连接失败,排查半天才发现是配置漏项。

4. 验证请求:从 tools/list 到 tools/call 的完整链路

Server 和配置都就绪了,接下来要验证它真的能用。开发阶段不建议手敲 JSON-RPC,直接用 FastMCP Client 最省事。

4.1 用 Client 列出工具并调用

新建client_demo.py:

import asyncio from fastmcp import Client async def main(): client = Client("server.py") async with client: tools = await client.list_tools() print("tools:", tools) resources = await client.list_resources() print("resources:", resources) prompts = await client.list_prompts() print("prompts:", prompts) result = await client.call_tool("add", {"a": 100, "b": 200}) print("result:", result) asyncio.run(main())

运行uv run python client_demo.py,预期能看到add工具、config://app资源、code_review提示词,以及工具调用结果300。FastMCP Client 会自动处理 Server 连接和 MCP 协议细节,业务代码只需要调list_tools()、list_resources()、list_prompts()和call_tool()。

4.2 底层 JSON-RPC 长什么样

如果你想理解协议本身,可以看看 Client 背后实际发的是什么。能力发现阶段,Client 发:

{ "jsonrpc": "2.0", "id": 2, "method": "tools/list" }

Server 返回:

{ "jsonrpc": "2.0", "id": 2, "result": { "tools": [ { "name": "add", "description": "计算两个整数之和", "inputSchema": { "type": "object", "properties": { "a": {"type": "integer"}, "b": {"type": "integer"} }, "required": ["a", "b"] } } ] } }

调用阶段,Client 发:

{ "jsonrpc": "2.0", "id": 3, "method": "tools/call", "params": { "name": "add", "arguments": {"a": 1024, "b": 2048} } }

Server 执行函数后返回结果。注意这里有个关键点:模型层返回的 Tool Call 还不是 MCP JSON-RPC,Host 拿到模型的{"name": "add", "arguments": {"a": 1024, "b": 2048}}之后,找到这个工具属于哪个 Server,再通过 MCP Client 发出上面的tools/call。这个翻译动作发生在 Host/Agent 与 MCP Client 这一层。

4.3 经典工作流与 2026 新规范的区别

你在大量项目源码里会看到这样的流程:initialize→notifications/initialized→tools/list→resources/list→resources/templates/list→prompts/list→tools/call。这是经典的 MCP Session 工作流,initialize用于协议版本协商、Client Capability 告知和身份信息交换,notifications/initialized是通知(没有 id,不需要 Response),表示初始化完成。

但从 2026-07-28 规范开始,MCP 进一步无状态化:删除了initialize和notifications/initialized握手,每次请求自己携带protocolVersion、clientCapabilities、clientInfo等元数据,并增加server/discover用于能力发现。为什么这么改?因为传统"建立连接 → initialize → 保存 Session State → 后续请求依赖 Session"的模式对 Serverless、多实例、Load Balancer、Kubernetes 横向扩展并不友好。无状态以后每个请求自描述,Server 更容易扩展。所以你现在看到旧项目用 initialize、新规范用 per-request metadata,两种实现同时存在,完全正常。

5. 常见报错逐条排查

跑通链路的过程中,最容易卡在几个典型报错上。这一节把真实遇到的错误和排查动作列出来。

5.1 401 Unauthorized

这个报错通常出现在远程模型服务调用阶段。原因一般是 API Key 没填、填错,或者 Base URL 和 Key 不匹配。排查动作:先确认mcp_config.json或环境变量里的 Key 是否正确,再确认 Base URL 是不是https://taotoken.net/api,最后确认 Model ID 是否在服务端支持列表里。三件套任何一项缺失都会导致 401。如果 Key 是从环境变量读的,检查env字段有没有正确传入,stdio 子进程不会自动继承 Host 的全部环境变量。

5.2 local proxy failed / connection refused

这个报错一般出现在 Client 连 Server 的阶段。常见原因:Server 进程没启动、command路径写错、args里的绝对路径不对。排查动作:先在终端手动执行配置里的command和args,看能不能正常启动。如果手动能跑但 Host 里报错,多半是路径问题——mcp_config.json里的路径必须是绝对路径,相对路径在不同工作目录下会失效。另外检查uv run --directory后面的目录是否存在。

5.3 reading choices / 返回结构解析失败

这个报错通常出现在模型返回结果解析阶段。原因可能是模型返回的 JSON 结构不符合预期,或者 Tool Result 的格式和 Host 期望的不一致。排查动作:先打印原始返回内容,确认choices字段是否存在。如果是自定义 Server 返回的 Tool Result,检查是否符合 MCP 规范——成功时外层是result,失败时如果是业务错误,应该用result加isError: true,而不是直接返回 JSON-RPCerror。

5.4 OAuth / 认证流程报错

如果 Server 需要 OAuth 认证,报错通常出现在 token 获取或刷新阶段。排查动作:确认 OAuth 配置的 client_id、client_secret、redirect_uri 是否和提供方一致,确认 token 是否过期。远程 HTTP Server 必须考虑认证,这是生产环境的基本要求。

5.5 两类错误的本质区别

MCP 最容易被忽视的一点是:Protocol Error 不等于 Tool Execution Error。前者表示这次 MCP 调用本身就不成立,比如 tool 不存在、JSON-RPC 格式错误、request schema 不合法、协议版本不支持,这类错误通常由 Client/Host 优先处理,记录日志、判断 Server 是否失效、决定是否重连,LLM 通常无需直接感知。后者表示工具已经正常进入执行阶段但业务执行失败,比如外部 API 超时、参数业务校验失败,这类错误通过{"result": {"content": [...], "isError": true}}返回给 LLM,让模型根据错误信息修正参数、重试或询问用户。

一个推荐的 Server 错误写法是让信息具备可操作性。不要只返回error或failed,而是返回Order 10001 does not exist.或Upstream API timed out after 5 seconds. Retry is allowed.或start_date must be earlier than end_date.。因为这个错误最终很可能进入 LLM Context,错误信息越明确,Agent 自愈能力越强。

6. 把 MCP 接入你的日常开发流

跑通最小链路之后,下一步就是把它用起来。如果你主要做长期编码或者 Agent 开发,建议直接上 Coding Plan,把 MCP Server 的配置和模型调用统一管理起来,省得每次手动改mcp_config.json。配置入口在 https://taotoken.net/api-keys ,接入文档在 https://taotoken.net/doc ,模型对话调试可以用 https://taotoken.net/chat ,Coding Plan 在 https://taotoken.net/coding-plan 。

生产环境还有几件事必须补上:所有外部调用设置 Timeout,只对幂等可恢复错误做 Retry,接 OpenTelemetry 做链路追踪,远程 HTTP 必须做 Auth,加 Rate Limit 防止 Tool 被高频调用,服务端做 Input Validation 和 Output Validation,高风险操作要求用户确认,Secret 和代码配置文件解耦,Tool Description 写清楚输入输出和副作用。永远不要因为参数来自 LLM 就跳过服务端校验,Tool 是真正进入业务系统的边界,它和传统 Web API 一样需要鉴权、校验、限流、审计、超时和异常处理。

把 Server 写出来、配置配好、请求验证通过这三件事亲手做完,MCP 基本就从"看懂了"变成真正"会用了"。它本身并不神秘,真正做的事情就是在快速膨胀的 Agent 世界里,给"模型如何连接外部能力"规定一套大家都能说的语言。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询