mcpField
S
sam-gov-mcp

sam-gov-mcp

本地服务
github.com/itcustomsolution法律与政务03 天前更新

关于 sam-gov-mcp

sam-gov-mcp 是只读 MCP 服务,用于搜索和读取 SAM.gov 的公开合同机会公告,面向需要公告信息的 AI 助手。它要求使用者提供免费的 SAM.gov API 密钥。

主要功能

  1. 01按日期、关键词、NAICS、预留类别或州搜索机会
  2. 02按 ID 获取公告
  3. 03列出预留类别代码及含义
  4. 04分页结果提供明确的后续页信息
  5. 05从错误和返回链接中隐藏 API 密钥

适用场景

  1. 01查找符合 NAICS 代码的近期机会
  2. 02按州或预留类别筛选公告
  3. 03获取特定公告的详细信息

MCP 相关

向 MCP 客户端提供 search_opportunities、get_opportunity 和 list_set_aside_codes 工具。服务调用 SAM.gov Get Opportunities API,并要求设置 SAM_API_KEY。

配置

本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。

mcp.json · 通用配置
{
"mcpServers": {
"itcustomsolution-sam-gov": {
"command": "npx",
"args": [
"-y",
"@itcustomsolution/sam-gov-mcp"
],
"env": {
"SAM_API_KEY": "<SAM_API_KEY>"
}
}
}
}
启动命令npx -y @itcustomsolution/sam-gov-mcp
需要配置的环境变量
SAM_API_KEY密钥Your free SAM.gov API key (sam.gov > Account Details > API Key)

本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。

安装包
npm@itcustomsolution/sam-gov-mcp0.1.3

工具

3

收录的工具列表 · 更新于 2 周前

参数类型必填说明
limitinteger否结果数量上限(默认值为 10)
naicsstring否NAICS 代码,例如 541512
statestring否履约地点所在州,2 个字母的代码
offsetinteger否
keywordsstring否要在机会标题中匹配的词语
posted_tostring否结束日期,格式为 YYYY-MM-DD 或 MM/DD/YYYY(默认:今天)
set_asidestring否保留项目代码,例如 SBA、8A、SDVOSBC(参见 list_set_aside_codes)
posted_fromstring否开始日期,格式为 YYYY-MM-DD 或 MM/DD/YYYY(默认:30 天前)

详细介绍

sam-gov-mcp

一个 MCP 服务,让 AI 助手(Claude Desktop、Cursor 和其他 MCP 客户端)通过公开的 SAM.gov Get Opportunities API 搜索 SAM.gov 合同机会。

  • 只读。 仅搜索和读取公开公告。
  • 自备密钥。 你需要在 SAM_API_KEY 中提供免费的 SAM.gov API 密钥。密钥绝不会被记录,并且会从错误和返回的链接中移除。
  • 不排名或评分。 返回 SAM.gov 的结果,并加以概述。
  • 明确报错。 响应不符合文档格式,或公告查询返回不同或有歧义的记录时,会产生工具错误,而不是返回空结果或无关的“成功”。

需要 Node.js 22 或更高版本。

快速入门

  1. 获取免费的 API 密钥:登录 sam.gov → Account Details → API Key。

  2. 安装。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

  3. 将其添加到 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" }
        }
      }
    }
    
  4. 提问:“查找过去 30 天内在 Maryland 发布的 NAICS 541512 小型企业专属机会。”

工具

工具功能输入
search_opportunities搜索公告,每次调用返回一页posted_from、posted_to(可选,默认过去 30 天;YYYY-MM-DD 或 MM/DD/YYYY,范围最多 1 年)、keywords(标题)、naics、set_aside、state、limit(每页 1–100 条记录,默认 10)、offset(从 0 开始的页索引,默认 0)
get_opportunity获取一条公告记录,格式与搜索 API 返回的相同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 的记录。每次搜索只返回一页,不会自行获取更多页面。结果中的 pagination 对象会说明当前分页位置:page_index、page_size、returned_count、has_more、next_offset(将其作为 offset 传回;没有下一页时为 null)、partial(若这一页未包含所有匹配记录,则为 true)以及 page_limit_reached(存在更多记录,但下一页索引将超过 1,000,000 时为 true;请缩小搜索范围或增大每页记录数)。total 对应 SAM.gov 的 totalRecords;如果第一页之后的页面返回 HTTP 404,则为 null,因为该响应不会说明记录总数。

