如何为 Claude 构建本地 MCP Server:文件、命令、截图与应用控制

@hrswatigupta
英语1个月前 · 2026年6月04日
1.0M
72
16
2
224

TL;DR

本指南演示了如何为 Claude 创建基于 Python 的本地 MCP Server,通过安全的白名单架构,实现对本地文件、命令及桌面工具的安全可控访问。

Claude 一旦不再只是个聊天界面,就会变得实用得多。

本地 MCP 服务器可以让 Claude 与你的实际机器交互:本地文件、获批命令、截图以及应用启动。关键不在于原始权限,而在于可控的访问权限。

在本指南中,我们将为 Claude 构建一个本地 MCP 服务器,它实用、专注且足够安全,可用于实际工作流。

开始构建之前的一点说明

本文特意避开了那种不负责任的“电脑控制”版本。

我们不会给 Claude 无限制的 Shell 权限、完整的文件系统权限,或者在没有防护措施的情况下操作你的机器。构建糟糕的本地 MCP 服务器最快的方式,就是暴露一个巨大的 run_anything() 工具,然后美其名曰创新。

更好的模式是:

  • 白名单目录
  • 白名单命令
  • 安全默认值
  • 人类可读的日志
  • 明确的响应
  • 只读工具与操作工具之间清晰分离

如果 Claude 什么都能做,那你只是建了个演示版。

如果 Claude 能安全地做正确的事,那你才真正构建了可用的东西。

为什么这套架构值得学习

本地 MCP 服务器的价值不在于新奇,而在于减少摩擦。

没有本地工具层,你的工作流会是这样:

  1. 问 Claude 该怎么做
  2. 复制答案
  3. 自己打开文件夹
  4. 自己运行命令
  5. 自己截图
  6. 把结果粘贴回聊天

有了本地 MCP 服务器,这个循环会大大缩短。Claude 可以检查它需要的上下文,使用范围狭窄的工具,然后返回一个基于你实际机器状态的答案。

这在以下场景中非常有用:

  • 开发工作流
  • 日志检查
  • 内容运营
  • 研究管线
  • 桌面自动化
  • 可重复的管理任务

而且因为工具层属于你,你可以精确决定模型在何处停止。

想学习 AI 并在职场中保持领先?

我分享:

1000+ 个 AI 提示词

AI 工具与自动化

职场与 LinkedIn 成长技巧

精选的技术与生产力资源

在此订阅:

https://bytebuilders.beehiiv.com/subscribe

关注我获取更多 AI 技巧

👇

我们要构建的设计

Swati Gupta - inline image

我们将构建一个包含五个工具的本地服务器:

  1. list_files — 查看已批准文件夹内的内容
  2. read_file — 打开安全的基于文本的文件
  3. run_command — 执行一小套获批的本地命令
  4. take_screenshot — 将截图保存到已知位置
  5. open_target — 打开应用、文件、文件夹或 URL
Swati Gupta - inline image

这个范围是经过深思熟虑的。

它足以让 Claude 在本地机器上发挥真正的作用,同时又不会陷入不安全的通用自动化。

心理模型应该是这样的:

Claude → MCP 客户端 → 本地 MCP 服务器 → 狭窄的工具 → 操作系统

Claude 永远不应该直接与你的操作系统对话。你的 MCP 服务器就是中间的控制平面。

技术栈

对于本地构建,Python 是一个简洁的选择,因为官方的 MCP SDK 已经成熟,FastMCP 抽象很简洁,而且 Python 仍然是文件系统、子进程和桌面脚本编程最容易的语言 4 2

我们将使用:

  • Python 3.11+
  • mcp[cli] 用于 MCP 服务器运行时
  • mss 用于跨平台截图
  • 标准库模块用于文件访问、子进程调用和操作系统处理

设置一个新项目:

bash
1mkdir local-mcp-server
2cd local-mcp-server
3uv init --python 3.11
4uv add "mcp[cli]>=1.0,<2.0" "mss>=9.0,<10.0"

