mcpField
R
RFP Hub

RFP Hub

ローカル
github.com金融データ21 日前に更新

RFP Hub について

RFP Hubは、Ethereumエコシステムの資金提供機会を検索し、必要に応じて申請を送るMCPサーバーです。検索と取得は匿名で行えますが、送信には認証情報と、書き込み前の別途の人間による承認が必要です。

主な機能

  1. 01条件を指定した資金機会の検索
  2. 02機会の完全なレコード取得
  3. 03認証情報不要の匿名検索
  4. 04ターミナル承認後の申請送信

活用シーン

  1. 01助成金、バウンティ、ハッカソンの検索
  2. 02資金機会の詳細確認
  3. 03人による審査に向けた申請の準備

MCP 対応

MCPクライアントに検索・取得ツールを提供し、認証情報を設定すると申請ツールも公開します。保留中の申請は、書き込み前に人がターミナルで承認します。

設定

ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。

mcp.json · 共通設定
{
"mcpServers": {
"the-rfp-hub-rfp-hub": {
"command": "npx",
"args": [
"-y",
"@the-rfp-hub/mcp"
],
"env": {
"RFPHUB_API_BASE": "<RFPHUB_API_BASE>",
"RFPHUB_API_KEY": "<RFPHUB_API_KEY>"
}
}
}
}
起動コマンドnpx -y @the-rfp-hub/mcp
設定が必要な環境変数
RFPHUB_API_BASEBase URL of the RFP Hub API. Defaults to https://api.rfpsear.ch. The origin of this URL is bound into every write approval.
RFPHUB_API_KEYシークレットRFP Hub API credential. Needed ONLY to submit; searching and fetching are anonymous and never send it. Setting it is what registers the write tool at all: without it tools/list returns two tools. The key must carry the write scope and must not carry publish; the server checks that before its first submission, so a submission waits for a reviewer's decision.

ローカルサーバーにはマシン上の実行環境(Node.js 18 以上など)が必要です。設定ファイルの場所はクライアントごとに異なります。

パッケージ
npm@the-rfp-hub/mcp0.1.4

ツール

2

掲載時のツール一覧 · 2 週間前に更新

パラメーター型必須説明
qstringいいえタイトル、概要、説明を対象とした全文検索。
pageintegerいいえ1 始まりのページ番号。
sortenumいいえソートキー。デフォルトは nextDeadlineAt で、今後に設定されている最も早い固定締切です。該当する締切がない項目は最後に並びます。nextDeadlineAt | opensAt | postedAt | updatedAt | createdAt
limitintegerいいえページあたりの結果数。1~25。デフォルトは 10。
orderenumいいえソート順。デフォルトは昇順。asc | desc
statusarrayいいえ次のいずれかのステータスに絞り込みます:upcoming、open、closed、archived。
categoryarrayいいえカテゴリ。複数の値は OR 条件で扱われます。
maxAwardnumberいいえ助成額または予算の上限。通貨の基本単位で指定します。
minAwardnumberいいえ助成額または予算の下限。通貨の基本単位で指定します。
ecosystemarrayいいえエコシステム名(例:Optimism、Base)。複数の値は OR 条件で扱われます。
fundingTypearrayいいえ次のいずれかの資金提供タイプに絞り込みます:grant、hackathon、bounty、accelerator、vc_fund、rfp。
organizationstringいいえ組織の slug。運営組織またはスポンサー組織に一致します。
deadlineAfterstringいいえRFC 3339 の時点。この時点以降に最も早く到来する固定締切があるエントリーのみを対象とします。今後の固定締切がないエントリー(随時受付のプログラム、締切がすべて過去のもの、または締切がないもの)は、このフィルターで除外されます。
deadlineBeforestringいいえRFC 3339 の時点。この時点以前に最も早く到来する固定締切があるエントリーのみを対象とします。今後の固定締切がないエントリーは、このフィルターで除外されます。

詳細

@the-rfp-hub/mcp

RFPSear.ch 用の Model Context Protocol サーバーです。任意の MCP クライアントから、Ethereum エコシステムの資金調達機会(助成金、ハッカソン、賞金付き課題、アクセラレーター、VC ファンド、RFP)を検索し、必要に応じて申請を送信できます。人による承認の手順はツールチャネルの外部にあります。MIT ライセンスです。

