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の代わりに、機械可読なdata: {"retryable":true,"reason":...}を伴う独自の JSON-RPC エラーコード-32029を返すようになりました。また、max_result_bytesをすり抜けた非テキスト結果にはresult._metaマーカーが付きます (contentはバイト単位でそのままです)。最後に、未設定の環境変数を参照するauth_tokenは、HTTP 認証を静かに無効化するのではなく、ゲートウェイの起動を拒否するようになりました。v0.5.0 へのアップグレード — 3 つの動作変更。いずれも設定ファイルの形式自体には触れません:
設定解析は厳格になりました。 以前は静かに無視されていた、未知の、またはスペルミスのあるトップレベルまたはアップストリームごとのキーを含む設定は、ロードに失敗するようになりました。キー名を修正するか (エラーがキー名を示します)、削除してください。
未設定の
VARを持つauth_token: ${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 リクエストで遅延して起動します。未実装: クライアントごとのアクセスポリシー。
Releases
クロスプラットフォームのバイナリは goreleaser (.goreleaser.yaml) でビルドされます: linux/darwin/windows × amd64/arm64、CGO なし、バージョンは -ldflags -X main.version=... で埋め込まれ、チェックサムは SHA256SUMS に置かれます。ローカルでのドライラン: goreleaser release --snapshot --clean。
Related MCP server: mcpproxy-go
Install from MCP registry
生のリリースバイナリに加えて、ゲートウェイは 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 の例外です: 実行中のゲートウェイが書き込むジャーナルファイルを読み取るため、稼働中のプロセスを反映します。そのためには、マウントされた設定の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 は以下を提供します:
単一のエントリポイント — クライアント設定の 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:
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 が読み取る同じファイルに書き込まれます:
イベント | 意味 |
| アップストリームが起動しませんでした。そのツールはカタログにありません。 |
| スーパーバイザーがアップストリームの再起動を停止しました(試行回数を使い果たした、再起動がリロードで無効化された、またはライブネスチャネルがない)そしてそれをカタログから削除しました。 |
| サブスクライバーのバッファが満杯だったため、転送された通知がドロップされました — 転送は設計上非ブロッキングです。 |
| アップストリームがクライアントだけが答えられる何か( |
| 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、または以下の upstreams なしガード)は1回報告され、再試行されません。
両方のトリガーでのバックストップとして、新しい設定が 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クライアントは独自の環境でゲートウェイを起動するため、クライアントがゲートウェイを実行する場所でそれらを設定してください。
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 }
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 を参照してください。
Maintenance
Related MCP Servers
- FlicenseNot gradedqualityDmaintenanceEnables centralized management and unified interface for multiple child MCP servers (filesystem, sqlite, etc.), allowing users to discover, launch, and execute tools across different MCP servers through a single gateway.
- 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.9321MIT
- AlicenseNot gradedqualityDmaintenanceMCP Gateway that aggregates multiple upstream MCP servers into a single endpoint with persistent connections, tool registry, and authentication.452MIT
Related MCP Connectors
Hosted MCP server for LLM cost estimation, model comparison, and budget-aware routing.
Hosted AgentLux MCP server for marketplace, identity, creator, services, and social flows.
Operator-as-agent MCP hub. 6 tools. First $5 free, then $0.001/call.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/akomyagin/aiMCPGate'
If you have feedback or need assistance with the MCP directory API, please join our Discord server