基于装饰器的 FastMCP 风格让协议层不会妨碍你,这样你可以专注于工具质量,而不是连接工作 4 5

一个简单的项目布局就很好:

text
1mkdir local-mcp-server
2cd local-mcp-server
3uv init --python 3.11
4uv add "mcp[cli]>=1.0,<2.0" "mss>=9.0,<10.0"

v1 版本不需要复杂的架构。你需要的是清晰。

实际的服务器

创建 server.py 并以策略驱动的实现开始。

python
1from __future__ import annotations
2
3import json
4import os
5import platform
6import shlex
7import subprocess
8from pathlib import Path
9from typing import Any
10
11import mss
12from mcp.server.fastmcp import FastMCP
13
14app = FastMCP("local-computer-control", json_response=True)
15
16HOME = Path.home()
17PROJECT_ROOT = Path(__file__).parent.resolve()
18CAPTURE_DIR = PROJECT_ROOT / "captures"
19CAPTURE_DIR.mkdir(exist_ok=True)
20
21ALLOWED_ROOTS = [
22 HOME / "Documents",
23 HOME / "Desktop",
24 PROJECT_ROOT,
25]
26
27ALLOWED_COMMANDS = {
28 "pwd",
29 "ls",
30 "git status",
31 "git diff --stat",
32 "python --version",
33 "node --version",
34 "npm --version",
35}
36
37READABLE_EXTENSIONS = {
38 ".txt",
39 ".md",
40 ".json",
41 ".py",
42 ".js",
43 ".ts",
44 ".tsx",
45 ".jsx",
46 ".yaml",
47 ".yml",
48 ".toml",
49 ".csv",
50 ".log",
51}
52
53def _resolve_path(raw_path: str) -> Path:
54 path = Path(raw_path).expanduser().resolve()
55 for root in ALLOWED_ROOTS:
56 root = root.resolve()
57 if path == root or root in path.parents:
58 return path
59 raise ValueError(f"路径不被允许: {path}")
60
61def _ensure_safe_command(command: str) -> str:
62 normalized = " ".join(shlex.split(command))
63 if normalized not in ALLOWED_COMMANDS:
64 raise ValueError(
65 "命令不被允许。如果确实需要,请将其显式添加到 ALLOWED_COMMANDS 中。"
66 )
67 return normalized
68
69@app.tool()
70def list_files(path: str = "~") -> dict[str, Any]:
71 """列出已批准目录内的文件和文件夹。"""
72 target = _resolve_path(path)
73 if not target.is_dir():
74 raise ValueError(f"不是目录: {target}")
75
76 items = []
77 for child in sorted(target.iterdir(), key=lambda p: (not p.is_dir(), p.name.lower())):
78 items.append(
79 {
80 "name": child.name,
81 "path": str(child),
82 "type": "directory" if child.is_dir() else "file",
83 }
84 )
85
86 return {
87 "path": str(target),
88 "count": len(items),
89 "items": items,
90 }
91
92@app.tool()
93def read_file(path: str, max_chars: int = 12000) -> dict[str, Any]:
94 """从已批准位置读取安全的文本文件。"""
95 target = _resolve_path(path)
96 if not target.is_file():
97 raise ValueError(f"不是文件: {target}")
98 if target.suffix.lower() not in READABLE_EXTENSIONS:
99 raise ValueError(f"不支持的文件类型: {target.suffix}")
100
101 content = target.read_text(encoding="utf-8", errors="replace")
102 truncated = len(content) > max_chars
103 content = content[:max_chars]
104
105 return {
106 "path": str(target),
107 "truncated": truncated,
108 "content": content,
109 }
110
111@app.tool()
112def run_command(command: str, cwd: str | None = None, timeout: int = 15) -> dict[str, Any]:
113 """运行一条白名单中的本地命令。"""
114 safe_command = _ensure_safe_command(command)
115 working_dir = _resolve_path(cwd) if cwd else PROJECT_ROOT
116
117 completed = subprocess.run(
118 safe_command,
119 shell=True,
120 cwd=str(working_dir),
121 capture_output=True,
122 text=True,
123 timeout=timeout,
124 )
125
126 return {
127 "command": safe_command,
128 "cwd": str(working_dir),
129 "returncode": completed.returncode,
130 "stdout": completed.stdout.strip(),
131 "stderr": completed.stderr.strip(),
132 }
133
134@app.tool()
135def take_screenshot(name: str = "latest") -> dict[str, Any]:
136 """截图并保存到本地。"""
137 output_path = CAPTURE_DIR / f"{name}.png"
138
139 with mss.mss() as sct:
140 sct.shot(output=str(output_path))
141
142 return {
143 "saved": True,
144 "path": str(output_path),
145 }
146
147@app.tool()
148def open_target(target: str) -> dict[str, Any]:
149 """使用本地操作系统打开已批准的文件、文件夹、应用或 URL。"""
150 system = platform.system().lower()
151
152 if target.startswith("http://") or target.startswith("https://"):
153 resolved = target
154 else:
155 resolved = str(_resolve_path(target))
156
157 if system == "darwin":
158 subprocess.run(["open", resolved], check=True)
159 elif system == "windows":
160 os.startfile(resolved) # type: ignore[attr-defined]
161 else:
162 subprocess.run(["xdg-open", resolved], check=True)
163
164 return {
165 "opened": True,
166 "target": resolved,
167 }
168
169if __name__ == "__main__":
170 app.run(transport="stdio")

