本地 MCP Server
通过 stdio 或本机 Streamable HTTP 暴露受限的本地工具与资源。
学习目标
- 能说明本地 MCP Server解决什么问题,以及何时适用。
- 能解释为什么“把整个磁盘作为根目录”是误区。
学习前需要掌握
背景与问题
本地部署避免把资料发送给远程服务,但仍可能读写本机文件,权限边界同样重要。
概念定义
本地 MCP Server 是在用户机器上运行、由 Client 连接的 MCP 服务进程。
直观理解
它像只在电脑内部开放的服务窗口,距离近不等于权限无限。
核心原理
stdio Server 的 stdout 只能输出协议消息,日志应写 stderr。
启动参数和工作目录必须明确,避免继承过宽环境。
只暴露白名单目录与必要工具,写操作单独审批。
理解与实践步骤
- 1
定义只读工具
- 2
限制根目录
- 3
启动 stdio Server
- 4
Client 连接并列出工具
- 5
验证调用与关闭进程
代码实现
示例 1
本地 MCP 消息流:Server 与 Client 的最小可复现模型
example_01.py用途:不依赖网络或第三方 SDK,用队列完整演示 initialize、tools/list 与 tools/call 的请求—响应结构;这是协议教学模型,不宣称替代完整 MCP SDK。
from queue import Queue
requests = Queue()
responses = Queue()
def server_once():
message = requests.get()
method = message["method"]
if method == "initialize":
result = {"protocolVersion": "2026-07-28", "capabilities": {"tools": {}}}
elif method == "tools/list":
result = {"tools": [{"name": "read_note", "readOnly": True}]}
elif method == "tools/call":
args = message["params"]["arguments"]
if not args["path"].startswith("notes/"):
raise PermissionError("path is outside the allowed root")
result = {"content": [{"type": "text", "text": "local note"}]}
else:
raise ValueError(f"unsupported method: {method}")
responses.put({"jsonrpc": "2.0", "id": message["id"], "result": result})
def client_call(method, params=None):
requests.put({"jsonrpc": "2.0", "id": 1, "method": method, "params": params or {}})
server_once()
return responses.get()
print(client_call("initialize"))
print(client_call("tools/list"))
print(client_call("tools/call", {
"name": "read_note",
"arguments": {"path": "notes/agent.md"},
}))代码解析
解析始终位于完整代码下方,并按实际代码段逐项对应。
输入数据与任务
不依赖网络或第三方 SDK,用队列完整演示 initialize、tools/list 与 tools/call 的请求—响应结构;这是协议教学模型,不宣称替代完整 MCP SDK。
Step 1 · 1–1 行
导入当前步骤需要的数值计算、预处理、模型或评价工具。依赖集中写在代码开头,便于复现。
from queue import QueueStep 2 · 3–4 行
initialize 声明协议版本与能力;tools/list 用于发现;tools/call 携带工具名和参数。
requests = Queue()
responses = Queue()Step 3 · 6–20 行
路径校验发生在 Server 执行层,readOnly 元数据不能替代真实授权。
def server_once():
message = requests.get()
method = message["method"]
if method == "initialize":
result = {"protocolVersion": "2026-07-28", "capabilities": {"tools": {}}}
elif method == "tools/list":
result = {"tools": [{"name": "read_note", "readOnly": True}]}
elif method == "tools/call":
args = message["params"]["arguments"]
if not args["path"].startswith("notes/"):
raise PermissionError("path is outside the allowed root")
result = {"content": [{"type": "text", "text": "local note"}]}
else:
raise ValueError(f"unsupported method: {method}")
responses.put({"jsonrpc": "2.0", "id": message["id"], "result": result})Step 4 · 22–25 行
执行当前代码段,并把得到的状态传给下一步。
def client_call(method, params=None):
requests.put({"jsonrpc": "2.0", "id": 1, "method": method, "params": params or {}})
server_once()
return responses.get()Step 5 · 27–32 行
输出中间参数、形状或最终指标,用于核对代码是否符合预期。
print(client_call("initialize"))
print(client_call("tools/list"))
print(client_call("tools/call", {
"name": "read_note",
"arguments": {"path": "notes/agent.md"},
}))预期输出或运行结果
依次打印初始化能力、只读工具目录和 local note 内容;越过 notes/ 根目录会抛出 PermissionError。
常见错误 · 2 条
- 把整个磁盘作为根目录
- stdout 混入日志破坏协议
实际应用
- 本地文档读取
- 开发工具
常见错误
输入、输出与执行边界
输入
- 本地进程命令
- 允许目录
输出
- 本地工具和资源
能力
- 本地文档读取
- 开发工具
官方来源与时效
资料记录日期:2026-08-30(不代表已逐项核验)。产品能力、SDK 参数和协议状态可能变化,请以链接页面的当前版本为准。
推荐学习资料
参考库不会生成虚假资源或无效外部链接。