検索は匿名で利用できます。認証情報やアカウントは不要で、クライアントの設定以外に構成するものもありません。認証情報が必要なのは、明示的に送信を有効にして申請を送信する場合のみです。


エージェントから送信する

エージェントが検索と取得だけを行う場合は、このセクションを読み飛ばし、認証情報なしで下記のサーバーをインストールしてください。送信を許可する場合:

  1. デプロイ先のAPI keysページにサインインします。ホスト版 Hub では https://rfpsear.ch/keys、ローカルまたはステージングのフロントエンドでは /keys です。ページ上の MCP カードで、以下の安全なスコープを設定できます。
  2. read と write を選択し、publish はオフのままにして、キーを作成します。これが安全な方法です。送信内容は保存されますが、すぐに公開されず、レビュー待ちになります。
  3. シークレットが表示されたらコピーします。シークレットは一度しか表示されません。また、このページには RFPHUB_API_BASE に設定する正確な API オリジンも表示されます。
  4. 以下のクライアント非依存の stdio 接続を設定し、クライアントのプライベートな MCP 環境内で、オリジンを RFPHUB_API_BASE に、シークレットを RFPHUB_API_KEY に設定します。シークレットをプロンプト、ツール引数、シェルコマンドに含めないでください。
  5. クライアントを再起動します。tools/list に submit_opportunity が表示されます。最初の呼び出しではキーのスコープが確認され、プレビューのみが行われます。呼び出しによって書き込みを行う前に、引き続きターミナルでの承認が必要です。

API keys ページが引き継ぎの起点です。認証情報を作成すると新しいタブでこのページに戻るため、クライアントを設定している間、1 回限りのシークレットを表示したままにできます。


インストール

クライアント非依存の接続

RFPSear.ch MCP は、モデルプロバイダーやクライアントに依存しません。ローカルの stdio サーバーです。クライアントが 1 つのプロセスを起動し、標準入力で MCP メッセージを送信し、標準出力から読み取ります。サーバーは、どのモデル、ベンダー、ホストから起動されたかを認識しません。

互換性のあるクライアントはすべて、同じ接続仕様を使用します。

設定値
トランスポートstdio
コマンドnpx
引数-y、@the-rfp-hub/[email protected]
検索と取得環境変数は不要です。匿名で利用できる読み取り専用ツールが 2 つ公開されます。
送信サーバープロセスのプライベートな環境に RFPHUB_API_BASE と RFPHUB_API_KEY を追加すると、書き込みツールが公開されます。

クライアントによってフィールド名が異なる場合がありますが、プロセスと環境は共通です。ユーザー専用の設定またはシークレット入力を使用し、キーをリポジトリにコミットしないでください。キーを追加またはローテーションした後は、クライアントを再起動してください。

上記のパッケージのバージョンは意図的に固定しています。npm のバージョンは不変ですが、@latest は不変ではありません。また、MCP サーバーはエージェントがユーザーの権限で実行するコードです。バージョンを固定し、意図的に更新してください。

クライアントごとの設定例

以下は同じ仕様を各クライアントに適用する例であり、個別のインテグレーションではありません。匿名で検索と取得のみを行う場合は、環境変数のブロックを省略してください。

Claude Code
claude mcp add --transport stdio rfp-hub -- npx -y @the-rfp-hub/[email protected]

# project-scoped instead of user-scoped — writes .mcp.json in the repo
claude mcp add --scope project --transport stdio rfp-hub -- npx -y @the-rfp-hub/[email protected]

これらのコマンドでは、匿名で検索と取得を行います。送信するには、Claude のローカル設定またはユーザー設定を使用して、サーバーのプライベートな環境に RFPHUB_API_BASE と RFPHUB_API_KEY を追加してください。共有プロジェクトの .mcp.json にシークレットを含めないでください。

プロジェクトスコープのサーバー(--scope project)は、そのディレクトリで claude を対話形式で 1 回実行するまで、「承認待ち」と表示されます。CI、スクリプト、claude -p などのヘッドレス実行で、この対話手順なしに読み込むには、claude -p ... --mcp-config .mcp.json --strict-mcp-config が必要です。

