当前位置: 首页 > news >正文

MCP 本地工具服务器实战:文件搜索和 SQLite 查询做好安全边界,再用 cpolar 远程联调

MCP 本地工具服务器实战:文件搜索和 SQLite 查询做好安全边界,再用 cpolar 远程联调

本地 MCP 工具最容易踩的坑,不是服务跑不起来,而是一上来就把“读文件、查数据库”开得太大。AI IDE 连上以后确实爽,但它能搜到什么目录、能查哪张表、能不能写库,这些边界如果没提前划好,后面排错会很难受。

这篇就做一个小而完整的本地 MCP 工具服务器:只允许搜索指定目录里的文件,只允许查询 SQLite 的白名单表,再用 token 做一层简单鉴权。最后用 cpolar 开一个临时 HTTPS 地址,方便远程同事或另一台机器联调。重点不是把服务暴露出去,而是把“能暴露什么、暴露多久、怎么收回来”讲清楚。

本文示例使用 Python MCP SDK。MCP 官方 Python SDK 支持stdioSSEStreamable HTTP等传输方式,本文用Streamable HTTP做本地和远程联调演示。

1 什么是 MCP 本地工具服务器?

MCP,全称 Model Context Protocol,可以把本地能力包装成标准工具,让 AI IDE、Agent 客户端按统一协议调用。放到今天这个场景里,它负责三件事:

  • 把文件搜索封装成一个工具
  • 把 SQLite 查询封装成一个工具
  • 给客户端提供统一的/mcp接入入口

说白了,它不是让模型“随便操作你的电脑”,而是你把能力切成一个个受控工具,再交给客户端调用。

这里有个血泪教训:文件工具和数据库工具都不能图省事。文件搜索如果直接从用户输入拼路径,很容易搜到工作目录之外;SQL 查询如果直接执行完整 SQL,就会把删表、改表、导出敏感字段这些风险一起带进来。

所以本文的目标很明确:能用,但只给最小权限。

2 环境准备:创建项目和测试数据

先准备一个干净目录,后面的命令都在这个目录里执行。这里别直接拿真实项目根目录开搞,先用示例数据跑通链路,后面再替换成自己的只读目录。

mkdir -p ~/mcp-local-tools-demo/{workspace,data} cd ~/mcp-local-tools-demo python3 -m venv .venv source .venv/bin/activate pip install "mcp[cli]" aiosqlite

确认 Python 和依赖已经装好:

python --version python -c "import mcp, aiosqlite; print('mcp demo ready')"

这一步不是走流程,而是提前确认环境没问题。如果这里报ModuleNotFoundError,优先检查虚拟环境是否已经source .venv/bin/activate

2.1 准备允许搜索的文件目录

我们只让工具搜索workspace目录,别让它碰整个用户目录。先放几份测试文件:

cat > workspace/readme.md <<'EOF' # Demo Project This project contains API notes and deployment records. EOF cat > workspace/api-notes.txt <<'EOF' The order API reads from SQLite and returns readonly reports. EOF cat > workspace/secret.env <<'EOF' API_KEY=demo_key_should_not_be_returned EOF

注意,secret.env是故意放进去的。后面代码会跳过.env文件,这样能验证过滤规则真的生效。

2.2 准备 SQLite 只读示例库

再创建一个 SQLite 数据库,只放一张允许查询的表:

python - <<'PY' import sqlite3 from pathlib import Path Path('data').mkdir(exist_ok=True) conn = sqlite3.connect('data/app.db') conn.execute('DROP TABLE IF EXISTS tickets') conn.execute('CREATE TABLE tickets (id INTEGER PRIMARY KEY, title TEXT, status TEXT)') conn.executemany( 'INSERT INTO tickets (title, status) VALUES (?, ?)', [ ('MCP file search boundary', 'open'), ('SQLite readonly query', 'done'), ('cpolar remote debug session', 'open'), ], ) conn.commit() conn.close() PY

查一下数据:

