No description
  • Rust 97.1%
  • Nix 2.3%
  • Just 0.6%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
gjz010's OpenCode agent 8d407b7b93
fix: tolerate unsupported resource template discovery
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.
2026-09-21 15:09:51 +08:00
.opencode/skills/configure-mcp-harbor docs: add configure-mcp-harbor skill 2026-09-20 18:27:26 +08:00
assets docs: add project logo and center it in README 2026-09-20 17:50:38 +08:00
docs fix: tolerate unsupported resource template discovery 2026-09-21 15:09:51 +08:00
src fix: tolerate unsupported resource template discovery 2026-09-21 15:09:51 +08:00
tests fix: tolerate unsupported resource template discovery 2026-09-21 15:09:51 +08:00
.envrc Initial commit 2026-09-20 05:12:34 +00:00
.gitignore feat: implement resilient async MCP proxy 2026-09-20 15:44:46 +08:00
.muxrun.toml Initial commit 2026-09-20 05:12:34 +00:00
Cargo.lock feat: implement resilient async MCP proxy 2026-09-20 15:44:46 +08:00
Cargo.toml feat: implement resilient async MCP proxy 2026-09-20 15:44:46 +08:00
CONTEXT.md docs: record agreed proxy design and acceptance criteria 2026-09-20 13:32:13 +08:00
devenv.lock Initial commit 2026-09-20 05:12:34 +00:00
devenv.nix feat: implement resilient async MCP proxy 2026-09-20 15:44:46 +08:00
devenv.yaml Initial commit 2026-09-20 05:12:34 +00:00
flake.lock feat: implement resilient async MCP proxy 2026-09-20 15:44:46 +08:00
flake.nix feat: implement resilient async MCP proxy 2026-09-20 15:44:46 +08:00
justfile Initial commit 2026-09-20 05:12:34 +00:00
README.md docs: add project logo and center it in README 2026-09-20 17:50:38 +08:00

mcp-harbor logo

mcp-harbor

中文 | English

一个用 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/callprompts/getresources/read 实时转发到上游。
  • 发现完整目录(含全部分页)并以原子替换写入 --cache
  • 目录变更按类别向前端发送 list-changed 通知。
  • 所有诊断输出到 stderrstdout 保留给 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。代理不校验身份也不把缓存与 --upstream URL 绑定。
  • 更换 --upstream 后复用同一缓存文件,即表示用户接受其中的旧目录;代理不做 自动身份检查。
  • 不支持多个进程同时写同一缓存文件;代理不加锁。请勿多进程共写同一路径。

构建与测试

开发环境由 devenv + direnv 提供见下。Rust 部分:

cargo test          # 单元测试与集成测试
cargo build --release

Nixflakes构建

命令 产物
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; initialize never 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, and resources/read are forwarded in real time.
  • A complete discovery (all pages) is persisted to --cache with atomic replacement.
  • Catalog changes emit per-category list-changed notifications.
  • All diagnostics go to stderr; stdout is reserved for MCP. RUST_LOG controls 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 --upstream URL.
  • Reusing a cache file after changing --upstream means 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 changes devenv.yaml and regenerates devenv.lock.
  • Harness reads its runtime environment from ${XDG_CONFIG_HOME:-$HOME/.config}/gjz010/harness/env by default. Set GJZ010_HARNESS_ENV_FILE to 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-harness and commit the updated devenv.lock to upgrade harness. Never hand-edit the lock or paste hashes.
  • Use devenv update for other inputs and commit devenv.lock with them.
  • To upgrade the flake's nixpkgs, bump the rev in flake.nix and regenerate flake.lock, keeping it aligned with the devenv.lock nixpkgs revision.