A Model Context Protocol (MCP) server for searching federal contract opportunities on SAM.gov.
Three tools with clear verb-noun naming (search_opportunities, get_opportunity, list_set_aside_codes). All have descriptions (194 - 250 chars, within baseline 34 - 392 range). Input schemas present with type definitions and parameter descriptions. However, output schemas are not documented, callers cannot see what fields to expect from search_opportunities or get_opportunity. Error handling returns {'error': str(exc)} but lacks recovery guidance or categorization. Parameters like 'procurement_type' and 'set_aside' accept free-form strings despite enumerable values (documented in descriptions but not enforced as enums). Missing tool annotations (readOnlyHint, idempotentHint). No pagination metadata (total_records mentioned in description but not visible in schema).
Fetch a single SAM.gov opportunity by its notice ID. Args: notice_id: The SAM.gov notice ID (the long alphanumeric id from a listing). posted_from: Optional earliest posted date (ISO YYYY-MM-DD) to widen the lookup window. The search defaults to the trailing year; pass this if the notice is older than that. Returns the full normalized opportunity, or an {"error": ...} dict if not found.
List the valid SAM.gov set-aside and procurement-type codes. Useful for translating a plain-English request (e.g. "small business HUBZone solicitations") into the codes the search_opportunities tool expects. This tool runs entirely offline and needs no API key.
Search active and historical federal contract opportunities on SAM.gov. Args: title: Keyword(s) to match against the opportunity title. posted_from: Earliest posted date, ISO format YYYY-MM-DD. Defaults to 30 days ago. posted_to: Latest posted date, ISO format YYYY-MM-DD. Defaults to today. The window between posted_from and posted_to cannot exceed one year. procurement_type: Notice-type code. One of: o=Solicitation, k=Combined Synopsis/Solicitation, p=Pre-solicitation, r=Sources Sought, s=Special Notice, a=Award Notice, u=Justification, g=Sale of Surplus Property, i=Intent to Bundle Requirements. set_aside: Set-aside code, e.g. SBA (Total Small Business), 8A, HZC (HUBZone), SDVOSBC, WOSB. Call list_set_aside_codes for the full table. naics_code: NAICS industry code (up to 6 digits), e.g. 541512. state: Two-letter place-of-performance state, e.g. VA. status: One of active, inactive, archived, cancelled, deleted. limit: Max results to return, 1-1000 (default 25). offset: Page index for pagination (default 0). Returns a dict with total_records, count, and a list of normalized opportunities.
Output schemas not documented. search_opportunities and get_opportunity return dicts but callers cannot see field names, types, or structure. LLMs cannot plan downstream tool calls or extract required data.
Enumerable parameters accept free-form strings. 'procurement_type' and 'set_aside' have valid values documented in descriptions but not enforced as JSON Schema enums. LLMs may hallucinate invalid codes.
Error responses lack recovery guidance. Returning {'error': str(exc)} gives LLMs no actionable next step. Should categorize as retryable vs user-fixable and suggest alternatives.
Missing tool annotations. No readOnlyHint, idempotentHint, or destructiveHint. All three tools are read-only and idempotent but this is not declared in the schema.
| Scored | Grade | Overall | Spec posture | Rubric |
|---|---|---|---|---|
| 2026-09-23 | D | 59 | 2026-07-28+ | v2 |
Pagination metadata unclear. search_opportunities description mentions 'total_records' and 'count' but schema does not show these fields or their types. Callers cannot determine if more results exist.