sqlite3 data/app.db "SELECT id, title, status FROM tickets;"

如果本机没有sqlite3命令行工具,也不影响后面的 Python 服务运行。这里主要是给自己看一眼数据是否写入成功。

3 编写 MCP 服务:只开放两个受控工具

现在开始写服务端。这个文件里有几个关键边界:文件目录白名单、后缀过滤、SQLite 表白名单、SQL 只读检查、token 校验。

新建server.py

cat > server.py <<'PY' import os import sqlite3 from pathlib import Path from typing import Annotated from mcp.server.fastmcp import FastMCP BASE_DIR = Path(__file__).resolve().parent WORKSPACE_DIR = (BASE_DIR / "workspace").resolve() DB_PATH = (BASE_DIR / "data" / "app.db").resolve() ACCESS_TOKEN = os.environ.get("MCP_DEMO_TOKEN", "change-me") ALLOWED_SUFFIXES = {".md", ".txt", ".py", ".json"} ALLOWED_TABLES = {"tickets"} mcp = FastMCP("local-safe-tools", json_response=True) def require_token(token: str) -> None: if token != ACCESS_TOKEN: raise ValueError("invalid token") def ensure_inside_workspace(path: Path) -> Path: resolved = path.resolve() if not resolved.is_relative_to(WORKSPACE_DIR): raise ValueError("path is outside workspace") return resolved @mcp.tool() def search_files( keyword: Annotated[str, "Keyword to search in allowed text files"], token: Annotated[str, "Access token"], ) -> list[dict[str, str]]: """Search keyword in whitelisted files under the workspace directory.""" require_token(token) keyword = keyword.strip() if not keyword: raise ValueError("keyword is required") results: list[dict[str, str]] = [] for file_path in WORKSPACE_DIR.rglob("*"): safe_path = ensure_inside_workspace(file_path) if not safe_path.is_file() or safe_path.suffix not in ALLOWED_SUFFIXES: continue text = safe_path.read_text(encoding="utf-8", errors="ignore") if keyword.lower() in text.lower(): results.append({"file": str(safe_path.relative_to(WORKSPACE_DIR)), "preview": text[:160]}) return results[:20] @mcp.tool() def query_tickets( status: Annotated[str, "Ticket status filter, such as open or done"], token: Annotated[str, "Access token"], ) -> list[dict[str, str | int]]: """Query tickets table with a readonly parameterized SQL statement.""" require_token(token) if "tickets" not in ALLOWED_TABLES: raise ValueError("tickets table is not allowed") readonly_uri = f"file:{DB_PATH}?mode=ro" with sqlite3.connect(readonly_uri, uri=True) as conn: conn.row_factory = sqlite3.Row rows = conn.execute( "SELECT id, title, status FROM tickets WHERE status = ? ORDER BY id LIMIT 20", (status,), ).fetchall() return [dict(row) for row in rows] if __name__ == "__main__": mcp.run(transport="streamable-http") PY

这里别把ACCESS_TOKEN写死到代码仓库里。示例里给了默认值,只是为了本地演示不至于卡住;正式用时用环境变量传入。

4 本地启动并用 Inspector 验证

启动服务前,先设置一个 token:

export MCP_DEMO_TOKEN="mcp-demo-20260615" python server.py

FastMCP 使用 Streamable HTTP 运行时,默认会提供本地 MCP 入口。打开另一个终端,用官方 Inspector 连接:

npx -y @modelcontextprotocol/inspector

Inspector 启动后,在页面里连接:

http://localhost:8000/mcp

连接成功后能看到search_filesquery_tickets两个工具。先调用search_files,参数填:

{ "keyword": "SQLite", "token": "mcp-demo-20260615" }

预期会返回api-notes.txtreadme.md里的片段,但不会返回secret.env。如果结果为空,先检查当前服务启动目录是否是~/mcp-local-tools-demo,再检查workspace里是否有刚才写入的文件。

再调用query_tickets

