# mcp-ProjectFiles
**Repository Path**: hfut-chf/mcp-ProjectFiles
## Basic Information
- **Project Name**: mcp-ProjectFiles
- **Description**: 可读去查看项目文件的mcp,可直接用于chatgpt
- **Primary Language**: Python
- **License**: Apache-2.0
- **Default Branch**: main
- **Homepage**: None
- **GVP Project**: No
## Statistics
- **Stars**: 0
- **Forks**: 0
- **Created**: 2026-09-25
- **Last Updated**: 2026-10-09
## Categories & Tags
**Categories**: Uncategorized
**Tags**: None
## README
[English](README.md) | 简体中文
# Research Project Files MCP
ResearchProjectFiles 是一个 MCP Server,用于读取管理员登记的研究项目和
明确授权的外部目录,以及在每个项目配置的可写目录下创建 Markdown(默认 `docs/`)。
服务默认只监听 loopback。本项目
支持本机 Streamable HTTP 和 OpenAI Secure MCP Tunnel;远程访问不需要把 MCP
监听端口公开到互联网。
## 项目状态
当前版本为 `0.4.0`,支持 Markdown 写入、分块上传及按项目配置可写目录;定位为早期公开版本
(early public release)。
部署以 Linux 为主;仓库提供的 user systemd unit 假定使用 Linux 和用户级 systemd
manager。当前没有验证或承诺原生 Windows/macOS 部署支持。
MCP listener 始终只监听 loopback。可选 Secure MCP Tunnel client 需另行安装,并依赖
用户自己的 OpenAI Platform 与 ChatGPT workspace 权限。服务仅允许在项目配置的可写目录
下新建 Markdown,不提供 shell 或 exec tool,不会自动发现项目,并拒绝互相重叠的
Project/Grant root。
本项目采用 Apache License 2.0,详见 [LICENSE](LICENSE)。
## 项目简介与核心能力
- 一个服务可登记多个项目,每次文件调用都显式传入 `project_id`,不会在并发
会话之间共享或切换“当前项目”。
- `grant_id` 用于访问服务器管理员明确授权的外部目录,支持临时 TTL 和永久
grant。
- 项目和 grant registry 支持安全 hot reload;常规 registry 变更不用重启 MCP
或 Tunnel。
- MCP tools 提供健康检查、registry 查询、目录树、目录列表、文本读取、glob、
grep、只读 Git 状态/差异,以及限定在项目可写目录内的 Markdown 新建功能。
- 大型结果有界分页,并明确报告结果是否完整。
## 架构
```mermaid
flowchart LR
C[ChatGPT Web] --> T[OpenAI Secure MCP Tunnel]
T --> M[ResearchProjectFiles MCP
127.0.0.1:8765]
L[本机 MCP 客户端] --> M
M --> P[Project Registry
project_id]
M --> G[External Grants
grant_id]
P --> PR[已登记项目根目录]
G --> AR[已明确授权的外部目录]
```
- MCP HTTP listener 只接受 loopback host,默认是 `127.0.0.1:8765`。
- 短文件和长文档工具仅在项目的 `write_directory` 及子目录下新建 `.md`,读取工具只读;
没有覆盖、删除、shell/exec、Git 修改或 MCP registry 管理工具。
- `project_id` 只能选择服务器管理员登记的项目。
- `grant_id` 只能选择服务器管理员创建的外部目录授权;调用方不能自行传入
任意绝对路径。
## Connection Modes(连接方式)
| 方式 | 支持程度 | 说明 |
| --- | --- | --- |
| Local Streamable HTTP | 正式支持 | 同一主机使用 `http://127.0.0.1:8765/mcp`。 |
| OpenAI Secure MCP Tunnel | 正式支持 | 推荐的远程接入方式;`tunnel-client` 在内网连接本机 MCP,并向 OpenAI 建立出站连接。 |
| Public HTTPS MCP / reverse proxy | 高级架构选项 | 本仓库不配置、不测试。MCP Server 仍应保持 loopback-only;如需公网入口,应由单独的安全外层网关提供认证和入口控制。 |
| Private network/VPN | 不能直接访问 | MCP listener 仍绑定 loopback;可在能够访问服务的网络边界运行 Tunnel client 或经过安全设计的外层网关。 |
不要为了远程访问将 MCP Server 改成监听 `0.0.0.0`。请使用
[Secure MCP Tunnel](README.md#secure-mcp-tunnel),或单独设计带认证的外层网络
入口。
## 快速开始
### A. Local MCP quick start
需要 Linux、Python 3.10+ 和 Python venv 支持。Debian/Ubuntu 缺少 venv 时,安装
与当前 Python 版本对应的 `python3-venv` 包即可。systemd 示例假设仓库位于
`$HOME/MCPServer/ResearchProjectFiles`。
以下 SSH clone 地址假定机器已经配置 Gitee SSH key:
```sh
mkdir -p "$HOME/MCPServer"
git clone git@gitee.com:hfut-chf/mcp-ProjectFiles.git \
"$HOME/MCPServer/ResearchProjectFiles"
cd "$HOME/MCPServer/ResearchProjectFiles"
```
如果没有配置 SSH key,请使用 Gitee 仓库页面显示的对应 HTTPS clone URL;本文
档不猜测或虚构该地址。
创建隔离环境、安装项目并初始化本机 registry。`cp -n` 不会覆盖已有运行时文件:
```sh
python3 -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install -e .
cp -n config/projects.example.yaml config/projects.yaml
cp -n config/grants.example.yaml config/grants.yaml
chmod 600 config/projects.yaml config/grants.yaml
```
登记一个已存在的项目目录。请替换绝对路径;只有当项目根目录是 Git worktree
并且需要只读 Git 工具时才保留 `--git`:
```sh
.venv/bin/research-project-files-admin project add \
--id my_project --name "My Project" \
--root /absolute/path/to/my/project --git
```
安装并启动本机 MCP 用户服务,再检查 health 并运行协议 smoke client:
```sh
mkdir -p "$HOME/.config/systemd/user"
cp deploy/research-project-files-mcp.service "$HOME/.config/systemd/user/"
systemctl --user daemon-reload
systemctl --user enable --now research-project-files-mcp.service
curl --fail http://127.0.0.1:8765/healthz
.venv/bin/python scripts/smoke_client.py \
--url http://127.0.0.1:8765/mcp --project-id my_project
```
此时 MCP Server 只能通过本机 `127.0.0.1` 访问。如果要让 ChatGPT Web 或其他远程
MCP 客户端使用它,请继续完成下面的 Remote access 步骤。
### B. ChatGPT Web / Remote access quick start
开始前确认本机 `/healthz` 已通过,并且你有权限在目标 Platform organization
和 ChatGPT workspace 使用 Secure MCP Tunnel。不要把真实 Tunnel ID、API key 或代理
地址写入仓库。
1. 打开 [OpenAI Platform Tunnel settings](https://platform.openai.com/settings/organization/tunnels),
创建 Secure MCP Tunnel,将它关联到要使用该 App 的 ChatGPT workspace,并记下
`tunnel_id`。
2. 按 Platform 页面下载选项或[官方 Tunnel 指南](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels)
安装官方 `tunnel-client`,放到 unit 约定的位置
`$HOME/.local/bin/tunnel-client`。
3. 创建 profile/state 目录,并复制仓库内模板和 Tunnel user unit:
```sh
mkdir -p "$HOME/.config/project-files-mcp/tunnel-profiles" \
"$HOME/.local/state/tunnel-client/health" \
"$HOME/.local/state/tunnel-client/logs" \
"$HOME/.config/systemd/user"
cp -n tunnel-profiles/project-files.example.yaml \
"$HOME/.config/project-files-mcp/tunnel-profiles/project-files.yaml"
cp deploy/research-project-files-tunnel.service \
"$HOME/.config/systemd/user/"
```
4. 编辑用户 profile:填写 `control_plane.tunnel_id`;将
`control_plane.api_key` 设置成单独 key 文件的 `file:/absolute/path/to/key`
引用;把 `health.url_file` 和 `log.file` 设置为
`$HOME/.local/state/tunnel-client/` 下的绝对路径(写入 profile 时要将 `$HOME`
展开成真实 home 路径)。安全保存 API key 并执行
`chmod 600 /absolute/path/to/key`。MCP URL 保持为
`http://127.0.0.1:8765/mcp`;不要提交填好的 profile 或 key。
5. 如果网络受限,通过
`$HOME/.config/project-files-mcp/tunnel.env` 为 `tunnel-client` 配置代理。
提供的 unit 只为 Tunnel 进程加载这个可选 EnvironmentFile。只填写当前
`tunnel-client` 版本支持的代理变量/选项;不要启用全局 TUN 或修改系统默认路由。
6. 启动 Tunnel unit 并检查运行状态和日志:
```sh
systemctl --user daemon-reload
systemctl --user enable --now research-project-files-tunnel.service
"$HOME/.local/bin/tunnel-client" runtimes status project-files --json
journalctl --user -u research-project-files-tunnel.service -n 100 --no-pager
```
7. 在 ChatGPT Web 中启用 Developer Mode,选择 **Create app**,将
**Connection** 设为 **Tunnel**,再选择列表中的 Tunnel 或填写
`tunnel_id`。Tunnel 必须关联到目标 ChatGPT workspace,App 创建者还需有
Tunnel read/use 权限。
8. 在新 App 中验证 `health`、`list_projects` 和 `read_file`。`read_file` 使用
已登记的 `project_id` 和该项目内允许读取的文本文件路径。
更完整的权限、网络及排障说明见英文 README 的
[Secure MCP Tunnel](README.md#secure-mcp-tunnel) 和
[ChatGPT Web configuration](README.md#chatgpt-web-configuration) 章节。
## Project Registry(项目 Registry)
`config/projects.yaml` 将稳定 ID 映射到 canonical project root。仓库只提供空的
`projects.example.yaml`;运行时 registry 已被 Git 忽略。推荐用本机唯一写入口
`research-project-files-admin` 修改:
```sh
.venv/bin/research-project-files-admin project list
.venv/bin/research-project-files-admin project show my_project
.venv/bin/research-project-files-admin project add \
--id my_project --name "My Project" --root /absolute/path/to/project --git
.venv/bin/research-project-files-admin project update my_project \
--name "Renamed Project" --root /absolute/path/to/project --no-git
.venv/bin/research-project-files-admin project disable my_project
.venv/bin/research-project-files-admin project enable my_project
.venv/bin/research-project-files-admin project remove my_project
```
ID 格式是 `[a-z0-9_-]{1,64}`。root 必须是绝对路径和已存在目录,服务会将其
canonicalize。`--git` 开启该项目的只读 Git 工具;`--no-git` 关闭。管理员本地
CLI 可以显示 root;MCP 的 `list_projects`、`project_info` 和 `health` 不返回绝对
root。
Project Registry 支持 hot reload。执行 `add`、`remove`、`update`、`enable` 或
`disable` 后,下一次相关 MCP 调用会使用完整校验后的新 registry;不需要重启 MCP、
不需要重启 Tunnel、也不需要 Refresh Actions。CLI 会联合验证两个 registry,并原子替换
文件。变更后的 registry 格式错误时,该 registry 的 scope 会 fail closed。如果 Project
与 Grant 的合并 root 集合发生重叠,两个 registry 都会标记为 invalid,所有 scope resolution
都会 fail closed,直到修正配置并重新加载;`health` 会报告 degraded,不会保留存在歧义的
授权。只有 MCP tool schema 变化时才需要 Refresh/reconnect ChatGPT App 以加载新工具。
### 按项目配置可写目录
在 `config/projects.yaml` 中为各项目设置 `write_directory`。省略时默认为
`docs`,旧 registry 无需修改即可继续使用:
```yaml
version: 1
projects:
my_project:
name: My Project
root: /absolute/path/to/project
enabled: true
git: false
write_directory: reports/proposals
```
本地管理命令支持在新增和更新项目时设置:
```sh
.venv/bin/research-project-files-admin project add \
--id my_project --name "My Project" --root /absolute/path/to/project \
--write-directory reports/proposals
.venv/bin/research-project-files-admin project update my_project \
--write-directory reports/proposals
```
不同项目可以使用不同目录,支持嵌套路径。配置值必须是相对项目根目录的非隐藏、
非敏感子目录;拒绝空值、整个项目根目录(`.`)、绝对/Windows 路径、目录遍历、
隐藏元数据目录和敏感组件。写入时仍拒绝符号链接父目录和被 ignore 隐藏的路径。
`list_projects` 和 `project_info` 返回当前生效的 `write_directory`,不暴露绝对根目录。
调用方应先查询,不能假定所有项目都使用 `docs/`。文档路径始终**相对项目根目录**,
必须包含可写目录前缀;上述配置应传 `reports/proposals/experiment.md`。
配置会在下一次调用时热更新。新写入按当前目录检查;原路径不再被允许的未完成上传
无法提交。已有文件保留,不会自动迁移。使用 systemd 时,还需在主机上创建新目录,
更新 `ReadWritePaths`(移除旧目录条目)并 reload/restart 服务;registry 热更新不会
更改操作系统权限。
## External Grant(外部目录授权)
External Grant 是管理员创建的窄范围只读授权,用于访问不在项目 Registry 内的目录。
ChatGPT 不能创建 grant,也不能向服务器传入任意绝对路径。调用方必须使用服务器
先前创建的 `grant_id`,路径只能相对该 grant 的 canonical root 解析。
创建临时 grant(必须提供 TTL)或明确的 permanent grant:
```sh
.venv/bin/research-project-files-admin grant add \
--id shared_papers --name "Shared Papers" \
--root /absolute/path/to/shared-papers --ttl 2h
.venv/bin/research-project-files-admin grant add \
--id reference_data --name "Reference Data" \
--root /absolute/path/to/reference-data --permanent
```
本机管理、检查与变更:
```sh
.venv/bin/research-project-files-admin grant list
.venv/bin/research-project-files-admin grant show shared_papers
.venv/bin/research-project-files-admin grant disable shared_papers
.venv/bin/research-project-files-admin grant enable shared_papers
.venv/bin/research-project-files-admin grant revoke shared_papers
```
TTL 使用正整数分钟 `m`、小时 `h` 或天 `d`,最多 365 天。到期时间以带时区的 UTC
保存;每次 scope resolution 都会检查当前时间,所以 TTL 到期不依赖 registry reload。
过期 grant 会保留用于审计,但立即变为 inactive,不能重新 enable;需要时创建新的
grant。过期或已撤销的 grant 不再保留 root reservation,因此可以对同一目录重新创建 Grant
或注册 Project;终止状态的 Grant 记录仍保留在 registry 和 `list_grants` 中用于审计。
`disable` 可恢复,除非已 revoke;临时 grant 只有在过期前才能重新 enable。`revoke` 是永久
撤销,不能恢复。Grant 和 Project 使用相同的 root safety policy:过宽或受保护的 root,以及
包含 hard secret deny component 的 root 都会被拒绝;应选择具体且已存在的目录。
Grant 变更也会在下一次相关 MCP 调用 hot reload,不需要重启 MCP 或 Tunnel,也不需要
Refresh Actions。Grant 的所有管理操作都只通过本机 admin CLI 完成,没有对应 MCP tool。
## MCP Tools
MCP 暴露以下只读工具,以及仅允许新建 Markdown 的文档工具:
| Tool | Scope / 参数 | 作用 |
| --- | --- | --- |
| `health` | 无 | Server/registry 健康状态、generation/status 和汇总计数。 |
| `list_projects` | 无 | 已登记项目 ID 与可用状态。 |
| `project_info` | `project_id` | 单个项目的状态、root basename 和可选 Git 摘要。 |
| `list_grants` | 无 | Grant 状态,包括 active/expired/disabled。 |
| `tree` | `project_id` / `grant_id` 二选一,可选 `path` | 有深度与条目数上限的目录树。 |
| `list_dir` | Scope、`path`、`offset`、`limit` | 稳定排序的目录分页。 |
| `read_file` | Scope、`path`、`offset_line`、`limit_lines` | 有长度上限的 UTF-8 文本页。 |
| `create_file` | `project_id`、`path`、`content` | 短 `.md` 单次写入;默认上限 2 MiB UTF-8 字节。 |
| `begin_document` | `project_id`、`path` | 开始长 `.md` 的临时上传,返回任务 ID 和限制。 |
| `write_chunk` | `project_id`、`upload_id`、`index`、`content` | 上传指定编号的片段;相同内容可安全重试。 |
| `finish_document` | `project_id`、`upload_id`、`total_chunks` | 校验并按顺序合并为新 `.md`,不覆盖。 |
| `glob` | Scope、`pattern`、`offset`、`max_results` | 文件路径匹配。 |
| `grep` | Scope、`pattern`、可选 `path`、`case_sensitive`、`offset`、`max_results` | 文本行搜索。 |
| `git_status` | 仅 Project scope | 只读 Git 摘要和分页条目。 |
| `git_diff` | 仅 Project scope | 只读 diff 摘要和行分页。 |
每次 filesystem tool 调用必须且只能传入一个有效 scope:
```json
{"project_id":"my_project","path":"README.md"}
```
或:
```json
{"grant_id":"shared_papers","path":"paper.txt"}
```
Git tools 只允许 `git: true` 且 root 是 Git worktree 的项目。Grant 不提供 Git 能力。
MCP 没有 registry 管理工具。
### 保存 proposal 并记住位置
`create_file` 用于**短文件,一次调用写入全部内容**。整个 `content` 必须同时满足
服务器字节上限和模型/客户端的单次调用限制。超限会整次拒绝,不会截断,也不支持追加。
长文档请使用下文的分块流程。
所有文档工具只支持已登记且启用的 Project,不支持 Grant。`path` 是相对项目
根目录的路径,必须以该项目的 `write_directory`(默认 `docs/`)开头;允许子目录,
缺失的子目录会自动创建。拒绝绝对路径、可写目录外的路径、符号链接父目录(即使指向项目内部)、非 `.md`
文件和已有目标。默认内容上限为 2 MiB UTF-8 字节,可通过
`PROJECT_MCP_MAX_WRITE_BYTES` 调整(范围为 1024~10485760 字节)。
```json
{
"project_id": "my_project",
"path": "docs/proposals/experiment-v1.md",
"content": "# 实验方案\n\n## 目标\n验证假设。\n"
}
```
成功后返回 `created: true`、`project_id`、规范化的 `path`、`bytes_written`、
`sha256` 和 `memory_reference`(项目 ID、相对路径、校验值),不暴露项目绝对根目录。
文件以私有权限原子发布,不会覆盖同名目标;修订时请使用新文件名。
### 长文档:分块上传与合并
1. 调用 `begin_document(project_id, path)`,例如路径 `docs/proposals/long-v1.md`,
保存返回的 `upload_id` 和当前限制。
2. 每次调用 `write_chunk(project_id, upload_id, index, content)`,始终传同一个
项目 ID。编号从 `0` 开始,允许乱序上传。同编号、同内容的重试不会重复追加;
同编号的不同内容会拒绝。
3. 调用 `finish_document(project_id, upload_id, total_chunks)`,传准确的片段数量。
编号必须恰好为 `0..total_chunks-1`,缺块或多块都会拒绝。服务器校验片段完整性,
按编号流式合并,再原子发布最终 `.md`。
开始上传:
```json
{"project_id":"my_project","path":"docs/proposals/long-v1.md"}
```
使用返回的任务 ID 逐块上传:
```json
{"project_id":"my_project","upload_id":"<返回的 ID>","index":0,"content":"# 实验方案\n\n"}
{"project_id":"my_project","upload_id":"<返回的 ID>","index":1,"content":"## 方法\n方法说明。\n"}
```
最后合并:
```json
{"project_id":"my_project","upload_id":"<返回的 ID>","total_chunks":2}
```
合并**不会自动添加换行或分隔符**,请在片段中保留需要的 Markdown 排版。
每块必须是有效 UTF-8 文本,不接受二进制。`finish_document` 成功前不会生成正式文档
或最终记忆引用。开始上传不预占文件名;如果其他写入者先创建同名文件,完成上传会拒绝。
片段私有暂存在已登记项目之外的临时目录。
| 配置项 | 默认值 | 允许范围 |
| --- | --- | --- |
| `PROJECT_MCP_MAX_WRITE_BYTES`(短文件) | 2 MiB / 2097152 字节 | 1024~10485760 字节 |
| `PROJECT_MCP_MAX_CHUNK_BYTES` | 64 KiB / 65536 字节 | 1024~1048576 字节 |
| `PROJECT_MCP_MAX_DOCUMENT_BYTES` | 20 MiB / 20971520 字节 | 1024~104857600 字节 |
| `PROJECT_MCP_MAX_DOCUMENT_CHUNKS` | 1024 | 1~4096 |
| `PROJECT_MCP_MAX_DOCUMENT_UPLOADS`(全服务未完成任务) | 16 | 1~128 |
| `PROJECT_MCP_DOCUMENT_UPLOAD_TTL_SECONDS` | 3600 秒 | 60~86400 秒 |
大小上限按 **UTF-8 字节**计算,不是字符数或 token 数。工具描述、
`health.document_creation` 和 `begin_document` 会报告当前生效限制。
模型/客户端的预算可能要求使用更小的片段。全服务暂存总量受未完成任务数与整篇
大小上限的乘积约束。
有效期从开始上传计时,上传片段不会续期。后台至少每 60 秒清理过期暂存,上传请求
和 health 也会清理。完成后立即删除片段。完成回执支持重复提交,直到任务过期、
服务重启或从最多 1024 条的回执缓存中淘汰。**服务重启后上传任务不保留**,未完成任务
需重新开始;已经发布的文档仍保留在磁盘上。
大于普通读取上限的项目可写目录内 `.md` 支持 `read_file` 流式行分页,文件大小可达配置的
整篇文档上限。`PROJECT_MCP_MAX_READ_BYTES` 仍限制每页和单行大小;请使用正常的
Markdown 换行,并跟随 `next_offset_line` 读取。其他文件和 Grant 的读取上限保持不变。
文档工具返回的 `memory_saved: false` 表明它们本身不写记忆。GPT 应在
`create_file` 或 `finish_document` 返回 `created: true` 后,
调用已有的项目记忆工具(例如 ResearchContext 对应项目的 `remember_project`),
保存文档摘要和返回的引用。只有记忆调用成功后,才能报告“已存入记忆”。
记忆保存失败时保留文件引用,只重试记忆步骤,不重复创建文件。后续凭记忆中的
`project_id` 和 `path` 调用 `read_file` 即可读取 proposal。
升级运行中的服务后,需要重启 MCP,并 Refresh/reconnect ChatGPT App 以加载新工具。
使用 systemd 时还需按下文开放 `docs/` 的写权限。Smoke client 会检查新增工具和
annotations,但不会在真实项目里创建测试文档。
## 分页与完整性语义
大型结果使用有界分页。常见字段含义如下:
- `total_count`:扫描完成时的匹配总数;超时且无法完成计数时可为 `null`。
- `truncated: true` 或 `complete: false`:当前结果对请求范围而言不完整。
- `has_more: true`:还有后续页。
- `next_offset`:继续读取条目/路径页时使用的 offset。
- `next_offset_line`:继续 `read_file` 或 `git_diff` 行页时使用的行 offset。
- `complete: true`:服务确认当前查询范围已到末尾。对 `tree` 还要看 `coverage` 和
`truncation_reason`:所请求深度范围完整,不代表整个递归子树完整。
普通探索使用小页即可。涉及“全部、所有、只有、没有遗漏、完整、exhaustive”等
穷尽判断时,必须继续使用对应的 next offset,直到 `complete=true`,或者缩小查询范围。
不能把单页或 `truncated` 结果当成全局完整结论。如果超时导致 `total_count` 未知,
应缩小查询后再判断。
## 安全模型
- **仅 loopback:** `PROJECT_MCP_HOST` 只接受 `127.0.0.1`、`::1` 或 `localhost`,
默认是 `127.0.0.1:8765`;不要绑定 `0.0.0.0`。
- **仅限新建文档:** 文档工具只在项目配置的可写目录及子目录内新建 `.md`,限制内容
字节数,拒绝覆盖和符号链接父目录。创建/开始上传声明非幂等,分块/完成支持安全重试;
四个工具都声明非只读、非破坏性 annotations。
其余工具仍声明只读。没有 shell/exec、删除、任意 Git 命令或 Git 修改 tool。
- **显式 scope:** Project root 来自管理员维护的 Project Registry;外部目录必须有
管理员创建的 Grant。
- **Scope 不重叠:** 所有 Project root 在登记期间都会保留其目录,无论是否启用。Grant 在
active 或 disabled 但仍可重新启用时保留 root reservation。过期或已撤销的 Grant 会释放
reservation,但记录仍保留在 `list_grants` 中用于审计。相同 root、祖先/后代 root 均会拒绝;
检查覆盖 project/project、project/grant 和 grant/grant。
- **统一 root safety policy:** Project 和 Grant 共用同一策略。禁止 `/`、`/home`、当前用户的
HOME,以及 `/etc`、`/proc`、`/sys`、`/dev`、`/run` 及其所有后代。`/root`、`/usr`、`/var`、
`/opt`、`/srv`、`/boot`、`/bin`、`/sbin`、`/lib`、`/lib32`、`/lib64`、`/media`、`/mnt`、
`/tmp` 等过宽顶层目录本身不能作为 root;具体子目录如 `/srv/research/foo`、`/opt/project`
可以登记。hard secret deny component 在任何 root 中仍然禁止。
- **路径约束:** 输入路径会规范化并解码;拒绝绝对路径、Windows 路径、遍历和 NUL,
解析后的路径必须仍在选定 root 内。
- **Symlink 防逃逸:** 指向选定 root 外部的 symlink 会被隐藏或拒绝;一个 scope 不能
通过 symlink 进入其他 scope。
- **强制 secret deny:** 永久拒绝 `.env`、`.env.*`、`*.pem`、`*.key`、`*.p12`、
`*.pfx`、`id_rsa`、`id_ed25519`、`credentials*`、`secrets*`、`token*`、`auth*`、
`service-account*.json`、`.netrc`、`.npmrc`、`.pypirc` 和 `*.kdbx`;`.aws`、
`.ssh`、`.gnupg` 目录也始终隐藏。Ignore 文件不能覆盖这些规则。
- **Registry 安全:** Admin CLI 验证并原子替换 YAML;MCP 按文件 signature 检查并
原子切换 validated snapshot。变化后的无效 registry fail closed,并令 health
degraded。
- **Grant TTL:** 每次 resolution 都与当前 timezone-aware UTC 比较,不会把有效/过期
状态永久缓存到加载时刻。
- **不泄露 root/key:** MCP 项目/health 响应不包含 absolute roots;audit log 不记录
roots、文件内容或 key。具有管理员权限的本机 CLI 会显示 root。
- **操作系统边界:** systemd unit 使用 `ProtectSystem=strict`、`ProtectHome=read-only`
等 hardening。仅通过 `ReadWritePaths` 开放具体项目可写目录的写权限。
应用自身的 PathPolicy 仍负责动态项目/grant root 的边界检查。
- **Git 只读:** 仅固定的 status/diff 操作可用;MCP 不提供 shell/exec。
默认 ignore 在 `config/default.mcpignore`;项目专属补充规则可放在
`config/project_ignores/.mcpignore`。默认规则隐藏 Git metadata、环境、
缓存和模型/数组产物,同时保留文本 outputs/logs。
## systemd 用户服务
### 开放项目可写目录的文档写入权限
默认 unit 的文件系统为只读。要让 GPT 保存 proposal,请先在主机上创建对应
项目的 `write_directory`,再通过 systemd drop-in 仅开放这个目录。将示例路径替换为
已登记的项目根目录:
```sh
mkdir -p /absolute/path/to/project/docs
mkdir -p "$HOME/.config/systemd/user/research-project-files-mcp.service.d"
```
将以下内容保存到
`~/.config/systemd/user/research-project-files-mcp.service.d/docs.conf`:
```ini
[Service]
ReadWritePaths=/absolute/path/to/project/docs
```
示例采用默认 `docs/`。若配置为 `write_directory: reports/proposals`,请将 `mkdir` 和
`ReadWritePaths` 中的路径均改为 `/absolute/path/to/project/reports/proposals`。
每个需要写文档的项目添加一条 `ReadWritePaths`。这些目录必须预先存在,且服务用户
具有写权限。仓库提供模板 `deploy/research-project-files-docs.conf.example`。
保留 `ProtectSystem=strict` 和 `ProtectHome=read-only`,不要开放整个项目根目录。
修改 drop-in 后运行:
```sh
systemctl --user daemon-reload
systemctl --user restart research-project-files-mcp.service
```
工具会自动创建已开放目录内的子目录。使用 systemd 时,配置的可写目录必须在
主机上预先创建,再启动服务。
### 安装服务
仓库中的 unit 假设安装路径为 `$HOME/MCPServer/ResearchProjectFiles`。MCP unit 使用
`.venv` 内安装的程序,将 `RESEARCH_FILES_CONFIG_DIR` 指向仓库的 `config/`,监听
`127.0.0.1:8765`:
```sh
mkdir -p "$HOME/.config/systemd/user"
cp deploy/research-project-files-mcp.service "$HOME/.config/systemd/user/"
systemctl --user daemon-reload
systemctl --user enable --now research-project-files-mcp.service
systemctl --user status research-project-files-mcp.service
```
Tunnel unit 是可选项,它依赖 MCP unit,并在本机 `/healthz` 通过后才启动。安装和
启用命令见 Remote quick start。用户 systemd manager 启动时会运行已 enable 的 user
units;登录/开机 lingering 等主机策略不由本仓库配置。
## Secure MCP Tunnel
Secure MCP Tunnel 是出站传输:`tunnel-client` 运行在能访问 MCP Server 的网络中,
通过出站 HTTPS 连接 OpenAI,再将请求转给私有的
`http://127.0.0.1:8765/mcp`。它不要求入站防火墙开放或公开 MCP listener。Platform
和 Tunnel client 的当前说明见[官方 Secure MCP Tunnel 指南](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels)。
本仓库只提供通用 profile 模板和用户 service:
- `tunnel-client` 需单独安装在 `$HOME/.local/bin/tunnel-client`。
- 示例模板为 `tunnel-profiles/project-files.example.yaml`;填好的本机 profile
路径是 `$HOME/.config/project-files-mcp/tunnel-profiles/project-files.yaml`。
- Profile 包含 `tunnel_id`、API key file 引用、本机 MCP URL、health/log 路径。
真实 Tunnel ID 和 key 必须只保留在本机。
- Unit 从 `$HOME/.config/project-files-mcp/tunnel.env` 读取可选环境配置;可写状态
目录限制在 `$HOME/.local/state/tunnel-client`。
- 更换 Tunnel 时只需使用另一个本机 profile/Tunnel ID;MCP Server 不依赖 Tunnel ID。
Registry 变更由 MCP hot reload,不需要重启 Tunnel。部署新 Python 代码可能只需重启
MCP service;只要 tool schema 没变,Tunnel 和 ChatGPT App 可保持不变。
## 受限网络与代理
Tunnel client 需要出站 HTTPS 到 OpenAI,并且本机可访问 MCP URL;不需要入站互联网
访问。若主机只能通过代理访问 OpenAI,将当前安装版本支持的代理设置写入
`~/.config/project-files-mcp/tunnel.env`。Tunnel unit 会通过可选的
`EnvironmentFile` 只向 Tunnel service 注入这些设置,因此不会修改全局 shell 环境。
具体代理变量名/参数因 client 版本而异,应查对应版本文档;仓库不预设真实代理地址
或凭证。按 unit 配置让 localhost 绕过代理;不要启用全局 TUN、改写系统路由或公开
本机 admin/health 端口。
## ChatGPT Web 配置
Tunnel 可见性和 Developer Mode 受不同权限控制:
- 在拥有 Tunnel 的 Platform organization 中创建/管理;创建需要 Tunnels Read +
Manage。
- 将 Tunnel 关联到要使用它的 ChatGPT workspace,而不只是关联 Platform organization。
运行 `tunnel-client` 或在 App 中选择 Tunnel 需要 Tunnels Read + Use。
- Developer Mode 是单独的 ChatGPT workspace 设置。如果没有 Developer Mode 或
Create app 选项,请联系 workspace 管理员。
随后在 ChatGPT Web 中启用 Developer Mode,选择 **Create app**,将
**Connection** 设为 **Tunnel**,选择或输入 `tunnel_id`。App 会从运行中的 MCP Server
发现工具。如果 Tunnel 不在列表中,检查 workspace association、权限以及
`tunnel-client` runtime status。当前界面/权限细节以[官方 Secure MCP Tunnel 指南](https://developers.openai.com/api/docs/guides/secure-mcp-tunnels)为准。
Registry 内容变化不改变 tool schema,不需要 MCP/Tunnel 重启或 ChatGPT Refresh
Actions。只有 MCP tool schema 变化时才刷新/重连 App 以加载新工具。
## Smoke Test 与手工验证
本机检查:
```sh
curl --fail http://127.0.0.1:8765/healthz
.venv/bin/python scripts/smoke_client.py \
--url http://127.0.0.1:8765/mcp --project-id my_project
```
Smoke 最终状态为 `passed`、`skipped` 或 `failed`;任何失败检查都会令 overall
status 为 `failed` 并使用非零退出码。`git: false` 时 Git 检查会标成 skipped。读取
文本时优先尝试 `README.md`,否则从项目根目录探测其他可读文本文件;找不到合适文件
时 `read_file` 与 `grep` 明确 skipped。
ChatGPT App 连通后验证 `health`、`list_projects` 和 `read_file`。`read_file` 使用
已登记的项目 ID 和项目内可读文本路径,例如:
```json
{"project_id":"my_project","path":"README.md"}
```
若项目没有可读 `README.md`,请换成 `list_dir` 返回的其他文本路径。
## 故障排查
- **Health degraded:** 查看 `projects_registry_status`、`grants_registry_status` 和
generation 字段。用 admin CLI 修正无效 registry;变化后的无效文件会 fail closed。
- **项目不可用:** 运行
`.venv/bin/research-project-files-admin project list` 和 `project show ` 检查
root 是否存在、可访问、已启用且未被策略隐藏。
- **Grant 不可用:** 运行
`.venv/bin/research-project-files-admin grant show ` 查看 disabled、revoked、
expired 或 unavailable。已撤销/过期 grant 需要新建。
- **Git tools 失败:** 确认项目 `git: true` 且 root 是 Git worktree;Grant 不支持 Git。
- **ChatGPT 看不到 Tunnel:** 确认关联了目标 ChatGPT workspace,并检查 App 创建者的
Tunnels Read + Use 权限。
- **Tunnel runtime 断开:** 先检查本机 `/healthz`,再查
`tunnel-client runtimes status project-files --json` 和 Tunnel unit journal;检查出站
HTTPS、代理策略以及 client 是否仍在运行。
- **Smoke 出现 skipped:** 当 Git 能力或可读文本文件不存在时,这是预期状态;查看 JSON
中对应检查的 reason。
- **结果不完整:** 按 `next_offset` 或 `next_offset_line` 继续读取到 `complete=true`,
或缩小查询。
## 仓库结构
```text
config/ 安全默认规则与空 registry 示例
deploy/ MCP 和可选 Tunnel systemd 用户 unit
scripts/ 协议 smoke client 与 Tunnel 状态脚本
src/research_project_files_mcp/
Server、admin CLI、registry 和路径策略
tests/ 单元、安全、registry reload 与 smoke 测试
tunnel-profiles/ 通用 Tunnel profile 模板
README.md 完整英文文档
README.zh-CN.md 完整中文文档
pyproject.toml 包元数据和 CLI 入口
uv.lock Python 依赖锁定文件
```
运行时 project/grant registry、填好的 Tunnel profile、key、代理设置、虚拟环境、
二进制文件和迁移备份均在本机且被 Git 忽略,不要提交。
## 从旧版单项目 MCP 迁移
旧服务在启动时从固定的 `PROJECT_ROOT`(或 Git root auto-discovery)读取唯一项目根。
当前服务不再读取 `PROJECT_ROOT`,每次文件调用都必须通过 registry ID 选择 scope。
1. 验证新服务前保留旧 root 和旧服务。先从 example 初始化
`config/projects.yaml`。
2. 用 `research-project-files-admin project add` 将旧 `PROJECT_ROOT` 登记成稳定
`project_id`;只有 Git worktree 才加 `--git`。多个 root 分开登记,避免一个 scope
覆盖无关目录。
3. 启动新的 `research-project-files-mcp.service`,检查 `/healthz`、`project_info` 和
本机 smoke client。
4. 按新 example 重建本机 Tunnel profile,不要直接复制含有 secret 的旧 profile/unit。
若旧 Tunnel 仍对正确的 Platform organization 和 ChatGPT workspace 可用,可以沿用
旧 `tunnel_id`。
5. 连接 ChatGPT 并验证 `health`、`list_projects` 和带 scope 的 `read_file`。新服务的
tool scope 参数与固定 root 的旧 schema 不同;首次迁移时如有需要,Refresh/reconnect
App 一次以发现新 schema。
6. 全部验证通过后,再按旧部署自己的 service 名称和流程停用旧单项目服务。不要在
本次迁移中删除旧配置或备份。