Openagenda について
Openagendaは、OpenAgenda v3 APIを探索・試作するエージェント向けのMCPサーバーです。エンドポイントごとではなく2つのツールで操作し、ホスト版はOAuthとコード分離を備えます。
主な機能
- 01APIドキュメントの検索
- 02コードによるAPI操作の実行
- 03読み取りと書き込み操作への対応
- 04ホスト版でOAuthによるアクセス範囲設定
- 05ローカルまたはホスト版で実行
活用シーン
- 01該当するOpenAgenda API操作の検索
- 02イベント管理操作の調査や試作
- 03OAuthでホスト版サービスに接続
MCP 対応
MCPを通じてOpenAgenda v3 APIを公開します。search_docsで操作を探し、executeでコードを実行します。ホスト版はStreamable HTTPとOAuthを使用し、ローカル版はstdioで実行できます。
設定
リモートサーバーはインストール不要です。クライアントを選んで設定をコピーするか、ワンクリックで導入できます。
https://mcp.openagenda.com/mcpclaude mcp add --transport http openagenda https://mcp.openagenda.com/mcp{"mcpServers":{"openagenda":{"type":"http","url":"https://mcp.openagenda.com/mcp"}}}Cursor で開く{"servers":{"openagenda":{"type":"http","url":"https://mcp.openagenda.com/mcp"}}}VS Code で開く{"mcpServers":{"openagenda":{"type":"http","url":"https://mcp.openagenda.com/mcp"}}}{"mcpServers":{"openagenda":{"serverUrl":"https://mcp.openagenda.com/mcp"}}}コマンドはターミナルで実行し、JSON は各クライアントの MCP 設定に貼り付けて再起動します。認可が必要なサーバーは初回接続時にログインを求めます。
ツール
詳細
@openagenda/mcp
コードモードパターンを通じて、LLM クライアントに OpenAgenda v3 API を公開する MCP サーバーです。エンドポイントごとに 1 つのツールを用意する代わりに、2 つのツール、search_docs(適切な操作を見つける)と execute(API に対してコードを実行する)を提供します。execute は、呼び出し元の任意のコードを v3 の API 領域に対して実行します(読み取りおよび書き込み/管理/モデレーション)。そのため、実行可能な操作の範囲は、サンドボックスの送信トラフィック境界、呼び出し元ごとの OAuth スコープ、破壊的なツールに対する人間の承認によって制限されます。
⚠️ ホスト型のマルチテナントサーバーとしてデプロイされています (
https://mcp.openagenda.com、OAuth による保護あり)。ホスト型のポリシー層(µVM 境界、OAuth、レート制限、監査、メンテナンス用停止機能)が整備されています。書き込み機能を利用する前に留意すべき点については、ホスト型/マルチテナントをご覧ください。
ホスト型サーバーに接続する
ホスト型サーバーは、この MCP の推奨利用方法です。インストールは不要で、OpenAgenda アカウントを使った OAuth(初回接続時にブラウザーでの同意画面が開きます)、呼び出し元ごとのスコープ設定、レート制限を利用できます。エンドポイントは https://mcp.openagenda.com/mcp(Streamable HTTP)です。
Claude Code
claude mcp add --transport http openagenda https://mcp.openagenda.com/mcp
続いて /mcp を実行して認証します。
Claude Desktop/claude.ai — Settings → Connectors → Add custom connector を選択し、https://mcp.openagenda.com/mcp を貼り付けます。
Cursor — ~/.cursor/mcp.json(またはプロジェクトの .cursor/mcp.json)に追加します。
{
"mcpServers": {
"openagenda": { "url": "https://mcp.openagenda.com/mcp" }
}
}
VS Code
code --add-mcp '{"name":"openagenda","type":"http","url":"https://mcp.openagenda.com/mcp"}'
その他 — このサーバーは、OAuth で保護された標準的な MCP リソースです(https://mcp.openagenda.com/.well-known/oauth-protected-resource/mcp で RFC 9728 のディスカバリーに対応)。リモート MCP と OAuth をサポートするクライアントであれば、どれでも利用できます。
自分で実行したい場合は、npx -y @openagenda/mcp を使うと、API キーを使って MCP over stdio で動作します。クイックスタート(ローカル)をご覧ください。
API を使って開発する
このサーバーは、OpenAgenda を探索し、プロトタイプを作成するエージェント向けです。サイトやツールをリリースするには、npm の型付き SDK @openagenda/api-client を使って同じ操作を実行してください。これは execute サンドボックスにバンドルされている SDK そのものなので、ここでエージェントが試作したコードは、アプリ内でも変更なしで実行できます。execute の本体と異なるのは、クライアントの初期化を一度行う点だけです。
npm install @openagenda/api-client
import { OpenAgenda, client } from '@openagenda/api-client';
client.setConfig({ baseUrl: 'https://api.openagenda.com/v3', auth: 'oa_pk_…' });
const oa = new OpenAgenda();
const { data, error } = await oa.agendas.events.list({
path: { agendaUid },
query: { relative: ['upcoming'] },
});
公開データの読み取りには読み取り専用の公開キー(oa_pk_…、ユーザー ID なし)を、書き込みにはシークレットキー(oa_sk_…、サーバー専用)を使います。schemas の Zod バリデーターもパッケージからエクスポートされています。操作のリファレンスは search_docs(および API ドキュメントの Scalar)を参照してください。そこに表示される呼び出しは SDK のものです。
アーキテクチャ
openapi.yaml ─▶ (docs index) ─▶ search_docs ┐
├─▶ MCP server (stdio) ─▶ SandboxExecutor ─▶ runtime
client writes JS ───────────────▶ execute ──┘ (pluggable)
設定に応じて選択される、シンプルなインターフェース SandboxExecutor が 1 つあります。サーバーの約 95%は共通で、コードを実行するアダプターだけが異なります。そのため、重複なく、広く互換性のあるローカルモードと強固なマルチテナント境界を同じコードベースから提供できます。
エンジン(OA_EXECUTOR) | ランタイムの分離 | 送信トラフィックを管理するか | 用途 |
|---|---|---|---|
node | 権限制限サンドボックス(fs/サブプロセス/ワーカーを拒否)+上限 | ❌ ホスト単位のネットワーク権限なし | ローカルのデフォルト(インストール不要) |
deno | デフォルトですべての権限を拒否+実時間による強制終了+ヒープ上限 | ✅ スコープを限定した --allow-net | ローカルのセキュリティ強化(送信先を限定) |
microsandbox | ハードウェア分離されたマイクロ VM(独自のカーネル) | ✅(µVM) | ホスト型/マルチテナント |
エンジンは 1 つの軸であり、ネットワーク送信を誰が管理するか(OA_CODE_EGRESS_AUTHORITY)がもう 1 つの軸です。srt はエンジンではなく、基準となる ラッパー です。両方の軸、有効性のマトリクス、フェイルクローズのルールについては、以下の実行モデルをご覧ください。
実行モデル — エンジンと隔離ラッパー
直交する2つの軸があります。
- 実行エンジン — 送信された JS を実際に実行するものです。
node·deno·microsandbox。 - コードの外部通信を許可する権限 — ネットワーク上でコードが到達できる場所を_誰が_決めるかです。権限は必ず1つだけで、明確でなければなりません。
executor— エンジンが適用します(deno の--allow-net=<host>、µVM)。wrapper— 外側の隔離レイヤーが管理します。srt はその_実装の1つ_です。Docker の--network、gVisor、カスタムプロキシなども該当します。none— 外部通信を適用するものがありません。信頼できるコード専用です。
srt はラッパーであり、エンジンではありません。 プロセスとその_すべての子孫プロセス_を隔離するため(名前空間 + seccomp + 外部通信プロキシ)、その配下で起動したエンジンは隔離境界を引き継ぎます。したがって、srt はエンジンの軸ではなく外部通信の軸に属します。srt -- node server.js は、エンジンとは独立して、デプロイする人が選択します。
MCP サーバー自体は常に Node.js で動作します(
nodeで起動します)。OA_EXECUTORが制御するのは、executeツールが_送信された_コードの実行に使用するランタイムのみです。そのため、OA_EXECUTOR=denoでも、サーバーを deno で起動するという意味ではありません。
外部通信の2つのスコープ — 混同しないでください
OA_CODE_EGRESS_AUTHORITY が制御するのは、実行コードのみです。
オーケストレーター自身の外部通信は、_別の_スコープです。コードが microsandbox 内で実行されている間、縦深防御として MCP プロセス全体を srt の配下で実行できます。これは microsandbox + wrapper ではありません。そこでラッパーが制限する対象は、コードの µVM ではなく、_こちら_のプロセス(microsandbox の制御エンドポイントのみを許可リストに登録)です。以下のコード外部通信マトリクスはスコープ1についてのもので、スコープ2はデプロイ方法のみで決まります。
単一の権限 — 有効性マトリクス(コードの外向き通信)
| エンジン + 権限 | 判定 |
|---|---|
deno + executor | ✅ deno が --allow-net=<apiHost> でスコープを限定します — 自己完結型です |
node + wrapper | ✅ 外向き通信の境界は wrapper が管理します。node はそのプロキシを使用します(NODE_USE_ENV_PROXY=1) |
deno + wrapper | ✅ deno は制限なしで実行されます — --allow-net でもスコープを限定してはなりません。限定すると wrapper のプロキシがブロックされます(macOS) |
microsandbox + executor | ✅ µVM が外向き通信を管理します |
node + executor | ❌ ポリシーにより拒否 — Node の権限モデルでは外向き通信のスコープを限定できません(Node 24 にはネットワーク権限がなく、Node 25 の --allow-net はホスト単位ではなく、すべて許可するか、すべて拒否するかの二択です)。実際の境界を設けるには、wrapper または microsandbox を使用してください。 |
node + none | ⚠️ ローカルのデフォルト(インストール不要)。エンジンは引き続き Node の権限サンドボックスを自己適用します(--permission、Node ≥ 24:fs/subprocess/workers を拒否)。ただし、OA_LOCAL_NO_SANDBOX=1 の場合を除きます — 外向き通信は制限されません。起動時にバナーで通知されます。 |
other + none | ⚠️ OA_MCP_MODE=local と明示的な OA_LOCAL_NO_SANDBOX=1 の承認がある場合のみ受け入れます — 暗黙のデフォルトにはなりません |
microsandbox + wrapper | ❌ 非対応 — srt の seccomp は µVM に必要な KVM/デバイスプリミティブを拒否します。また、µVM がすでに強固な境界となっているため、重複します |
hosted + anything else | ❌ フェイルクローズ — hosted には microsandbox + executor が必要です |
wrapper は、アプリでは検証できないデプロイ契約です
wrapper の場合、境界はデプロイ担当者が交わす約束です — アプリは srt が正しく設定されていることを証明できません。そのため、アプリはその設定を信用するのではなく、不正な設定を明確に警告し、設定のずれを防ぎます。
- 上記の❌の行ではフェイルクローズします(警告ではなく、起動を拒否します)。
- ポリシーを出力することで、デプロイ担当者が手作業でコピーしたリストではなく、アプリが独自に導出した許可リストを wrapper に適用できるようにします:
openagenda-mcp print-egress-policy(--format=srt|json、およびpolicySha256フィンガープリント)。 authority=wrapperの場合、起動バナーを表示します:「このプロセスは外向き通信を独自には制御しません。wrapper は<apiHost>のみを許可する必要があります。」
推奨されるデプロイ
- ローカルのデフォルト →
node+none(インストール不要、Node ≥ 24)。エンジンは Node の権限サンドボックス(--permission)内でコードを実行します。ファイルシステム、サブプロセス、ワーカー、アドオンにはアクセスできません。ただし、制限されないのは外部への通信です。プロンプトインジェクションされたスクリプトは、API キーをどこにでも送信できます(ローカルファイルは読み取れなくなります)。起動時にバナーが表示されます。 - ローカルの強化構成(推奨) →
OA_EXECUTOR=deno(およびデフォルトのexecutor)。インストールを 1 つ追加すると、スコープ付きの外部通信境界(--allow-net=<apiHost>)が追加されます。キーが別のホストに送信されることは物理的に不可能です。 - ローカルの制限なし構成(オプトアウト) →
OA_LOCAL_NO_SANDBOX=1。権限サンドボックスも無効になり、ファイルシステム/ネットワークの境界は一切ありません。明示的に承認した信頼できる用途に限ります。 - ローカルの多重防御構成 →
node+wrapperを、(後述の)srtの下で実行します。OS レベルのファイルシステム/プロセス分離と、wrapper が管理する外部通信制御が追加されます。 - ホスト型/公開構成 → コードには
microsandbox+executorを使用し、これとは独立して、オーケストレーターを srt の下で実行します(microsandbox の制御エンドポイントのみを許可リストに登録)。2 つの層、2 つのスコープです。microsandbox を srt の下で実行しないでください。
このモデルが制御するのはネットワーク境界のみです。リソース上限(タイムアウト、ヒープ)はエンジンとオーケストレーターの役割であり、レート制限、同時実行数、監査はオーケストレーターの役割です。いずれも上記の軸とは独立しています。
srt の下で実行する(wrapper の権限)
正確なポリシーを出力してください。OA_BASE_URL とずれたり、後述の読み取りに関する落とし穴に陥ったりしないよう、手書きしないでください。
openagenda-mcp print-egress-policy --format=srt > srt-settings.json
このファイルは完全な srt 設定オブジェクトです。
{
"network": { "allowedDomains": ["api.openagenda.com"], "deniedDomains": [] },
"filesystem": {
"denyRead": ["~"],
"allowRead": [".", "/home/you/.nvm/versions/node/vXX"],
"allowWrite": [],
"denyWrite": []
}
}
境界は 3 つあり、読み取りの境界は省略できません。
- ネットワーク — 許可した対象のみ。API ホストにのみアクセスできます。
- 書き込み — 許可した対象のみ。書き込み可能な場所はありません。
- 読み取り — srt はデフォルトですべての場所の読み取りを許可します(拒否してから許可)。
denyReadが空だと、~/.ssh、.env、クラウド認証情報などが読み取り可能になります。さらに、コードモードは実行した値を呼び出し元に返すため、return readFileSync('~/.ssh/id_rsa')は結果の経路を通じてデータを外部に流出させ、ネットワークの許可リストを完全に回避します。そのため、出力されるポリシーではホームディレクトリを拒否し、ワークスペース(.)とランタイムのインストールルートのみ再許可します(/usrなどのシステムパスは読み取り可能なままです)。
allowReadのランタイムパスはマシンごとに異なります(node/deno のインストール場所)。ホストごとにポリシーを再生成してください。deno エンジンを srt の下で実行する場合は、ホームディレクトリ内にある deno のキャッシュディレクトリ(DENO_DIR、デフォルトは~/.cache/deno)も再許可してください。
そのポリシーを使って、srt の下で MCP を起動します。
OA_EXECUTOR=node OA_CODE_EGRESS_AUTHORITY=wrapper \
srt --settings srt-settings.json -- node public/mcp/src/index.js
srt はプロセスと、それが起動するエンジンを隔離するため、外部通信と読み取りの両方を単独で制御します。node は srt のプロキシを NODE_USE_ENV_PROXY=1(エンジンによって設定)で使用します。ホスト型デプロイでは、同じラッパーでオーケストレーターを強化できます。その場合、許可リストに登録するのは microsandbox の制御エンドポイントです。
クイックスタート(ローカル)
前提条件: Node ≥ 24 — 以上です。デフォルトのエンジンは、権限サンドボックス内で動作する node 自体です(ファイルシステム/サブプロセスへのアクセスなし。ネットワークは制限されません — 推奨デプロイを参照)。推奨されるセキュリティ強化策として、deno をインストールし、OA_EXECUTOR=deno を設定して、スコープを限定した外部通信の境界を設けてください。srt ラッパーの設定については、srt での実行を参照してください。
# From npm — no checkout needed:
# The API needs a credential (no anonymous read): any OpenAgenda API key for stdio (oa_pk_… shown; a least-privilege/read key is advised — it's baked into the sandboxed code). HTTP uses OAuth.
OA_API_KEY=oa_pk_xxx npx -y @openagenda/mcp # speaks MCP over stdio (node engine)
# Or from a checkout:
yarn install
export OA_API_KEY=oa_pk_xxx
export OA_BASE_URL=https://dapi.openagenda.com/v3 # dev; defaults to production
node public/mcp/src/index.js
MCP クライアント(例:Claude Desktop / Claude Code)に登録します。
{
"mcpServers": {
"openagenda": {
"command": "npx",
"args": ["-y", "@openagenda/mcp"],
"env": { "OA_API_KEY": "oa_pk_xxx" }
}
}
}
(チェックアウトから使う場合は、代わりに "command": "node"、"args": ["/abs/path/to/public/mcp/src/index.js"] を指定してください。)
Claude Desktop ならワンクリック — 最新リリースから openagenda.mcpb を取得し、ダブルクリックするか、設定にドラッグしてください。デスクトップアプリが API キーを求め、上記の stdio サーバーを自動で設定します。このバンドルは薄いランチャーであり、npx @openagenda/mcp を実行するため、常に現在公開されているバージョンを取得します。.mcpb 自体が更新されるのは、メタデータが変更されたときだけです。(これは API キーを使う方法です。キーなしで OAuth を使う場合は、代わりに URL でホストされたリモートサーバーを追加してください — ホストされたサーバーへの接続を参照。)yarn pack:mcpb で自分でビルドすることもできます。
次に、たとえば 「アジェンダ X では、市ごとの今後のイベント数はいくつですか?」 と尋ねてください。モデルが search_docs を呼び出し、oa.agendas.events.facets(...) を使うスクリプトを作成し、execute がサンドボックス内で実行します。
CLI フラグ
秘密情報ではない呼び出し設定はフラグとしても指定できます。それぞれ対応する環境変数に割り当てられます(フラグが優先されます。全一覧は --help を参照してください)。
npx -y @openagenda/mcp --executor=deno --base-url=https://dapi.openagenda.com/v3
シークレット(OA_API_KEY など)は、設計上環境変数でのみ指定できます。argv は ps//proc から見えるため、認証情報を渡すフラグは用意されていません。
設定(環境変数)
| 変数 | デフォルト | 意味 |
|---|---|---|
OA_MCP_MODE | local | local | hosted(デフォルト設定とフェイルクローズのゲーティングを制御します) |
OA_EXECUTOR | node (local) / microsandbox (hosted) | エンジン:node | deno | microsandbox(「実行モデル」を参照。deno はスコープ付きエグレスのアップグレードです) |
OA_CODE_EGRESS_AUTHORITY | none (node) / executor (others) | コードのエグレスを管理する主体:executor | wrapper | none |
OA_LOCAL_NO_SANDBOX | off | ベアな node の場合:権限サンドボックスも無効にします(境界は一切ありません)。node 以外のエンジンでは egress=none の確認にも使われます |
OA_BASE_URL | https://api.openagenda.com/v3 | v3 のベース URL |
OA_API_KEY | none | 任意の OpenAgenda API キー(Bearer)— stdio の認証情報です。最小権限の読み取り専用キーを推奨します(HTTP では OAuth を使用します) |
OA_SANDBOX_TIMEOUT_MS | 5000 | ハードな実時間制限による強制終了 |
OA_SANDBOX_MEMORY_MB | 256 | V8 ヒープ上限(node/deno)/µVM RAM のハード上限(microsandbox では、より多くの余裕が必要です) |
OA_MAX_CONCURRENCY | 4 | 同時に実行できる execute の最大数 — ホスト RAM の保護上限 |
OA_EXEC_MAX_QUEUE | OA_MAX_CONCURRENCY × 10 | 再試行可能な「容量上限」エラーが返されるまでに、スロット待ち状態にできる execute 呼び出しの最大数(デフォルトは上限に応じて自動調整されます) |
OA_EXEC_QUEUE_TIMEOUT_MS | 30000 | 容量超過時に、再試行可能な「容量上限」エラーが返されるまで、execute が空きスロットを待つ時間 |
OA_RATE_LIMIT_PER_MIN | 60 | 呼び出し元あたりの持続可能な execute 呼び出し数/分(OAuth sub)。transport=http のみ — トークンごとのレート制限です |
OA_RATE_LIMIT_BURST | 20 | 再試行可能な「レート制限に達しました」エラーが返されるまでに許可される、呼び出し元あたりの execute のバースト数(トークンバケットのサイズ) |
OA_MAX_CONCURRENCY_PER_CALLER | 2 | 1 つの呼び出し元が保持できる execute の同時実行数の最大値(OAuth sub)。transport=http のみ — 1 つの呼び出し元がグローバルスロットをすべて占有できないようにする公平性の上限です。実質的に無効化するには、OA_MAX_CONCURRENCY + OA_EXEC_MAX_QUEUE 以上に設定します |
OA_INSIGHT_OPS_TOKEN | none | InsightOps ログトークン — ログと監査情報を送信します(本番環境)。代わりに stderr に出力するには、DEBUG=openagenda-mcp* を設定します |
OA_EXECUTE_DISABLED | off | 1 → execute を拒否します(メンテナンス/インシデント時)。search_docs は引き続き提供されます。呼び出し元ごとの禁止設定はここではなく AS にあります |
OA_MICROSANDBOX_IMAGE | node:24-alpine | µVM 用の OCI イメージ(microsandbox。Node Alpine の公式イメージで、node が PATH 上にあります。本番環境ではダイジェストを固定してください — config.js を参照) |
OA_SANDBOX_RUNTIME | node | µVM 内部の JS ランタイム:node | llrt(microsandbox のみ。llrt は RAM を約 52%削減し、起動後は約 3 倍高速です — docs/microsandbox.md を参照) |
OA_LLRT_BIN | (unset) | バインドマウントする llrt 静的バイナリのホストパス(任意。llrt.Dockerfile のようにイメージに llrt が PATH 上の状態で組み込まれている場合は未設定にします) |
OA_MICROSANDBOX_POOL_SIZE | 0 (off) | ウォーム状態の単回使用 µVM の予備数(microsandbox。スループット最適化用ですが、RAM を保持します — docs/microsandbox.md を参照) |
OA_USE_SYSTEM_CA | off | 開発専用:OS の証明書ストアを信頼します(Node は独自の証明書を同梱しています) |
OA_EXTRA_CA_CERTS | none | 開発専用:追加の PEM CA バンドルへのパス |
開発用 TLS。
dapi.openagenda.comは Node に同梱されているルート証明書では信頼されないプライベート CA(O=OADEV)を使用しているため、UNABLE_TO_VERIFY_LEAF_SIGNATUREが発生します。OA_USE_SYSTEM_CA=1(開発用 CA はシステムの証明書ストアにあります)またはOA_EXTRA_CA_CERTS=docker/devinstaller/ssl/certs/ca.crtを設定してください。本番環境(api.openagenda.com)ではどちらも不要です。設定しないでください。
ログの出力先。 環境変数で個別に有効化する、独立したログ出力先が 2 つあります(NODE_ENV は関係ありません)。stderr:
DEBUG=openagenda-mcp*(標準のdebug設定)を指定します。ターミナルまたは dev/docker でログを確認する方法です。InsightOps:OA_INSIGHT_OPS_TOKEN(prod)を指定します。どちらも設定しない場合、サーバーは起動時に「ログ出力先が設定されていません」と一度警告し、その後は何も出力しません。これにより、ログや監査証跡が黙って破棄されることはありません。致命的エラーと起動時の安全性バナーは、常に stderr に直接出力されます。両方を有効にしてもかまいません。どちらも stdout には影響しません(stdio の安全性を維持します)。
ホステッド/マルチテナント
これをパブリックサーバーとして実行する前に必要な対策です。つまり、インターネット上の誰もが、お客様のインフラ上で実行されるコードを送信できる場合を指します。ローカル環境(deno、または srt 下の node)は、信頼範囲が限定されている場合のガードレールであり、信頼できないコードに対する強固な境界ではありません。config.js はフェイルクローズの設計です。OA_MCP_MODE=hosted では OA_EXECUTOR=microsandbox と OA_CODE_EGRESS_AUTHORITY=executor が必須で、それ以外はすべて拒否されます。
microsandbox のリファレンス(実測レイテンシ/フットプリント、SNI エグレスモデル、イメージ選定の根拠、プールのサイズと存続期間の不変条件)は、docs/microsandbox.md にあります。エンジン自体は実装済みです(起動、ホスト側で強制するエグレス、厳格な上限)。実際の KVM ホストで検証済みです。呼び出し元ごとの OAuth トークン、レート制限、ツールごとの監査ログ、メンテナンス用キルスイッチ(
OA_EXECUTE_DISABLED)も導入済みです。サーバーは現在、本番環境(https://mcp.openagenda.com、本番 AS に対する OAuth 強制)にデプロイされています。仮想化ホストでOA_MSB_IT=1 yarn workspace @openagenda/mcp testを実行し、µVM 統合テストを行ってください。
データ流出を防ぐ境界はエグレス許可リストであり、
oaクライアントではありません。 実行されるコードは信頼できず、oaクライアントと同じスコープを共有しています。埋め込み済みのキーを読み取り、fetchを直接呼び出せるため、oaクライアントは境界として機能しません。キーの外部流出を実際に防ぐのは、サンドボックスのネットワークエグレス許可リスト(deno の--allow-net、srt のallowedDomains)であり、API ホストに対象を限定します。ここを強化してください。クライアントはセキュリティ境界ではありません。
各項目は具体的な脅威に対応しています。
- 任意コードの実行/サンドボックスからの脱出 → すべての実行を一時的なハードウェア分離型マイクロ VMで実行します(microsandbox。実績のある代替手段として gVisor
--runtime=runsc)。呼び出しごとに VM を 1 つ作成し、実行後に破棄します。呼び出し元をまたいで再利用することはありません。共有カーネルのコンテナーやsrt/denoでは、ここで必要な分離を実現できません。 - 無限ループ/ハング → VM レベルで厳密な実時間制限による強制終了を行います(コード内での
setTimeoutでは不十分です。信頼できないコードはこれを無視します)。 - 過剰に重い処理 → VM 内の cgroups でRAM、CPU、PID に上限を設けます。
srtにはリソース制限がありません。これも、srt のようなラッパーをホスト環境の境界として使えない理由です。 - 疑わしいフェッチ/SSRF/データ流出 → デフォルトですべての送信を拒否し、API ホストのみを許可リストに登録します。さらに、クラウドのメタデータエンドポイント
169.254.169.254、RFC1918 のアドレス範囲、localhost もブロックします。(典型的な攻撃経路は SSRF → クラウド認証情報の窃取です。) - 悪用/自己 DoS → 相互に補完する 3 つのガードレールを設けています。いずれも実装済みです。
- 同時に実行するサンドボックスのグローバル同時実行数上限と、サイズに上限のあるキュー(
OA_MAX_CONCURRENCY。デフォルトは 4 です。負荷が上限に達した状態で急増した呼び出しは、約 116 MiB の µVM を無制限に起動してホストのメモリ不足を招くのではなく、キューに入り、その後リトライ可能なビジー応答を受け取ります)。concurrencyLimit.jsはすべてのエンジンとトランスポートでエグゼキューターをラップし、ID に関係なく瞬間的な負荷をグローバルに制限します。 - 呼び出し元ごとの同時実行数上限(
callerConcurrency.js。OAuth のsubをキーにします。OA_MAX_CONCURRENCY_PER_CALLERのデフォルトは 2 です。transport=httpのみ)により、1 人の呼び出し元が同時にパイプライン内で保持できる実行数(実行中、またはグローバルキューで待機中)を制限します。これにより、1 人の呼び出し元がグローバル枠をすべて占有して他の呼び出し元を締め出すことを防ぎます(グローバル上限だけでは呼び出し元を識別できず、急増した呼び出し数が上限を大幅に超えると、1 人の呼び出し元がすべての枠を取得できてしまいます)。上限に達した呼び出し元は、リトライ可能なビジー応答を受け取ります。インスタンス内のメモリで管理します。 executeに対する呼び出し元ごとのレート制限(rateLimiter.js。OAuth のsubをキーにしたトークンバケットです。OA_RATE_LIMIT_PER_MIN/OA_RATE_LIMIT_BURST。transport=httpのみ)により、呼び出し元ごとの継続的な負荷を制限し、長期にわたって共有枠を独占できないようにします。レート上限を超えた呼び出しは、リトライ可能な「レート制限に達しました」という結果を受け取ります。インスタンス内のメモリで管理します(同時実行数の上限と同様、レプリカ数に応じて実効上限が変わります)。
- 同時に実行するサンドボックスのグローバル同時実行数上限と、サイズに上限のあるキュー(
- API 自体を介した権限昇格 → 暗黙の権限は持たせません。呼び出し元にスコープ設定された OAuth トークンを実行コードに注入します。API がサーバー側でスコープを適用するため、実行が成功しても呼び出し元自身の権限を超えることはありません。サーバーには、コードがアクセスできる共有シークレットを保持しません。OAuth が必須となるのはこのケースです(下記の認証を参照)。ここで共有キーを使うと、すべての呼び出し元に有効な権限を、誰でも使用または流出させることができます。
- 監査とフォレンジック → 実装済みです。ツールごとの監査ログ(
log.js)により、search_docs/executeの呼び出しごとに構造化されたレコードを 1 件出力します。レコードには、呼び出し元のsubとクライアントアプリ、トランスポート、結果、所要時間、8 KiB を上限とするコード本文とその完全な sha256、bytes_out、元に戻せないcredential_fpが含まれます。シークレットや結果ペイロードは含まれません。出力先は InsightOps(本番環境ではOA_INSIGHT_OPS_TOKENを使用)または stderr(DEBUG)です。停止スイッチにはOA_EXECUTE_DISABLEDを使います(executeを停止し、search_docsは継続します)。特定の呼び出し元の利用停止は AS の役割(許可の取り消し)であり、ローカルの拒否リストで行うものではありません。 - サプライチェーン/成熟度 → microsandbox はまだ新しく、ベータ版です。バージョンを固定し、アップグレードを確認したうえで、境界として実績のある代替手段である gVisor を維持してください。
サンドボックスが担うのは分離であり、ポリシー全体を担うわけではありません。送信先の許可リスト、リソース上限、スコープ付きトークンのモデルは、その上に自分で設定する必要があります。どのエンジンでも、これらが自動で備わるわけではありません。
認証:キーまたは OAuth、トランスポートとは独立
認証方式、トランスポート、テナント構成は互いに独立した軸です。「ホスト型」と「OAuth」を混同しないでください。
- API キー(stdio トランスポート):最も手軽です。運用者が自身の OpenAgenda API キーを使ってサーバーを実行します(どのキーでも使用できます。
oa_pk_…は単なる例であり、サンドボックス化されたコードに組み込まれるため、最小権限の読み取りキーを推奨します)。ローカルでの利用にも、独自の境界の内側で、信頼できる呼び出し元向けに組織がホストするサーバーにも適しています(シングルテナント、プライベート)。この場合、共有キーを使うことは正当な運用上の選択です。 - OAuth 2.1(HTTP トランスポート):スタンドアロンのOAuth リソースサーバーです。ベアラー JWT を AS JWKS に対してローカルで検証し(issuer + audience)、各
executeで呼び出し元のトークンを短期間有効なaud=apiトークンに交換します(RFC 8693)。これにより、実行されるコードは共有の環境変数シークレットではなく、同意した呼び出し元として実行されます。slice-auth/better-auth 上で動作します。これがパブリックかつマルチテナントの場合に必要な方式です。
トランスポートの選択によって認証モデルが決まり、危険な設定を許す余地をなくします。
transport=http は OAuth 設定(issuer、resource、exchange secret)がなければフェイルクローズするため、共有キーがネットワーク経由で公開されることはありません。HTTP 経由で共有キーを使う方法は選択できません。制限のない送信通信についてもフェイルクローズします。http はネットワークに公開されるため、node-first のデフォルト(egress=none)は拒否されます。セルフホストの http サーバーでは、OA_EXECUTOR=deno(または microsandbox)で送信通信を制限するか、wrapper の下で実行するか、信頼されたシングルテナントのマシンであることを明示的に受け入れて OA_LOCAL_NO_SANDBOX=1 を設定する必要があります。
OA_API_KEYはstdio の認証情報であり、その性質上、共有され、環境内で常に利用可能です。サーバーは返すエラーテキストからこのキーを必ず削除します。このキーはローカル/シングルテナントでの使用を想定しています。パブリックなマルチテナントのインターフェースでは、上記の呼び出し元ごとの OAuth を使用します。
書き込み操作とモデレーション
v3 のインターフェースには書き込み/管理/モデレーション機能が含まれ、execute は任意のコードを実行するため、変更を加える可能性があります。また、コードモードでは危険な呼び出しがスクリプト内に隠れます。監査ログにはスクリプトが記録されます(上記の「監査とフォレンジック」を参照)が、アクションごとの承認機能はありません。そのため、クライアントが引き続き実行を制御できるよう、execute には読み取り専用ではないという注釈が付いています。予定されている緩和策はハイブリッド方式です。読み取り/組み合わせ処理には execute を維持し、機密性の高い操作(アジェンダの削除、モデレーション/拒否、メンバーの削除)には、人間による承認を必須とする、destructiveHint の注釈付き明示的なツールをいくつか追加します。これはまだ実装されていないため、現在は execute が唯一の書き込み経路であり、OAuth スコープの許可とサンドボックス境界に依存しています。
設計上の判断
- ツールは2つ、往復は1回です。 Code-mode では
describe_operation(呼び出しごとに LLM↔MCP の往復が1回増えます)を3つ目のツールとして用意せず、search_docsとexecuteを公開します。search_docsはシグネチャ、型付きパラメーター、レスポンスの形式、実行可能なサンプルを一度に返し、順位に応じて詳細度を調整します(上位の検索結果は全文、下位の結果は簡潔に表示する)ため、カタログが数十件の操作に増えてもペイロードのサイズを抑えられます。操作 ID と完全に一致するクエリではその項目だけを返すため、下位の項目を開く方法として使えます。空のクエリ、または一致する結果がないクエリでは、すべての操作を簡潔に表示したカタログを返します。これは Stainless の「SDK Code Mode」と同様の形式です。 search_docsは埋め込みではなく、MiniSearch(字句 BM25)で順位付けします。 自然言語で検索する、小規模(数十件の操作)のキーワード中心のカタログには、字句検索が適しており、業界標準でもあります(Anthropic はツール検索に BM25 と正規表現を標準で採用しており、数百から数千のツールを扱う場合に埋め込みを代替手段として用います)。MiniSearch は、すべての制約を余裕なく満たす唯一の候補です。**純粋な ESM、依存関係ゼロ、ネイティブバイナリなし、同期処理、決定論的な BM25+**に加え、あいまい検索、プレフィックス検索、フィールドの重み付けに対応しています。検討した代替案については、以下のとおりです。- Orama(信頼できる唯一の有力なアップグレード候補)はベクトル検索とハイブリッド検索を追加できますが、オフラインのプロセス内埋め込み器には TensorFlow.js のネイティブバックエンドが必要です。また、コア API が
T | Promise<T>のため、すべての呼び出し箇所に非同期処理が入り、変更もより頻繁です。多言語セマンティック検索に予算が付き、要件になった場合にのみ再検討します。その場合も、tfjs-nodeではなく、事前計算済みベクトルを持ち込む方式にします。 - セマンティック検索/埋め込みは、この用途では性能が劣ります。識別に重要なトークンは識別子、パラメーター名、列挙値であり、まさに密ベクトルでは BM25 と比べて特異性が薄れる箇所です。また、インデックス対象のコーパスは英語の API 契約なので、多言語ステミングは意味をなしません。どの言語の自然言語クエリでも、必要なのはステミングではなく、同義語や表現の拡張への対応です。これは、小規模な手動管理の
SYNONYMSマップとあいまい検索で対応します。
- Orama(信頼できる唯一の有力なアップグレード候補)はベクトル検索とハイブリッド検索を追加できますが、オフラインのプロセス内埋め込み器には TensorFlow.js のネイティブバックエンドが必要です。また、コア API が
- あいまい検索は長い用語(
> 6文字)のみに適用します。 一律にあいまい検索の距離を設定すると、短い単語が誤って一致します(show→how、events→event)。その結果、誤った操作が表示されるため、部分的な単語にはプレフィックス検索を使い、長いトークンのタイプミスにのみあいまい検索を適用します。 - サンプルは OpenAPI 契約内の
x-codeSamplesとして保持します(各操作と同じ場所に置き、Scalar からも利用できます)。フォールバックとして自動生成のスケルトンを使用するため、MCP と将来のリファレンスドキュメントで同じ情報源を利用できます。
このサーバーが意図的に行わないこと
-
microsandboxエンジン(ホスト型 µVM 境界)は 実装済みであり、実際の KVM ホスト上で検証済みです。ホスト型の ポリシーレイヤーも導入済みです。具体的には、グローバルな同時実行数の上限と有界キュー(concurrencyLimit.js)、呼び出し元ごとの同時実行数の上限(callerConcurrency.js)、呼び出し元ごとのレート制限(rateLimiter.js)、ツールごとの監査ログ(log.js→ InsightOps)、メンテナンス用の停止機能(OA_EXECUTE_DISABLED)です。docs/microsandbox.md を参照してください。 -
HTTP トランスポートと OAuth 2.1 リソースサーバーは実装済みで、稼働中です(ローカル JWKS 検証、オーディエンスのバインド、呼び出し元ごとの RFC 8693 トークン交換を実装しており、
transport=httpは OAuth がない場合にフェイルクローズします)。本番 AS(https://openagenda.com/api/auth)に接続され、https://mcp.openagenda.comにデプロイされています。stdio トランスポートでは引き続き共有OA_API_KEYを使用します(任意のキーを使用できます。最小権限を推奨します。エラーテキストからは削除されます)。ローカル利用や単一テナントでの利用には適していますが、共有キーによる公開デプロイには適していません。上記の認証に関する説明を参照してください。 -
監査ログの呼び出し元情報を補完する
emailクレームはまだありません(OA のuidクレームは、すでにsubとともに公開されています)。AS 側での呼び出し元ごとの禁止措置は 実装済みです。ユーザーを禁止または削除すると OAuth の許可が取り消され、トークン交換時にユーザーが引き続き有効か再確認されるため、禁止された呼び出し元はaud=apiトークンを発行できなくなります。 -
変更操作に対する アクションごとの承認機能はありません(監査ログには API 呼び出しごとではなく、
executeスクリプト全体が記録されます)。「変更操作とモデレーション」を参照してください。 -
監査ログに記録する入力の プライバシーと保持期間は未決定です。監査ログには、ユーザーの
search_docsクエリと(上限を設けた)executeコードが InsightOps に保存されます。公開デプロイでは、この点を開示し、保持期間を設定する必要があります。コード上のブロッカーではありません。 -
アプリケーションコードから取得する ホストリソースのテレメトリーはありません。CPU/RAM/KVM の計測値は node_exporter によるスクレイプ(例:alloy)で取得し、意図的にアプリケーションコードには含めていません。サーバー自体は OpenTelemetry を出力します。3 種類すべてのシグナルを 1 つの
NodeSDK(telemetry.js)で扱い、同じホストエージェント(Alloy)に OTLP で送信します。ホスト型モードでは OTLP エンドポイントが設定されている場合に有効になり、それ以外の場合は無効です。- メトリクス → Mimir:ツールごとの結果・レイテンシー・レスポンスサイズ、ウォームプールのヒット/ミス、現在の同時実行数、各 µVM のホスト RAM のピーク値と CPU 時間。これらは libkrun VMM の
/procから読み取るVmHWM(単調増加)とutime+stime(累積値)で、SDK の 1 秒間隔のsb.metrics()サンプルでは捉えられない 150 ms 未満の実行も記録します。 - トレース → Tempo:ツール呼び出しごとのスパン(
mcp.tool/execute、…/search_docs)と、時間のかかる処理の子スパン(mcp.credential.exchange、mcp.sandbox.run)を記録します。これにより、executeの遅延が単なる合計時間ではなく、どの段階で発生したかを特定できます。µVM 内の API 呼び出しにコンテキストが伝播され次第、executeスパンが v3 API トレースの親になる予定です(ロードマップ)。 - ログ → Loki:
@openagenda/logsのレコードも InsightOps に加えて OTLP 経由で送信されます(otel: true)。ツールのスパン内で出力されたレコードは、そのトレースに自動的に関連付けられます。
つまり、メトリクス/トレース/ログは OTel→Alloy(Mimir/Tempo/Loki)経由、ログと監査ログは
@openagenda/logsを通じて InsightOps にも送信され、ホストの情報は node_exporter→Mimir 経由です。役割ごとに分離しており、ログは意図的に InsightOps と OTel の両方へ送信されます。 - メトリクス → Mimir:ツールごとの結果・レイテンシー・レスポンスサイズ、ウォームプールのヒット/ミス、現在の同時実行数、各 µVM のホスト RAM のピーク値と CPU 時間。これらは libkrun VMM の
-
稼働確認:認証不要の
GET /healthは、稼働時間の確認(および将来の LB/水平スケーリングのプローブ)用に{ "status": "ok" }を返します。意図的に軽量にしており、AS/トークン交換には接続せず、サンドボックスも起動しません。そのため、プローブによって負荷が増えたり、稼働状態の確認が AS の可用性に左右されたりすることはありません。 -
ローカルエンジン(node/deno)では、呼び出しごとのプロセス分離を行いません。暴走したプロセスはプロセスグループへの SIGKILL と 1 MiB の出力上限によって停止されますが、VM 境界による分離ではありません(microsandbox エンジンでは実行ごとに分離されます)。
https://mcp.openagenda.com にデプロイ済みです(OAuth による強制適用済み。ホスト型のポリシーレイヤーも導入済み)。書き込み操作を公開する前に、上記の注意事項への対応が引き続き必要です。変更操作に対するアクションごとの承認、監査ログのプライバシーと保持期間、および単一ホストによる単一障害点(SPOF)です。
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。