本地 MCP Server
通过 stdio 或本机 Streamable HTTP 暴露受限的本地工具与资源。
学习目标
- 能说明本地 MCP Server解决什么问题,以及何时适用。
- 能解释为什么“把整个磁盘作为根目录”是误区。
学习前需要掌握
背景与问题
本地 Server 可以把文件读取和开发工具留在用户机器上执行,但“本地”不代表整个数据链都留在本机:返回内容仍可能进入远程模型或追踪服务。部署位置、执行权限和后续数据流要分别检查。
一个本地能力是否适合开放,取决于它能读写哪些路径、运行什么程序、继承哪些环境变量,以及宿主能否限制和结束它。
概念定义
本地 MCP Server 是在用户机器上运行、由 Client 连接的 MCP 服务进程。
直观理解
它像只在电脑内部开放的服务窗口,距离近不等于权限无限。
核心原理
stdio Server 通常作为宿主启动的子进程运行,stdout 只传协议消息,日志写 stderr。启动命令、工作目录、依赖与环境应明确,避免依赖不可复现的交互式终端状态。
文件工具应以配置的允许目录为边界,解析规范化路径后检查是否仍在该目录内。字符串前缀判断无法阻止 ../ 穿越;符号链接、并发替换与操作系统权限也需要在真实实现中考虑。
工具目录只是能力声明,读取和写入权限仍由实现落实。优先以只读窄接口验证连接与结果,写操作另外设计审批、幂等和审计;同机其他进程也不能默认视为可信用户。
实际协议连接应使用与目标版本兼容的实现或 SDK。当前规范与旧版初始化流程不同;下方代码刻意只验证本地读取工具的路径边界,不实现 stdio、HTTP 或完整 MCP 消息处理,不能当作可接入宿主的 Server。
传输行为可参照 官方 stdio 绑定。
案例:只开放课程笔记目录
假设允许目录是 notes,Agent 可以读取其中的 agent.md。请求 ../private.md 即使在字符串上看似与笔记有关,解析后已越过允许根目录,执行端必须拒绝;不能让模型自己决定是否遵守边界。
下方示例在临时目录创建一份笔记,成功读取后验证越界路径会被拒绝,最后自动清理。它没有开放网络或启动外部进程,也没有实现完整沙箱。真实部署还要检查链接与并发变化、操作系统身份,以及读出内容是否允许被发送给模型服务。
理解与实践步骤
- 定义只读工具
- 限制根目录
- 启动 stdio Server
- Client 连接并列出工具
- 验证调用与关闭进程
代码实现
示例 1
本地读取工具:成功读取与越界拒绝
example_01.py用途:使用 Python 标准库与临时目录验证路径边界,无需网络、模型或第三方 SDK。这是工具实现测试,不包含 MCP 传输或完整 Server。
from pathlib import Path
from tempfile import TemporaryDirectory
def read_note(root: Path, relative_path: str) -> str:
allowed_root = root.resolve()
candidate = (allowed_root / relative_path).resolve()
if not candidate.is_relative_to(allowed_root):
raise PermissionError("path is outside the allowed root")
if candidate.suffix != ".md":
raise ValueError("only Markdown notes are allowed")
return candidate.read_text(encoding="utf-8")
with TemporaryDirectory() as temporary:
notes_root = Path(temporary) / "notes"
notes_root.mkdir()
(notes_root / "agent.md").write_text("local note", encoding="utf-8")
print(read_note(notes_root, "agent.md"))
try:
read_note(notes_root, "../private.md")
except PermissionError as error:
print(f"blocked: {error}")代码解析
解析始终位于完整代码下方,并按实际代码段逐项对应。
输入数据与任务
使用 Python 标准库与临时目录验证路径边界,无需网络、模型或第三方 SDK。这是工具实现测试,不包含 MCP 传输或完整 Server。
Step 1 · 1–2 行
导入当前步骤需要的数值计算、预处理、模型或评价工具。依赖集中写在代码开头,便于复现。
from pathlib import Path
from tempfile import TemporaryDirectoryStep 2 · 4–11 行
示例实际创建并读取一份 Markdown 笔记,越界请求在读取前被拒绝,临时目录退出时清理。
def read_note(root: Path, relative_path: str) -> str:
allowed_root = root.resolve()
candidate = (allowed_root / relative_path).resolve()
if not candidate.is_relative_to(allowed_root):
raise PermissionError("path is outside the allowed root")
if candidate.suffix != ".md":
raise ValueError("only Markdown notes are allowed")
return candidate.read_text(encoding="utf-8")Step 3 · 13–21 行
输出中间参数、形状或最终指标,用于核对代码是否符合预期。
with TemporaryDirectory() as temporary:
notes_root = Path(temporary) / "notes"
notes_root.mkdir()
(notes_root / "agent.md").write_text("local note", encoding="utf-8")
print(read_note(notes_root, "agent.md"))
try:
read_note(notes_root, "../private.md")
except PermissionError as error:
print(f"blocked: {error}")预期输出或运行结果
local note blocked: path is outside the allowed root
常见错误 · 3 条
- 把整个磁盘设为允许目录,或用字符串 startswith 判断路径是否安全。
- 在 stdout 混入调试日志,或者任由子进程继承不必要的秘密与权限。
- 把本地执行等同于数据从不离开本机,或把简化工具测试当成完整 MCP Server。
实际应用
- 本地文档读取
- 开发工具
常见错误
- 把整个磁盘设为允许目录,或用字符串 startswith 判断路径是否安全。
- 在 stdout 混入调试日志,或者任由子进程继承不必要的秘密与权限。
- 把本地执行等同于数据从不离开本机,或把简化工具测试当成完整 MCP Server。
输入、输出与执行边界
输入
- 本地进程命令
- 允许目录
输出
- 本地工具和资源
能力
- 本地文档读取
- 开发工具
安全风险与信任边界
本地服务进程可能继承启动环境的目录、变量和文件访问能力,不能因为没有远程地址就默认隔离。应核对服务实际读取与写入的路径;stdio 的协议输出与诊断日志也要分开,避免把日志或敏感环境信息混入返回内容。
性能与失败模式
冷启动、依赖加载、子进程生命周期和本地 IO 都会影响响应。测试应覆盖首次启动、重复请求、取消与异常退出,区分协议错误和后端库错误;运行在本机不保证比远程服务更快或更稳定。
推荐学习资料
modelcontextprotocol/servers
MCP 官方参考服务器与 SDK 使用示例,适合学习工具和资源暴露方式。
MCP Steering Group · GitHub
仓库信息
展示 MCP 服务器能力和协议用法
主要语言:TypeScript / Python不包含数据集或实验需要额外环境