Claude Desktop と Cursor

claude_desktop_config.json または .cursor/mcp.json 内のルートキー mcpServers:

{
  "mcpServers": {
    "rfp-hub": {
      "command": "npx",
      "args": ["-y", "@the-rfp-hub/[email protected]"],
      "env": {
        "RFPHUB_API_BASE": "https://api.rfpsear.ch",
        "RFPHUB_API_KEY": "rfph_…"
      }
    }
  }
}
VS Code

.vscode/mcp.json 内のルートキー servers:

{
  "servers": {
    "rfp-hub": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@the-rfp-hub/[email protected]"],
      "env": {
        "RFPHUB_API_BASE": "https://api.rfpsear.ch",
        "RFPHUB_API_KEY": "${input:rfphub-key}"
      }
    }
  },
  "inputs": [
    {
      "type": "promptString",
      "id": "rfphub-key",
      "description": "RFPSear.ch API key",
      "password": true
    }
  ]
}
Codex CLI
codex mcp add rfp-hub -- npx -y @the-rfp-hub/[email protected]

このコマンドで、匿名の検索と取得をインストールできます。提出するには、~/.codex/config.toml(または信頼できるプロジェクトの .codex/config.toml)を編集し、サーバーエントリに以下のプライベート環境テーブルを追加してください。API キーページに表示された正確なオリジンとワンタイムキーに、それぞれの値を置き換えてから Codex を再起動します。これは Codex の公式 MCP 設定に従ったものです。

[mcp_servers.rfp-hub]
command = "npx"
args = ["-y", "@the-rfp-hub/[email protected]"]

[mcp_servers.rfp-hub.env]
RFPHUB_API_BASE = "https://api.rfpsear.ch"
RFPHUB_API_KEY = "rfph_…"

コンパニオンスキル

funding-search は、このサーバーの検索ツールと取得ツールをうまく使う方法をクライアントに教える、読み取り専用のエージェントスキルです。この npm パッケージには同梱されていません。クライアントが対応している以下のいずれかの方法で、リポジトリからインストールします。

npx skills add The-RFP-Hub/the-rfp-hub --skill funding-search
claude plugin marketplace add The-RFP-Hub/the-rfp-hub

または、スキルディレクトリを .claude/skills/、.agents/skills/(Codex)、あるいは .gemini/skills/ に手動でコピーしてください。


設定

環境変数は2つ、フラグは1つです。変数はシークレット(マシン上のあらゆるプロセスから読み取れる argv に決して含めてはならないもの)か、デプロイ先の識別情報のために使います。それ以外はすべてフラグか固定値なので、このサーバーを実行するために環境を管理する人から何かを受け取る必要はありません。

変数デフォルト機能
RFPHUB_API_BASEhttps://api.rfpsear.ch接続先のデプロイ先です。オリジンのみを指定し、ループバック以外では https を使用します。オリジンは、すべての書き込み承認に紐付けられます。
RFPHUB_API_KEY(なし)認証情報です。必要なのは提出時のみです。読み取り時には送信されません。この変数を設定すると書き込みツールが登録されます。設定しない場合、tools/list は2つのツールを返し、汚染された検索結果が呼び出せる書き込みツールは存在しません。
フラグデフォルト機能
--state-dir <dir>~/.rfphub承認情報、レート制限カウンター、監査ログの保存先です。書き込み可能である必要があります。すべてのモードで使用でき、サーバーと rfphub-mcp approve には同じディレクトリを指定してください。

リクエストごとの期限は、ヘッダーと本文を合わせて固定の20秒です。変更できません。オペレーターが延長できる期限では、応答の止まった接続先がツール呼び出しを長時間占有できてしまうためです。

