Skip to main content
Glama

samgov-mcp

An MCP server over the SAM.gov Contract Opportunities API, built for agents that have to read United States federal solicitations and then be believed about what they read.

Everything this server returns carries the exact URL it came from. Anything SAM.gov did not send comes back as null with its name listed, never as a zero, an empty string, or a plausible guess.

npm install
npm run smoke     # 20 checks, no API key, no network
npm run dev       # starts the server on stdio

Run it with no key and it serves recorded fixtures, so you can inspect every tool contract before signing up for anything.


Why this exists

I build AI systems that live inside a company's operation and answer from its own data. The failure that costs you the room is never a model that underperforms. It is a confident number nobody can trace back to anything.

An MCP server is where that gets decided. By the time a figure reaches the model it is just a token, and no prompt reliably repairs a tool that quietly returned the wrong record. So the discipline goes in the tool layer, where it can be tested.

Related MCP server: fpds-mcp

The envelope

Every result has the same shape:

{
  "source": {
    "url": "https://api.sam.gov/opportunities/v2/search?postedFrom=07%2F01%2F2026&...&api_key=REDACTED",
    "retrieved_at": "2026-08-07T18:33:52.792Z",
    "mode": "live"
  },
  "data": { "returned": 2, "total_records_upstream": 412, "notices": [ ... ] },
  "missing": ["notices[1].response_deadline", "notices[1].set_aside_code"],
  "abstained": { "reason": "..." }
}

source is the request that produced the payload, with the API key stripped so a transcript never leaks a credential.

missing names every field the upstream did not provide. SAM.gov signals "not provided" three different ways, null, "", and an object whose inner name is "", and a model cannot tell those apart from real values. They all become null here, and they all get named.

abstained appears when the server declines. "I could not find this" and "this does not exist" are different answers, and collapsing them into an empty list is how an agent ends up telling somebody a solicitation was cancelled when it was simply posted outside the window it searched.

data.returned is counted off the array actually being returned. total_records_upstream is reported beside it, separately, because they are different numbers and mixing them is how a report claims a pipeline processed 412 documents when it read 10.

Tools

Tool

What it does

search_solicitations

Search a posted-date window. Filters for NAICS, procurement type, set-aside, state, title

get_solicitation

One notice by its exact solicitation number

list_attachments

The downloadable resource links for one notice

fetch_attachment

Download one attachment

server_status

Which mode it is in and where the data is coming from

Two things I got wrong, written down

1 · A 404 from this gateway means the key is bad, not the path.

api.sam.gov answers 404 with an empty body for every path when the API key is missing or unrecognised, including paths that exist. DEMO_KEY does not work, because SAM.gov runs its own gateway rather than the shared api.data.gov one. I spent a while probing endpoint variants before realising the endpoint was never the problem. The client now says so in the error text, because the obvious reading of a 404 sends you somewhere useless.

2 · The first version of this server did the exact thing it was built to prevent.

get_solicitation originally ended with ?? rows[0], a harmless-looking fallback: if the exact number is not found, use the first result. But SAM.gov's solnum filter is fuzzy and returns neighbouring notices. So for any number that did not exist, the server would have confidently handed back a different solicitation under the number that was asked for. Different deadline, different set-aside, different scope.

The smoke test did not catch it, because I had asserted that the call returned something. It now asserts abstention, and the abstention message names the neighbours it refused to pass off:

No notice numbered exactly "DOES-NOT-EXIST-0000" was posted between 07/01/2026 and 07/31/2026.
The search returned 2 nearby notice(s) which are NOT this one: FIXTURE-70FA-26-R-0001, ...

It is the same lesson as the first run of any eval suite. The first pass grades your harness, not the thing you pointed it at.

Use it

Live. Generate a free key at sam.gov under Account Details → Public API Key, then:

export SAM_API_KEY=your-key
npm run build

Claude Desktop or Claude Code, in claude_desktop_config.json or via claude mcp add:

{
  "mcpServers": {
    "samgov": {
      "command": "node",
      "args": ["/absolute/path/to/samgov-mcp/dist/index.js"],
      "env": { "SAM_API_KEY": "your-key" }
    }
  }
}