错误

工具错误采用 JSON 格式:{"error": {"code", "message", "status"?}},并带有 isError: true。错误代码: MISSING_CONFIGURATION、INVALID_INPUT、NETWORK_ERROR、TIMEOUT、UPSTREAM_HTTP_ERROR、 RESPONSE_TOO_LARGE、RESPONSE_TOO_COMPLEX(检查回复中的密钥会超出以下限制;不会返回任何内容)、 INVALID_RESPONSE、NOT_FOUND、AMBIGUOUS_RESULT(同一通知 ID 对应多条记录)、 IDENTITY_MISMATCH(记录对应的是不同的通知 ID)、CANCELLED(MCP 客户端取消了调用)和 INTERNAL_ERROR(此服务内部发生意外故障)。TIMEOUT 消息会指出未能完成的步骤:请求、读取正文或检查回复。不符合工具输入架构的参数(例如 limit: 0)会在此服务运行之前被 MCP SDK 拒绝,错误以纯文本而非 JSON 返回。根据 GSA 的规定,HTTP 404 表示“无数据”,结果为空;响应正文会被释放,不会读取。根据 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,或摘要字段的类型错误。任何一条记录格式错误,都会导致整页失败。这些检查依据 GSA 的文档示例以及本项目的模拟测试。2026-10-22,当天使用真实密钥对在线服务进行了 1 次测试(连续获取两个每页 5 条的页面、获取 1 个 100 条记录的页面、查询 1 条通知、查询未知通知 ID、查询超出范围的页索引,以及使用无效密钥);所有回复均成功解析,且没有回复回显密钥。这只是单日样本,并非持续覆盖,因此 GSA 格式不同的在线回复仍可能被拒绝。

范围与限制

  • SAM.gov 要求每次搜索的发布日期范围不超过 1 年;此服务会在调用 API 前进行验证。
  • GSA 为公共 API 密钥设置了每日速率限制。此服务本身不会限制速率或排队调用;每次工具调用都会发起 1 个 SAM.gov 请求。SAM.gov 返回的错误会作为工具错误传回(密钥会被隐藏)。
  • 不在范围内:实体/UEI 查询、授标数据、附件下载、投标/不投标评分。