RFPHUB_API_BASE は起動時に検証され、プレビューが作成される前に、次の4種類は拒否されます。

  • 127.0.0.1、[::1]、localhost 以外に対する通常の http: — 書き込みリクエストにはベアラー認証情報が含まれます。人間がオリジンを承認しても、平文通信が安全になるわけではありません。
  • URL に含まれるユーザー名またはパスワード — サーバーが出力するすべての診断情報に含まれてしまうためです(同じ理由で、エラーメッセージがこの変数の値を繰り返すことはありません)。
  • パス、クエリ、フラグメント — 承認はオリジンに紐付けられるため、ホスト以降だけが異なる2つのベース URL では同じ承認が生成され、異なるエンドポイントに到達することになります。
  • 絶対 http/https URL ではないもの。

https://api.rfpsear.ch、https://api.rfpsear.ch/、https://api.rfpsear.ch:443、https://API.rfpsear.ch は同じ接続先であり、同じ承認 ID が生成されます。ポート444、または別のホストを指定すると、接続先も ID も異なります。

キーをローテーションするには、サーバーを再起動する必要があります。 設定は起動時に一度だけ読み込まれ、認証情報のフィンガープリントは、承認が紐付けられる5つの情報の1つです。クライアントの env ブロックでキーを変更しても、このプロセスに反映されるのは次回の起動時です。それまではプレビューが古いフィンガープリントに紐付いたままになり、新しいキーでのコミットは不一致として拒否されます。新しいキーでプレビューする前に再起動してください。

キーはクライアントの env ブロックに設定してください。プロンプトにも、ツールの引数にも絶対に含めないでください。キーを渡せるパラメーターはどのツールにもなく、そのことを検証するテストもあります。

{
  "mcpServers": {
    "rfp-hub": {
      "command": "npx",
      "args": ["-y", "@the-rfp-hub/[email protected]"],
      "env": {
        "RFPHUB_API_KEY": "rfph_…"
      }
    }
  }
}

キーには write が含まれ、publish が含まれていてはなりません。このサーバーは、最初の提出を行う前にこれを確認します。publish キーは拒否されます。承認された提出が即座に公開されるためです。


ツール

search_opportunities (読み取り)

フィルター:q、fundingType[]、status[]、ecosystem[]、category[]、organization、minAward、 maxAward、deadlineAfter、deadlineBefore、sort、order、page、limit(最大25件)。未知のパラメーターはエラーとなり、何もせずに無視されるフィルターになることはありません。

各結果行には、ID、名前空間、タイトル、資金提供の種類、ステータス、運営組織、 エコシステム、レンダリングされた賞金の行、次の固定締切、外部リンクのURLが2つ含まれます。

descriptionもsummaryも返しません。 これは意図的なもので、このサーバーの最も重要な安全特性です。これらは公開者が管理する最も長い自由記述フィールドであり、エージェント宛ての指示が含まれる場所でもあるため、20件もあればコンテキストウィンドウの大半を占めてしまいます。説明文が本当に必要な場合に、IDを指定して完全なレコードを取得してください。

fetch_opportunity (読み取り)

1件のレコード全体を、{ notice, opportunity, links }のエンベロープに入れたRFP Hub Standardドキュメントとして返します。このドキュメントは構造的に変更されていません。フィールドの削除や追加、値の変更はありません。ただし、APIキーのような形式の値は、この応答を含むすべての送信メッセージでマスキングマーカーに置き換えられます。それ以外はバイト単位で同一ではありません。転送中にパースして再シリアライズされるため、キーの順序や空白はトランスポート側の扱いとなります。

submit_opportunity (書き込み — キーが設定されている場合のみ登録)

人を介して2回呼び出します。次のセクションを参照してください。

送信結果では、APIのisListed値を編集上の設定として保持し、実際の公開状態を示すシグナルとしてisPubliclyVisibleを追加します。レコードが公開されるのは、reviewStatusがapprovedかつisListedがtrueの場合のみです。isListed: trueの保留中レコードも、審査が終わるまでは公開されません。


送信と承認手順

1. call submit_opportunity { document }
     → checks the key's scope, validates locally, writes nothing, returns:
       { status: "pending", approvalId: "<64 hex>", preview: {...}, instruction: "..." }

2. a person runs, in their own terminal:
     npx @the-rfp-hub/[email protected] approve <approvalId>
     → prints the destination, the credential fingerprint, the operation, the protocol
       revision and the whole document, then asks for confirmation

3. call submit_opportunity { document, approvalId }
     → claims the approval (single-use), POSTs, returns the result