这是一个紧凑的服务器,但重要的不是它的长度。重要的是接口的设计:

  • 每个工具都有非常明确的职责
  • 每个工具都返回结构化数据
  • 命令执行受限制
  • 文件访问限定在根目录
  • 截图保存到已知文件夹

这正是你想要的本地 MCP 服务器。

为什么这些工具要这样设计

关于 Agent 工具的高质量内容绝不应止步于“这是代码”。工具的设计才是真正的经验。

list_files

这个工具为 Claude 提供了一个安全的探索面。它应该能回答这样的问题:

  • 这个项目文件夹里有什么?
  • Documents 里有哪些笔记?
  • 是否已经有我可以检查的日志文件?

但它不应该成为递归的整盘爬虫。

read_file

这通常是最有用的本地工具之一。大量的实际工作仍然隐藏在本地 Markdown 笔记、日志、CSV 文件、文档和项目文件中。

max_chars 上限很重要。大文件既是上下文问题也是延迟问题。返回一个巨大日志文件的全部内容很少有用。

run_command

这是大多数人最容易疏忽的地方。

安全模式不是“允许 Shell 访问,然后听天由命”。安全模式是“允许一小组精确的、可审查的命令”。这就是示例中使用严格白名单的原因。

take_screenshot

截图工具很有价值,因为它让 Claude 能参与桌面工作流。即使你的第一个版本只将图片保存到磁盘,对于报告、UI 调试、文档捕获和结构化交接来说已经很有用了。

open_target

应用控制不需要从 GUI 自动化开始。对于许多工作流来说,“打开正确的文件夹、文件或 URL”就足够了。

这比在第一天就假装你需要完整的鼠标光标自动化要持久得多。

将服务器连接到 Claude

本地 MCP 服务器通常通过 stdio 运行,这意味着 Claude 在本地启动进程并通过 stdin/stdout 直接与之通信。对于本地计算机控制服务器,这是正确的默认设置,因为它避免了不必要的网络暴露 4 5

Claude Desktop 通过配置支持本地 MCP 服务器,它会为你启动服务器进程。在实践中,为解释器和脚本使用绝对路径是最不脆弱的设置,因为本地 GUI 应用环境通常比你的终端更严格 2

一个最小的配置如下:

