
MCPModel Context Protocol模型上下文协议正在改变 AI 客户端接入外部数据的方式。以前要让 Claude、Codex、Cursor 这类工具拿到数据库、设计稿或内部文档往往需要写插件、写脚本或者直接把文本粘贴进对话而基于 MCP 实现上下文共享后一组标准化的接口就能让多个客户端在同一份数据上读写。这篇文章围绕一个典型的个人项目——“在朋友之间通过 MCP 共享上下文”——从协议机制、最小服务器实现、客户端配置到常见故障排查逐步展开适合正在研究 MCP、想用轻量方式搭建共享记忆、团队知识库或个人跨客户端背景同步的开发者。读完可以复现一个可运行的 friends-context server并理解 MCP 中 Resource、Tool、传输层、权限和安全边界的具体取舍。1. 先理解 MCP 为什么适合做上下文共享1.1 朋友之间共享上下文的实际场景“共享上下文”这几个字初看像是把一段文字转发给对方但在 AI 使用场景里它的含义更具体当你使用 MCP 客户端时客户端里的 AI 助手需要知道某个朋友当前在做什么、有什么偏好、之前讨论过什么。这个信息不能靠每次手动粘贴更不能靠复制聊天记录最好是一个可查询、可追加、可过滤的上下文源。实际的例子包括朋友之间共享各自最近研究的主题让彼此的 AI 助手在回答时带上背景几个同学共建一个 MCP server把课程笔记、待办和资料链接放进去两个人一起维护同一个开源项目通过共享 MCP 上下文让 AI 在写代码时记得项目的演进历史和约定。这类需求的核心不是聊天而是让 AI 在工作时能读取到一组结构化的、持续更新的背景资料。MCP 的价值恰好落在这一点上。它不是一个具体的聊天软件而是一个让 AI 客户端和外部数据源互联的标准协议。也就是说MCP 负责解决“AI 助手如何安全、规范地拿到我指定的上下文”这件事至于上下文内容是什么、放在哪里、如何组织完全由 server 端决定。1.2 MCP 的三层架构Host、Client、ServerMCP 的架构可以理解为三层最上层是 Host即用户直接面对的 AI 应用例如 Claude Desktop、Codex、Cursor、Dify 这类产品中间是 Client它运行在 Host 内部负责与远程或本地的 MCP Server 建立连接最下层是 Server也就是你写的服务程序它负责暴露资源、工具或提示词给客户端使用。通信基于 JSON-RPC 协议。Host 启动时Client 会读取 MCP 配置文件拉起 Server 进程并通过标准输入输出或 HTTP 等传输方式进行消息交换。客户端可以做的操作有三类列举可用的工具或资源、调用工具、读取资源内容。这些操作完成后结果会回到 Host 里变成 AI 对话上下文的一部分。理解这层架构的意义在于排查问题。例如你在客户端里看不到新增的工具问题可能不在 server 逻辑而在 Client 是否重新加载了配置工具调用一直超时可能不是代码 bug而是 stdio 传输下的工作目录、环境变量或日志输出干扰了通信。1.3 Resource、Tool、Prompt 三种能力该如何选择MCP Server 可以暴露三种能力Resource、Tool 和 Prompt。Resource 有点像只读文件适合暴露“内容型数据”。例如朋友的列表、某个朋友的完整上下文、团队知识库的条目。AI 客户端可以浏览资源列表也可以按 URI 读取具体内容。因为它是读取语义写操作不应该放在 Resource 里做。Tool 是可执行函数适合暴露“动作型能力”。例如向朋友列表追加一条上下文、按关键词搜索所有朋友的分享、修改某条记录的标签。Tool 可以接收参数、返回结构化结果也可以附带错误信息。Prompt 是可复用的提示模板适合把“固定的使用方式”封装起来。例如定义“读取好友 A 的上下文并生成一个更新摘要”这样的模板。对于朋友间共享上下文这个项目Prompt 不是必需的但可以作为进阶功能。在 3.2 和 3.3 节的实现里我会把“读”交给 Resource“写和搜索”交给 Tool。这个划分不是随意定的而是因为 Resource 的读取会直接进入 AI 上下文适合给模型“看”的内容Tool 则允许带参数、有副作用适合需要修改数据的操作。2. 环境准备与最小工程骨架2.1 环境要求与依赖版本确认在开始写代码之前先确认本机环境。MCP 生态更新很快不同版本的 SDK、客户端对配置的支持有差异最稳妥的方式是先确认自己手里版本的文档再照着写。依赖项建议版本用途Python3.10 及以上编写 MCP Servermcp Python SDK使用 pip 安装时的最新稳定版提供 FastMCP 封装和协议实现Node.js 与 npx18 及以上运行 MCP Inspector 调试工具MCP 客户端Claude Desktop、Dify、Codex、Cursor 任一验证 MCP Server 是否真正可用安装 Python SDK 时可以按常见方式操作pip install mcp如果你希望使用更贴近社区习惯的封装也可以安装 fastmcp。不过 fastmcp 有自己的 API 风格下文示例使用官方 Python SDK 中提供的FastMCP类。落地前先执行pip show mcp查看版本避免照搬示例后因为 API 变更出现AttributeError。pip show mcp python -c from mcp.server.fastmcp import FastMCP; print(FastMCP import ok)2.2 项目目录结构一个最小的 friends-context 项目可以这样组织friends-context/ ├── server.py ├── data/ │ └── friends_context.json ├── requirements.txt └── README.mdserver.py是 MCP Server 入口data/friends_context.json是共享上下文的持久化文件requirements.txt记录 Python 依赖README.md记录启动命令和客户端配置方式。这样一个目录已经足够支撑本地验证。要注意的是data目录不能依赖相对路径因为很多 MCP 客户端启动子进程时工作目录可能是客户端自己的安装目录而不是你的项目目录。更稳妥的做法是把数据文件路径通过环境变量传入或者在代码里使用绝对路径。2.3 调试工具准备MCP 官方提供了一个比较方便的调试工具MCP Inspector。它会在本地起一个 Web 页面让你可以查看 MCP Server 暴露了哪些 Resource 和 Tool也可以直接调用它们。常见启动方式是npx modelcontextprotocol/inspector python server.py如果server.py不在当前目录需要先进入项目目录再执行。这个工具的价值在于它能帮你绕开 AI 客户端的缓存和会话层直接验证 MCP Server 本身是否正常。很多“客户端里看不到工具”的问题用 Inspector 一测就能定位到底是 server 异常还是客户端没刷新。注意调试时不要让server.py的日志直接打印到标准输出。MCP 的 stdio 传输使用标准输入输出和客户端通信日志混入 stdout 会导致协议解析失败。日志应该写文件、stderr 或日志目录。3. 用 Python 实现一个 friends-context MCP Server3.1 定义共享上下文的存储结构共享上下文的数据结构要支持三类操作按朋友读取、按朋友追加、跨朋友搜索。一个简单但可扩展的 JSON 结构如下{ friends: [ { id: alice, name: Alice, context: Alice 最近在研究 Python 数据管道周末计划做一个 MCP 示例。, tags: [python, mcp], updated_at: 2025-01-15T10:00:00Z } ] }字段说明字段类型说明idstring朋友唯一标识通常由名字生成用于 Resource URI 定位namestring展示名称contextstring共享上下文的正文内容可以包含多行文本tagsstring[]标签方便后续过滤或搜索updated_atstring最后更新时间ISO 8601 格式这里的 context 字段是核心。它不一定要是自然语言段落也可以存储“朋友最近关注的 Issue 链接”、“他做过的技术选型结论”等结构化描述。AI 客户端读取这些内容后在回答相关问题时就能带上背景信息。3.2 通过 Resource 暴露朋友的上下文在server.py里先用 FastMCP 创建一个服务器实例再注册两个 Resource一个查看全部朋友一个按 id 查看单个朋友。import json import os import re from datetime import datetime, timezone from pathlib import Path from mcp.server.fastmcp import FastMCP DATA_FILE Path(os.getenv(FRIENDS_DATA_FILE, ./data/friends_context.json)) mcp FastMCP(friends-context-server) def load_data(): if not DATA_FILE.exists(): return {friends: []} with DATA_FILE.open(encodingutf-8) as f: return json.load(f) def save_data(data): DATA_FILE.parent.mkdir(parentsTrue, exist_okTrue) with DATA_FILE.open(w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2) mcp.resource(context://friends) def get_all_friends() - str: data load_data() return json.dumps(data, ensure_asciiFalse, indent2) mcp.resource(context://friends/{friend_id}) def get_one_friend(friend_id: str) - str: data load_data() friend next((f for f in data[friends] if f[id] friend_id), None) if friend is None: raise ValueError(ffriend not found: {friend_id}) return json.dumps(friend, ensure_asciiFalse, indent2) if __name__ __main__: mcp.run()这段代码的关键点有三个第一DATA_FILE从环境变量读取避免工作目录不同导致找不到数据文件第二Resource URI 使用context://friends/{friend_id}这种带参数形式客户端可以根据朋友 id 精确读取第三读取函数返回字符串MCP 客户端会把它作为上下文提供给模型。3.3 通过 Tool 实现追加、搜索和权限控制接下来注册三个 Tool新建朋友、追加上下文、按关键字搜索。Tool 和 Resource 的区别在于它可以接收参数也可以修改数据而且返回结果可以控制长度。mcp.tool() def add_friend(name: str, initial_context: str , tags: list[str] | None None) - str: data load_data() friend_id re.sub(r[^a-z0-9_-], -, name.lower()) if any(f[id] friend_id for f in data[friends]): return ferror: friend already exists: {friend_id} data[friends].append({ id: friend_id, name: name, context: initial_context, tags: tags or [], updated_at: datetime.now(timezone.utc).isoformat(), }) save_data(data) return ffriend created: {friend_id} mcp.tool() def append_context(friend_id: str, content: str) - str: data load_data() friend next((f for f in data[friends] if f[id] friend_id), None) if friend is None: return ferror: friend not found: {friend_id} friend[context] friend[context] \n content friend[updated_at] datetime.now(timezone.utc).isoformat() save_data(data) return fcontext updated for {friend_id}, length{len(friend[context])} mcp.tool() def search_context(keyword: str, limit: int 5) - str: data load_data() results [] for f in data[friends]: if keyword.lower() in f[context].lower() or keyword.lower() in [t.lower() for t in f[tags]]: results.append({ id: f[id], name: f[name], snippet: f[context][:200], }) if len(results) limit: break return json.dumps(results, ensure_asciiFalse, indent2)这里需要注意search_context返回的 snippet 做了截断。原因是 MCP 工具返回的内容会成为 AI 上下文的一部分如果把每个朋友几千字的完整上下文全部返回会导致上下文窗口快速膨胀。搜索工具适合返回摘要切片完整内容留给 Resource 读取。这正是 4.3 节要展开说明的设计取舍。3.4 注册到 MCP 客户端MCP Server 写好之后需要在客户端配置里声明。以 Claude Desktop 为例配置文件通常是claude_desktop_config.json{ mcpServers: { friends-context: { command: python, args: [/absolute/path/to/friends-context/server.py], env: { FRIENDS_DATA_FILE: /absolute/path/to/friends-context/data/friends_context.json } } } }配置要点command和args要写绝对路径尽量不要依赖 PATH。部分客户端不支持env字段如果配置了却不生效就在server.py里改用绝对路径或者把默认值写得足够合理。修改配置后需要重启客户端或者让客户端重新加载 MCP Server否则新增的 Tool 不会出现。4. 关键设计与参数解释4.1 为什么写入和搜索用 Tool读取用 Resource许多人第一次写 MCP Server 时会纠结同一个数据到底应该用 Resource 还是 Tool。这里的判断标准是Resource 是“内容视图”Tool 是“操作入口”。能力语义适合场景示例Resource只读内容可被浏览和读取让 AI 直接看某份数据朋友完整上下文、知识库文章Tool可执行操作可带参数、有副作用让 AI 完成某个动作追加上下文、搜索、更新标签Prompt可复用提示模板把固定工作流封装起来“生成朋友周报”模板如果写成mcp.tool()的get_friend_context()它也能工作但客户端浏览资源列表时会很不自然。Resource 允许客户端通过 URI 直接定位内容不需要额外传参数语义也更清晰。反过来如果把append_context做成 Resource那就要用 HTTP PUT 或自定义写语义而 MCP Resource 本身并不保证这种能力。Tool 的参数、返回和错误处理语义更完整适合写入和搜索。4.2 stdio 与 SSE/HTTP 传输的取舍MCP Server 可以运行在不同传输层上。最常见的两种是 stdio 和 SSE/HTTP。stdio 模式适合本地开发和单机使用。客户端直接拉起 Python 进程通过标准输入输出通信。优点是配置简单、不需要开端口缺点是只能被本机客户端使用而且日志不能乱打印到 stdout。SSE/HTTP 模式适合远程访问、多客户端共享。Server 启动后监听一个 HTTP 端口多个客户端可以通过 URL 连接。这样做的好处是朋友之间可以各自在自己的电脑上接入同一个共享 Server代价是你需要解决网络可达、端口开放、鉴权等额外问题。传输方式优点缺点推荐场景stdio配置简单无端口暴露只能本机访问进程随客户端生命周期本地调试、单机学习SSE/HTTP可供远程多客户端访问需要处理端口、鉴权、并发朋友间共享、团队服务对于“朋友之间共享上下文”这个场景如果两个人不在同一台电脑上就必须用 SSE/HTTP 模式并且要加上鉴权。本地演示用 stdio 就足够了。4.3 上下文过大、自动总结与协议限制搜索热词里经常看到“上下文过大”“已进行多次自动总结但上下文大小仍超出限制”这样的报错。这个问题在 MCP 工具使用中非常常见。原因在于MCP 工具或 Resource 返回的所有内容会拼接到 AI 的上下文窗口里。如果一个搜索工具返回了 100 条完整记录每条 5000 字那么一次调用就可能耗尽上下文。客户端尝试自动总结旧对话来腾空间但如果新增加的内容本身就很大总结多少次都没用。处理办法包括工具返回前做截断只返回snippet或摘要。Resource 按单个朋友粒度读取避免一次读全部。搜索接口支持limit和分页参数。在 prompt 里告诉 AI 优先使用小工具查询摘要再按需读取完整内容。如果条件允许用向量检索或关键词索引替代全量扫描。这个约束不是 MCP 独有的而是 LLM 应用的通用规律上下文窗口是稀缺资源server 设计时要把返回内容当作“给模型看的材料”来规划而不是当作“API 响应”随意返回。4.4 数据落盘、并发与隐私边界示例里的 JSON 文件存储只适合学习或低并发场景。追加操作包含“读取文件 - 修改内存 - 写回文件”三步如果多个客户端同时调用可能出现覆盖。生产环境建议使用 SQLite配合事务和行级锁。也可以直接用 SQLite 的upsert来避免并发覆盖INSERT INTO friends(id, name, context, tags, updated_at) VALUES(?, ?, ?, ?, ?) ON CONFLICT(id) DO UPDATE SET context excluded.context, tags excluded.tags, updated_at excluded.updated_at;隐私边界是这个项目的核心风险点。朋友之间共享上下文意味着每个人写入的内容都会成为 AI 客户端的输入。写入前要明确告知哪些内容适合共享、哪些不能共享。至少要做到不共享密码、Token、身份证号、银行卡等敏感信息。服务层加入访问控制例如只允许指定的 friend_id 读取自己的数据。写入前做内容脱敏或敏感词过滤。删除接口要能物理删除数据而不能只标记删除。5. 运行验证与结果分析5.1 用 MCP Inspector 验证 Resource 和 Tool启动 Inspectornpx modelcontextprotocol/inspector python server.py打开浏览器里的调试界面后应该能看到Resources 列表里有context://friends和context://friends/{friend_id}。Tools 列表里有add_friend、append_context、search_context。可以按顺序验证调用add_friend参数nameAlice确认返回friend created: alice。调用append_context参数friend_idalicecontent正在学习 MCP。读取context://friends/alice确认返回的 JSON 里 context 字段包含刚才追加的内容。调用search_context参数keywordMCP确认能搜索到 Alice。如果某一步失败Inspector 会显示具体的错误堆栈方便定位。5.2 在真实客户端里验证共享上下文Inspector 验证通过后再回到真实客户端。在 Claude Desktop 的配置里加好 server重启客户端然后向 AI 提问“请查看 Alice 的共享上下文并告诉我她最近在做什么。”此时 AI 会通过 Resource 读取context://friends/alice然后基于内容回答。如果配置正常回答里应该出现“分享前的背景信息”。如果回答大意为“我无法访问”就要进入第 6 节排查。5.3 日志与异常观察MCP Server 的日志建议写到 stderr 或文件。在server.py里可以加最简单的方式import sys def log(msg: str): print(f[friends-context] {msg}, filesys.stderr)使用filesys.stderr是因为不能污染 stdio 标准输出。客户端启动 Server 时stderr 通常会进入客户端日志或者在你从终端手动启动时直接显示。需要观察工具调用是否异常时可以在每个 Tool 入口打一条带参数摘要的日志。6. 常见问题排查6.1 配置了 server 但客户端不显示工具这是最常见的问题。可能原因有几个客户端没有重启MCP Server 列表还停留在旧状态。配置文件的 JSON 格式有误例如多了一个逗号。server 启动即报错导致客户端连接失败。自定义命令需要的 PATH 在客户端环境里不存在例如python实际是python3。检查方式先在终端里手动运行配置里的命令确认能正常进入 stdio 监听状态再用 Inspector 连接同一命令最后再看客户端日志。6.2 Resource 列表为空或读取不到如果 Resource 能列出但读取时报错优先检查 URI 拼写。context://friends/{friend_id}中的friend_id必须与实际数据一致。如果朋友 id 是alice读取context://friends/alice才能返回写成context://friends/Alice就会失败。另一个常见问题是数据文件路径。客户端启动的工作目录可能不是项目目录导致./data/friends_context.json指向错误位置。解决方法是启动时传入环境变量FRIENDS_DATA_FILE。6.3 工具调用超时、无响应或返回超长上下文现象可能原因检查方式处理建议工具一直转圈server 进程崩溃或阻塞查看 stderr 日志修复异常并确保无阻塞调用返回结果太长没有截断或分页查看返回 JSON 大小减少 snippet 长度增加 limit 参数客户端自动总结后仍超限单次返回占用上下文太多统计单次 Tool 返回字符数改为摘要模式完整内容走 Resource无响应数据库文件被锁检查是否有两个进程同时写文件切换到 SQLite 或加文件锁6.4 写入数据丢失文件锁、工作目录与缓存写入后客户端读取不到或者重启后数据消失优先检查数据文件是否真的写到了预期路径。是否因为工作目录不同读的是 A 文件写的是 B 文件。是否同时开了多个 server 进程后写覆盖前写。客户端是否有资源缓存需要刷新或重启才能看到新内容。修复方向是用环境变量统一数据文件路径单进程运行写入后立刻读回验证如果是 SQLite使用事务确保一致性。6.5 MCP 与 Agent Skill、插件外挂的区别混淆MCP 常见的热搜词里有一类问题是“agent skill 和 mcp 有什么区别”。简单来说MCP 是标准协议它定义的是“不同客户端如何连接不同数据源/工具”的通用规则Agent Skill 通常是某个 Agent 框架里的能力抽象描述一个 Agent 可以执行的技能集合。MCP 更偏跨平台、跨语言Skill 更偏单一 Agent 内部的封装。在上下文共享这个项目里你应该用 MCP 构建数据访问层如果之后再在某个特定 Agent 里把“读取朋友上下文 - 生成报告”封装成 Skill那是上层业务逻辑两者不是替代关系。7. 最佳实践与扩展方向7.1 从实验项目到可维护服务的检查清单如果要把这个 friends-context server 从本地演示变成真正可用的服务建议按下面清单过一遍检查项落地方式数据持久化从 JSON 文件迁移到 SQLite 或 PostgreSQL并发安全使用事务、行级锁或唯一索引输入校验对 friend_id、name、content 做长度和格式校验访问控制增加 token 或 API Key按朋友维度隔离数据日志和监控记录每个工具调用的耗时、参数、错误码上下文大小控制统一限制单次返回字符数提供摘要和分页隐私协议在 README 里明确共享数据的边界和删除方式部署SSE/HTTP 模式HTTPS 传输端口限制7.2 从朋友间共享扩展到多 Agent 协作这个项目的思路可以扩展到更大的范围。同一套 MCP Server如果数据结构从“朋友列表”改成“项目列表”“任务列表”“文档列表”就能变成团队知识库、跨客户端任务同步或 Agent 协作记忆。比较典型的扩展方向是把 context 字段换成“事件流”或“决策记录”让每个加入的 AI 客户端都有能力读取历史决策避免重复讨论。另一个方向是接入向量数据库把朋友的上下文向量化搜索时用语义相似度而不是关键字匹配。7.3 进一步学习方向如果想继续深入 MCP可以从这几个方向入手完整阅读 MCP 协议规范里的 Resource、Tool、Prompt 和采样部分。在项目中加入鉴权和用户隔离观察 MCP 的消息结构如何承载 metadata。实现一个 SSE/HTTP 模式的 server用 curl 直接调试 HTTP 端点。研究现有开源 MCP Server例如连接数据库、连接设计稿、控制浏览器的实现方式理解不同 Server 如何组织工具和资源。关注你常用客户端的 MCP 配置差异因为命令、环境变量和缓存策略并不完全一致。回到最初的问题朋友之间共享上下文难点不在于把文字发给对方而在于让 AI 客户端能够稳定、安全、按需地获取这些上下文。MCP 把这件事标准化了剩下要做的就是控制好数据粒度、上下文大小和访问边界。建议先把这个最小 server 跑通再逐步加入权限、持久化和远程访问把每一步的日志和错误都记录下来你会比只看文档更快理解 MCP 的整套工作机制。