rfphub-mcp pendingは保留中の項目を一覧表示します。rfphub-mcp revoke <id>は1件を削除します。

承認の対象

承認は単に「このドキュメントが承認された」というものではありません。そのIDは5つの要素のハッシュであり、approveは確認を求める前に5つすべてを表示します。

送信先RFPHUB_API_BASEの正規オリジン
認証情報キーのSHA-256の先頭8桁の16進数 — キーそのものでも、そのプレフィックスでもありません
操作submit_opportunity
プロトコルこのサーバーが話すMCPリビジョン
ドキュメントドキュメントの正規形式に対するSHA-256

5つの要素のうちどれか1つでも変更されると、承認は適用されなくなり、拒否時にはどの要素が変更されたかが示されます。ステージング環境で得た承認を本番環境で使うことはできません。キーのフィンガープリントが紐付けられているため、あるキーで得た承認は、そのキーをローテーションした後は拒否されます。

承認は1回限りで、リクエストの送信前にアトミックな名前変更によって消費され、タイムアウト後も含めて復元されることはありません。送信結果が不確かな場合は、ほかの操作をする前にGET /v1/me/opportunitiesを確認してください(公開読み取りでは審査待ちの項目は表示されません)。保留中のプレビューは取得から15分で期限切れになります。承認すると、送信用に新たな15分間のウィンドウが開きます。

できること — できないこと

できること。 承認すると、MCP チャネルから完全に切り離されます。ツールの応答に承認シークレットが含まれることはないため、モデルが自身のコンテキストからそれを読み取り、同じターンで使用することはできません。承認は、書き込み先と使用する認証情報が画面に正確に表示された状態で、ターミナル上で意図的に行う操作になります。

できないこと。 これは、シェルとファイルシステムを同じオペレーティングシステムユーザーとして使用できるエージェントに対する境界ではありません。コーディングエージェントは、通常その両方を使用できます。そのようなエージェントは、この CLI を疑似端末で実行することも、承認ファイルを直接書き込むこともできます。0600/0700 の権限設定では防げません。そのエージェント自身がそのユーザーだからです。ここにある内容を「モデルは承認できない」と解釈してはいけません。

本当に分離された境界が必要な場合、選択肢はこのパッケージの範囲外にあります。ホストアプリケーションが提供する承認 UI、エージェントのプロセスに対する別個の OS アイデンティティ、またはエージェントがアクセスできない署名鍵です。adr/0012 を参照してください。


エラー

すべての失敗には、次の 7 つのコードのいずれかが付与されます。

コード意味
tool_not_found該当するツールがありません。送信が無効になっている場合、書き込みツールは実際に登録されていません。
invalid_input引数またはドキュメントの検証に失敗しました。スキーマの検証失敗は、フィールドごとに報告されます。
policy_denied設定または API の認可によって拒否されました。認証情報がない、write 権限のないキー、または publish 権限を持つキーが指定された、あるいは保留中の送信件数の上限に達した場合です。
rate_limitedローカルの種類別の上限、または API 独自のレート制限に達しました。
confirmation_requiredドキュメントはプレビューされましたが、承認されていません。何も送信されていません。
confirmation_invalid承認が適用されません。期限切れ、使用済み、または宛先、認証情報、プロトコル、ドキュメントのいずれかが異なります。何も送信されていません。
exec_failedAPI が失敗した、到達できなかった、JSON 以外の内容を返した、または 1 MB を超える内容を返しました。

1 MB を超える応答は切り詰めずに失敗として扱われます。上限は本文の読み込み中に適用されるため、巨大な応答でもメモリ使用量は一定の範囲内に収まります。途中までの JSON ドキュメントは、短い回答ではなく誤った回答です。その場合はリクエストの範囲を狭めてください。

結果が不明な送信については、その旨が報告されます。 リクエストが送信された後、明確な回答となるのはコード付きの 4xx だけです。API はリクエストを読み取り、判定したうえで拒否しました。それ以外はすべて、単なる失敗ではなく、反映された可能性があります として報告され、GET /v1/me/opportunities が案内されます。

  • 接続が切断された、または本文のストリームが途中で止まった場合。
  • 本文が JSON ではない、または上限を超えている場合。
  • 本文の形式が正しくても 5xx が返された場合。API は行をコミットしてからさらに処理を行うため、応答時のサーバーエラーは、行が存在することと十分に両立します。
  • 2xx でも、本文が送信結果ではない場合。空の 200、{}、またはこの API 以外からの回答などです。
  • 3xx の場合(下記参照)。