{ "status": "open", "token": "mcp-demo-20260615" }

这一步会返回状态为open的工单列表。注意这里没有让客户端提交任意 SQL,而是只提交status参数,服务端自己拼好参数化查询语句。

5 安全边界:文件、SQL、token 三层都要收紧

这类工具能不能放心给团队用,关键看边界有没有落实到代码里。

文件搜索这边,最重要的是WORKSPACE_DIRensure_inside_workspace()。客户端不能传目录,服务端也不会从用户输入里拼根路径,所有搜索都固定在白名单目录下。后缀也做了限制,.env不在ALLOWED_SUFFIXES里,密钥类文件不会进入返回结果。

SQLite 这边,示例用了mode=ro打开数据库,并且只写了固定查询:

readonly_uri = f"file:{DB_PATH}?mode=ro" conn.execute( "SELECT id, title, status FROM tickets WHERE status = ? ORDER BY id LIMIT 20", (status,), )

划重点:不要把“请输入 SQL”做成 MCP 工具参数。真要做通用查询,也要在服务端解析并限制只允许SELECT,再叠加表白名单、字段白名单和行数上限。教程里我更推荐固定工具固定 SQL,排错轻松,权限也更清楚。

Token 这一层不复杂,但很有用。它不能替代完整的认证系统,不过足够挡住误连、乱连和临时地址被转发后的低级风险。生产环境要接入更严格的鉴权和审计,不要只靠一个演示 token。

6 用 cpolar 做远程联调:只暴露 MCP 调试入口

本地验证完成后,再考虑远程联调。比如同事在另一台电脑上调 AI IDE,或者你想让一台测试机连到这台开发机的 MCP 服务,localhost:8000对外不可见,这时可以用 cpolar 开一个临时 HTTPS 入口。

先安装并启动 cpolar。Linux 官方一键安装命令如下:

curl -L https://www.cpolar.com/static/downloads/install-release-cpolar.sh | sudo bash cpolar version

macOS 可以通过 Homebrew 安装:

brew tap probezy/core && brew install cpolar cpolar version

安装后打开本地 Web UI:

open http://127.0.0.1:9200

如果是纯命令行环境,也可以手动绑定账号 token:

cpolar authtoken YOUR_CPOLAR_AUTHTOKEN

这里别把 SQLite 端口、SSH 端口、整个开发面板一起暴露出去。本文只映射 MCP 服务端口8000

cpolar http 8000

命令启动后,终端会显示一个公网 HTTPS 地址。把远程客户端的 MCP 地址改成:

https://你的随机地址.cpolar.cn/mcp

免费随机公网地址会在 24 小时内变化,适合短时联调。需要固定二级子域名时,cpolar 需要基础服务版本或以上;需要自定义域名时,需要专业服务版本或以上。对这篇教程来说,随机地址已经够用,因为我们追求的是短时调试,调完就关。

7 远程联调后的回收和排错

远程联调时,建议只把地址发给可信同事,并同步 token。调试结束后,在运行 cpolar 的终端按Ctrl+C关闭隧道;如果是 Web UI 创建的隧道,就到状态 -> 在线隧道列表确认它已经下线。

如果远程访问失败,按这个顺序查:

  1. 本机http://localhost:8000/mcp是否能被 Inspector 连接
  2. cpolar http 8000是否还在运行
  3. cpolar Web UI 的在线隧道列表里是否显示公网 HTTPS 地址
  4. 远程客户端填的路径是否带/mcp
  5. 工具参数里的 token 是否和环境变量MCP_DEMO_TOKEN一致

别一上来就怀疑 MCP 或 cpolar。很多问题其实是服务没开、路径少了/mcp,或者 token 复制时多了空格。

如果你只是自己在局域网里调试,这一节可以先跳过。cpolar 的价值在于“临时让外部机器接进来”,不是把本地工具长期裸挂在公网。

8 总结

