# 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 名称和流程停用旧单项目服务。不要在 本次迁移中删除旧配置或备份。