いずれの場合も、承認は使用済みになります。

送信でリダイレクトが追跡されることはありません — ただし、追跡されなかったリダイレクトでも結果は不明です。 このサーバーが解決しておらず、誰も承認していないホストに、ドキュメントや認証情報が再送信されることはありません。承認は宛先のオリジンに紐付いているため、別の場所へ処理を続行すると、別の場所について行われた判断を使うことになります。ただし、追跡されなかったことは、書き込まれなかったことを意味しません。POST/Redirect/GET は、サーバーが作成直後の内容を確認するために通常使われる方法です。そのため、3xx は反映された可能性がありますとして報告され、どこへリダイレクトされようとしていたか確認できるよう、宛先も示されます。

引数は公開されているスキーマに照らして検証されます。不正な呼び出しにも、他のすべての処理と同じコードが返されます。不明なパラメーターは、何もせず無視されるフィルターではなくエラーになります。


ローカル状態

すべてのデータは状態ディレクトリ(--state-dir、デフォルトは ~/.rfphub)内に保存されます。ディレクトリのモードは 0700、ファイルのモードは 0600 です。

--state-dir は、このユーザーのホームディレクトリに関するオペレーティングシステムの認識より優先されます。 その認識が信頼できない場所、たとえばコンテナ、サービスアカウント、systemd ユニット、またはローンチエージェントでは、--state-dir を指定してください。ホームディレクトリがない場合や、読み取り専用の場合、別のユーザーと共有されている場合があります。このサーバーは推測せず、処理を拒否します。詳しくは以下をご覧ください。

{
  "mcpServers": {
    "rfp-hub": {
      "command": "npx",
      "args": ["-y", "@the-rfp-hub/[email protected]", "--state-dir", "/var/lib/rfphub"]
    }
  }
}

承認コマンドにも同じディレクトリを指定してください。 サーバーはそこにプレビューを書き込み、rfphub-mcp approve はそこから読み戻します。異なるディレクトリを指定すると、実際のプレビューが別の場所で待機しているのに pending には何も表示されません。このサーバーが人に実行するよう表示するコマンドには、フラグが指定されている場合、すでにそのフラグが含まれています。プレビューの instruction、未承認の状態でコミットが届いた場合の拒否メッセージ、rfphub-mcp --state-dir /var/lib/rfphub pending の approve 行が該当します。コマンドはシェル用に引用符で囲まれているため、パスに空白があってもそのまま貼り付けられます。

パス内容
pending/<id>.json承認待ちのプレビュー。ターミナルに表示できるよう、ドキュメントも含まれます
approvals/<id>.json承認済みの承認情報
approvals/claimed/<id>.json使用済みの承認情報。再利用できません
pending/claimed/<id>.json人が承認に変換済みのプレビュー
policy-counters.json種類ごとのレートカウンター
policy-counters.lockカウンターの更新中のみ保持されます
policy-counters.lock.stale.*プロセスのクラッシュにより放置されたロック。削除予定です
audit.log呼び出しごとに1行のJSONを記録するログ。5 MiBでローテーションされます
audit.log.1ログの直前の世代
audit.lockログのローテーション中のみ保持されます

モードは要求するだけでなく、実際に確認されます。 使用のたびにホームディレクトリを lstat し、シンボリックリンクやファイルではなく、実際のディレクトリであることを確認します。また、すべての状態ファイルが通常のファイルであり、ハードリンクがほかに存在しないことを確認します。このサーバーが書き込もうとしているパスには chmod を実行して 0700/0600 に設定し、モードを読み戻して確認します。POSIXのモードに対応していないファイルシステムでは、成功を返す chmod を実行しても何も保証されないためです。

