- Rust 97.1%
- Nix 2.3%
- Just 0.6%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
Treat METHOD_NOT_FOUND on the first resource template page as an empty template catalog while preserving discovery failures for other errors and later pages. Add HTTP 400 regression coverage and document the compatibility exception. Verified all 36 tests and live catalog discovery and offline cache behavior against Rider and Unreal MCP upstreams. |
||
| .opencode/skills/configure-mcp-harbor | ||
| assets | ||
| docs | ||
| src | ||
| tests | ||
| .envrc | ||
| .gitignore | ||
| .muxrun.toml | ||
| Cargo.lock | ||
| Cargo.toml | ||
| CONTEXT.md | ||
| devenv.lock | ||
| devenv.nix | ||
| devenv.yaml | ||
| flake.lock | ||
| flake.nix | ||
| justfile | ||
| README.md | ||
mcp-harbor
一个用 Rust 编写的 stdio MCP 代理:对本地 MCP 客户端表现为稳定的 MCP
server,并将请求转发到可能暂时不可用的远程 Streamable HTTP MCP 后端。后端离线
时前端仍可完成 initialize 并继续服务磁盘上的最后一次目录快照。
行为规格见 docs/SPEC.md,实现选择与理由见 docs/DESIGN.md,术语见
CONTEXT.md。
中文
功能概览
- 前端:stdio MCP server;后端:Streamable HTTP MCP client。
- 后端离线时前端进程保持存活,
initialize不被后端连接阻塞。 - 目录查询(tools/prompts/resources/resource templates)由代理本地持有的目录 提供,不逐次回源;离线时继续返回最后一次成功发现的有效快照。
tools/call、prompts/get、resources/read实时转发到上游。- 发现完整目录(含全部分页)并以原子替换写入
--cache。 - 目录变更按类别向前端发送 list-changed 通知。
- 所有诊断输出到 stderr,stdout 保留给 MCP 协议;由
RUST_LOG控制详细程度。
范围与不做的特性以 docs/SPEC.md 为准(无认证、无多上游聚合、无工具搜索、无
数据库、无 Web UI)。
CLI
mcp-harbor
--upstream <URL>
--cache <PATH>
[--name <NAME>] (默认: mcp-harbor)
[--description <TEXT>] (默认: 省略)
[--revalidate-interval <SECONDS>] (默认: 30)
--name / --description 是占位元数据:仅在既无实时后端元数据、又无有效缓存
元数据时使用。
示例(客户端通常以 stdio 子进程方式启动它):
mkdir -p ~/.cache/mcp-harbor
mcp-harbor --upstream https://example.invalid/mcp --cache ~/.cache/mcp-harbor/catalog.json
缓存文件的父目录不会被自动创建,需要先自行建立(如上)。
缓存身份
- 一个缓存文件对应用户认定的一个 MCP identity。代理不校验身份,也不把缓存与
--upstreamURL 绑定。 - 更换
--upstream后复用同一缓存文件,即表示用户接受其中的旧目录;代理不做 自动身份检查。 - 不支持多个进程同时写同一缓存文件;代理不加锁。请勿多进程共写同一路径。
构建与测试
开发环境由 devenv + direnv 提供(见下)。Rust 部分:
cargo test # 单元测试与集成测试
cargo build --release
Nix(flakes)构建:
| 命令 | 产物 |
|---|---|
nix build .#mcp-harbor |
x86_64-linux 原生二进制 |
nix build .#mcp-harbor-windows |
x86_64-pc-windows-gnu PE 可执行文件(MinGW 交叉构建) |
nix build |
.#mcp-harbor 的默认别名 |
Windows 目标仅验证构建成功;本项目不声明已验证 Windows 运行行为。两个构建共用
Cargo.lock。flake 固定到与 devenv.lock 相同的 nixpkgs revision,避免 flake
与开发 shell 出现不同版本的包集。
环境与 devenv
首次使用需要宿主机安装支持 flakes 的 Nix、devenv 和 direnv:
direnv allow # 首次必须由宿主手动审查并授权 .envrc
just check # 校验配置、Nix 格式化与 shell/recipe 语法
direnv allow 是宿主侧安全授权动作,agent 不代为执行。当前固定 harness 仅支持
x86_64-linux;启动 harness 沙箱需要宿主内核允许 bubblewrap 的 namespaces,
受限容器可能拒绝启动,just check 通过并不证明沙箱可运行。
入口
| 命令 | 作用 |
|---|---|
just |
列出全部 recipe |
just check |
校验配置、Nix 格式化与 shell/recipe 语法 |
just opencode [ARGS...] |
启动 harness 包装的 OpenCode TUI |
just opencode-web [ARGS...] |
启动 harness 包装的 OpenCode Web |
just update-harness |
通过 devenv update harness 更新固定版本并写入 lock |
环境与 runtime
- 全体开发者共享的包写在
devenv.nix;新增 input 需同步修改devenv.yaml并 重新生成devenv.lock。 - harness 的 runtime 环境变量默认从
${XDG_CONFIG_HOME:-$HOME/.config}/gjz010/harness/env读取;需要固定其他位置 时显式设置GJZ010_HARNESS_ENV_FILE。默认文件不存在不会阻止启动,但显式 指定的文件必须存在。该文件是可执行 shell 代码,只使用可信内容;不要把秘密 写入 Nix 表达式、store、日志或提交文件。 - 宿主的 OpenCode config、auth、data 与 cache 由 harness 共享,本项目不再隔离 或覆盖。
- 修改 config、skills、instructions、MCP、agents、commands、plugins 或安装包 后需要重启 OpenCode;新建 chat session 不等于重载。
维护
- 升级 harness 时运行
just update-harness,提交更新后的devenv.lock。 不要手工编辑 lock 或填入 hash。 - 升级其他 input 时使用
devenv update,并把devenv.lock一并提交。 - 升级 flake 的 nixpkgs 时更新
flake.nix中的 rev 并重新生成flake.lock; 保持与devenv.lock的 nixpkgs revision 一致。
English
A small async Rust MCP proxy. It presents a stable stdio MCP server to a local client and forwards to a remote Streamable HTTP MCP backend that may be unavailable, serving the last successfully discovered catalog from disk while the backend is offline.
Behavior is specified in docs/SPEC.md, implementation choices in
docs/DESIGN.md, and terminology in CONTEXT.md.
Overview
- Frontend: stdio MCP server. Backend: Streamable HTTP MCP client.
- The frontend stays alive when the backend is down;
initializenever blocks on the backend connection. - Catalog queries (tools/prompts/resources/resource templates) are served from the catalog the proxy holds, not by contacting the upstream on every request. A valid snapshot keeps serving while offline.
tools/call,prompts/get, andresources/readare forwarded in real time.- A complete discovery (all pages) is persisted to
--cachewith atomic replacement. - Catalog changes emit per-category list-changed notifications.
- All diagnostics go to stderr; stdout is reserved for MCP.
RUST_LOGcontrols verbosity.
Out-of-scope features are listed in docs/SPEC.md (no auth, no multi-upstream
aggregation, no tool search, no database, no web UI).
CLI
mcp-harbor
--upstream <URL>
--cache <PATH>
[--name <NAME>] (default: mcp-harbor)
[--description <TEXT>] (omitted by default)
[--revalidate-interval <SECONDS>] (default: 30)
--name / --description are placeholder metadata, used only when neither
live backend metadata nor valid cached metadata exists.
Example (clients normally launch it as a stdio child process):
mkdir -p ~/.cache/mcp-harbor
mcp-harbor --upstream https://example.invalid/mcp --cache ~/.cache/mcp-harbor/catalog.json
The cache file's parent directory is not created automatically; create it first, as above.
Cache Identity
- One cache file corresponds to one user-asserted MCP identity. The proxy does
not check identity and does not bind the cache to the
--upstreamURL. - Reusing a cache file after changing
--upstreammeans the user accepts its old catalog; there is no automatic identity check. - Multiple processes writing the same cache file are not supported, and the proxy adds no locking. Do not let several processes share one cache path.
Build And Test
The development environment comes from devenv + direnv (below). For Rust:
cargo test # unit and integration tests
cargo build --release
Nix (flakes) builds:
| Command | Artifact |
|---|---|
nix build .#mcp-harbor |
native x86_64-linux binary |
nix build .#mcp-harbor-windows |
x86_64-pc-windows-gnu PE executable (MinGW cross-build) |
nix build |
alias defaulting to .#mcp-harbor |
The Windows target is build-acceptance only; no Windows runtime behavior is
claimed. Both builds share Cargo.lock. The flake pins the same nixpkgs
revision as devenv.lock, so the flake and the development shell do not diverge.
Environment And devenv
First use requires a host with flakes-enabled Nix, devenv, and direnv:
direnv allow # first authorization must be reviewed and granted on the host
just check # validate config, Nix formatting, and shell/recipe syntax
direnv allow is a host-side authorization step the agent does not perform.
The pinned harness supports only x86_64-linux; its launcher requires the host
to allow bubblewrap's namespaces, and restricted containers may reject startup.
Passing just check does not prove the sandbox can run.
Entrypoints
| Command | Purpose |
|---|---|
just |
List all recipes |
just check |
Validate config, Nix formatting, and shell/recipe syntax |
just opencode [ARGS...] |
Launch the harnessed OpenCode TUI |
just opencode-web [ARGS...] |
Launch the harnessed OpenCode Web |
just update-harness |
Update the pinned harness input through devenv update harness |
Environment And Runtime
- Shared packages belong in
devenv.nix; a new input also changesdevenv.yamland regeneratesdevenv.lock. - Harness reads its runtime environment from
${XDG_CONFIG_HOME:-$HOME/.config}/gjz010/harness/envby default. SetGJZ010_HARNESS_ENV_FILEto pin another path. A missing default file is allowed, but an explicitly selected file must exist. It is executable shell code: use only trusted content and keep secrets out of Nix expressions, the store, logs, and committed files. - Host OpenCode config, auth, data, and cache are shared with harness instead of being isolated or overridden by this project.
- Restart OpenCode after changing config, skills, instructions, MCP, agents, commands, plugins, or packages; a new chat session is not a reload.
Maintenance
- Run
just update-harnessand commit the updateddevenv.lockto upgrade harness. Never hand-edit the lock or paste hashes. - Use
devenv updatefor other inputs and commitdevenv.lockwith them. - To upgrade the flake's nixpkgs, bump the rev in
flake.nixand regenerateflake.lock, keeping it aligned with thedevenv.locknixpkgs revision.