Fixtures. Omit SAM_API_KEY and it runs offline against fixtures/search.json, which is synthetic and labelled as such inside the file. Every tool contract, the grounding envelope and both abstention paths are exercised without a credential. SAM_MODE=live|fixtures overrides the default.

Environment

Variable

Default

Meaning

SAM_API_KEY

none

Free key from SAM.gov. Its presence is what selects live mode

SAM_MODE

auto

live or fixtures, overriding the default

SAM_TIMEOUT_MS

20000

Upstream request timeout

SAM_MAX_ATTACHMENT_BYTES

8000000

Refuse attachments larger than this rather than filling a context window

Notes on the upstream

  • postedFrom and postedTo are mandatory, MM/dd/yyyy, at most one year apart. This server refuses other formats at the schema rather than reformatting them, so a wrong window fails loudly instead of silently returning the wrong year.

  • SAM.gov puts a URL in the description field, not prose. It is returned as description_link, so an agent does not quote a link as if it were the scope of work.

  • Attachments in binary formats are reported with their content type and byte length rather than being decoded into noise. Extracting fields from fillable government forms is a separate step and is deliberately not guessed at here.

Status

Working: the five tools, the grounding envelope, both abstention paths, fixtures mode, 20 smoke checks over the real MCP stdio protocol driven by the reference client.

Next: requirement extraction from fillable PDFs into schema-validated JSON, where every extracted field carries the page and span it came from, plus an eval suite over that extraction using the harness in agent-evals.

MIT.

Available Tools

5 tools
fetch_attachmentDownload one attachmentA

Downloads a single attachment by its resource URL, as returned by list_attachments. Returns UTF-8 text when the document is text, and otherwise returns the content type and byte length without guessing at the contents. Binary formats such as PDF are reported, not silently decoded into noise.

ParametersJSON Schema
NameRequiredDescriptionDefault
urlYesA resource link from list_attachments

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations exist, so the description carries the full burden. It discloses exact return behavior: returns UTF-8 text for text documents, and reports content type and byte length for binary formats instead of guessing or decoding. This is unusually transparent.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Two focused sentences, front-loaded with the main action. Every sentence adds value – the first states the operation and source, the second explains output handling. No filler or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Despite no output schema, the description fully explains return behavior. The single parameter is well-defined in the schema, and the link to list_attachments provides necessary context for when this tool is used. Nothing important is missing for a simple fetch operation.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100% (the 'url' parameter is described as 'A resource link from list_attachments'). The description reinforces this but adds no new semantics beyond the schema, so baseline 3 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Description clearly states 'Downloads a single attachment by its resource URL' – a specific verb, resource, and source. This distinguishes it from siblings like list_attachments, which lists attachments, and get_solicitation, which fetches a different entity.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The phrase 'as returned by list_attachments' explicitly indicates the prerequisite workflow. It doesn't name when not to use alternatives, but the context is clear and there is no competing download tool among siblings.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_solicitationGet one SAM.gov solicitationA

Retrieves a single notice by its solicitation number, within a posted-date window. Abstains explicitly when the number is not found in that window, because 'not in this window' and 'does not exist' are different answers.

ParametersJSON Schema
NameRequiredDescriptionDefault
postedToYesEnd of the window to look in, MM/dd/yyyy
postedFromYesStart of the window to look in, MM/dd/yyyy
solicitationNumberYesThe solicitation number as printed on the notice

TDQS