このサーバーが信頼しようとしているファイルは検証し、決して修復しません。すでに誰でも読み取れる状態の承認ファイルやカウンターファイル、または別のハードリンクがあるファイルは、その時点ですでにそれを保持する対象に公開されています。後からモードを厳しくしても、公開を取り消すことも、ファイル内の判断を自分のものにすることもできません。このようなファイルの読み取りは policy_denied で拒否され、そのファイルが削除されるまで拒否し続けます。rfphub-mcp pending は、内容を保証できないレコードを単に表示しません。監査ログは信頼するのではなく書き込むため、修復されます。それでも、安全性を確保できないパスへの書き込みは拒否します。また、ローテーションによって既定のモードの新しいファイルが追加書き込みの下に紛れ込まないよう、単一のファイル記述子を通じて開き、判定します。ログのパスがシンボリックリンク、または通常のファイル以外の場合は、リンクをたどらずに拒否します。プラットフォームが対応している場合は O_NOFOLLOW を使い、対応していないプラットフォームでは、すべての場所で開く前に lstat を実行します。後者のプラットフォームで利用できる保護はこれだけです(2つの操作の間には短い隙間があります。この場合は安全側に倒し、リンク経由で書き込むのではなく、行を破棄します)。

監査ログのサイズには上限があります。 5 MiBに達すると、ロックを取得したうえで audit.log.1 にローテーションされ、0600 のログが直前の世代として1つだけ保持されます。ローテーションに失敗しても失われるのはローテーションだけであり、呼び出しやログ行が失われることはありません。

時計が逆戻りしても、何も得られません。 ディスク上のカウンター期間が現在の期間より先に進んでいる場合(NTPの補正、スナップショットの再開、手動での時計設定など)、保存済みのカウントはリセットされず、そのまま加算されます。また、未来の日付が付いた承認は期間外として扱われます。実時間が追いつくまで予算は使用済みのままです。これはレート制限にとって安全な方向です。 レート制限はフェイルクローズです。 カウンターストアを読み書きできない場合、呼び出しは拒否されます。カウントできない予算は適用できないためです。状態ディレクトリを書き込み可能にしてください。

カウンターはプロセス間で正確です。 MCP クライアントと、この同じパッケージを実行するターミナルは、設計上、同じホームディレクトリを共有するため、チェックとインクリメントはロックディレクトリ(policy-counters.lock)内で実行されます。ロックなしの読み取り・変更・書き込みでは、上限が 1 のときに 2 つのプロセスがどちらも通過してしまいます。停止したプロセスが残したロックは、別の場所へ名前を変更してから 5 秒後に解除されます。これにより、放置された同じロックを 2 つのプロセスが同時に解除しようとしても、稼働中のロックを削除してしまうことはありません。

書き込み予算は、承認を確保する前に予約されます。 日次予算の超過や承認の競合負けなど、ローカルで拒否された場合は、予算が戻され、承認もそのまま残ります。そのため、同じ送信を承認するよう再度求められることはありません。

承認の可否は、プレビューが表示された時点ではなく、回答した時点で決まります。 確認後、アトミックな名前変更によってプレビューが確保されるため、2 つのターミナルが同じ ID を承認しようとしても、承認されるのは 1 つだけです。また、画面に表示されている間に取り消された、または期限切れになったプレビューは拒否されます。

拒否された送信も計測されます。 呼び出し元が処理内容を判断できる種類は、read、preview、commit の 3 つです。実際に行われた作業の各段階につき 1 つずつです。attempt は4 つ目の内部用の種類で、処理段階ではなく、不正利用を抑制するための計測です。書き込みツールを呼び出すたびに、作業を始める前に 1 つ消費されます。ハンドラーが実行される前に引数が拒否される呼び出しも対象です。これは最も低コストで繰り返せる処理です。偽の承認 ID、不正な形式の引数、サイズ超過のドキュメントを繰り返し送るループも、計測を免れることはできません。attempt を使い切っても、本来のエラーが隠されることはありません。不正な引数には、引き続き不正な引数として応答します。

監査ログに記録されるのは引数のキー名とバイト数であり、値は記録されません。 値を保存するログは、あらゆるドキュメントや検索語の複製をもう 1 つ作ることになります。ログへの書き込みに失敗しても、呼び出しが失敗することはありません。