到这里,我们已经做完了一个可联调的 MCP 本地工具服务器:文件搜索只能读白名单目录,SQLite 查询只能查固定表和固定条件,远程访问只临时暴露8000上的 MCP 入口。这个版本不花哨,但边界清楚,适合作为团队内部工具的起点。

  • 本地工具先收权限:目录白名单、文件后缀过滤、SQLite 只读连接和参数化查询都要放在服务端。
  • 联调入口短时开放:用 cpolar 暴露 MCP HTTP 端口即可,不要顺手暴露数据库、SSH 或整个开发机。
  • 调试结束要回收:关闭 cpolar 隧道、轮换演示 token,必要时把测试库和测试目录删掉。

后面要扩展也很自然:可以继续加只读日志检索、只读接口状态查询、固定报表生成等工具。我的建议是每次只加一个工具,每个工具都写清楚输入、输出和权限边界;MCP 真正好用的地方,恰恰是把能力做小、做准、做安全。

http://www.jsqmd.com/news/1018243/

相关文章:

  • 2026自贡旧金回收避坑:大盘减3至10元才是真实价,六家连锁店免费上门 - 余生黄金回收
  • 如何快速掌握百度网盘秒传脚本:3步搞定永久文件分享难题
  • 如何快速获取全球地理数据:Geo-JSON数据集的终极应用指南
  • 全国食品厂洁净室检测合规服务机构排行盘点 - 奔跑123
  • AI聊天隐私风险与三道物理隔离防护墙
  • 温州高莱居原木定制工厂 本土全屋木作定制优选 联系电话:15858009555 地址:温州瑞安市经济开发区大道3588号--望新路198号C 幢 - 资讯速览
  • 2026重庆天然翡翠回收,合扬实体老店更可信 - 奢侈品交易观察员
  • 魔兽世界字体合并补全工具:5分钟彻底告别游戏乱码
  • 告别网络卡顿!手把手教你用UnityHub国际版链接直下Unity 2022~2017(附完整版本清单)
  • 戴尔笔记本风扇控制终极方案:告别噪音困扰,轻松实现智能散热管理
  • 如何在Windows电脑上免费实现AirPlay 2投屏接收:跨平台无线屏幕共享终极指南
  • Rust Unsafe 安全规范:从避免未定义行为到构建安全抽象的工程实践
  • B站直播推流码工具:高效获取第三方推流码的完整解决方案
  • 如何让Windows掌机游戏体验媲美专业游戏主机:HandheldCompanion深度解析
  • 从‘False’到‘True’:手把手教你诊断并修复PyTorch CUDA不可用问题(Anaconda环境)
  • Windows Defender完全控制:开源工具defender-control的技术深度解析
  • 【2026年6月】库房货架厂家推荐指南|库房货架厂家,中型货架厂家,轻型货架厂家优选+广东恒隆智能储存设备有限公司 - 多才菠萝
  • Tickets:基于Rust+Tauri+Vue的高效演唱会抢票智能解决方案
  • 轻量数据库桌面客户端火了:本地连 MySQL/Redis,外出怎么用 cpolar 安全访问?
  • PXD10嵌入式开发实战:SRAM ECC安全机制与步进电机SMC驱动详解
  • MPC866异步HDLC协议硬件配置与实战解析
  • 2026 靠谱北京工商注册代办/公司注册代办公司推荐 实测数据全面解析 - 互联网科技品牌测评
  • 深入解析MPC8533E中断控制器:从架构原理到实战配置
  • 报价透明有保障 郑州十大诚信装修品牌合集 - 装修新知
  • 如何用DouyinLiveRecorder一站式录制40+平台直播内容?
  • 专业声音分析利器:Voice Pitch Analyzer深度解析
  • 【趣解】HTTP协议:浏览器和服务器“聊天“的语言
  • 零基础转行产品经理必看!3步打造高薪职场新赛道
  • C++前缀和差分(练习题)
  • 2026 年专业设计显示器怎么选?皓丽 27RUA-LA 核心卖点与选购建议 - 服务品牌热点