A4.2/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of behavioral disclosure. It explicitly discloses the abstention behavior when the number is not found within the window, and explains the rationale (distinguishing 'not in window' from 'does not exist'). This is valuable context beyond the schema, though it does not mention error formats or return details.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no filler. The first sentence states the core action; the second adds an important behavioral caveat. Every word earns its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The tool has three required parameters with full schema coverage and no output schema. The description covers the main purpose and a critical edge case (abstention). It implies the return value ('retrieves a single notice'), which is acceptable. Missing explicit return format is a minor gap, but the description is sufficiently complete for an agent to use the tool correctly.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the parameter descriptions already document all three fields. The description repeats the concept of a 'posted-date window' but adds no new syntax, defaults, or relationships beyond what the schema provides. Baseline 3 applies.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the verb 'Retrieves' and the resource 'a single notice' by its solicitation number within a posted-date window. This distinguishes it from siblings like search_solicitations (which presumably does broad searches) and fetch_attachment (which handles attachments).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description makes the usage context clear: this is for retrieving a single notice when you have the exact solicitation number and a specific date window. It does not explicitly list exclusions or alternatives, but the context is unambiguous enough that an agent can infer when to use it.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

list_attachmentsList attachments for a solicitationA

Returns the downloadable attachment URLs for one notice. An empty list means the notice carries no attachments, which is a real and common state and is reported as zero rather than as an error.

ParametersJSON Schema
NameRequiredDescriptionDefault
postedToYesEnd of the window to look in, MM/dd/yyyy
postedFromYesStart of the window to look in, MM/dd/yyyy
solicitationNumberYesThe solicitation number

TDQS

A4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the full burden of behavioral disclosure. It proactively explains a common edge case: an empty list is a real state and returns zero, not an error. This adds genuine transparency beyond a simple 'list attachments' statement, though it does not cover authentication or other behaviors.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences with no wasted words. The first sentence states the core function, and the second clarifies an important behavioral nuance. It is front-loaded with the action and resource.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple listing tool with fully documented schema parameters and no output schema, the description adequately covers what the tool does and the notable empty-list behavior. It could mention usage relative to fetch_attachment, but that gap is more about usage guidelines. Overall it is sufficiently complete for the tool's complexity.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema covers 100% of parameters with descriptions, so the baseline is 3. The description does not add any additional parameter-level meaning; it only references the notion of 'one notice', which maps to solicitationNumber but adds no new detail.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description clearly states the tool returns 'downloadable attachment URLs for one notice', specifying both the action (returns) and the resource (attachment URLs for a specific notice). This distinguishes it from siblings like fetch_attachment (which likely downloads a single attachment) and search_solicitations (which searches rather than lists for a known notice).

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description implies use for a single known notice ('for one notice') but does not explicitly contrast with alternatives or state when not to use it. Sibling tool names provide context, but the description itself lacks direct usage guidance.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

search_solicitationsSearch SAM.gov solicitationsA

Searches federal contract opportunities in a posted-date window. postedFrom and postedTo are required, MM/dd/yyyy, at most one year apart. Returns normalised notices, each with its attachment links. The returned count is the length of the list actually returned, which is not the same as totalRecords across all pages.

ParametersJSON Schema
NameRequiredDescriptionDefault
limitNoRecords per page, up to 1000
ncodeNoNAICS code, up to 6 digits. Lodging and housing: 721110 hotels and motels, 721310 rooming and boarding houses, 531110 lessors of residential buildings
ptypeNoProcurement type, e.g. o for solicitation, p for presolicitation, k for combined synopsis
stateNoTwo-letter place-of-performance state
titleNoFree-text match against the notice title
offsetNoPage offset
postedToYesEnd of the posted-date window, MM/dd/yyyy
postedFromYesStart of the posted-date window, MM/dd/yyyy
typeOfSetAsideNoSet-aside code, e.g. SBA, SDVOSBC, 8A, WOSB

TDQS

A4.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