json
1{
2 "mcpServers": {
3 "local-computer-control": {
4 "command": "/absolute/path/to/python",
5 "args": [
6 "/absolute/path/to/local-mcp-server/server.py"
7 ]
8 }
9 }
10}

如果你更喜欢 uv,那也没问题:

json
1{
2 "mcpServers": {
3 "local-computer-control": {
4 "command": "/absolute/path/to/uv",
5 "args": [
6 "--directory",
7 "/absolute/path/to/local-mcp-server",
8 "run",
9 "python",
10 "server.py"
11 ]
12 }
13 }
14}

保存配置并重启 Claude 后,服务器工具应该会出现在本地 MCP 工具列表中。Claude Desktop 的本地 MCP 设置正是基于这个模型:启动一个本地进程,通过 stdio 连接,并将工具暴露给模型 2 3

Swati Gupta - inline image

实际有用的测试提示词

服务器连接后,不要一开始就尝试复杂的编排。从一个直接、枯燥的检查开始。

试试这些提示词:

  • “列出我桌面文件夹里的文件。”
  • “读取 ~/Documents/todo.md 并总结最重要的三件事。”
  • “在我的本地项目文件夹中运行 git status 并解释有什么变化。”
  • “拍一张名为 workspace-check 的截图。”
  • “打开我的项目 README。”

如果这些简单的流程能稳定工作,那你就有了一个值得扩展的服务器。

如果不行,添加更多工具只会掩盖真正的问题。

本地 MCP 服务器真正有价值的地方

显而易见的用例是开发,但这只是其中一条路子。

开发者工作流

Claude 可以:

  • 检查 repo 文件夹
  • 读取配置文件
  • 运行 git status
  • 捕获错误状态的截图
  • 打开项目目录

这已经消除了很多上下文切换。

研究工作流

Claude 可以:

  • 列出研究文件夹
  • 打开并总结 Markdown 笔记
  • 读取结构化 CSV 文件
  • 保存来自工具或仪表盘的截图
  • 打开源文件或浏览器链接

内容工作流

Claude 可以:

  • 扫描草稿文件夹
  • 读取现有的帖子想法
  • 捕获设计参考截图
  • 打开正确的写作文件或 URL
  • 运行一个有限命令来生成构建产物或草稿导出

运维工作流

Claude 可以:

  • 检查已批准目录中的日志
  • 运行只读的诊断命令
  • 打开相关文件夹或仪表盘链接
  • 保存证据截图

这才是这套架构的真正意义:不是作为噱头的“计算机控制”,而是工作流压缩

安全层就是产品本身

这是太多技术文章轻描淡写的部分。

本地 MCP 的危险不在于协议,而在于糟糕的权限设计。

如果你想让这个服务器不止于演示,请尽早构建安全模型。

Swati Gupta - inline image

使用目录白名单

Claude 只能看到你明确批准的路径。这就是为什么 _resolve_path() 是文件工具的核心。

使用命令白名单

在第一版中永远不要暴露任意的 Shell 执行。从你可以逐行审核的精确命令开始。

将读取工具与操作工具分开

只读工具应该是默认的。操作工具应该有意地逐步引入。

记录所有操作

即使是一个简单的仅追加 JSON 日志,也能极大地提升调试和信任度。

为写入操作添加确认层

如果你以后添加 write_filemove_filedelete_file,请让这些工具要么需要第二个确认令牌,要么默认保持禁用。

考虑干运行模式

对于可执行操作的工具,干运行模式被低估了。它让 Claude 可以在执行前解释它将要做什么。

尽可能在受限用户下运行

如果你认真对待本地自动化,不要给你的 MCP 服务器超过所需的操作系统权限。

一个有用的经验法则:

  • 级别 1: 只读工具
  • 级别 2: 低风险操作,如打开文件/打开应用
  • 级别 3: 需确认的写入操作
  • 级别 4: 破坏性操作,通常不应随意暴露

大多数人永远不需要级别 4。

Swati Gupta - inline image

v1 之后可以改进的地方

一个坚实的第一版为你赢得变得更强大的权利。