资源限制
  • 每次工具调用有一个截止时间(默认 30 s),MCP 客户端的取消操作也会中止请求;该限制涵盖读取正文(最多 5 MB)、JSON 解析、格式检查和密钥检查。读取正文后的所有操作都在工作线程中运行;截止时间到达或调用被取消时,工作线程会被停止(terminate),因此,即使回复让检查一直忙碌,也不会导致调用超过截止时间,或阻止服务端响应其他请求。结果为 TIMEOUT 或 CANCELLED,且不会返回任何内容。
  • 工作线程的 V8 堆限制为 256 MB(老生代,另加 32 MB 新生代);需要更多内存的检查将返回 RESPONSE_TOO_COMPLEX。单个进程中最多同时运行 2 项检查,只有在每个工作线程退出后才会计数结束(若线程被原生代码占用,则会一直占用名额,直到退出);其他检查须在各自的截止时间内排队等待(TIMEOUT,"did not start")。这限制的是工作线程的 JavaScript 堆,而不是整个进程:主线程、缓冲区和代码还会使用额外内存。
  • 在密钥检查之前,会测量每个字符串(值和属性名):若某个字符串的 NFKC 形式长度超过其原长度的 4 倍再加 256,或者所有字符串合计增长超过原总长度的 2 倍再加 65,536,则会拒绝该回复(RESPONSE_TOO_COMPLEX);超出 JSON 深度和节点数量限制也会拒绝(INVALID_RESPONSE)。测量时不会创建扩展后的副本;解码过程中若增长将超过相同限制,也会拒绝该回复。
  • 密钥检查有固定的工作量预算:按所检查的所有字符串的总字符数,每个字符 48 个单位,另加 1,000,000 个单位。一个单位是读取任意字符串(包括拼接后的字符串)时检查的一个字符;解码一个转义序列需 32 个单位,每个字符串另需 16 个单位。对于搜索,检查的字符串是返回的摘要,而不是回复中的每个字段;不过,整个回复仍会在工作线程中解析并进行格式检查。预算耗尽时会拒绝该回复(RESPONSE_TOO_COMPLEX);绝不会返回部分脱敏的内容,也不会为了不超出预算而减少脱敏范围。预算不受机器影响。
  • 本次发布在 macOS(Apple M3 Ultra,Node 22.23.2)上针对每条 64 至 256 KB 的回复进行了测量:普通回复每个字符约需 19 至 21 个单位;由百分号编码 URL 构成的文本需 44 个单位;相同的 URL 每行一个需 65 个单位(会被拒绝,因为含有控制字符的字符串会被读取第三次);含有字面量 \u 序列的文本需 39 个单位;为上一版本记录的精心构造形状需 60 至 121 个单位,均会被拒绝(两种 NFKC 扩展形状在扫描前就会被拒绝)。仅由 %XX 转义构成的文本约需 58 个单位,也会被拒绝;这虽然安全,但会降低可用性。构建者测试中,每个单位耗时约 9 至 12 ns;内部审查者测试中约为 3.3 至 17 ns,因此,达到完整预算的 5 MB 回复预计需 1 至 4.5 s;实际未对 5 MB 回复进行测量。 这些限制并未基于 SAM.gov 的实时回复进行校准:不常见的实时回复可能会被拒绝,这虽然安全,但会降低可用性。SamGovClient 接受 { limits } 以更改这些限制。
  • 直接作为库函数使用时,redactDeep 会应用相同的限制,但它在调用线程中运行,因此这些限制约束的是工作量,而不是耗时。上游错误正文(最多 16 KB)和网络错误文本(截断至 16 KB)会在主线程中脱敏;其处理受大小限制(测得处理 16 KB 约需 15 ms),而不受截止时间限制。