With no annotations, the description carries the burden of disclosing behavior. It does so effectively by adding non-obvious details: required date format and span limit, return of normalized notices with attachment links, and a warning that the returned count differs from totalRecords across all pages. This is valuable transparency, though it omits error behavior or pagination semantics beyond the count.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences long, front-loaded with the core purpose, followed by constraints and then return/count behavior. Every sentence earns its place; no filler or redundant repetition of schema info.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a tool with 9 parameters and no output schema, the description covers the essential operational context: purpose, mandatory date window restrictions, return format, and a pagination count caveat. It does not detail how filters like limit/offset interact with the count, but those are covered by the schema descriptions, so the overall package is quite complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3. The description adds value by stating that postedFrom/postedTo are required and at most one year apart—a rule not defined in the schema. It also hints at output semantics ('normalised notices'), which complements the schema's parameter descriptions.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description opens with a specific verb+resource+scope: 'Searches federal contract opportunities in a posted-date window.' This clearly distinguishes it from sibling tools like get_solicitation (retrieves a specific one) and attachment tools. The purpose is unambiguous and immediately identifiable.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description provides clear context on when to use the tool (search within a posted-date window) and states hard requirements (postedFrom and postedTo required, MM/dd/yyyy, at most one year apart). It does not explicitly compare with alternative sibling tools, but the usage constraints and scenario are clear, so only a slight deduction for lacking explicit exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

server_statusServer statusA

Reports which mode the server is in and where its data is coming from. Call this first when results look surprising.

ParametersJSON Schema
NameRequiredDescriptionDefault

No parameters

TDQS

A4.4/5.0
Behavior3/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

No annotations are provided, so the description carries the full burden. It discloses that the tool reports mode and data source, implying a read-only operation, but it does not explicitly state that there are no side effects or describe the output format. This is adequate but not rich behavioral detail.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two sentences, front-loaded with the core action and usage cue. There is zero waste or redundancy.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

The description covers the essential aspects of a simple diagnostic tool: what it reports and when to call it. It could elaborate on what 'mode' means or what constitutes 'surprising results,' but for a tool with no parameters and no output schema, it is substantially complete.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The tool has zero parameters, so the description cannot add parameter-level meaning. Per the baseline for zero-parameter tools, a score of 4 is appropriate.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description uses a specific verb ('Reports') and clearly defines the resource (server mode and data source). It distinguishes itself from sibling tools by focusing on server state rather than data retrieval.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states when to use the tool: 'Call this first when results look surprising.' This gives a clear, actionable usage guideline, which is especially valuable for a diagnostic tool with no obvious alternative.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

  1. 5 tool updatesv0.1.0
    • First observedfetch_attachment
    • First observedget_solicitation
    • First observedlist_attachments
    • First observedsearch_solicitations
    • First observedserver_status

TDQS

A4.4/5.0

Scored across 5 tools

Disambiguation5/5

Each tool has a clearly distinct role: searching solicitations, retrieving a single solicitation, listing attachments, fetching an attachment, and checking server status. No two tools could be confused for the same purpose.

Naming Consistency4/5

Most tools follow the verb_noun pattern (fetch_attachment, get_solicitation, list_attachments, search_solicitations). server_status deviates as a noun phrase but is still logically named and not confusing.

Tool Count5/5

The 5 tools are well-scoped for a focused read-only SAM.gov API server. Each tool serves a distinct need without redundancy or bloat.

Completeness5/5

The tool set covers the full read lifecycle: search and retrieve notices, list and download attachments, and diagnose issues with server status. No obvious missing operations given the domain.

Maintenance

ActivitySlowing
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers

  • A
    license
    A
    quality
    A
    maintenance
    The most comprehensive keyless federal-data MCP server. 36 tools for SAM.gov + USAspending + Federal Register + eCFR + Grants.gov. No API key, no registration, no signup. Works in Claude Desktop, Claude Code, Codex CLI, Cursor, Continue, Gemini CLI, and any MCP-aware host.
    6
    152
    497 npm
    8
    MIT
  • F
    license
    Not graded
    quality
    C
    maintenance
    MCP server for querying U.S. Federal Procurement Data System (FPDS-NG) to search federal contract actions by keyword, agency, NAICS, or vendor, with USDC micropayments via x402.
    -
  • A
    license
    A
    quality
    D
    maintenance
    Read-only MCP server for exploring US federal spending data via the USAspending.gov API, enabling natural language queries on awards, agencies, recipients, and spending trends.
    12
    MIT
  • F
    license
    A
    quality
    D
    maintenance
    MCP server for searching government tenders from CanadaBuys and SAM.gov with free stats and paid search, latest, and AI matching tools using x402 micropayments.
    4
    1
    -