一旦基本服务器稳定下来,下一个合理的升级包括:

  1. 集中化策略文件

将你的规则移到 config/policy.json 中,这样修改就是声明式的。

示例:

json
1{
2 "allowed_roots": [
3 "~/Documents",
4 "~/Desktop",
5 "./"
6 ],
7 "allowed_commands": [
8 "pwd",
9 "ls",
10 "git status",
11 "git diff --stat",
12 "python --version"
13 ]
14}
  1. 结构化日志

将工具调用、时间戳、参数和结果记录到 logs/server.log 或 JSONL 文件中。

  1. 更安全的命令执行

不要使用一个通用的命令工具,而是将命令拆分成更窄的工具,比如:

  • git_status
  • show_current_directory
  • list_project_files

这样 Claude 更容易选择工具,对你来说也更安全。

  1. 更好的截图处理

你可以从“将截图保存到磁盘”演变为:

  • 带时间戳的截图
  • 活动窗口截图
  • 区域截图
  • 文件保留规则
  1. 特定操作系统的自动化适配器

在 macOS 上,你以后可以添加 AppleScript 或 Shortcuts。在 Windows 上,添加 PowerShell 或 UI Automation。在 Linux 上,添加桌面特定的启动器和窗口工具。

但这应该在枯燥的本地核心功能可靠之后再进行。

人们在使用本地 MCP 服务器时常犯的错误

这些错误是可以预见的。

错误 1:过早赋予太多权力

人们喜欢完全控制电脑的想法,但讨厌调试它。从小处开始。

错误 2:模糊的工具名称

如果你的工具名称含糊不清,Claude 就会用得很糟糕。要明确。

不好的例子:

  • system_action
  • computer_control

更好的例子:

  • list_files
  • read_file
  • run_command
  • take_screenshot
  • open_target

错误 3:非结构化输出

混合文本的块状内容比干净的 JSON 对象更难让 Claude 推理。

错误 4:没有日志

如果工具失败而你无法看到原因,系统就成了猜谜游戏。

错误 5:把模型当作控制层

Claude 是推理层。你的服务器仍然是执行层。

这个区别是不可妥协的。

这套架构比普通自动化好在哪

传统的桌面自动化通常是以下两种之一:

  • 脆弱的 GUI 脚本
  • 孤立的脚本,需要人类知道何时运行它们

本地 MCP 服务器改变了这一点,因为 Claude 可以根据用户的请求和可用上下文决定使用哪个工具。

这意味着你不仅仅是在自动化一个命令。你是在构建一个模型可以推理的本地能力层。

这就是为什么 MCP 感觉很重要。它不仅仅是另一种集成模式。它是一种更清晰的方式,将工具使用暴露给模型,而无需在应用层硬编码每一种可能的工作流。

你应该尊重的限制

即使是一个好的本地 MCP 服务器也有实际的限制。

  • 桌面自动化在不同操作系统之间可能不稳定。
  • 截图有用,但不是魔法。
  • 启动应用容易;可靠的 UI 操作更难。
  • 通用的 Shell 访问是危险的。
  • 如果工具输出太大,上下文膨胀是真实存在的问题。
  • 对于任何重要的事情,人类批准仍然有价值。

换句话说:不要混淆“模型可以采取行动”和“模型应该在没有监督的情况下行动”。

更有价值的模式是协作控制,而不是盲目自主。

一键保存

使用 YouMind AI 深度阅读爆款文章

保存原文、追问细节、总结观点,并在一个 AI 工作空间里把爆款文章沉淀成可复用笔记。

了解 YouMind
写给创作者

把你的 Markdown 变成干净的 𝕏 文章

图片上传、表格、代码块,往 𝕏 上手动重排太痛苦。YouMind 把整篇 Markdown 一键转成干净、可直接发布的 𝕏 文章草稿。

试试 Markdown 转 𝕏

更多可拆解样本

近期爆款文章

探索更多爆款文章