デフォルト値:read は毎分 60 回、preview は毎分 10 回、commit は毎分 2 回、1 日あたり 5 回、attempt は毎分 20 回、1 日あたり 400 回です。


セキュリティに関する注意事項

  • 認証情報は環境変数でのみ扱います。 どのツールも認証情報を受け取らず、読み取り時に認証情報を送信することもありません。
  • 送信されたドキュメント内のどこかにキーのような文字列が含まれている場合、リクエストを行う前に拒否します。 API は受け取ったテキストを保存するため、description 内のキーは永続化された後にのみ返信から秘匿されます。出力の秘匿化では対処できません。
  • 秘匿化は最後の防護策であり、制御策ではありません。 テキスト、構造化コンテンツ、エラーメッセージ、監査ログなど、すべての送信対象を再帰的にスキャンし、キーのような文字列がないか確認します。
  • URL は動作しません。 このサーバーはどのレコードに含まれる URL もたどりません。また、すべての結果に添付される注意書きで、クライアントにもたどらないよう伝えます。
  • サードパーティー由来の文字列にはすべて長さ制限があります。 タイトルは 140 文字、エコシステムラベルは 40 文字、値は 8 個までに切り詰めます(削除された数は行に記載されます)。ecosystems は標準で自由形式のリストであり、レジストリも列挙型もありません。そのため、長さを制限しなければ、ペイロードを置く格好の場所になってしまいます。
  • 残存リスクを率直に示します。 title、組織名、エコシステムラベルはサードパーティー由来のテキストであり、モデルに渡されます。区切ってラベルを付けていますが、ラベル付けはヒントであって制御策ではなく、悪意のあるタイトルもラベルを付けるだけで無害になるわけではありません。制御策は、description を含めない投影です。
  • プレビューでは API が拒否する内容も拒否します。 API の受け入れ制限(タイトル 256、概要 1 000、説明 50 000、トップレベル配列は 100 エントリ、本文は 256 KiB)をローカルで確認するため、受け入れられるはずのない送信内容に対して承認を求めることはありません。配列の上限を確認するのはトップレベルのみで、API の適用方法とまったく同じです。ここでそれ以上を確認すると、API なら受け入れたはずのドキュメントまで拒否してしまいます。
  • 重複の疑いがあるタイトルもサードパーティー由来のテキストです。 140 文字に切り詰め、区切ってラベルを付けて返します。呼び出し元が最も行動を起こしやすいのは書き込み時であるため、検索結果と同じ扱いにします。
  • structuredContent は安全性の境界ではありません。 ほかの出力と同様にモデルへ渡されます。ここでは契約と検証のために存在します。
  • すべてのリクエストには期限があります。 接続を受け入れた後に何も返さないピアや、本文の途中まで送って停止するピアは、20 秒後に打ち切ります。読み取りの場合はその旨を報告し、再試行しません。書き込みの場合はリクエストがすでに送信されているため、結果が不確定であることを報告します。
  • 本文を確認するまでは 2xx を信用しません。 本文がこのビルドで想定している形式ではない読み取りは、もっともらしい空のレコードを返すのではなく失敗します。本文が送信結果の形式ではない書き込み(204 や、このビルドより新しい duplicateCheck の値を含む)の場合は、「反映された可能性がある」と報告し、確認先として GET /v1/me/opportunities を示します。未知の追加メンバーは常に許可します。このクライアントは契約を所有していません。
  • 上記の例と同様に、正確なバージョンを固定してください。 これは、npm パッケージの将来のバージョンが、レビュー済みのバージョンと異なる動作をすることに対する唯一の実質的な防御策です。

開発

pnpm --filter @the-rfp-hub/mcp build   # required before the test suite: it drives dist/cli.js
pnpm --filter @the-rfp-hub/mcp test

トランスポートは src/cli.ts で選択します。src/server.ts はツールを登録しますが、トランスポートについては把握しません。HTTP のエントリーポイントを追加する場合は、新しいファイルを作成し、リファクタリングは行いません。

コメント

MCP は初めてですか?

プロトコルの概要から最初のサーバーの導入まで、よくある疑問にまとめて答えます。

AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。