Imported from nazo6/deno-cm-proxy (
AGENTS.md). Install upstream withnpx skills add nazo6/deno-cm-proxy. Copyright stays with the author.
AGENTS.md
このファイルは本リポジトリで開発を行う AI エージェント・開発者向けのガイドです。プロジェクト概要・構成・開発フロー・重要な技術的注意点をまとめています。
プロジェクト概要
deno-cm-proxy は、多数の上流 MCP サーバーを集約し、「code mode」として公開する MCP
プロキシです(pctx / cmcp に影響を受けた設計)。LLM が数百のツール定義ではなく 4
ツール(overview / search_tools / inspect /
execute)だけを見ることでトークンを大幅削減します。
execute() では各上流サーバーが TypeScript
のグローバル変数(ネームスペース)になり、サンドボックス内で実行・加工され、ツール呼び出しは主プロセス経由で認証済み
MCP クライアントに委譲されます。(設定上の上流サーバー名を server、コードモード内の TS
グローバル変数名を namespace と呼びます。)
特徴:
- フル Deno スタック(Deno + Hono / Deno Worker サンドボックス / React + Vite + Tailwind を Deno でビルド)
- MCP Streamable HTTP で公開(
/mcp)、REST 管理 API(/api/*)、Web UI 付き - デュアルエラ対応(MCP 2026-07-28 + 2025-11-25)。サーバーは
Mcp-Session-Id前提の旧セッション方式とステートレスの新方式を併走。上流クライアントはversionNegotiation: autoで新旧どちらの上流にも接続 - セッション単位の上流コネクションプール(stateful 上流 MCP 対応)
- 実行前型チェック(
npm:typescript)+ 未知サーバー名への候補シグネチャ提示 - シークレットはサーバー側でのみ解決(
${env:VAR})、サンドボックス・LLM に漏れない
ディレクトリ構成
.
├── deno.json # ルート Monorepo ワークスペース設定
├── deno.lock # 依存ロック(コミット必須。.gitignore で除外しない)
├── backend/ # バックエンドパッケージ (@cm-proxy/backend)
│ ├── deno.json # バックエンド設定(タスク・imports・コンパイラオプション)
│ ├── src/
│ │ ├── main.ts # bootstrap() + import.meta.main の起動エントリ
│ │ ├── server.ts # Hono 配線(/mcp, /api/*, Web UI 静的配信)
│ │ ├── config/ # 設定の型・読込/保存・シークレット解決・ConfigStore
│ │ ├── log/ # 構造化ログ・リングバッファ
│ │ ├── catalog/ # ツールカタログ(メタデータ・型変換)
│ │ ├── upstream/ # 上流 MCP への接続・プール管理
│ │ ├── sandbox/ # Worker サンドボックス実行環境
│ │ ├── engine/ # code mode エンジン
│ │ ├── mcp/ # MCP プロキシ
│ │ └── api/ # REST 管理 API
│ └── tests/ # 統合テスト・モック上流
├── web/ # フロントエンドパッケージ (@cm-proxy/web, React + Vite + Tailwind)
│ ├── deno.json # Web 用の独立設定(jsx/dom lib + react imports + tasks)
│ ├── vite.config.ts
│ ├── index.html
│ └── src/
│ ├── main.tsx / App.tsx # エントリ + シェル
│ ├── api.ts # REST API クライアント
│ ├── components.tsx # UI コンポーネント
│ └── pages/ # 各種画面
├── Dockerfile / docker-compose.yml
├── deno-cm-proxy.example.json # 設定サンプル
└── README.md # ユーザー向けドキュメント
主要コマンド(deno task)
| コマンド | 内容 |
|---|---|
deno task dev |
バックエンド(:8080)+ Web 開発サーバー(Vite :5173)を同時起動(concurrently、Ctrl+C で両方終了) |
deno task dev:server |
バックエンド単体を watch 起動 |
deno task dev:front |
Web 開発サーバー単体起動 |
deno task check |
バックエンドと Web の型チェックを両方実行 |
deno task lint |
バックエンドと Web の lint を両方実行 |
deno task test |
バックエンドの全テスト実行 |
deno task fmt |
フォーマット |
依存の追加は deno add jsr:@std/xxx または deno add npm:xxx。deno.lock はコミットに含める。
開発フロー
- 設定を用意:
cp deno-cm-proxy.example.json deno-cm-proxy.json(シークレットは${env:VAR}参照) - 起動して動作確認:
deno task start→ Web UI はhttp://localhost:8080/、MCP は/mcp、管理 API は/api/* - 変更後は必ず:
deno task check→deno task lint→deno task web:check→deno task test→deno fmt - Web を変更したら:
deno task web:build(ビルド済みでなければサーバーは API のみ配信)
アーキテクチャ / データフロー
AI agent ── MCP Streamable HTTP (/mcp) ──▶ deno-cm-proxy
自前 agent ── REST /api/search|inspect|execute ──▶ deno-cm-proxy
│
Hono ── /mcp ─ McpProxy ── dual-era ── SessionManager ── ConnectionPool ──▶ 上流 MCP
│ ├─ legacy (2025): Mcp-Session-Id 単位の McpServer(4 tools) + WebStandardStreamableHTTPServerTransport
│ └─ modern (2026-07-28): createMcpHandler() の per-request サーバー + throwaway セッション
└─ /api ─ CodeModeEngine ── TypeChecker / SandboxRunner / Catalog ─┘
- Catalog: 起動時 + サーバー追加/更新時に各上流へ
listTools。失敗サーバーはstatus:"error"(クラッシュしない)。overview/search_tools/inspectはカタログのみを使う。 - execute フロー: 参照名前空間検出 → ambient 宣言生成 → 型チェック(失敗時は diagnostics
返却、未知グローバルは候補提示)→ dryRun なら終了 → サンドボックス実行 → 結果/出力を
maxLengthでトランケート。 - デュアルエラ ルーティング(
proxy.ts):isLegacyRequest()で旧世代を判定。旧世代はMcp-Session-Idベースのセッション方式(defaultMode:"stateless"なら毎リクエスト破棄)、 新世代はcreateMcpHandler(factory, { legacy: "reject" })でリクエスト毎の throwaway セッションを割り当て、レスポンス後に破棄。新世代はセッションが無いため、ステートフル上流の 永続化はツール引数の明示ハンドル等で行う必要がある。 - サンドボックス: 実行ごとに1個の
Worker(deno.permissionsで read/write/env/run/ffi/sys/net を無効化)。ツール呼び出しはpostMessageで main へ委譲し、main 側がConnectionPool.call()を実行。Worker にシークレットは渡らない。
重要な技術的注意点
--unstable-worker-optionsが必須。Worker のdeno.permissionsを指定するため。タスク・テスト・Docker CMD すべてに付与済み。これを忘れると実行時エラー。- MCP SDK v2 の structuredContent は
{ result: <value> }にラップされる。pool.tsのmcpResultToJsonが unwrap する。改修時はここを通ることを前提に。 - デュアルエラの分岐を壊さない。
proxy.tsはisLegacyRequest()で旧世代 (2025-11-25、Mcp-Session-Id+ initialize)と新世代(2026-07-28、リクエスト毎_metaenvelope)を振り分け、新世代はcreateMcpHandler(factory, { legacy: "reject" })で処理する。 旧クライアントのセッション方式(#handleStateful)とステートレス方式(#handleStateless) は維持すること。新世代はステートレスなので、リクエスト毎に throwaway セッションを作り レスポンス後に破棄する(#modernSessionsByRequestの WeakMap 方式)。 - 上流クライアントは
versionNegotiation: { mode: "auto" }。client.tsのnewUpstreamClient()でserver/discoverをプローブし、旧世代上流なら自動でinitializeにフォールバックする。既定は"legacy"なので、これを外すと 2026-07-28 専用の 上流に接続できなくなる。v2 のClientコンストラクタは(clientInfo, options)の2引数。 npm:typescriptは 5.x に固定(npm:typescript@5.9.3)。デフォルトのnpm:typescript(6.x)は default import が壊れる。- 型チェックは
noImplicitAny: false(ツール戻り値がPromise<any>のため、.map(i => ...)の暗黙 any エラーを防ぐ)。ambient の戻り値はPromise<any>。引数の型検証が目的。 - Web はルート deno.json を使わない。
web/deno.jsonに jsx/dom lib と react imports を独立定義。ルートにlib:["dom"]を入れると Deno のWorker型が DOM 版に上書きされバックエンドの型チェックが壊れる。web:checkは--config web/deno.jsonを明示。 worker_runtime.tsは Worker スコープで実行される。postMessage等はdeclare functionで補完。selfに依存しない書き方を維持(型チェックが main スコープで通るように)。deno test内で MCP SDK の Client(StreamableHTTPClientTransport)を使うとハングすることがある(SSE セッションストリームの終了待ち)。MCP のテストはapp.fetch()に raw fetch で JSON-RPC を送る方式(tests/mcp_smoke_test.ts)にしている。この方式を維持すること。- テスト用の上流モック(
tests/mock_upstream.ts)は既定でcreateMcpHandler(デュアルエラ)、{ legacyOnly: true }で 2025 世代専用(server.connect()+ リクエスト毎 transport)になる。close()では全 transport / モダンハンドラを明示的に閉じる。Deno.Server.shutdown()は開いた接続があると待ち続けるため、実装上流/サーバーの close にもタイムアウトを入れる。 - 設定の servers は API から動的変更可能。
applyCatalogでcatalog.sync()→ 新規/更新サーバーを非同期 refresh。sandbox/typecheck/session の設定はConfigStore経由(実行時に参照)。 - 静的配信:
web/distが存在すればサーバーが自動で配信。存在しなければ「API only」のログ。配信ルートはserver.tsのserveStaticで root にdistDirを指定。
テスト
deno task testで全部実行(worker フラグ付き)tests/integration_test.ts: エンジンの overview/search/inspect/execute、型エラー検出、未知グローバル候補、REST API スモーク。実上流モックをstartMockUpstream()で起動。tests/mcp_smoke_test.ts: MCP initialize → tools/list → overview → execute → セッション永続化 (旧世代、raw fetch)に加え、server/discover/ tools/list / tools/call を 2026-07-28 の_metaenvelope 形式で検証するテストを含む。tests/integration_test.tsにlegacyOnlyモック上流を使い、versionNegotiation: autoが 2025 世代上流へinitializeでフォールバックすることを検証するテストを含む。- 新機能追加時は、モック上流にツールを足し、該当フェーズ(カタログ/検索/実行/型チェック)の検証を追加すること。
コーディング規約
- 型は明示、
strict: true+noUncheckedIndexedAccess(arr[0]は| undefined扱い。必要なら!かガード) - プライベートフィールドは
#(例:#sessions) - コメントは最小限(推奨しつつ必要最小限)
deno fmtに従う(lineWidth 100)- npm 依存は
deno.jsonのimportsに追加し、deno addを使う - ログは
Logger経由(console.logを実装に使わない)。logger.info/warn/errorに構造化フィールドを渡す - Web 側 API 型は
web/src/api.tsに集約し、バックエンドの応答形状と揃える