Server data from the Official MCP Registry
Search US government contracts, grants and jobs from matchawards.com. Read-only, no key.
About
Search US government contracts, grants and jobs from matchawards.com. Read-only, no key.
Remote endpoints: streamable-http: https://matchawards.com/mcp
Security Report
Valid MCP server (1 strong, 1 medium validity signals). No known CVEs in dependencies. Package registry verified. Imported from the Official MCP Registry. Trust signals: trusted author (3/3 approved).
10 tools verified · Open access · 1 issue found
Security scores are indicators to help you make informed decisions, not guarantees. Always review permissions before connecting any MCP server.
Permissions Required
This plugin requests these system permissions. Most are normal for its category.
What You'll Need
Set these up before or after installing:
Environment variable: MATCHAWARDS_API_BASE
How to Install & Connect
Available as Local & Remote
This plugin can run on your machine or connect to a hosted endpoint. during install.
Documentation
View on GitHubFrom the project's GitHub README.
MatchAwards MCP server
MatchAwards gives AI agents direct access to continuously updated government and business contracts, grants, jobs, awards and collaboration opportunities.
This is the Model Context Protocol server for MatchAwards. It lets Claude, Cursor, VS Code and other MCP clients search US government contract opportunities, grants and jobs, and past contract and grant awards. It only reads data, and it needs no account and no API key.
Tools
| Tool | What it returns | Example prompt |
|---|---|---|
search_contracts | Federal and state contract opportunities. Filters: NAICS (up to 10 codes), state, keyword, set-aside, federal or state only, posted within N days, open only, agency (by name), 1 to 50 results, paging. | "Find open roofing contracts in Virginia posted in the last week." |
search_grants | Federal grant and funding opportunities. Filters: NAICS, keyword, posted within N days, open only, 1 to 50 results, paging (grant notices carry no state). | "Are there open broadband grants?" |
search_jobs | Job postings from the last 30 days. Filters: NAICS, state, keyword, posted within N days, 1 to 50 results, paging. | "Show electrician jobs in Texas from the last 7 days." |
get_opportunity | The full record of one opportunity: the search row plus its description, published points of contact (name and title) and, where MatchAwards has them, AI-generated notes (ai: fit, reasoning, subcontracting and large-prime angles, summary), labelled as AI-generated. | "Who do I contact about the second result?" |
lookup_naics | NAICS codes from words ("cybersecurity") or from a 2 to 6 digit code prefix, with each code's page on matchawards.com. 1 to 25 codes. | "What NAICS code covers cybersecurity consulting?" |
search_awards | Past federal contract awards and grant awards: awardee (name, UEI, DUNS), amount, date, agency, NAICS, set-aside, place, solicitation, contract and CFDA numbers, links to matchawards.com and to SAM.gov or USAspending. Filters: type, NAICS, agency (by name), state, awardee name, UEI, keyword, awarded within N days (up to 1,095), 1 to 50 results, paging. | "Who won VA contracts under NAICS 541512 in the last year?" |
get_company | One firm's award history by UEI: names, location (city, state, country), contract and grant totals, first and last award dates, top NAICS codes and agencies, the 10 most recent awards. No contact details. | "Show the award history of UEI C3KXNLTAAY98." |
assess_opportunity | A qualification brief on one opportunity: days left (US Eastern), open or closed, set-aside and NAICS (and whether they match the user's my_set_aside and my_naics), the AI-generated notes, the 5 newest similar awards (same NAICS at the same agency, or all agencies when that agency has none; the newest 150 at most from the last 3 years, and sample says which dates were read and whether that was all of them), the top 5 firms that won similar work (by count, then total), the quartiles of their award amounts and a coverage note. | "Should we bid on the first result? We're NAICS 336413, small business." |
find_teaming_partners | Up to 10 firms that won similar awards, from the newest records read (sample; same NAICS in the opportunity's state, then at its agency; the NAICS anywhere when neither returns any and award requests are left), de-duplicated by UEI, ranked by count, then the most recent, then the total, each with a why line written from the records. Firms without a UEI in the records are listed with uei: null, and the note says their history can't be looked up. role (sub, prime, any) puts smaller or larger typical awards first among firms with the same count. Public award data only: no contacts, no profile links. | "Who could we team with on that Air Force notice?" |
The agency filter takes a name. The server looks it up among MatchAwards' agencies (exact name first, then names that start with it, then the rest in the API's order) and filters by the best match only, for now; the result's note names that agency and lists up to three other matches (one department can have several agency accounts), and when nothing matches, the search covers all agencies and the note says so. Lookups are cached for 10 minutes.
Award records may be incomplete (federal awards especially), so every award result says so. The two brief tools describe firms as having won similar work, never as the incumbent; they make at most 1 opportunity request and 6 award requests per call (at most 3 per query, shared by the queries), within 6 seconds, and when either limit cuts the lookup short they answer with what they read and a warning. Their sample says how many award records the figures rest on, their dates, and whether every matching record was read. A grant award's title is its recipient's name, so the model is asked to head it with the awardee name and CFDA number instead.
Every tool is read-only and needs no account: each declares readOnlyHint, idempotentHint, a title and securitySchemes: [{"type": "noauth"}] (in _meta). Results are structured: each tool publishes an outputSchema and returns structuredContent, with the same JSON as text for clients that read only text. Every result carries a url on matchawards.com (and a sam_url for federal notices), and the server asks the model to show them as markdown links. Links to matchawards.com carry utm_source, utm_medium and utm_campaign parameters naming the AI host the request came from (for example utm_source=chatgpt); links to other sites are left unchanged.
Changes in 1.0.4
- The result card renders in Claude: its
_meta.ui.domainis left out for Claude, which accepts only its own sandbox domain; other hosts still gethttps://matchawards.com.
Changes in 1.0.3
- Hosted server: usage-stat rows are kept 180 days by default (was 365), set by
MATCHAWARDS_USAGE_RETENTION_DAYS.
Changes in 1.0.2
- Cards come from a new tool,
show_results, which the model calls once with the ids it picked; the search tools return data only. The card (results-v2.html) expands a row in place and has an "Ask about this" button. - Results filtered by
naicssay which requested codes matched:matched_naics(the record's own codes) and, on contract notices,ai_matched_naics(codes MatchAwards' AI suggested). - Links to matchawards.com carry UTM parameters naming the AI host.
- Hosted server: the shared limit is 1200 requests per minute, and
/metricsadds tool latency, distinct-caller and client counts.
Changes in 1.0
search_by_naicsis removed:search_contracts,search_grantsandsearch_jobstake the samenaicsfilter on its own.find_contactsis removed:get_opportunityreturns the published contacts with the rest of the record.- Results are structured output (
outputSchemaplusstructuredContent). Fields the API adds later appear in results only once the schema declares them.get_opportunityreturnsai(AI-generated notes, labelled as such) where MatchAwards has them, else null. - Tool annotations are closed-world (
openWorldHint: false): the tools read the MatchAwards catalog only. - New tools:
lookup_naics,search_awards,get_company,assess_opportunityandfind_teaming_partners;search_contractstakes anagencyname.
Cards
Hosts that render MCP Apps (ChatGPT, Claude) get one inline card per answer: after searching, the model calls show_results with up to 10 contract or grant ids it picked, and the card lists up to 8 of them with title, agency, place, a federal/state/grant badge, the deadline (days left, closed, or no fixed deadline), set-aside and NAICS code. Ids it could not show (not found, a job, or not fetched because MatchAwards was busy) are counted under the list. The search tools return data only, with no card. Clicking a row expands it (posted date, solicitation, office, NAICS, set-aside); its "Ask about this" button asks the model for that opportunity's details, and its "Open on MatchAwards" link opens its page. The card is one self-contained HTML resource, ui://matchawards/results-v2.html (text/html;profile=mcp-app): no scripts or styles from elsewhere and no network requests, so its CSP allows nothing. It renders only the tool's structuredContent, and never calls a tool itself.
The other tools return their structured results without a card.
Cards are off by default. Set MATCHAWARDS_WIDGETS=1 to list the resource and show_results, which points at it (_meta.ui.resourceUri); the other tools' results are the same either way. tests/widget_harness/ holds a minimal host page with sample results for checking the card in a browser.
Hosted
A hosted endpoint runs at https://matchawards.com/mcp. Add it as a remote MCP server (connector) by URL, with nothing to install and no key:
- ChatGPT: add a custom connector with the URL
https://matchawards.com/mcp. - Claude (claude.ai or Claude Desktop): Settings, Connectors, add a custom connector with the same URL. Claude Code:
claude mcp add --transport http matchawards https://matchawards.com/mcp. - Cursor: in
mcp.json,{"mcpServers": {"matchawards": {"url": "https://matchawards.com/mcp"}}}.
The hosted endpoint serves the same tools. It is limited per client address (IPv6: per /64) to 30 requests per minute and 10 per 5 seconds, per IPv6 /48 to 120 per minute, and to 1200 per minute for all clients together (ChatGPT's shared pool: 600); over a limit it answers HTTP 429 with Retry-After.
Install
The server runs with uvx, which comes with uv.
Claude Desktop
Add this to claude_desktop_config.json (macOS: ~/Library/Application Support/Claude/claude_desktop_config.json, Windows: %APPDATA%\Claude\claude_desktop_config.json), then restart Claude Desktop:
{
"mcpServers": {
"matchawards": {
"command": "uvx",
"args": ["matchawards-mcp"]
}
}
}
Claude Code
claude mcp add matchawards -- uvx matchawards-mcp
Cursor
Add this to ~/.cursor/mcp.json (all projects) or .cursor/mcp.json (one project):
{
"mcpServers": {
"matchawards": {
"command": "uvx",
"args": ["matchawards-mcp"]
}
}
}
VS Code
Add this to .vscode/mcp.json in your workspace:
{
"servers": {
"matchawards": {
"type": "stdio",
"command": "uvx",
"args": ["matchawards-mcp"]
}
}
}
Other clients
Any MCP client that can start a stdio server works. The command is uvx matchawards-mcp.
OpenClaw
integrations/openclaw-plugin holds a native OpenClaw plugin (@matchawards/openclaw-plugin on ClawHub) whose nine tools call the hosted endpoint, and a ClawHub skill, matchawards, that teaches an agent the public REST API with curl. Both are read-only and need no account. The plugin's tools.json is generated from this server's tool list (integrations/openclaw-plugin/scripts/gen_tools.py), and the test suite fails when it is out of date.
Run the HTTP server yourself
matchawards-mcp --http serves the same tools over stateless Streamable HTTP (JSON replies, no sessions, no SSE) at http://127.0.0.1:8765/mcp:
uvx matchawards-mcp --http
curl -s http://127.0.0.1:8765/mcp -H 'Content-Type: application/json' \
-H 'Accept: application/json, text/event-stream' -H 'MCP-Protocol-Version: 2025-06-18' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
How requests are handled, in order:
- Repeated and trailing slashes are folded, so
/mcp/and//mcpcount as/mcp. Any path other than/mcpand/healthzgets 404. GET /healthzreturnsokand is not rate-limited.- A
Hostheader not inMATCHAWARDS_ALLOWED_HOSTS(or127.0.0.1,localhost) gets 421, whatever the method. - Only
POST /mcpis served.GET,DELETEandOPTIONSget 405 (no SSE stream, no sessions, no CORS). - A POST needs
Content-Length(411 without it), and bodies over 64 KB get 413. - Rate limit per client (per ChatGPT user from OpenAI's addresses), then the server-wide cap: 429 with
Retry-After.
The client is the socket address, or the X-Real-IP header when the socket address is in MATCHAWARDS_TRUSTED_PROXIES (IPv6 is keyed per /64; all /64s inside one /48 also share MATCHAWARDS_RATE_PER_PREFIX_MIN). Run it behind a reverse proxy that sets X-Real-IP, and do not expose the port directly. The limits are kept in memory per process. A bad value in any of the variables below stops the server at startup. The log has one line per request (method, path, status, duration, a hashed client key), never headers or bodies.
ChatGPT calls come from OpenAI's shared egress addresses (published list, bundled with the package and refreshed daily; a failed refresh keeps the last good list), so one address carries many users and a per-IP limit would throttle them together. From those addresses only, a request is limited per x-openai-subject (ChatGPT's per-user id, kept only as a keyed hash) and all ChatGPT traffic shares MATCHAWARDS_EGRESS_PER_MIN; a request without the header counts in that pool only. The header from any other address is ignored, and the User-Agent is never used.
In Docker, nginx on the host reaches the container from the compose network's gateway, not from loopback, so name that address exactly. Read it with docker network inspect <network> --format '{{(index .IPAM.Config 0).Gateway}}' and set, for example:
MATCHAWARDS_HTTP_HOST=0.0.0.0
MATCHAWARDS_TRUSTED_PROXIES=127.0.0.1/32,::1/128,172.18.0.1/32
Do not trust a broad private range such as 172.16.0.0/12: it may overlap your internal network, and any host in it could then pick its own rate-limit key.
Configuration
| Variable | Default | Purpose |
|---|---|---|
MATCHAWARDS_API_BASE | https://matchawards.com | API base URL. Change it only for development. |
MATCHAWARDS_HTTP_HOST | 127.0.0.1 | --http only: address to listen on. |
MATCHAWARDS_HTTP_PORT | 8765 | --http only: port to listen on. |
MATCHAWARDS_RATE_PER_MIN | 30 | --http only: requests per minute per client. |
MATCHAWARDS_RATE_BURST | 10 | --http only: requests per 5 seconds per client. |
MATCHAWARDS_RATE_PER_PREFIX_MIN | 120 | --http only: requests per minute for all IPv6 /64s inside one /48 together. |
MATCHAWARDS_GLOBAL_PER_MIN | 1200 | --http only: requests per minute for all clients together. Must be above MATCHAWARDS_EGRESS_PER_MIN (otherwise the server will not start). |
MATCHAWARDS_RATE_PER_SUBJECT_MIN | 30 | --http only: requests per minute per ChatGPT user (x-openai-subject, from OpenAI's egress addresses only). |
MATCHAWARDS_RATE_SUBJECT_BURST | 10 | --http only: requests per 5 seconds per ChatGPT user. |
MATCHAWARDS_EGRESS_PER_MIN | 600 | --http only: requests per minute for all ChatGPT traffic together. |
MATCHAWARDS_GLOBAL_API_PER_MIN | 900 | --http only: API calls per minute for the whole server (one search can make up to 3). Over it, a tool answers "MatchAwards is busy, retry in N seconds" without calling the API. |
MATCHAWARDS_TRUSTED_PROXIES | 127.0.0.1/32,::1/128 | --http only: proxies (CIDRs, comma-separated) whose X-Real-IP header is trusted. Loopback only by default. |
MATCHAWARDS_ALLOWED_HOSTS | matchawards.com,staging.matchawards.com | --http only: accepted Host headers, comma-separated (127.0.0.1 and localhost are always accepted). |
MATCHAWARDS_USAGE_DB | /data/usage.db | --http only: SQLite file for the usage stats. If its directory is missing or not writable, the server logs one warning and records nothing. |
MATCHAWARDS_USAGE_SALT | unset | --http only: secret for the ChatGPT caller fingerprint. Unset: no fingerprint is stored. |
MATCHAWARDS_METRICS_TOKEN | unset | --http only: Bearer token for /metrics. Unset: the metrics listener does not start. |
MATCHAWARDS_METRICS_HOST | 127.0.0.1 | --http only: address of the metrics listener. |
MATCHAWARDS_METRICS_PORT | 9765 | --http only: port of the metrics listener. |
MATCHAWARDS_USAGE_RETENTION_DAYS | 180 | --http only: days usage-stat rows are kept. |
MATCHAWARDS_WIDGETS | unset | 1: serve the result card (see Cards). Unset: no card resource and no show_results. |
Usage stats (hosted mode)
With --http the server keeps usage stats in a SQLite file (MATCHAWARDS_USAGE_DB, WAL mode). stdio mode records nothing. Writes run on two background threads and are best-effort: a failed write is counted in mcp_ledger_errors_total and never fails a request. Rows older than MATCHAWARDS_USAGE_RETENTION_DAYS days (default 180) are deleted, at most once a day.
What is recorded:
- Tool calls: one row per call that reached a tool: time, tool name, outcome (
ok,invalid_input,upstream_error,bad_response(a reply the tools cannot read),upstream_timeout(the API ran out of time; retry),partial(assess_opportunityorfind_teaming_partnersanswered, but its request or time limit cut the award lookup short),rate_limited_upstream,busy,internal_error), the client network (IPv4 /24 or IPv6 /48), the User-Agent (first 200 characters), its family (openai-mcp,claude-user,claude,cursor, ...,proberorother) and the latency. Calls stopped by the rate limit are counted in a metric, with no row. Arguments the SDK rejects before the tool runs leave no row either; they still count in the transport table astools/call. - Transport, per day: one counter per client network, JSON-RPC method (known MCP methods only, anything else is
_other) andclientInfoname and version, with the last User-Agent seen.clientInfois read from the first 8 KiB of aninitializebody, or fromparams._meta(2026-07-28 clients), as the body streams past; the body is not buffered. At most 50 counters per client network and day; past that they fold into_other. - ChatGPT callers: when
MATCHAWARDS_USAGE_SALTis set and a request from ChatGPT's egress addresses carriesx-openai-subject(ChatGPT's anonymous per-user id; from any other address it is ignored), the tool-call row stores a 16-hex HMAC-SHA256 of it, to count distinct ChatGPT callers (openai-mcpfamily only in the stats).x-openai-sessionis not used: it changes per conversation.
Never recorded: tool arguments (search terms, ids), request or response bodies, full client addresses, raw header values.
Read the stats with:
matchawards-mcp stats --days 7 # --db PATH to read another file
It prints tool calls per tool and outcome, calls per family, real calls vs probers vs our own tests (matchawards-test/ UA), distinct client networks (probers and own tests left out), distinct ChatGPT fingerprints, transport requests per method and the top clientInfo names. In Docker: docker exec <container> matchawards-mcp stats --days 1.
Metrics
When MATCHAWARDS_METRICS_TOKEN is set, a second listener in the same process (MATCHAWARDS_METRICS_HOST:MATCHAWARDS_METRICS_PORT, default 127.0.0.1:9765) serves GET /metrics in the Prometheus text format, with Authorization: Bearer <token> (401 without it). The /mcp port never serves /metrics (404): it trusts X-Real-IP from the proxy, so publish only the metrics port to the network Prometheus scrapes from. All metrics are in memory and reset on restart; every label value comes from a fixed set, never a client address, subject or argument.
mcp_tool_calls_total{tool,outcome}(counter): tool calls (the ledger writes one row for each).mcp_tool_duration_seconds{tool}(histogram, buckets 0.1 0.25 0.5 1 2.5 5 10 20 30 s): tool call latency, for the same calls.mcp_requests_total{method}(counter): requests served on/mcpby JSON-RPC method.mcp_rejected_total{reason}(counter):rate_limit_client,rate_limit_prefix,rate_limit_subject,rate_limit_egress,rate_limit_global,size,host,path,method.mcp_agent_requests_total{family}(counter): requests served by User-Agent family.mcp_clients_total{client}(counter):initializerequests byclientInfo.name, folded intochatgpt,claude,cursor,vscode,n8n,matchawards,mcp-inspector,otherorunknown.mcp_distinct_callers{kind,window}(gauge,window=1hor24h): distinctclientnetworks (/24 or /48, probers and own tests left out) and distinct ChatGPT users (chatgpt_subject:x-openai-subjecton requests from ChatGPT's egress addresses only). Kept as per-process salted hashes, at most 100,000 per kind. The distinct-caller gauges reset on restart, so a 24h value is only complete 24 hours after a start.mcp_distinct_evictions_total{kind}(counter): distinct-caller keys dropped to stay under that cap (non-zero only under a flood of new networks or subjects). While it is above zero the distinct counts are a lower bound: a caller rotating subjects or networks can push others out.mcp_ledger_errors_total(counter): usage ledger writes that failed.
Ops notes
- Mount a volume at
/data(or setMATCHAWARDS_USAGE_DB) so the file survives a container rebuild. - For ChatGPT fingerprints, nginx must pass
x-openai-subjectthrough to the container (it does by default unless headers are cleared), andMATCHAWARDS_USAGE_SALTmust be set and kept stable: a new salt starts new fingerprints. - For Docker, set
MATCHAWARDS_METRICS_HOST=0.0.0.0and publish the metrics port only on the internal network.
Data
The data is US federal and state contract opportunities, grants, jobs, federal contract awards and grant awards from matchawards.com. Only public posts are returned, and each result links back to its page on matchawards.com.
- Contract and grant searches return open items by default: a response deadline of today or later, US Eastern. An item with no deadline counts as closed.
- Job search covers the last 30 days.
- Results come newest first. When
has_moreis true, the model can ask for the next page withcursor. - A narrow filter (an agency, a rare name) can scan the newest records without a match while more exist further back; the result's
warningthen says so and gives thecursorto continue with. - If the first request of a search fails, the tool returns an error. If a later page fails, the tool returns the rows it already has, with a
warningthat says why it stopped. - A result row in an unexpected format is left out and counted in
warning; the rest of the search is returned. - Each API call times out after 10 seconds.
- The API is rate-limited per IP: 60 requests per minute, plus a burst limit of 20 requests per 5 seconds. One search call can use up to three requests when the filters are narrow and results are sparse, plus one to look up an agency name (cached for 10 minutes). When the limit is reached, the tool returns an error that tells the model how many seconds to wait.
Development
python -m venv .venv
.venv/bin/pip install -e ".[test]"
.venv/bin/pytest
The tests make no network calls.
tests/golden/prompts.json lists prompts with the tool a model should pick (or none), for manual checks of tool selection in a client.
License
MIT. See LICENSE.
Reviews
No reviews yet
Be the first to review this server!
More Developer Tools MCP Servers
Fetch
Freeby Modelcontextprotocol · Developer Tools
Web content fetching and conversion for efficient LLM usage
Git
Freeby Modelcontextprotocol · Developer Tools
Read, search, and manipulate Git repositories programmatically
Toleno
Freeby Toleno · Developer Tools
Toleno Network MCP Server — Manage your Toleno mining account with Claude AI using natural language.
MCP Marketplace
Freeby mcp-marketplace · Developer Tools
Search and install MCP servers from inside your AI client.
mcp-creator-python
Freeby mcp-marketplace · Developer Tools
Create, build, and publish Python MCP servers to PyPI — conversationally.
FinAgent
Freeby mcp-marketplace · Finance
Free stock data and market news for any MCP-compatible AI assistant.