安全说明

  • SAM.gov 要求将密钥作为 api_key 查询参数,因此它会通过 HTTPS 随请求 URL 发送至 api.sam.gov。服务会从错误、链接和返回的 JSON 中移除密钥:任何 api_key= 值;对于长度为 8 个或更多字符的密钥,还会移除直接写出的密钥本身、百分号编码形式(十六进制字母大小写均可,也包括重复编码)或 JSON 转义形式,包括在密钥字符之间插入任意数量分隔符的情况。分隔符是任何既非字母也非数字(Unicode 类别 L 和 N)、不是 % 或 \、也不是密钥自身字符的字符:换行符、空格、其他标点、控制字符、格式字符、组合字符和其他不可见字符,以及 Hangul 填充字符。之后,作为失败时关闭的检查,会以所有可能的转义解读方式读取每个字符串:每个百分号编码字节(多字节 UTF-8 序列视为一个)、JSON \u 转义、JSON 短转义(按 JSON 写法,仅限小写的 \n、\t、\b、\f、\r、\/、\\、\")和 HTML 数字实体(读取全部前导零,无论是否带 ;),都会分别按原样或解码后读取;命名实体均忽略,但双线体、手写体和哥特体字母(例如 &Sopf;),以及转义字符 &amp;、&num;、&percnt;、&bsol; 和 &semi; 除外,它们会被解码;应用 NFKC 规范化,这会折叠全角及其他兼容形式,包括 %、\、& 和 #;反复执行,直至结果不再变化(纯粹收缩字面反斜杠对不计入语义解码轮数;仍会扫描并计入每个中间形式;每轮依次解码重复编码的 %、百分号编码、\u 转义、数字实体和命名实体);并且还会进行同样的处理,但每轮开始前先移除所有非 ASCII 字母、数字、%、\、&、# 和 ; 的字符,以免在转义中插入字符阻止解码。每种解读在分解(NFKD)后都会缩减为 ASCII 字母和数字,因此字母后接组合标记时仍视为该字母;在移除插入字符后的解读中,会在 NFKC 之前移除组合标记,因此转义最后一位数字后的标记(%4e + U+0301)不会阻止解码,而预组字符(é)仍是单个字符,会被移除。控制字符或孤立代理项会以转义形式写入 JSON(\b、\n、\u0001、\udc00),会同时作为分隔符和转义中的字母读取;如果字符串包含此类字符,还会进行第三种解读:移除其他插入字符,但保留控制字符。若记录包含类整数属性名(JSON 会优先写入此类属性名),还会按 JSON 写入顺序读取所有字符串、仅值和仅名称。未涵盖的情况:一个字符串末尾的孤立高代理项与下一个字符串开头的孤立低代理项在合并读取时配对。如果任何一种解读中出现密钥的字母和数字、字符串在 8 轮语义解码后仍需继续解码,或字符串包含超出 U+10FFFF 的数字实体(仅在移除插入字符后拼合出的数字实体不计),则整个字符串会替换为 REDACTED;如果只有在相邻字符串合并读取时才出现密钥(相邻的两个值、属性名与其值、嵌套属性名),包括拆分点位于转义内部的情况(%5 后接 3...、%25 后接 53...、&amp;#8 后接 3;...、全角 % 后接 53...),则会以 INVALID_RESPONSE 拒绝响应:所有字符串都替换为 REDACTED 后,会再次将所有字符串按顺序、仅值和仅属性名分别首尾相连,并按上述所有方式进行解读;如果超出 U+10FFFF 的数字实体只在合并后形成,则会将其丢弃,而不是拒绝响应。合并后的文本若在 8 轮语义解码后仍需继续解码,也会导致响应被拒绝;属性名发生冲突并获得数字后缀时,也会连同后缀一起读取。检查会用一个小型自动机同时跟踪所有解读,因此其成本与文本长度呈线性关系。合并读取至少会对响应进行两遍扫描;合并文本每增加一轮解码,就再增加一遍扫描;这项工作受资源限制章节所述的约束,超出限制的响应会被拒绝,不会部分返回。这涵盖了用 %、\ 或密钥自身的标点作为分隔符的情况,在密钥两个十六进制数字前写入 % 的情况,在编码字符内部插入分隔符或对分隔符进行编码的情况(%0A、\u200b、零宽空格或非 ASCII 字母插入 %53 或 \u0053 中),编码字符与保留为文本的转义混用的情况(%35%67,其中只有 %35 被解码;在密钥两个数字前写入 %25;&amp;#83;),以及全角字母和转义字符、带前导零的 HTML 实体。凡是按顺序包含密钥字母和数字的文本也会被清空;语义解码超过 8 轮的字符串同样如此。仅包含连续字面反斜杠不再触发此深度限制;相同的工作量、增长、内存和截止时间限制仍然适用。无法识别的情况:将 ASCII 字母或数字用作分隔符(其他字母,包括大小写折叠后为一个字符的 U+0345,会被此检查移除)、在转义内部写入 %、\、&、# 或 ;、密钥中某个字符被多次编码而其余某处的转义仍保留为文本(包括此类密钥拆分到多个字段中的情况)、其他命名实体、用于替换密钥字符的其他文字系统中的相似字符、拆分到不相邻字段或数字中的密钥,以及 base64 等其他编码。这些是此检查能够识别的转换方式,并不意味着可以防范上游对密钥进行的所有编码。短密钥的保护较少:长度少于 8 个字符的密钥只会移除 api_key= 值(密钥在其他位置出现时会原样保留);长度为 8 个或更多字符但 ASCII 字母和数字少于 8 个的密钥会进行字面和编码模式检查,但不会进行解码检查。错误摘录会在截取到 300 个字符之前和之后分别进行脱敏。若密钥不是由 1 至 256 个可打印 ASCII 字符组成(空格、% 和 \ 除外),或看起来像脱敏标记(其字母和数字包含 redacted,或出现在 api_key=REDACTED 中),服务会拒绝该密钥,且不会回显。每次调用都有一个截止时间(默认 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 内置的类型剥离功能(在 Node 22.23.2 和 26.7.0 中默认启用,这也是经过测试的版本);已发布的包运行编译后的 JavaScript。

切勿提交真实密钥。.env 已被 Git 忽略。

许可证

MIT © IT Custom Solution

评论

支持 Markdown

第一次接触 MCP?

从协议是什么,到在客户端里装上第一个服务,这几条回答覆盖了大多数入门问题。

一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。