sam-gov-mcp について
sam-gov-mcp は、SAM.gov の公開契約機会公告を検索・取得する読み取り専用の MCP サーバーです。公告情報を扱う AI アシスタント向けで、利用者が無料の SAM.gov API キーを用意します。
主な機能
- 01日付、キーワード、NAICS、留保区分、州で検索
- 02IDで公告を取得
- 03留保区分コードと意味を一覧表示
- 04次ページ情報を含むページネーション
- 05エラーと返却リンクで API キーを秘匿
活用シーン
- 01NAICS コードに合う最近の機会を検索
- 02州や留保区分で公告を絞り込み
- 03特定の公告の詳細を取得
MCP 対応
MCP クライアントに search_opportunities、get_opportunity、list_set_aside_codes のツールを提供します。SAM.gov Get Opportunities API を利用し、SAM_API_KEY が必要です。
設定
ローカルサーバーの設定はどのクライアントでも共通です。コピーしてクライアントの MCP 設定ファイルに貼り付けてください。
ツール
3掲載時のツール一覧 · 2 週間前に更新
| パラメーター | 型 | 必須 | 説明 |
|---|---|---|---|
| limit | integer | いいえ | 結果の最大数(デフォルトは 10) |
| naics | string | いいえ | NAICS コード(例:541512) |
| state | string | いいえ | 履行場所の州を示す 2 文字のコード |
| offset | integer | いいえ | |
| keywords | string | いいえ | 案件のタイトルで照合する語句 |
| posted_to | string | いいえ | 終了日。形式は YYYY-MM-DD または MM/DD/YYYY(デフォルト:今日) |
| set_aside | string | いいえ | 優先枠コード(例:SBA、8A、SDVOSBC。list_set_aside_codes を参照) |
| posted_from | string | いいえ | 開始日。形式は YYYY-MM-DD または MM/DD/YYYY(デフォルト:30 日前) |
詳細
sam-gov-mcp
AI アシスタント(Claude Desktop、Cursor、その他の MCP クライアント)から、公開されている SAM.gov Get Opportunities API を通じてSAM.gov の契約機会を検索できる MCP サーバーです。
- **読み取り専用です。**公開通知の検索と読み取りのみを行います。
- **API キーはご自身で用意してください。**無料の SAM.gov API キーを
SAM_API_KEYに設定します。キーはログに記録されず、エラーや返されるリンクからも秘匿されます。 - **ランキングやスコア付けは行いません。**SAM.gov から返される内容を要約します。
- **不正な応答は明確にエラーになります。**ドキュメントに記載された形式に一致しない応答や、異なるレコードまたは曖昧なレコードを返す通知の検索結果は、空の結果や無関係な「成功」ではなく、ツールエラーになります。
Node.js 22 以降が必要です。
クイックスタート
-
無料の API キーを取得します。sam.gov にサインインし、Account Details → API Key に進みます。
-
インストールします。Claude Code の場合:
claude mcp add sam-gov -e SAM_API_KEY=your-key-here -- npx -y @itcustomsolution/sam-gov-mcpまたは、直接実行します:
SAM_API_KEY=your-key-here npx -y @itcustomsolution/sam-gov-mcp -
MCP クライアントに追加します。Claude Desktop(
claude_desktop_config.json)の場合:{ "mcpServers": { "sam-gov": { "command": "npx", "args": ["-y", "@itcustomsolution/sam-gov-mcp"], "env": { "SAM_API_KEY": "your-key-here" } } } } -
次のように質問します:「過去30日間にメリーランド州で掲載された、NAICS 541512 の中小企業向け留保契約機会を探してください。」
ツール
| ツール | 内容 | 入力 |
|---|---|---|
search_opportunities | 通知を検索します。1回の呼び出しにつき1ページです | posted_from、posted_to(任意、既定値は過去30日間。YYYY-MM-DD または MM/DD/YYYY、期間は最大1年)、keywords(タイトル)、naics、set_aside、state、limit(1ページあたり1~100件、既定値は10)、offset(0始まりのページインデックス、既定値は0) |
get_opportunity | 検索 API が返す形式で、1件の通知レコードを取得します | notice_id(過去1年分を検索します) |
list_set_aside_codes | 留保コードとその意味を一覧表示します | なし |
検索結果は次の項目に要約されます:noticeId、title、solicitationNumber、agency、postedDate、responseDeadline、type、setAside、naics、state、uiLink。
ページ
GSA のドキュメントに記載されているとおり、offset はレコードのオフセットではなく、ページインデックスです。ページ N には、N x limit から N x limit + limit - 1 までのレコードが含まれます。各検索で返されるのは1ページのみで、それ以降のページを自動取得することはありません。結果の pagination オブジェクトで現在のページ情報を確認できます。項目は page_index、page_size、returned_count、has_more、next_offset(offset として渡します。次のページがない場合は null)、partial(この1ページに一致するレコードがすべて含まれていない場合は true)、page_limit_reached(レコードがまだあるものの、次のページインデックスが1,000,000を超える場合は true。検索条件を絞るか、ページサイズを大きくしてください)です。total は SAM.gov の totalRecords です。最初のページ以降のページで HTTP 404 が返された場合、その応答にはレコード数が記載されていないため、null になります。
エラー
ツールのエラーは {"error": {"code", "message", "status"?}} 形式の JSON で、isError: true が設定されます。コード:
MISSING_CONFIGURATION、INVALID_INPUT、NETWORK_ERROR、TIMEOUT、UPSTREAM_HTTP_ERROR、
RESPONSE_TOO_LARGE、RESPONSE_TOO_COMPLEX(キーの確認で以下の上限を超える場合。内容は一切返されません)、INVALID_RESPONSE、NOT_FOUND、AMBIGUOUS_RESULT(1つの通知 ID に複数のレコードがある場合)、IDENTITY_MISMATCH(別の通知 ID のレコードがある場合)、CANCELLED(MCP クライアントが呼び出しをキャンセルした場合)、INTERNAL_ERROR(このサーバー内部で予期しない障害が発生した場合)。TIMEOUT メッセージには、リクエスト、本文の読み取り、返信の確認のいずれの処理が完了しなかったかが示されます。ツールの入力スキーマに適合しない引数(例:limit: 0)は、このサーバーが実行される前に MCP SDK によって拒否されます。この場合、JSON ではなくプレーンテキストで返されます。HTTP 404 は GSA の仕様では「データなし」を意味し、空の結果になります。本文は読み取らずに解放されます。2026-10-22 にライブサービスで確認したところ、認識されない SAM_API_KEY に対しても空の HTTP 404 が返されました。そのため、間違ったキーはエラーではなく、結果ゼロ(total 0)として扱われます。一方、実際に検索結果が空の場合は、totalRecords: 0 を含む HTTP 200 が返されます。すべての検索で結果が返されない場合は、まずキーを確認してください。
INVALID_RESPONSE は、本文がドキュメントに記載された成功時の形式に一致しないことを意味します。たとえば、空のページにエラーを示すようなフィールド(error、message、status、fault、warning のいずれかの形式)や、ドキュメントに記載のないトップレベルフィールドが含まれる場合、totalRecords/limit/offset が整数ではない場合やリクエストと一致しない場合、最終ページより前のページが満杯でない場合、または最終ページのレコード数が残り件数と一致しない場合、有効な noticeId がないレコードや、サマリーフィールドの型が誤っているレコードがある場合などが該当します。1件でも不正なレコードがあれば、ページ全体が失敗します。これらの確認は、GSA のドキュメントに記載された例と、このプロジェクトのモックテストに準拠しています。2026-10-22 には、実際のキーを使ってライブサービスに対して1回テストしました(連続する5件のページを2つ、100件のページ、通知の検索1件、不明な通知 ID、範囲外のページインデックス、無効なキー)。すべての返信が解析され、キーが返信に含まれることはありませんでした。これは1日分のサンプルであり、継続的に確認しているわけではないため、GSA が異なる形式で返すライブの返信は拒否される可能性があります。
対象範囲と制限
- SAM.gov では、検索ごとに掲載日の範囲を1年以内にする必要があります。このサーバーは API を呼び出す前に検証します。
- 公開 API キーには、GSA が設定した1日あたりのレート制限があります。このサーバー自体は呼び出しをレート制限したりキューに入れたりしません。ツールの呼び出しごとに SAM.gov へリクエストを1回送信します。SAM.gov からのエラーは、キーを伏せたうえでツールエラーとして返されます。
- 対象範囲外:事業体/UEI の検索、受賞データ、添付ファイルのダウンロード、入札/入札辞退のスコアリング。
リソース制限
- 各ツール呼び出しには期限があり(デフォルトで30秒)、MCP クライアントのキャンセルもリクエスト全体に適用されます。対象には、本文の読み取り(最大5 MB)、JSON の解析、形式チェック、キーのチェックが含まれます。本文の読み取り後の処理はすべてワーカースレッドで実行され、期限切れまたは呼び出しのキャンセル時には停止(
terminate)されます。そのため、チェックを長引かせる返信が呼び出しを期限後まで拘束したり、サーバーが他のリクエストに応答できなくなったりすることはありません。結果はTIMEOUTまたはCANCELLEDとなり、何も返されません。 - ワーカーの V8 ヒープは256 MB(旧世代に加えて若年世代32 MB)に制限されています。それ以上を必要とするチェックは
RESPONSE_TOO_COMPLEXとなります。1つのプロセスで同時に実行できるチェックは最大2つです。各ワーカースレッドの終了まで実行中として数えられます(ネイティブコード内で停止したスレッドは、終了するまで枠を占有します)。次のチェックは、独自の期限の範囲内で順番を待ちます(TIMEOUT、「did not start」)。これにより制限されるのはワーカーの JavaScript ヒープであり、プロセス全体ではありません。メインスレッド、バッファー、コードはこれを超えるメモリを使用します。 - キーのチェック前に、すべての文字列(値とプロパティ名)を測定します。NFKC 形式の長さが元の長さの4倍に256を加えた値を超える文字列がある場合、または全文字列の合計が元の長さの2倍に65,536を加えた値を超える場合、返信は拒否されます(
RESPONSE_TOO_COMPLEX)。JSON の深さとノード数の制限を超えた場合も同様に拒否されます(INVALID_RESPONSE)。測定のために展開後のコピーが作成されることはありません。また、同じ展開上限を超えるデコードの処理も返信を拒否します。 - キーのチェックには固定の処理予算があります。チェック対象の全文字列の文字数に対して48単位ずつ、さらに1,000,000単位が割り当てられます。1単位は、文字列の読み取り(結合後の読み取りを含む)で調べる1文字に相当します。デコードされるエスケープ文字1つにつき32単位、文字列1つにつきさらに16単位がかかります。検索の場合、チェック対象の文字列は返信のすべてのフィールドではなく、返された要約です。ただし返信全体は引き続きワーカー内で解析され、形式チェックされます。予算を使い切った場合、返信は拒否されます(
RESPONSE_TOO_COMPLEX)。一部だけを編集した状態で返信されることはなく、予算内に収めるために編集対象を減らすこともありません。この予算はマシンに依存しません。 - このリリースで macOS(Apple M3 Ultra、Node 22.23.2)を使い、返信サイズ64~256 KBで測定した結果は次のとおりです。通常の返信は1文字あたり約19~21単位、パーセントエンコードされた URL で構成されるテキストは44単位、同じ URL を1行ずつ並べたテキストは65単位でした(制御文字を含む文字列が3回目に読み取られるため拒否されます)。リテラルの
\uシーケンスを含むテキストは39単位、前回のリリースで記録された細工済みのデータ形状は60~121単位で、これらは拒否されます(NFKC による展開が大きい2つのデータ形状は、スキャン前に拒否されます)。%XXエスケープだけで構成されたテキストには約58単位が必要で、これも拒否されます。安全ではありますが、可用性が損なわれます。ビルダーの実行では1単位あたり約9~12 ns、内部レビュアーの実行では3.3~17 nsでした。そのため、予算を最大まで使う5 MBの返信には約1~4.5秒かかります。ただし、5 MBでの測定は行っていません。これらの制限は実際の SAM.gov の返信を基準に調整されていません。通常とは異なる実際の返信が拒否される可能性がありますが、安全性は保たれる一方で可用性が損なわれます。SamGovClientに{ limits }を渡すと変更できます。 - ライブラリ関数として直接使用する
redactDeepにも同じ制限が適用されますが、呼び出し元のスレッドで実行されるため、制限されるのは処理時間ではなく処理量です。上流のエラー本文(最大16 KB)とネットワークエラーのテキスト(16 KBに切り詰め)はメインスレッドで編集されます。これは期限ではなくサイズによって制限されます(16 KBで約15 msと測定)。
セキュリティに関する注意事項
- SAM.gov ではキーを
api_keyクエリパラメーターとして指定する必要があるため、HTTPS 経由でapi.sam.govに送信されるリクエスト URL に含まれます。サーバーは、エラー、リンク、返却される JSON からキーを除去します。api_key=の値、および 8 文字以上のキーについては、キーそのものがリテラル、パーセントエンコード(16 進数の大文字・小文字どちらも可、繰り返しも可)、JSON エスケープのいずれかで記述された場合も除去します。キーの文字の間に任意の数の区切り文字が挿入されている場合も対象です。区切り文字とは、文字または数字(Unicode カテゴリー L および N)ではなく、%、\、キー自体の文字のいずれでもない文字を指します。改行、空白、その他の句読点、制御文字、書式文字、結合文字、その他の不可視文字、ハングル字母のフィラーが含まれます。さらに、フェイルクローズのチェックとして、各文字列をエスケープの解釈可能なすべての方法で読み取ります。すなわち、各パーセントエンコードバイト(マルチバイト UTF-8 シーケンスは 1 つとして数えます)、JSON の\uエスケープ、JSON の短縮エスケープ(JSON で記述される小文字のみの\n、\t、\b、\f、\r、\/、\\、\")、HTML 数値文字参照(先頭のゼロを何個付けてもよく、;の有無も問わず、全体を読み取ります)を、それぞれ他とは独立に、記述されたまま、またはデコードした状態で読み取ります。名前付き文字参照は、𝕊のような二重線、スクリプト体、フラクトゥール体の文字、および&、#、%、\、;のエスケープ文字を除いて無視します。これらはデコードされます。NFKC を適用し、全角文字などの互換文字表現も、%、\、&、#の互換表現も変換します。変化がなくなるまで繰り返します(リテラルのバックスラッシュペアだけを縮約する処理では、意味的デコードのラウンド数は消費しません。中間形式もすべてスキャンして計算量に含めます。各ラウンドでは、繰り返された%のエンコード、パーセントエンコード、\uエスケープ、数値文字参照、名前付き文字参照の順にデコードします)。また、各ラウンドの前に、ASCII の英字、数字、%、\、&、#、;以外のすべての文字を除去してから同じ処理を行います。これにより、エスケープの途中に挿入された文字がデコードを妨げることはありません。各読み取り結果は、分解(NFKD)後に ASCII の英字と数字だけにします。そのため、結合文字の後に続く文字はその文字として数えます。挿入文字を除去した読み取りでは、NFKC の前に結合文字を除去するため、エスケープの最後の数字の後に続く結合文字(%4e+ U+0301)はデコードを妨げません。一方、合成済み文字(é)は 1 文字のまま残り、除去されます。JSON でエスケープ(\b、\n、\u0001、\udc00)として記述される制御文字または単独サロゲートは、区切り文字としても、そのエスケープの文字としても読み取ります。また、そのような文字を含む文字列に限り、挿入された他の文字を除去しつつ制御文字は残す 3 つ目の読み取りも行います。レコードに整数のようなプロパティ名がある場合、JSON ではそれが先に記述されるため、すべての文字列、値のみ、名前のみについても、JSON で記述される順序で読み取ります。対象外:ある文字列の末尾にある単独の上位サロゲートと、次の文字列の先頭にある単独の下位サロゲートが、文字列をまとめて読み取る際に対になるケースです。キーの英字と数字がいずれかの読み取りで現れた場合、意味的デコードを 8 ラウンド行ってもなおデコードが続く場合、または文字列に U+10FFFF を超える数値文字参照が含まれる場合(挿入文字の除去によってのみ結合されるものは除く)、文字列全体をREDACTEDに置き換えます。キーの英字と数字が、隣接する文字列(値と次の値、プロパティ名とその値、入れ子になったプロパティ名)をまとめて読み取った場合にのみ現れる場合は、分割位置がエスケープの途中(%5の次に3...、%25の次に53...、&#8の次に3;...、全角の%の次に53...)であっても、応答をINVALID_RESPONSEとして拒否します。各文字列をすべて秘匿した後、すべての文字列、値のみ、プロパティ名のみを、それぞれ順番に連結し、上記すべての読み取り方法で再度読み取ります。連結によってのみ U+10FFFF を超える数値文字参照ができる場合は、拒否する代わりにその参照を削除します。連結した読み取りで 8 回の意味的デコード後もなおデコードが続く場合も、応答を拒否します。衝突後に数値のサフィックスが付いたプロパティ名は、そのサフィックスも含めて読み取ります。このチェックでは小さなオートマトンで、すべての読み取りを同時に追跡するため、処理コストはテキストの長さに対して線形です。連結した読み取りには応答全体のパスが少なくとも 2 回必要であり、連結テキストのデコードラウンドが増えるごとにパスも 1 回増えます。その処理量には「リソース制限」で説明する上限があり、それを超える必要がある応答は一部だけ返すのではなく、拒否されます。この処理では、%、\、キー自体の句読点を区切り文字として使う場合、キーの 16 進数字 2 文字の前に%が記述される場合、エンコードされた文字の途中に区切り文字が入る場合や区切り文字自体がエンコードされている場合(%0A、\u200b、ゼロ幅スペース、または非 ASCII 文字が%53や\u0053の途中にある場合)、エンコードされた文字とテキストのままのエスケープが混在する場合(%35%67の%35だけをデコードする場合、キーの 2 つの数字の前に%25がある場合、S)、全角の文字やエスケープ文字、先頭にゼロがある場合を含む HTML 文字参照が対象になります。たまたまキーの英字と数字が順番どおりに含まれるテキストも空文字に置き換えます。8 ラウンド後も意味的デコードが必要な文字列も同様です。リテラルのバックスラッシュだけが連続していても、この深度上限には達しませんが、同じ処理量、増加量、メモリ、期限の制限は適用されます。認識されないもの:区切り文字として使われる ASCII の英字または数字(U+0345 のように大文字小文字変換で 1 文字になるものを含む他の文字は、このチェックで除去されます)、エスケープの途中に記述された%、\、&、#、;、文字が別々の箇所でエンコードされ、そのうちの 1 文字が他のエスケープでテキストのまま残っているキー(そのようなキーがフィールドに分割されている場合も含む)、その他の名前付き文字参照、キー自体の文字を他のスクリプトの類似文字に置き換えたもの、隣接していないフィールドや数値に分割されたキー、base64 などのその他のエンコード方式です。これらはチェックが認識する変換であり、上流がキーをエンコードするあらゆる方法に対する保護ではありません。短いキーに対する保護はより限定的です。8 文字未満のキーでは、api_key=の値のみを除去し、他の場所に書かれたキーはそのまま残します。ASCII の英字と数字が 8 文字未満の 8 文字以上のキーでは、リテラルおよびエンコードされたパターンは検出しますが、デコード後のチェックは行いません。エラーの抜粋は、300 文字に切り詰める前と後に秘匿します。サーバーは、空白、%、\以外の印字可能な ASCII 文字を 1~256 文字含まないキー、または秘匿マーカーに見えるキー(英字と数字にredactedが含まれるか、api_key=REDACTED内に現れるキー)を、キーを出力せずに拒否します。各呼び出しには 1 つの期限(デフォルトは 30 秒)があり、リクエスト、本文、解析、キーのチェックに適用されます。また、カスタムのfetchImplが中止シグナルを無視する場合も適用されます。完全な URL を記録するもの(たとえば HTTP プロキシ)にはキーが見えてしまいます。 - 通知テキスト、リンク、上流のエラーテキストはサードパーティーのコンテンツです。各結果にはその旨を示す
source注記が含まれ、上流およびネットワークエラーの抜粋には[untrusted upstream text]のラベルが付き、制御文字、双方向テキスト文字、不可視の書式文字が除去されます。また、両方のコンテンツツールの説明にもその旨が記載されています。これらはすべて指示(プロンプトインジェクション)ではなくデータとして扱ってください。このサーバーは、モデルをそれらに耐性のあるものにすることはできず、その能力もありません。 - 5 MB を超える応答は拒否されます。各呼び出しには 30 秒の期限があり、リクエスト、応答本文の読み取り、チェックに適用されます(「リソース制限」を参照)。使用されない本文のクリーンアップは開始されますが、完了を待つことはありません。期限前に到着した HTTP エラーステータスは
UPSTREAM_HTTP_ERRORとして報告され、本文を期限内に読み取れなかった場合はその旨が注記されます。
開発
npm ci --ignore-scripts
npm run build
npm test # vitest, HTTP fully mocked, no key needed
開発には Node.js 22.12 以降(Vitest 5)が必要です。テストでは TypeScript ソースからチェックワーカーを実行するため、Node.js 組み込みの型ストリッピングが必要です(テスト対象のバージョンである Node 22.23.2 および 26.7.0 ではデフォルトで有効です)。公開パッケージでは、コンパイル済み JavaScript を実行します。
実際のキーをコミットしないでください。.env は Git の追跡対象外です。
ライセンス
MIT © IT Custom Solution
コメント
AI アプリケーションが外部のツールやデータソースに統一的な方法で接続するためのオープンなプロトコルです。クライアントはモデルとの対話を担い、MCP サーバーはツール・リソース・プロンプトテンプレートを提供します。標準メッセージでやり取りするため、1 つのサーバーを複数のクライアントで再利用できます。