aiMCPGate
aiMCPGate
Go で書かれた MCP サーバー(Model Context Protocol)用のゲートウェイ / プロキシです。 MCP クライアント(Claude Code、Cursor など)には 1 つの MCP サーバーとして見えますが、内部では複数のアップストリーム MCP サーバーへの呼び出しを 多重化 し、それらのツール、プロンプト、リソースを 集約 して 1 つのカタログにまとめ、すべての呼び出しを ログ に記録します。
ステータス: MVP 完了(ステージ 0〜6)+ ポスト MVP ステージ 7〜18 をリリース済み、最新リリース v0.5.0。 フェーズ 1 — stdio エンドポイントの背後で stdio アップストリームを多重化し、呼び出しログを記録。フェーズ 2 — HTTP/SSE クライアント向けトランスポート、HTTP アップストリーム、CLI ログビューア(
mcp-gate logs)、リリースパイプライン(goreleaser、linux/darwin/windows × amd64/arm64 向けクロスコンパイル、CGO なし)。ポスト MVP では、アップストリームの自動再起動、設定のホットリロード、ツールのフィルタリング/リネーム、doctorを追加し、v0.3.0 ではprompts/resources/resources/templates/completionの完全集約、ping、進捗転送と実際のキャンセル、logging/setLevelのファンアウト、アップストリームごとの呼び出し制限(レート制限 / 並行性 / 結果の切り詰め / タイムアウト)、遅延カタログとtools/listのページネーション、クライアント側とアップストリーム側の両方での SSE サーバー→クライアントストリームを追加しました。v0.4.0 はサーバー→クライアント方向を完成させました。サーバー開始の 3 つのメソッド —elicitation/create、sampling/createMessage、roots/list— は、4 つのトランスポートの組み合わせすべて(クライアント側の stdio または HTTP × アップストリーム側の stdio または HTTP)でプロキシされます。ゲートウェイは、アップストリームに対して、自クライアントが宣言した機能をそのまま宣言するようになり、一律の{}ではなくなりました。HTTP トランスポートには、DELETE /mcpによる終了を伴うサーバー側Mcp-Session-Idセッションが追加されました。v0.5.0 はオペレーター向け可観測性(ステージ 18)を追加します。8 種類のイベント — アップストリーム起動失敗とスーパーバイザーの諦め、ドロップされた通知とサーバー→クライアント要求、GET SSE のない HTTP アップストリーム、カタログの衝突と不正な URI テンプレート、max_result_bytesを静かにバイパスした結果 — は、MCP クライアントが通常所有するstderrではなく、呼び出しジャーナル(mcp-gate logs)に記録されるようになりました。設定解析は厳格になりました(未知のキーやスペルミスのキーは致命的です)。また、クライアント側のガード/切り詰めの話の半分を閉じます。レート制限または並行性ガードによって拒否されたtools/callは、区別できない-32603ではなく、独自の JSON-RPC エラーコード-32029と機械可読なdata: {"retryable":true,"reason":...}を返すようになり、max_result_bytesをバイパスした非テキスト結果にはresult._metaマーカーが付きます(contentはバイト単位でそのままです)。最後に、未設定の環境変数を参照するauth_tokenは、HTTP 認証を静かに無効にする代わりに、ゲートウェイの起動を拒否するようになりました。v0.5.0 へのアップグレード — 3 つの動作変更。いずれも設定ファイル形式自体には触れません。
設定解析は厳格になりました。 未知のキーやスペルミスのあるトップレベルまたはアップストリームごとのキーを含む設定は、以前は静かに無視されていましたが、今は読み込みに失敗します。キー名を修正するか(エラーが名前を示します)、削除してください。
auth_token: ${VAR}でVARが未設定の場合、起動を拒否するようになりました、変数名を示します。以前は静かに空のトークンになり、HTTP ゲートウェイではベアラーチェックが警告なしで完全に無効になりました。変数を設定するか(または--env-fileを渡す)、意図的に認証なしで実行する場合はauth_tokenを削除してください。呼び出しジャーナル(
log_file/calls.jsonl)に 2 番目のレコード種類、"kind":"event"が既存の呼び出しレコードに加えて追加されました。v0.4.0 以前のバイナリが v0.5.0 のジャーナルを読み取ると、イベント行を疎なERRエントリとしてレンダリングし、失敗しません。ジャーナルは、書き込んだバイナリと同じかそれより新しいバイナリで読み取ってください。v0.4.0 へのアップグレード: 設定ファイルの変更はありませんが、HTTP モードの動作変更が 2 つあります —
initializeの後、POST /mcpにはセッション ID が必須になり(ヘッダーはinitialize応答で返されます)、アップストリームレジストリはプロセス起動時ではなく、最初の実際の MCP 要求で遅延して起動します。未実装: クライアントごとのアクセスポリシー。
リリース
クロスプラットフォームのバイナリは goreleaser(.goreleaser.yaml)でビルドされます: linux/darwin/windows × amd64/arm64、CGO なし、バージョンは -ldflags -X main.version=... で組み込まれ、チェックサムは SHA256SUMS に置かれます。ローカルドライラン: goreleaser release --snapshot --clean。
Related MCP server: mcpstead
MCP レジストリからインストール
生のリリースバイナリに加えて、ゲートウェイは GitHub Container Registry の OCI イメージと npm ラッパーパッケージとして提供されます — MCP レジストリがインストールする 2 つの形式です。
Docker:
docker run --rm -i -v $(pwd)/config.yaml:/config.yaml ghcr.io/akomyagin/aimcpgate serve-i は必須です: ゲートウェイは stdio で MCP を話すため、クライアントは stdin を開いたままにする必要があります(これがないとコンテナは EOF を見てすぐに終了します)。イメージには独自の設定がないため、あなたの設定をマウントしてください — 上記の例ではデフォルトパス /config.yaml にマウントしています。他のパスでも serve -c で機能します。
実際のアップストリームなしでレジストリサンドボックスチェック(Glama.ai など)を再現するには、イメージに組み込まれたデモ設定を使用してください — この正確なコマンドがサンドボックスが実行すべきものです:
docker run --rm -i ghcr.io/akomyagin/aimcpgate serve -c /demo.config.yamlnpx(初回インストール時にプラットフォーム用のプリビルドバイナリをダウンロードし、SHA256 チェックサムを検証します):
npx aimcpgate serve -c ./config.yamlイメージポリシー: OCI イメージには mcp-gate バイナリのみが含まれます — stdio アップストリーム用のランタイム(node/npx、python、シェル)は含まれません。設定で stdio アップストリームサーバーを起動する場合は、イメージを自分で拡張して必要なものをインストールしてください。HTTP アップストリームはそのまま機能します(CA 証明書が含まれています)。
デモ設定: demo.config.yaml と隠し __demo-echo サブコマンドは、レジストリサンドボックス(Glama.ai)が実際のアップストリームなしでゲートウェイをイントロスペクトできるようにするためだけに存在します — 実際のデプロイメントでは決して使用しないでください。
コンテナ内での CLI コマンドの実行
doctor、catalog、call、logs は、オペレーターがデプロイメントを検査する方法です。コンテナ内でそれらを呼び出す方法を決定する 3 つの事実があります:
バイナリは
/mcp-gateであり、$PATHにありません。DockerfileはCOPY mcp-gate /mcp-gateとENTRYPOINT ["/mcp-gate"]を行います — 検索パスに置くものは何もありません(これが間違っているように見える場合はDockerfileを確認してください)。したがって、明白な形式は失敗します:$ docker exec mcp-gate mcp-gate catalog -c /config.yaml OCI runtime exec failed: exec failed: unable to start container process: exec: "mcp-gate": executable file not found in $PATH代わりに絶対パスを使用してください — それが唯一の違いです。
イメージは distroless であるため、シェルがまったくありません。 ベースは
gcr.io/distroless/static-debian12:nonrootで、バイナリと CA 証明書のみが含まれ、他には何もありません。docker exec mcp-gate sh -c '…'は、shが単に存在しないのと同じように失敗し、ls/catもありません。パイプ、グロブ、リダイレクトはコマンドの HOST 側に置いてください。docker execは新しいプロセスを開始します。実行中のserveをクエリしません。doctor、catalog、callは独自のレジストリを構築し、アップストリームへの独自の接続を開き、レポートして終了します。したがって、それらの出力は 現時点での アップストリーム到達可能性であり、ライブゲートウェイの状態ではありません。実行中のプロセスがアップストリームを失い、カタログから削除した場合、これらのコマンドはそれを表示しません。また、呼び出しジャーナルをクリーンに保ちます — ジャーナリングを無効にして実行されるため、この方法で行ったcallはlogsに表示されません。
docker exec mcp-gate /mcp-gate version
docker exec mcp-gate /mcp-gate doctor -c /config.yaml
docker exec mcp-gate /mcp-gate catalog -c /config.yaml
docker exec mcp-gate /mcp-gate call demo__echo '{"text":"hi"}' -c /config.yaml
docker exec mcp-gate /mcp-gate logs -c /config.yaml --tail 50コマンドは、デタッチされ名前が付けられた コンテナを想定しています。例: docker run -d --name mcp-gate … — 上記のフォアグラウンド docker run --rm -i … の例とは異なり、stdio クライアントが切断するとすぐに終了し、docker exec が到達できるものは何も残りません。設定は、同じ例のようにデフォルトパス /config.yaml にマウントされていると想定されています。demo__echo は、独自のカタログのツールの代わりです。いくつかの注意点:
logsは事実 3 の例外です: 実行中のゲートウェイが書き込むジャーナル FILE を読み取るため、ライブプロセスを反映します。これには、マウントされた設定のlog_fileがコンテナ内で見えるパスを指し、そこにボリュームがマウントされている必要があります。そうしないと、ジャーナルはコンテナの stderr(つまりdocker logs)に送られ、mcp-gate logsは読み取るものを持ちません。-cはジャーナルの場所を指定するもので、--fileはそれを上書きします。これは実際には HTTP モードに関するものです。 stdio モードでは、MCP クライアントがコンテナを生成して所有するため、通常、
execできる長命のコンテナはありません。検査できるゲートウェイは、transport: httpで別途開始されたものです(docker run -d --name mcp-gate …)。HTTP モードには非デフォルトの
listen_addrが必要です。 デフォルトは127.0.0.1:28080— コンテナ内のループバックで、-pがあってもホストから到達できません。設定でlisten_addr: 0.0.0.0:<port>を設定してください。ゲートウェイは、auth_tokenなしでは意図的に起動を拒否します(「HTTP エンドポイントが認証なしでネットワークから到達可能になるため」)。
なぜ
アクティブな MCP ユーザーは通常、複数のサーバー(ファイルシステム、GitHub、検索、カスタム)を設定しており、それぞれが各クライアントの独自の設定に複製されています。aiMCPGate は以下を提供します:
1 つのエントリポイント — クライアント設定の N エントリの代わりに、単一の MCP エンドポイント。
1 つのカタログ — 各アップストリームサーバーのツールとプロンプトをすべてマージ(名前が衝突しないように
<upstream>__<tool>として名前空間化)、さらにリソースとリソーステンプレート(URI でアドレス指定されるため、名前は変更されません)。呼び出しログ — どのアップストリーム、どのツール、いつ、成功/失敗。これは「単なるプロキシ」に追加される価値です。
ソロのペットプロジェクト: 優先事項は Go を学ぶこと(並行性、os/exec、JSON-RPC 2.0、stdio および HTTP/SSE トランスポート)。コスト — デフォルトで $0/月(ローカルプロセス)、テレメトリなし。
仕組み(短縮版)
MCP client ──stdio/HTTP──▶ aiMCPGate ──JSON-RPC──▶ upstream A (stdio)
│ ├─────────▶ upstream B (stdio)
call log └─────────▶ upstream C (http, Phase 2)MVP(2 つのフェーズ)
フェーズ 1 — 1 つの stdio エンドポイント(Claude Code が話すのと同じトランスポート)の背後で 2 つ以上の stdio アップストリームを多重化し、基本的なロギングを追加。
フェーズ 2 — HTTP/SSE トランスポート、HTTP アップストリームサーバー、ログビューア(CLI は構築済み。Web ビューは意図的に削除)、オプションでアクセスポリシー — これは検討され、却下されました。
ビルド
export PATH="$HOME/sdk/go/bin:$PATH" # if go isn't already on PATH
go build ./...
go vet ./...
go test -race ./...
go run ./cmd version使用法
# stdio mode (the client launches the gateway as a subprocess):
mcp-gate serve --config ./config.yaml
# http mode (transport: http in the config) — endpoint at http://<listen_addr>/mcp;
# every request after initialize carries the issued Mcp-Session-Id (see below):
mcp-gate serve --config ./config-http.yaml
# check every enabled upstream once (launch → handshake → tools/list) and print
# a per-upstream OK/FAIL table; exit code is non-zero if any upstream failed
# (scriptable for CI/cron), no auto-restart, no call logging — one pass then exit.
# It also WARNs (POSIX only) if config.yaml or --env-file is readable by
# group/others — either may hold a secret literal, and nothing else checks:
mcp-gate doctor --config ./config.yaml
# call one aggregated tool once from the shell (single bring-up, no supervisor —
# the fastest way to debug a config, a filter or a rename without a live client):
mcp-gate call github__search_repositories '{"query":"mcp"}' --config ./config.yaml
# report the aggregated catalog size per upstream (tools / bytes / ~tokens) plus
# the heaviest individual tools — the data behind allow-list / strip decisions:
mcp-gate catalog --config ./config.yaml --top 20
# view the journal — tool calls AND operator events (last 50 lines; filter by
# upstream/tool/status):
mcp-gate logs --file ./logs/calls.jsonl --tail 50
mcp-gate logs --config ./config.yaml --upstream github --status err
# show ONLY the operator events (see "Operator events" below):
mcp-gate logs --config ./config.yaml --events
# keep watching the log as it grows, or aggregate it instead of listing records
# (--follow and --stats are mutually exclusive):
mcp-gate logs --config ./config.yaml --follow
mcp-gate logs --config ./config.yaml --stats
# generate a random auth token (for the HTTP transport) and see how to wire it in:
mcp-gate token --generate
# print the auth token currently set in the config:
mcp-gate token --config ./config-http.yaml
# print ready-to-paste MCP client config snippets (Claude Code / Cursor / Claude
# Desktop) for whichever transport the config uses: a launch command for stdio, or
# the endpoint URL plus the Bearer header (when auth_token is set) for http:
mcp-gate client-config --config ./config.yaml
# print a SKILL.md teaching an agent how to use the aggregated catalog
# (built-in text by default; overridable via skill_file in the config):
mcp-gate skill > .claude/skills/mcp-gate/SKILL.md
# shell completions (cobra's built-in command; the release archives also ship
# pre-generated ones):
mcp-gate completion bash > /etc/bash_completion.d/mcp-gatetoken --generate、completion、skill(組み込みガイドにフォールバック)を除くすべてのコマンドは設定を読み込みます: --config を渡すか、バイナリの隣に config.yaml を置きます(下記の設定を参照)。
serve、doctor、call、catalog は --env-file ./.env も受け入れます — 設定が読み込まれる 前に 適用される最小限の KEY=VALUE パーサーで、設定内の ${VAR} 参照がそのファイルから解決されます。実際のプロセス環境は常にファイルより優先されます。
HTTP セッション(Mcp-Session-Id)
http モードでは、ゲートウェイは Streamable HTTP セッションを実行します: initialize への応答には Mcp-Session-Id ヘッダーが含まれ、その後のすべての要求 — POST、GET SSE ストリーム、DELETE — はそのヘッダーを返送する必要があります。それがない場合、応答は 400 です。不明または期限切れの ID の場合は 404 で、クライアントに再度 initialize するように指示します。セッションは DELETE /mcp(204)または 30 分間要求がない場合に解放されます — 開いている SSE ストリームはアクティビティとしてカウントされ、セッションを維持します。
MCP クライアントはこれらすべてを自動的に行います。手作りの curl 呼び出しの場合は、initialize 応答からヘッダーを取得してエコーバックしてください:
SID=$(curl -sD - -o /dev/null -X POST http://127.0.0.1:28080/mcp \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-06-18","capabilities":{},"clientInfo":{"name":"curl","version":"1"}}}' \
| tr -d '\r' | awk -F': ' '/^[Mm]cp-[Ss]ession-[Ii]d/{print $2}')
curl -s -X POST http://127.0.0.1:28080/mcp \
-H 'Content-Type: application/json' -H "Mcp-Session-Id: $SID" \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/list"}'
curl -s -X DELETE http://127.0.0.1:28080/mcp -H "Mcp-Session-Id: $SID"セッションは呼び出しログも正直にします: すべての呼び出しは、それを行ったセッションの clientInfo の下で監査されるため、複数の HTTP クライアントは、1 つの空白の client フィールドを共有する代わりに、calls.jsonl で区別されます。
HTTP 上のサーバー→クライアント要求(elicitation、sampling、roots)
アップストリームが通話中に何かを尋ねてきた場合 — elicitation/create、
sampling/createMessage、roots/list — その質問は、あるセッションの GET /mcp ストリーム 上の SSE イベントとして配信され、クライアントは同じ id と同じ Mcp-Session-Id を持つ JSON-RPC レスポンスを載せた通常の POST で答えます。質問が投げられたセッションだけがそれに答えることができ、他のセッションからの回答は無視されます。ストリームが開いている状態でその能力を宣言した者が誰もいない場合、アップストリームは仕様が定める形(elicitation では {"action":"decline"}、他の2つでは -32601)で即座に拒否され、タイムアウトに任されることはありません。また、質問が未解決の間にセッションが終了した場合も同様です。
知っておく価値のある3つの結果:
アップストリームには、最初に初期化したクライアントの能力が伝えられ、そのセットはプロセスの存続期間中固定されます。MCP 2025-06-18 には再交渉がないため、より多くの能力を宣言する2番目のクライアントは、すでに行われたハンドシェイクを変更できません — アップストリームは、それが知らされていないクライアントに代わって能力を約束されることは決してありません。
アップストリームは、ゲートウェイがポートにバインドするときではなく、それらを必要とする最初のリクエストで起動します。 それが上記の宣言を可能にしている理由です。ハンドシェイクは、クライアントが何をサポートしているかを述べた後に行われる必要があります。アップストリームを起動できない場合、クライアントは JSON-RPC
-32603を受け取り、ゲートウェイはエラーで終了します。これは、以前にそれらを先行的に起動していたときと同じです。質問は、その能力を宣言したクライアントに送られます — 必ずしもその呼び出しを引き起こしたクライアントではありません。 ルーティングは宣言された能力によって行われ、一致するセッションの中で最も最近アクティブだったものが勝ちます。アップストリームのリクエストには、それがどの呼び出し元に属するかを示すものは何もありません。単一のクライアント(通常のケース)ではこれは見えませんが、2つ実行すると、あるクライアントの
tools/callによって発生したフォームが別のクライアントの UI に表示される可能性があります。
同じ交換のアップストリーム側も HTTP で動作します。url: で到達したリモート MCP サーバーは、その質問を SSE フレームとして送信できます — 長期の GET ストリーム上、またはゲートウェイ自身の POST の1つに応答するストリームにインターリーブして。SDK サーバーは tools/call 内で発生した elicitation/create をそこに置きます。ゲートウェイはそれを同じパイプラインを通じてプロキシし、クライアントの回答を、サーバー自身のリクエスト id の下で JSON-RPC レスポンスを載せた1つの通常の POST として送り返します。そのようなアップストリームには、stdio のものと同じ誠実なポリシーでゲートウェイのクライアント能力が伝えられます — 能力は、ゲートウェイ自身のクライアントがそれを宣言した場合にのみ提供され、クライアントをまったく持たない doctor/call/catalog は、正確に {} を宣言し続けます。回答の POST は再試行されません。それを受け取らないアップストリームは、自身のタイムアウトにフォールバックします。
ジャーナル内のオペレーターイベント
log_file のジャーナルには2種類の行が保持されます。ツール呼び出しごとに1つ、オペレーターイベントごとに1つです — そうでなければ決して知ることのないゲートウェイの状態です。stdio モードでは MCP クライアントがターミナルを所有しているため、ゲートウェイの stderr はあなたには見えず、これらの条件のいくつかはデバッグレベルでのみログに記録されていました。現在は、mcp-gate logs が読み取るのと同じファイルに書き込まれます:
イベント | 意味 |
| アップストリームが起動しませんでした。そのツールはカタログにありません。 |
| スーパーバイザーがアップストリームの再起動を停止し(試行回数を使い果たした、リロードで再起動が無効になった、または liveness チャネルがない)、それをカタログから削除しました。 |
| サブスクライバーのバッファが満杯だったため、転送された通知がドロップされました — 転送は設計上非ブロッキングです。 |
| アップストリームがクライアントだけが答えられる何かを尋ね( |
| HTTP アップストリームが |
| 2つのエントリが同じクライアント向けツール/プロンプト名またはリソース URI を主張しました。先着優先で、敗者はクライアントから隠されます。 |
| リソース URI テンプレートがコンパイルされません。クライアントにはリストされますが、読み取りに一致することはできません。 |
| 結果が |
イベントは呼び出しとインラインで表示され、EVT とマークされます。mcp-gate logs --events はそれらだけを表示し、--stats はイベントごとのテーブルを追加します。--tool と --status は呼び出し専用のフィルターであるため、どちらかが設定されている間はイベントは除外されます(--upstream は両方に適用されます)。知っておく価値のある結果の1つ:notification_dropped はアップストリームを指定しません — ドロップは、通知を送信した人ではなく、バッファが満杯だったサブスクライバーの特性です — したがって、--upstream X はそれを決して表示しません。そのフィルターなしで探してください。繰り返されるドロップは合体されます — 最初のものはすぐに書き込まれ、1分以内のさらなるものはそのキーの次の行の count= に数えられ、残りはシャットダウン時にフラッシュされます。そのようなバックログを運ぶ行は、detail= にそれを示し、折り込まれる最も古い発生の時刻を指定します — 行自体のタイムスタンプは最新のものであるため、2つを合わせるとバーストが実際にいつ発生したかがわかります。
2つの実用的な注意点:
log_fileを設定してください。 空の場合、ジャーナルは stderr に送られます。stdio モードではそれは MCP クライアントに属します — イベントはあなたが見えない場所に書き込まれることになります。ジャーナルは、それを書いたのと同じ(またはそれより新しい)バイナリで読んでください。 イベントには古いバージョンが知らない
"kind"フィールドが含まれるため、≤ v0.4.0 のmcp-gate logsはそれらをまばらで、ほとんど空のレコードとしてレンダリングします。
これらは MCP クライアントには何も見えません。エラーコード、結果ボディ、能力は変更されていません — イベントはジャーナルにのみ送られます。
ゲートウェイがルーティングできなかった呼び出しはイベントではありません — それは通常の失敗した CALL 行です。 どのアップストリームも提供しないツール名をクライアントが要求すると、他のものと同じ CallRecord が得られ、その upstream 列はセンチネル (unrouted) に設定されます。mcp-gate logs --upstream '(unrouted)' は正確にそれらの行だけを選択し、他は何も選択しません。2番目の、異なるケースはほぼ同じに見えますが、実際のアップストリームを指定します:ルートは存在します(ツールはカタログにあります)が、アップストリームの接続がなくなっています(再起動中またはドロップされた)— その行は実際のアップストリーム名を運ぶため、センチネルではなく通常どおり --upstream <name> でフィルターします。
設定のリロード(SIGHUP)
ゲートウェイは SIGHUP で設定をライブでリロードします — 再起動も、クライアント接続の切断もありません。config.yaml を編集してシグナルを送信します:
kill -HUP $(pgrep -f 'mcp-gate serve')リロード時に、ゲートウェイは新しい設定を実行中のアップストリームと差分し、最小限の変更を適用します:新しく追加されたアップストリームは起動され、削除された(または enabled: false の)ものはシャットダウンされ、起動フィールド(command/args/url/env/headers)が変更されたアップストリームは再起動され、ツールフィルターのみが変更されたアップストリーム(allow/deny/rename、またはカタログ投影ルール strip_annotations/strip_output_schema/max_description/describe)は、再起動なしで再投影されます。呼び出し制限(rate_limit、max_concurrent、max_result_bytes、call_timeout — グローバルまたはアップストリームごと)もライブで適用されます:それらは再起動を必要とせず、次の呼び出しが単に新しい値を使用します。変更されていないアップストリームは、触れられずに実行を続けます。不正な編集(無効な YAML、検証の失敗)はログに記録され無視されます — 現在実行中の設定は有効なままなので、タイプミスでゲートウェイがダウンすることはありません。
動作上の注意: ゲートウェイは SIGHUP ハンドラをインストールするため、SIGHUP は OS のデフォルトのようにプロセスを終了しなくなります。ゲートウェイを停止するには、Ctrl-C、SIGINT、または SIGTERM を使用してください。
SIGHUP は Unix のみです。Windows — またはシグナルを送りたくない場所 — では、オプトインのポーリング代替手段を使用してください:
mcp-gate serve --config ./config.yaml --watch-config # bare flag = poll every 2s
mcp-gate serve --config ./config.yaml --watch-config=10s # note the "=", not a spaceこれはその間隔で設定ファイルのフィンガープリントを取得し、SIGHUP が取るのと同じリロードパスを適用します。SIGHUP ハンドラと並行して実行しても安全です。
ウォッチャーはファイルのmtime とサイズを比較し、そのフィンガープリントが次のティックで繰り返されるのを待ってからファイルを読み取ります。これが、2段階保存(切り詰め、次に書き込み)が実際に安全である理由です。ライターは、チェックを欺くために、ファイルを完全なポーリング間隔より長く半分書き込まれた状態に保持する必要があります。代償はレイテンシです — リロードは最大2つのポーリング間隔(デフォルトの2秒で最大4秒)以内に着地します。
stdio では、アップストリームはクライアントの最初のリクエストで起動するため、クライアントが接続する前に行われた編集はまだ適用できません。ウォッチャーはその編集を保持し、ゲートウェイが起動するまで毎回のポーリングで再試行し、その後適用します — 有効にするためにファイルを2回保存する必要はありません。永続的に拒否された編集(解析不能な YAML、または以下の no-upstreams ガード)は一度報告され、再試行されません。
両方のトリガーに対するバックストップとして、新しい設定が upstreams をまったく宣言しないリロードは拒否され、ログに記録されます:それは半分書き込まれたファイルの兆候であり、それを適用すると実行中のすべてのアップストリームが破棄されます。すべてのアップストリームを意図的に削除するには、ゲートウェイを再起動してください。明示的な enabled: false は影響を受けません — 最後のアップストリームを無効にしても適用されます。
設定
--config がない場合、ゲートウェイは自身のバイナリの隣にある config.yaml を探します(例:mcp-gate が /etc/gate/ にインストールされている場合、起動された作業ディレクトリに関係なく /etc/gate/config.yaml を探します)。そのファイルが存在せず、--config も渡されなかった場合、空のゲートウェイを起動する代わりに明示的にエラーになります。設定内の相対パス(log_file、skill_file、debug_payload_log)は、現在の作業ディレクトリではなく、設定ファイル自身のディレクトリに対して解決されます。
未知のキーは起動エラーです。 設定は厳密に解析されます:スペルミスや認識されないキーは、かつてのように黙って無視される代わりに、キー名とその行番号でゲートウェイを停止します。具体的な利点:enabled のタイプミスでアップストリームが静かに実行され続けることはもうありません。カスタムの x- スクラッチキーも拒否されます — ブロックを共有するには、最初の実際のアップストリームに YAML アンカーを置き、それを他のものにマージ(<<: *anchor)します。アンカーとマージキーは通常どおり機能します。
アップストリームはデフォルトで有効です:enabled: を完全に省略すると、他のものと同じように起動されます。設定を削除せずにゲートウェイから除外するには、enabled: false で明示的に無効にします — そうすると、tools/list にも mcp-gate doctor のテーブルにも表示されません。注意:値のない enabled:(または enabled: null)は省略として読み取られるため、値をコメントアウトするとアップストリームは実行されたままになります — 無効にするのはリテラルの false だけです。
注:「バイナリの隣」のルックアップは、実行中の実行可能ファイルのパスを使用します。
go run ./cmd ...の下では、その実行可能ファイルは一時ディレクトリ内の使い捨てビルドであるため、デフォルトのルックアップはあなたのconfig.yamlを見つけられません —go runを使用するときは--configを明示的に渡すか、ビルドされたバイナリを実行してください。
すべてのフィールドを含む完全な例 — config.example.yaml。
アップストリームサーバーのセットはYAMLで宣言されます。シークレット(トークン)はenv/.envを通して渡され(読み込み時に${VAR}展開)、設定にコミットされることはありません。各アップストリームはcommand(stdioサブプロセス)またはurl(HTTPサーバー、Streamable HTTP)のいずれか一方だけを設定します — 接続種別は自動的に推測されます。
未設定の${VAR}参照はフィールドごとに動作が異なります:
**
auth_token**が未設定の変数を参照している場合、起動に失敗し、変数名が示されます — 空のauth_tokenはHTTPベアラーチェックを静かに無効化してしまうため、これが静かに起こることは決して許されません。認証なしで実行するには、auth_tokenキーを完全に削除してください。アップストリームの**
env/headers内の未設定変数はエラーではありません**:値は空になり、欠落したシークレットは後でそのアップストリームからの401として表面化します。ゲートウェイは事前にそれを報告します — ジャーナル(mcp-gate logs)内のunresolved_secret_varイベントと、mcp-gate doctor内のWARN行です。stdioモードでは、
mcp-gate client-configは(stderrに)オペレーターの環境変数がMCPクライアントによって継承されないことを警告します。MCPクライアントは独自の環境でゲートウェイを起動します — クライアントが実行する場所で設定してください。
デフォルトでは、すべてのstdioアップストリームはゲートウェイの完全なプロセス環境を継承します — --env-fileが他のアップストリームのために読み込んだシークレットとauth_tokenを含みます。env_isolation: strict(グローバル)を設定すると、各stdioアップストリームは代わりに最小限のベース(PATH、HOME、TMPDIR、およびWindows相当のもの)と、常に子プロセスに届く独自のenv:ブロックのみを受け取ります。オープンレジストリからアップストリームを実行する場合(完全には信頼していないnpx起動パッケージ)に推奨されます。厳格なアップストリームに見せたいゲートウェイ変数は、そのenv:に明示的に列挙してください(例:PYTHONPATH: ${PYTHONPATH})。値は起動時に読み取られます:稼働中のアップストリームは、(再)起動されるまで起動時の環境を保持します。
別途、常に有効です:ゲートウェイが${VAR}シークレットを展開すると正確な値を把握し、オペレーター向けの自由テキスト — ジャーナル(mcp-gate logs)とクラッシュログ — でその値を***に**編集(redact)**します。シークレットが設定されていない場合、これはコストをかけません。
平文の暗号化されていないHTTPで非ループバックホストに送信されるシークレットは、起動時にWARNを受け取り、mcp-gate doctorに行が表示されます(決してハード障害ではありません — 信頼されたインフラストラクチャ上の平文、例えばVPNや隔離されたLANは、ゲートウェイが設定だけから推測できない意図的な選択であり得ます):ループバックを超えてバインドされた非空のauth_token、または非https://の非ループバックurlに送信されるアップストリームのheadersです。TLS自体はゲートウェイに組み込まれていません — 必要な場合はTLS終端リバースプロキシを前面に配置してください。これは相互TLSですでに推奨されているのと同じパターンです。
transport: stdio # stdio (Phase 1) | http (Phase 2)
listen_addr: "127.0.0.1:28080" # only used for transport: http; loopback by default
# auth_token: ${AIMCPGATE_TOKEN} # required if you widen listen_addr past loopback;
# # the variable must be set or startup fails
log_file: ./logs/calls.jsonl
# debug_payload_log: ./logs/payloads.jsonl # OPT-IN, off by default: logs raw
# # arguments AND results — can contain secrets
# Optional global call limits (each can be overridden per upstream):
# rate_limit: { rps: 5, burst: 2 } # token bucket per upstream for tools/call
# # (refusal → client error -32029, retryable)
# max_result_bytes: 65536 # truncate oversized textual results (0 = off;
# # non-text over-limit results get a _meta marker)
# call_timeout: 30s # bounds one upstream request
# How the catalog is presented to the client (both hot-reloadable):
# catalog_mode: lazy # normal (default) | lazy: the client sees only
# # gate_search_tools / gate_describe / gate_call
# page_size: 50 # paginate tools/list (0/omitted = whole catalog;
# # ignored in lazy mode)
# Auto-restart policy for crashed stdio upstreams (defaults: on, 1s→30s, 5 tries):
# restart: { enabled: true, initial_backoff: 1s, max_backoff: 30s, max_attempts: 5 }
# env_isolation: strict # opt-in ("" default = inherit the gateway's full
# # env): a stdio upstream then gets only a minimal
# # base (PATH/HOME/...) plus its own env: block —
# # NOT other upstreams' secrets or the auth token
upstreams:
- name: filesystem # stdio upstream
command: npx
args: ["-y", "@modelcontextprotocol/server-filesystem", "/home/user"]
enabled: true
- name: github
command: github-mcp-server
env:
GITHUB_TOKEN: ${GITHUB_TOKEN} # from the environment, not hardcoded
enabled: true
# Optional per-upstream tool filter / catalog projection (keys are ORIGINAL
# tool names; all editable live via SIGHUP with no upstream restart):
# tools:
# allow: ["search_repositories"] # if non-empty, only these survive
# deny: ["delete_repository"] # always subtracted, even from allow
# rename: { search_repositories: "gh_search" }
# strip_annotations: true # drop heavyweight catalog fields
# strip_output_schema: true
# max_description: 200 # truncate descriptions to N runes
# describe: { get_issue: "Fetch one issue." } # replace wholesale
# Optional per-upstream call limits (override the globals for this upstream):
# rate_limit: { rps: 1, burst: 1 } # rps: 0 disables the global limit here
# # (refusal → client error -32029, retryable)
# max_concurrent: 4 # cap on simultaneous in-flight calls
# # (refusal → client error -32029, retryable)
# max_result_bytes: 32768 # 0 disables the global cap here
# call_timeout: 120s # this upstream is slow — give it longer
- name: remote # http upstream (Phase 2)
url: https://mcp.example.com/mcp
headers:
Authorization: "Bearer ${REMOTE_MCP_TOKEN}" # secret, never logged
enabled: true呼び出し制限が効いたときにクライアントが見るもの
上記の呼び出し制限のうち2つは、オペレーターのジャーナルだけでなくMCPクライアント(エージェント)にも表面化します:
ガード拒否(
rate_limit/max_concurrent)。 ゲートウェイが、アップストリームごとのレートリミッターまたは同時実行数の上限が受け入れられなかったためにtools/callを拒否すると、クライアントはゲートウェイ独自のコード**-32029**と機械可読なdata: {"retryable": true, "reason": "rate_limit" | "concurrency_limit"}を含むJSON-RPCエラーを受け取ります。呼び出しはアップストリームに到達していないため、エージェントは二重実行のリスクなしに待機して再試行できます。通常のトランスポート/ルーティング障害は従来の-32603を維持し、アップストリーム自体が返すエラーはコードとデータを変更せずにそのまま転送されます — アップストリームからの-32029はゲートウェイのシグナルではありません。切り詰められない過大な結果(
max_result_bytes)。 テキスト結果はコンテンツ内の[truncated by mcp-gate: …]マーカーで縮小されます。制限を超えているが切り詰め可能なテキストがない非テキスト/非標準の結果(例:画像のみ)は、完全かつバイト単位でそのまま渡されます — そのcontent[]は決して変更されません — ただし、結果の_metaには{"limitBytes": N, "resultBytes": M}を持つゲートウェイキー**io.github.akomyagin.aimcpgate/result-over-limit**が追加され、エージェントが制限がバイパスされたことを認識できます。キーを知らないクライアントは単にそれを無視します。オペレーターのresult_truncation_skippedジャーナルイベントはこれまでどおり発生します。
ライセンス
MIT — LICENSEを参照してください。
Available Tools
2 toolsdemo-echo__echoB
Echo the given text back verbatim.
| Name | Required | Description | Default |
|---|---|---|---|
| text | Yes | Text to echo back verbatim. |
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations exist, so the description bears full responsibility. It merely repeats the parameter description ('Echo the given text back verbatim') without adding behavioral details such as side effects, error handling, or output format.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
The description is a single, efficient sentence with no extraneous information. Every word serves the purpose.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Given the tool's simplicity (one required parameter, no output schema), the description is largely complete. It could explicitly mention the return format, but the behavior is trivially inferable.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 100%, so the baseline is 3. The description adds no new meaning beyond the parameter's description, which already states 'Text to echo back verbatim.'
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
Description clearly states the action ('Echo') and the resource ('the given text back verbatim'), making the purpose unmistakable. The sibling tool 'ping' likely serves a different function (connectivity test), so this description distinguishes well.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
No guidance is provided on when to use this tool versus the sibling 'ping' or any alternatives. The description simply states what it does without contextual usage advice.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
demo-echo__pingA
Health check: always returns "pong".
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
No annotations provided, but the description fully discloses the output ('always returns pong'), leaving no ambiguity about its behavior for a simple ping tool.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
One concise sentence that is front-loaded with purpose and output, no unnecessary words.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a simple health check tool with no parameters and no output schema, the description is complete and sufficient for an agent to use correctly.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
No parameters in the schema; description adds no parameter info, but baseline for 0-param tools is 4.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description clearly states it is a health check that always returns 'pong', which is specific and distinguishes it from the sibling tool 'echo'.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
The description implies usage for health checks, and with only one sibling ('echo'), the purpose is clear without explicit alternatives. Slightly lacking explicit when-not-to-use guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
Tool Schema Changelog
Recent tool additions, removals, and schema changes observed during successful MCP inspections.
2 tool updates
v0.3.0- First observed
demo-echo__echo - First observed
demo-echo__ping
TDQS
Scored across 2 tools
The two tools serve completely different purposes: echo returns input text, ping returns a fixed health response. No ambiguity.
Both tools follow the same pattern: prefix 'demo-echo__' followed by a verb. The naming is uniform and predictable.
With only 2 tools, the set is minimal but appropriate for a simple demonstration server. It covers the core functionality without being overly sparse.
For the stated purpose of a demo echo server, the tools provide exactly what is needed: echo text and health check. No obvious gaps.
Maintenance
Related MCP Connectors
Governed MCP gateway: one endpoint for your tools, with credential custody and audit log.
MCP Gateway: wrap any MCP server with cold-start retries, uptime SLA, and per-execution MPP billing.
MCP server for your apps' tools and custom tools, plus hosted AI agents and approval-gated workflows
Unified gateway exposing 150+ tools across all NexGenData MCP servers via one endpoint.
Related MCP Servers
- AlicenseAqualityAmaintenanceLocal-first MCP proxy with BM25 tool discovery, quarantine security, Docker isolation, OAuth support, activity logging, and web UI. Routes multiple upstream MCP servers through a single endpoint.9388MIT
- AlicenseNot gradedqualityDmaintenanceMCP Gateway that aggregates multiple upstream MCP servers into a single endpoint with persistent connections, tool registry, and authentication.31 npm2MIT
- FlicenseAqualityDmaintenanceUniversal MCP proxy server that discovers, searches, and executes tools across all configured MCP servers from a single entry point.7-
- AlicenseAqualityBmaintenanceA zero-dependency MCP gateway: host your own tools, forward and curate tools from other MCP servers, expose them leanly to cut agent token cost, and gate every call through your own policy hooks before it runs.434 npm38MIT