govconapi-mcp 0.2.1__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. govconapi_mcp-0.2.2/Dockerfile +17 -0
  2. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/PKG-INFO +22 -4
  3. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/README.md +21 -3
  4. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/tools/agencies.py +2 -1
  5. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/tools/award_compliance.py +14 -13
  6. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/tools/awards.py +2 -1
  7. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/tools/bid_negotiate.py +10 -9
  8. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/tools/capture_teaming.py +13 -12
  9. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/tools/exclusions.py +2 -1
  10. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/tools/identifiers.py +2 -1
  11. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/tools/market_research.py +13 -12
  12. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/tools/opportunities.py +4 -3
  13. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/pyproject.toml +1 -1
  14. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/server.json +4 -3
  15. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/.gitignore +0 -0
  16. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/LICENSE +0 -0
  17. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/TOOLS.md +0 -0
  18. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/glama.json +0 -0
  19. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/__init__.py +0 -0
  20. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/core.py +0 -0
  21. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/server.py +0 -0
  22. {govconapi_mcp-0.2.1 → govconapi_mcp-0.2.2}/govconapi_mcp/tools/__init__.py +0 -0
@@ -0,0 +1,17 @@
1
+ # Image for running the GovCon API MCP server (stdio transport). Used by Glama's build and
2
+ # security checks, and by anyone who wants to run the client in a container.
3
+ #
4
+ # Introspection works with no configuration: the API key is read at CALL time inside core._get(),
5
+ # not at import, so `tools/list` returns all 53 tool definitions without a key and without network
6
+ # access. Actually invoking a tool does need a key, since every tool is a live query against
7
+ # https://govconapi.com. Free 14-day trial, no credit card: https://govconapi.com
8
+ FROM python:3.12-slim
9
+
10
+ WORKDIR /app
11
+ COPY . /app
12
+
13
+ RUN pip install --no-cache-dir .
14
+
15
+ # GOVCONAPI_KEY is required to call tools, not to list them.
16
+ # GOVCONAPI_BASE is optional and defaults to https://govconapi.com.
17
+ ENTRYPOINT ["govconapi-mcp"]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: govconapi-mcp
3
- Version: 0.2.1
3
+ Version: 0.2.2
4
4
  Summary: MCP server for GovCon API: 53 tools spanning the federal contracting lifecycle (market research, opportunity discovery, capture and teaming, bid and proposal, award and compliance) for Claude Desktop, Claude Code, Cursor, Zed, or any local MCP client.
5
5
  Project-URL: Homepage, https://govconapi.com
6
6
  Project-URL: Documentation, https://govconapi.com/api-guide
@@ -27,11 +27,13 @@ Description-Content-Type: text/markdown
27
27
 
28
28
  # GovCon API MCP
29
29
 
30
- ![MCP server](https://img.shields.io/badge/MCP-server-5865F2) ![53 tools](https://img.shields.io/badge/tools-53-1f6feb) ![corpus](https://img.shields.io/badge/corpus-10.8M_FPDS_transactions-1f6feb) [![from $19/mo](https://img.shields.io/badge/from-%2419%2Fmo-2ea44f)](https://govconapi.com/pricing)
30
+ ![MCP server](https://img.shields.io/badge/MCP-server-5865F2) ![53 tools](https://img.shields.io/badge/tools-53-1f6feb) ![5 federal sources](https://img.shields.io/badge/sources-5_federal-1f6feb) [![from $19/mo](https://img.shields.io/badge/from-%2419%2Fmo-2ea44f)](https://govconapi.com/pricing)
31
31
 
32
32
  **Most SAM.gov MCP servers wrap one dataset behind six to ten tools. This is 53 tools across five federal sources, already joined on the identifiers they share, including full text extracted from inside solicitation attachments.**
33
33
 
34
- US federal procurement data for agents: **668,576** SAM.gov opportunities, **10,787,941** FPDS prime contract transactions, **887,310** SAM-registered entities, **225,945** FFATA subawards, **164,322** exclusions, **13,778** decided GAO protests, and **~262,000** GSA CALC labor rates. Cross-linked on UEI, CAGE, PIID and NAICS, so one question does not become five integrations.
34
+ US federal procurement data for agents: **668K** SAM.gov opportunities, **10.6M** FPDS prime contract transactions, **880K** SAM-registered entities, **226K** FFATA subawards, **164K** exclusions, **14K** decided GAO protests, and **~262K** GSA CALC labor rates. Cross-linked on UEI, CAGE, PIID and NAICS, so one question does not become five integrations.
35
+
36
+ *Counts as of 2026-08-31, rounded. The corpus grows daily, so this file is a snapshot: current per-source totals and freshness dates are always live at [`/api/v1/status`](https://govconapi.com/api/v1/status), no key required.*
35
37
 
36
38
  > **Free 14-day trial, no credit card.** [Get a key](https://govconapi.com), then `pipx install govconapi-mcp`.
37
39
 
@@ -41,7 +43,7 @@ US federal procurement data for agents: **668,576** SAM.gov opportunities, **10,
41
43
 
42
44
  Most federal-contracting MCP servers are thin proxies over the official SAM.gov API. Four concrete differences.
43
45
 
44
- - **It searches inside the documents, not just the notice.** We extracted and indexed the full text of **506,382 documents** attached to **209,095 opportunities**, 31.4 billion characters, including **57,527 files recovered from archives nested up to five levels deep** that never appear in SAM's own attachment list, and **41,764 scanned documents recovered with OCR** (measured 2026-07-16). A requirement like CMMC, a clearance level, or an option year is usually in the SOW, not the notice. Most tools structurally cannot see it.
46
+ - **It searches inside the documents, not just the notice.** We extract and index the full text of every document attached to a notice. A corpus study on **2026-07-16** measured **506,382 documents** across **209,095 opportunities**, 31.4 billion characters, including **57,527 files recovered from archives nested up to five levels deep** that never appear in SAM's own attachment list, and **41,764 scanned documents recovered with OCR**. Those five figures are one dated measurement and belong together; the index has grown since, and the running total is on [`/api/v1/status`](https://govconapi.com/api/v1/status). A requirement like CMMC, a clearance level, or an option year is usually in the SOW, not the notice. Most tools structurally cannot see it.
45
47
  - **Five sources, already joined.** SAM.gov opportunities and entities, FPDS contract transactions, USAspending/FFATA subawards, SAM exclusions, SBA DSBS certifications and GSA CALC rates, resolved onto shared identifiers. A wrapper hands your agent one dataset and leaves the joins to it.
46
48
  - **No SAM.gov entity registration and no company UEI required.** GSA documents 10 requests/day for a non-federal user without a SAM role, and 1,000/day with one. Access here is an email and a key.
47
49
  - **53 tools, organised by lifecycle stage.** Market research, opportunity discovery, capture and teaming, bid and negotiate, award and compliance. You should not have to know our URL structure to know which tool answers your question.
@@ -145,11 +147,27 @@ MCP access is included with every plan, including the trial. No separate tier.
145
147
 
146
148
  ---
147
149
 
150
+ ## Privacy Policy
151
+
152
+ Full policy: **<https://govconapi.com/privacy>**
153
+
154
+ What this server does with your data, in short:
155
+
156
+ - **Your API key** is read from `GOVCONAPI_KEY` in your own client's config and sent to `https://govconapi.com` in the `Authorization` header. This server process does not write it to disk or transmit it anywhere else.
157
+ - **Your tool calls** become ordinary GovCon API requests. As with any request to the API, we log the timestamp, endpoint, query parameters, response status, response time and which key made the call, for billing, rate-limit enforcement and abuse detection. Usage logs are retained for 90 days.
158
+ - **No conversation content leaves your machine.** Only the tool parameters your client fills in are sent, never the surrounding chat, and we log the status a request returned, not the contents of the response.
159
+ - **We do not sell your data.**
160
+ - **The data returned is public US federal procurement data** from SAM.gov, FPDS, USAspending, SBA DSBS and GSA CALC.
161
+
162
+ ---
163
+
148
164
  ## Links
149
165
 
150
166
  - API reference: <https://govconapi.com/api-guide>
151
167
  - Tool reference: <https://govconapi.com/mcp/tools>
168
+ - Live coverage and freshness: <https://govconapi.com/api/v1/status>
152
169
  - Machine-readable docs: <https://govconapi.com/llms.txt> and <https://govconapi.com/openapi.json>
170
+ - Privacy: <https://govconapi.com/privacy> · Terms: <https://govconapi.com/terms>
153
171
  - Support: <support@govconapi.com>
154
172
 
155
173
  MIT licensed. Data sourced from SAM.gov, FPDS, USAspending, SBA DSBS and GSA CALC, all public federal data.
@@ -2,11 +2,13 @@
2
2
 
3
3
  # GovCon API MCP
4
4
 
5
- ![MCP server](https://img.shields.io/badge/MCP-server-5865F2) ![53 tools](https://img.shields.io/badge/tools-53-1f6feb) ![corpus](https://img.shields.io/badge/corpus-10.8M_FPDS_transactions-1f6feb) [![from $19/mo](https://img.shields.io/badge/from-%2419%2Fmo-2ea44f)](https://govconapi.com/pricing)
5
+ ![MCP server](https://img.shields.io/badge/MCP-server-5865F2) ![53 tools](https://img.shields.io/badge/tools-53-1f6feb) ![5 federal sources](https://img.shields.io/badge/sources-5_federal-1f6feb) [![from $19/mo](https://img.shields.io/badge/from-%2419%2Fmo-2ea44f)](https://govconapi.com/pricing)
6
6
 
7
7
  **Most SAM.gov MCP servers wrap one dataset behind six to ten tools. This is 53 tools across five federal sources, already joined on the identifiers they share, including full text extracted from inside solicitation attachments.**
8
8
 
9
- US federal procurement data for agents: **668,576** SAM.gov opportunities, **10,787,941** FPDS prime contract transactions, **887,310** SAM-registered entities, **225,945** FFATA subawards, **164,322** exclusions, **13,778** decided GAO protests, and **~262,000** GSA CALC labor rates. Cross-linked on UEI, CAGE, PIID and NAICS, so one question does not become five integrations.
9
+ US federal procurement data for agents: **668K** SAM.gov opportunities, **10.6M** FPDS prime contract transactions, **880K** SAM-registered entities, **226K** FFATA subawards, **164K** exclusions, **14K** decided GAO protests, and **~262K** GSA CALC labor rates. Cross-linked on UEI, CAGE, PIID and NAICS, so one question does not become five integrations.
10
+
11
+ *Counts as of 2026-08-31, rounded. The corpus grows daily, so this file is a snapshot: current per-source totals and freshness dates are always live at [`/api/v1/status`](https://govconapi.com/api/v1/status), no key required.*
10
12
 
11
13
  > **Free 14-day trial, no credit card.** [Get a key](https://govconapi.com), then `pipx install govconapi-mcp`.
12
14
 
@@ -16,7 +18,7 @@ US federal procurement data for agents: **668,576** SAM.gov opportunities, **10,
16
18
 
17
19
  Most federal-contracting MCP servers are thin proxies over the official SAM.gov API. Four concrete differences.
18
20
 
19
- - **It searches inside the documents, not just the notice.** We extracted and indexed the full text of **506,382 documents** attached to **209,095 opportunities**, 31.4 billion characters, including **57,527 files recovered from archives nested up to five levels deep** that never appear in SAM's own attachment list, and **41,764 scanned documents recovered with OCR** (measured 2026-07-16). A requirement like CMMC, a clearance level, or an option year is usually in the SOW, not the notice. Most tools structurally cannot see it.
21
+ - **It searches inside the documents, not just the notice.** We extract and index the full text of every document attached to a notice. A corpus study on **2026-07-16** measured **506,382 documents** across **209,095 opportunities**, 31.4 billion characters, including **57,527 files recovered from archives nested up to five levels deep** that never appear in SAM's own attachment list, and **41,764 scanned documents recovered with OCR**. Those five figures are one dated measurement and belong together; the index has grown since, and the running total is on [`/api/v1/status`](https://govconapi.com/api/v1/status). A requirement like CMMC, a clearance level, or an option year is usually in the SOW, not the notice. Most tools structurally cannot see it.
20
22
  - **Five sources, already joined.** SAM.gov opportunities and entities, FPDS contract transactions, USAspending/FFATA subawards, SAM exclusions, SBA DSBS certifications and GSA CALC rates, resolved onto shared identifiers. A wrapper hands your agent one dataset and leaves the joins to it.
21
23
  - **No SAM.gov entity registration and no company UEI required.** GSA documents 10 requests/day for a non-federal user without a SAM role, and 1,000/day with one. Access here is an email and a key.
22
24
  - **53 tools, organised by lifecycle stage.** Market research, opportunity discovery, capture and teaming, bid and negotiate, award and compliance. You should not have to know our URL structure to know which tool answers your question.
@@ -120,11 +122,27 @@ MCP access is included with every plan, including the trial. No separate tier.
120
122
 
121
123
  ---
122
124
 
125
+ ## Privacy Policy
126
+
127
+ Full policy: **<https://govconapi.com/privacy>**
128
+
129
+ What this server does with your data, in short:
130
+
131
+ - **Your API key** is read from `GOVCONAPI_KEY` in your own client's config and sent to `https://govconapi.com` in the `Authorization` header. This server process does not write it to disk or transmit it anywhere else.
132
+ - **Your tool calls** become ordinary GovCon API requests. As with any request to the API, we log the timestamp, endpoint, query parameters, response status, response time and which key made the call, for billing, rate-limit enforcement and abuse detection. Usage logs are retained for 90 days.
133
+ - **No conversation content leaves your machine.** Only the tool parameters your client fills in are sent, never the surrounding chat, and we log the status a request returned, not the contents of the response.
134
+ - **We do not sell your data.**
135
+ - **The data returned is public US federal procurement data** from SAM.gov, FPDS, USAspending, SBA DSBS and GSA CALC.
136
+
137
+ ---
138
+
123
139
  ## Links
124
140
 
125
141
  - API reference: <https://govconapi.com/api-guide>
126
142
  - Tool reference: <https://govconapi.com/mcp/tools>
143
+ - Live coverage and freshness: <https://govconapi.com/api/v1/status>
127
144
  - Machine-readable docs: <https://govconapi.com/llms.txt> and <https://govconapi.com/openapi.json>
145
+ - Privacy: <https://govconapi.com/privacy> · Terms: <https://govconapi.com/terms>
128
146
  - Support: <support@govconapi.com>
129
147
 
130
148
  MIT licensed. Data sourced from SAM.gov, FPDS, USAspending, SBA DSBS and GSA CALC, all public federal data.
@@ -6,9 +6,10 @@ import json
6
6
  import re
7
7
 
8
8
  from ..core import mcp, _get
9
+ from mcp.types import ToolAnnotations
9
10
 
10
11
 
11
- @mcp.tool()
12
+ @mcp.tool(title="Lookup Agency", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
12
13
  async def lookup_agency(query: str) -> str:
13
14
  """Resolve an agency acronym or partial name to canonical SAM.gov agency strings.
14
15
 
@@ -8,11 +8,12 @@ import json
8
8
  from typing import Optional
9
9
 
10
10
  from ..core import mcp, _get
11
+ from mcp.types import ToolAnnotations
11
12
 
12
13
 
13
14
  # ─── Contracts (FPDS prime awards) ─────────────────────────────────────────
14
15
 
15
- @mcp.tool()
16
+ @mcp.tool(title="Search FPDS Contract Transactions", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
16
17
  async def search_contracts(
17
18
  uei: Optional[str] = None,
18
19
  parent_uei: Optional[str] = None,
@@ -65,7 +66,7 @@ async def search_contracts(
65
66
  return json.dumps(data, indent=2, default=str)
66
67
 
67
68
 
68
- @mcp.tool()
69
+ @mcp.tool(title="Get Contract", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
69
70
  async def get_contract(piid: str) -> str:
70
71
  """Get one contract's LATEST transaction plus a roll-up of obligation/value totals
71
72
  across every modification.
@@ -88,7 +89,7 @@ async def get_contract(piid: str) -> str:
88
89
  return json.dumps(data, indent=2, default=str)
89
90
 
90
91
 
91
- @mcp.tool()
92
+ @mcp.tool(title="Get Contract Modifications", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
92
93
  async def get_contract_modifications(piid: str, limit: int = 100, offset: int = 0) -> str:
93
94
  """Get EVERY transaction row for a contract, oldest action first, the full
94
95
  modification trail (amendments, options exercised, partial terminations), not just
@@ -109,7 +110,7 @@ async def get_contract_modifications(piid: str, limit: int = 100, offset: int =
109
110
  return json.dumps(data, indent=2, default=str)
110
111
 
111
112
 
112
- @mcp.tool()
113
+ @mcp.tool(title="Get the Vehicle Behind a Contract", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
113
114
  async def get_contract_vehicle(piid: str) -> str:
114
115
  """Get the contract vehicle (IDIQ, GWAC, FSS schedule, or BPA) this order was placed
115
116
  against.
@@ -130,7 +131,7 @@ async def get_contract_vehicle(piid: str) -> str:
130
131
 
131
132
  # ─── Vehicles (IDVs) ────────────────────────────────────────────────────────
132
133
 
133
- @mcp.tool()
134
+ @mcp.tool(title="Search Vehicles", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
134
135
  async def search_vehicles(
135
136
  uei: Optional[str] = None,
136
137
  parent_uei: Optional[str] = None,
@@ -175,7 +176,7 @@ async def search_vehicles(
175
176
  return json.dumps(data, indent=2, default=str)
176
177
 
177
178
 
178
- @mcp.tool()
179
+ @mcp.tool(title="Get Vehicle Detail", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
179
180
  async def get_vehicle(piid: str) -> str:
180
181
  """Get one contract vehicle's detail: ceiling, period, and what's been ordered
181
182
  through it.
@@ -198,7 +199,7 @@ async def get_vehicle(piid: str) -> str:
198
199
  return json.dumps(data, indent=2, default=str)
199
200
 
200
201
 
201
- @mcp.tool()
202
+ @mcp.tool(title="Get Vehicle Holders", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
202
203
  async def get_vehicle_holders(piid: str, limit: int = 50) -> str:
203
204
  """Get who holds a vehicle AND who's actually earning through it, two distinct
204
205
  populations, don't conflate them: a firm can hold a vehicle for years and earn
@@ -223,7 +224,7 @@ async def get_vehicle_holders(piid: str, limit: int = 50) -> str:
223
224
 
224
225
  # ─── Subawards (FFATA) ──────────────────────────────────────────────────────
225
226
 
226
- @mcp.tool()
227
+ @mcp.tool(title="Search Subawards", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
227
228
  async def search_subawards(
228
229
  prime_uei: Optional[str] = None,
229
230
  sub_uei: Optional[str] = None,
@@ -268,7 +269,7 @@ async def search_subawards(
268
269
  return json.dumps(data, indent=2, default=str)
269
270
 
270
271
 
271
- @mcp.tool()
272
+ @mcp.tool(title="Get Subaward", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
272
273
  async def get_subaward(subaward_sam_report_id: str) -> str:
273
274
  """Get one FFATA subaward report by its SAM report ID.
274
275
 
@@ -280,7 +281,7 @@ async def get_subaward(subaward_sam_report_id: str) -> str:
280
281
  return json.dumps(data, indent=2, default=str)
281
282
 
282
283
 
283
- @mcp.tool()
284
+ @mcp.tool(title="Get Prime Subawards", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
284
285
  async def get_prime_subawards(
285
286
  uei: str,
286
287
  date_from: Optional[str] = None,
@@ -307,7 +308,7 @@ async def get_prime_subawards(
307
308
  return json.dumps(data, indent=2, default=str)
308
309
 
309
310
 
310
- @mcp.tool()
311
+ @mcp.tool(title="Get Prime Relationships", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
311
312
  async def get_prime_relationships(
312
313
  uei: str,
313
314
  date_from: Optional[str] = None,
@@ -333,7 +334,7 @@ async def get_prime_relationships(
333
334
 
334
335
  # ─── Bid Protests ────────────────────────────────────────────────────────────
335
336
 
336
- @mcp.tool()
337
+ @mcp.tool(title="Search Protests", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
337
338
  async def search_protests(
338
339
  outcome: Optional[str] = None,
339
340
  agency: Optional[str] = None,
@@ -374,7 +375,7 @@ async def search_protests(
374
375
  return json.dumps(data, indent=2, default=str)
375
376
 
376
377
 
377
- @mcp.tool()
378
+ @mcp.tool(title="Get Protests on a Solicitation", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
378
379
  async def get_protests_on_solicitation(solicitation_number: str) -> str:
379
380
  """Get every protest filed on ONE solicitation, the contestability read for a
380
381
  specific opportunity or award: any protest pending right now, and the statutory date
@@ -13,9 +13,10 @@ import json
13
13
  from typing import Optional
14
14
 
15
15
  from ..core import mcp, _get
16
+ from mcp.types import ToolAnnotations
16
17
 
17
18
 
18
- @mcp.tool()
19
+ @mcp.tool(title="Search SAM Award Notices", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
19
20
  async def search_awards(
20
21
  awardee: Optional[str] = None,
21
22
  uei: Optional[str] = None,
@@ -8,11 +8,12 @@ import json
8
8
  from typing import Optional
9
9
 
10
10
  from ..core import mcp, _get
11
+ from mcp.types import ToolAnnotations
11
12
 
12
13
 
13
14
  # ─── Contract Price Benchmarks ─────────────────────────────────────────────
14
15
 
15
- @mcp.tool()
16
+ @mcp.tool(title="Get Price Benchmark", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
16
17
  async def get_price_benchmark(
17
18
  naics: str,
18
19
  set_aside: Optional[str] = None,
@@ -54,7 +55,7 @@ async def get_price_benchmark(
54
55
  return json.dumps(data, indent=2, default=str)
55
56
 
56
57
 
57
- @mcp.tool()
58
+ @mcp.tool(title="Get Price Position", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
58
59
  async def get_price_position(
59
60
  naics: str,
60
61
  value: str,
@@ -94,7 +95,7 @@ async def get_price_position(
94
95
 
95
96
  # ─── Labor Rate Benchmarks ──────────────────────────────────────────────────
96
97
 
97
- @mcp.tool()
98
+ @mcp.tool(title="Get Labor Rate Benchmark", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
98
99
  async def get_labor_rate_benchmark(
99
100
  labor_category: str,
100
101
  match: str = "contains",
@@ -147,7 +148,7 @@ async def get_labor_rate_benchmark(
147
148
 
148
149
  # ─── Wage Determinations ────────────────────────────────────────────────────
149
150
 
150
- @mcp.tool()
151
+ @mcp.tool(title="Search Wage Determinations", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
151
152
  async def search_wage_determinations(
152
153
  type: Optional[str] = None,
153
154
  state: Optional[str] = None,
@@ -190,7 +191,7 @@ async def search_wage_determinations(
190
191
  return json.dumps(data, indent=2, default=str)
191
192
 
192
193
 
193
- @mcp.tool()
194
+ @mcp.tool(title="Get Wage Determinations by Location", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
194
195
  async def get_wds_by_location(
195
196
  state: str, county: Optional[str] = None, type: Optional[str] = None,
196
197
  limit: int = 25, offset: int = 0,
@@ -216,7 +217,7 @@ async def get_wds_by_location(
216
217
  return json.dumps(data, indent=2, default=str)
217
218
 
218
219
 
219
- @mcp.tool()
220
+ @mcp.tool(title="Query Wage Rates Across Determinations", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
220
221
  async def get_wage_rates(
221
222
  classification: Optional[str] = None,
222
223
  type: Optional[str] = None,
@@ -252,7 +253,7 @@ async def get_wage_rates(
252
253
  return json.dumps(data, indent=2, default=str)
253
254
 
254
255
 
255
- @mcp.tool()
256
+ @mcp.tool(title="Get Wage Rate Summary", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
256
257
  async def get_wage_rate_summary(
257
258
  occupation_code: Optional[str] = None,
258
259
  classification: Optional[str] = None,
@@ -285,7 +286,7 @@ async def get_wage_rate_summary(
285
286
  return json.dumps(data, indent=2, default=str)
286
287
 
287
288
 
288
- @mcp.tool()
289
+ @mcp.tool(title="Get Wage Determination", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
289
290
  async def get_wage_determination(wd_id: str) -> str:
290
291
  """Get one wage determination's full record: location array, every classification's
291
292
  hourly wage + fringe, and (for CBAs) the contractor/union detail block.
@@ -301,7 +302,7 @@ async def get_wage_determination(wd_id: str) -> str:
301
302
 
302
303
  # ─── Vendor Risk ─────────────────────────────────────────────────────────────
303
304
 
304
- @mcp.tool()
305
+ @mcp.tool(title="Get Vendor Risk Report", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
305
306
  async def get_vendor_risk_report(uei: str) -> str:
306
307
  """Get a 7-signal vendor risk report for one UEI, screening facts for teaming or
307
308
  subcontracting due diligence, before you commit to a partner.
@@ -8,11 +8,12 @@ import json
8
8
  from typing import Optional
9
9
 
10
10
  from ..core import mcp, _get
11
+ from mcp.types import ToolAnnotations
11
12
 
12
13
 
13
14
  # ─── Companies (won at least one award) ───────────────────────────────────
14
15
 
15
- @mcp.tool()
16
+ @mcp.tool(title="Search Companies", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
16
17
  async def search_companies(
17
18
  q: Optional[str] = None,
18
19
  naics: Optional[str] = None,
@@ -51,7 +52,7 @@ async def search_companies(
51
52
  return json.dumps(data, indent=2, default=str)
52
53
 
53
54
 
54
- @mcp.tool()
55
+ @mcp.tool(title="Get Company Profile", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
55
56
  async def get_company_profile(uei: str) -> str:
56
57
  """Get one company's aggregate profile: SAM registration (legal name, address, NAICS,
57
58
  PSC, certifications) combined with award-history totals.
@@ -72,7 +73,7 @@ async def get_company_profile(uei: str) -> str:
72
73
  return json.dumps(data, indent=2, default=str)
73
74
 
74
75
 
75
- @mcp.tool()
76
+ @mcp.tool(title="Get Company Awards", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
76
77
  async def get_company_awards(
77
78
  uei: str,
78
79
  limit: int = 50,
@@ -100,7 +101,7 @@ async def get_company_awards(
100
101
  return json.dumps(data, indent=2, default=str)
101
102
 
102
103
 
103
- @mcp.tool()
104
+ @mcp.tool(title="Get Company Peers", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
104
105
  async def get_company_peers(uei: str, limit: int = 10) -> str:
105
106
  """Find companies similar to this one by NAICS + agency overlap, the competitive
106
107
  landscape around a firm, not its own history.
@@ -121,7 +122,7 @@ async def get_company_peers(uei: str, limit: int = 10) -> str:
121
122
 
122
123
  # ─── Entities (the full SAM registry, award or not) ───────────────────────
123
124
 
124
- @mcp.tool()
125
+ @mcp.tool(title="Search Entities", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
125
126
  async def search_entities(
126
127
  q: Optional[str] = None,
127
128
  naics: Optional[str] = None,
@@ -159,7 +160,7 @@ async def search_entities(
159
160
  return json.dumps(data, indent=2, default=str)
160
161
 
161
162
 
162
- @mcp.tool()
163
+ @mcp.tool(title="Get Expiring SAM Registrations", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
163
164
  async def get_entities_expiring(
164
165
  within_days: int = 60,
165
166
  state: Optional[str] = None,
@@ -186,7 +187,7 @@ async def get_entities_expiring(
186
187
  return json.dumps(data, indent=2, default=str)
187
188
 
188
189
 
189
- @mcp.tool()
190
+ @mcp.tool(title="Get Entity", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
190
191
  async def get_entity(uei: Optional[str] = None, cage_code: Optional[str] = None) -> str:
191
192
  """Get one SAM entity's full registration record by UEI or CAGE code, the same
192
193
  question, two different keys, so this is one tool, not two.
@@ -208,7 +209,7 @@ async def get_entity(uei: Optional[str] = None, cage_code: Optional[str] = None)
208
209
 
209
210
  # ─── Partners (teaming) ────────────────────────────────────────────────────
210
211
 
211
- @mcp.tool()
212
+ @mcp.tool(title="Search Partners", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
212
213
  async def search_partners(
213
214
  naics: Optional[str] = None,
214
215
  agency: Optional[str] = None,
@@ -247,7 +248,7 @@ async def search_partners(
247
248
 
248
249
  # ─── Company-contact resolver ──────────────────────────────────────────────
249
250
 
250
- @mcp.tool()
251
+ @mcp.tool(title="Get Company Decision-Maker Contact", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
251
252
  async def get_company_contact(uei: str) -> str:
252
253
  """Resolve a vendor's REAL decision-maker contact, SAM registration agents
253
254
  (third-party filing services) are filtered out, so this is the actual point of
@@ -267,7 +268,7 @@ async def get_company_contact(uei: str) -> str:
267
268
 
268
269
  # ─── Contacts (buyer intel, contracting officers) ─────────────────────────
269
270
 
270
- @mcp.tool()
271
+ @mcp.tool(title="Search Contracting Officer Contacts", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
271
272
  async def search_contacts(
272
273
  name: Optional[str] = None,
273
274
  agency: Optional[str] = None,
@@ -293,7 +294,7 @@ async def search_contacts(
293
294
 
294
295
  # ─── Recompetes (also an early Capture signal; see Post-Award for the monitoring use) ──
295
296
 
296
- @mcp.tool()
297
+ @mcp.tool(title="Search Recompetes", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
297
298
  async def search_recompetes(
298
299
  naics: Optional[str] = None,
299
300
  agency: Optional[str] = None,
@@ -347,7 +348,7 @@ async def search_recompetes(
347
348
  return json.dumps(data, indent=2, default=str)
348
349
 
349
350
 
350
- @mcp.tool()
351
+ @mcp.tool(title="Get Recompete", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
351
352
  async def get_recompete(piid: str) -> str:
352
353
  """Get one recompeting/expiring contract by PIID, plus incumbent-vulnerability
353
354
  signals (cert-lapse, lone-holder, single-agency dependence) composed from the
@@ -6,9 +6,10 @@ import json
6
6
  from typing import Optional
7
7
 
8
8
  from ..core import mcp, _get
9
+ from mcp.types import ToolAnnotations
9
10
 
10
11
 
11
- @mcp.tool()
12
+ @mcp.tool(title="Check Exclusions for a Vendor", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
12
13
  async def check_exclusion(
13
14
  name: Optional[str] = None,
14
15
  uei: Optional[str] = None,
@@ -5,9 +5,10 @@ from __future__ import annotations
5
5
  import json
6
6
 
7
7
  from ..core import mcp, _get
8
+ from mcp.types import ToolAnnotations
8
9
 
9
10
 
10
- @mcp.tool()
11
+ @mcp.tool(title="Resolve DUNS to UEI", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
11
12
  async def resolve_identifier(identifier: str) -> str:
12
13
  """Resolve between legacy DUNS (9 digits, or 13-digit DUNS+4) and current UEI
13
14
  (12 alphanumeric), accepts either side, returns both plus the entity name.
@@ -7,11 +7,12 @@ import json
7
7
  from typing import Optional
8
8
 
9
9
  from ..core import mcp, _get
10
+ from mcp.types import ToolAnnotations
10
11
 
11
12
 
12
13
  # ─── Contracting Offices ──────────────────────────────────────────────────
13
14
 
14
- @mcp.tool()
15
+ @mcp.tool(title="Discover Offices", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
15
16
  async def discover_offices(
16
17
  naics: str,
17
18
  sort: str = "biggest",
@@ -39,7 +40,7 @@ async def discover_offices(
39
40
  return json.dumps(data, indent=2, default=str)
40
41
 
41
42
 
42
- @mcp.tool()
43
+ @mcp.tool(title="Get Office Profile", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
43
44
  async def get_office_profile(office_code: str) -> str:
44
45
  """Get one contracting office's full buying profile: obligations, competition,
45
46
  set-aside lean, and every NAICS it buys.
@@ -59,7 +60,7 @@ async def get_office_profile(office_code: str) -> str:
59
60
 
60
61
  # ─── Federal Hierarchy ────────────────────────────────────────────────────
61
62
 
62
- @mcp.tool()
63
+ @mcp.tool(title="List Organizations", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
63
64
  async def list_organizations(
64
65
  type: Optional[str] = None,
65
66
  cgac: Optional[str] = None,
@@ -98,7 +99,7 @@ async def list_organizations(
98
99
  return json.dumps(data, indent=2, default=str)
99
100
 
100
101
 
101
- @mcp.tool()
102
+ @mcp.tool(title="Get Organization", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
102
103
  async def get_organization(organization_id: int) -> str:
103
104
  """Get one federal organization's full record, with its parent, immediate children,
104
105
  and full ancestor chain (root department down to immediate parent) all included in
@@ -117,7 +118,7 @@ async def get_organization(organization_id: int) -> str:
117
118
  return json.dumps(data, indent=2, default=str)
118
119
 
119
120
 
120
- @mcp.tool()
121
+ @mcp.tool(title="Get Organization Relationships", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
121
122
  async def get_org_relationships(organization_id: int, direction: str = "children") -> str:
122
123
  """Get JUST an organization's immediate children or its ancestor chain, without the
123
124
  rest of the record (get_organization already includes both if you need everything).
@@ -138,7 +139,7 @@ async def get_org_relationships(organization_id: int, direction: str = "children
138
139
 
139
140
  # ─── Procurement Forecasts ────────────────────────────────────────────────
140
141
 
141
- @mcp.tool()
142
+ @mcp.tool(title="Search Forecasts", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
142
143
  async def search_forecasts(
143
144
  source: Optional[str] = None,
144
145
  agency: Optional[str] = None,
@@ -193,7 +194,7 @@ async def search_forecasts(
193
194
 
194
195
  # ─── NAICS / Market Pulse ─────────────────────────────────────────────────
195
196
 
196
- @mcp.tool()
197
+ @mcp.tool(title="Find NAICS Codes", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
197
198
  async def find_naics_codes(
198
199
  sector: Optional[str] = None,
199
200
  prefix: Optional[str] = None,
@@ -236,7 +237,7 @@ async def find_naics_codes(
236
237
  return json.dumps(data, indent=2, default=str)
237
238
 
238
239
 
239
- @mcp.tool()
240
+ @mcp.tool(title="Get NAICS Leaderboard", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
240
241
  async def get_naics_leaderboard(board: str, limit: int = 30) -> str:
241
242
  """Browse curated, ranked NAICS market leaderboards, a fixed set of named rankings,
242
243
  distinct from find_naics_codes' open filtered search.
@@ -256,7 +257,7 @@ async def get_naics_leaderboard(board: str, limit: int = 30) -> str:
256
257
  return json.dumps(data, indent=2, default=str)
257
258
 
258
259
 
259
- @mcp.tool()
260
+ @mcp.tool(title="Get NAICS Market", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
260
261
  async def get_naics_market(code: str) -> str:
261
262
  """Get the federal market profile for one NAICS code: spending, competition,
262
263
  set-aside leverage, top buyers, and top incumbents.
@@ -278,7 +279,7 @@ async def get_naics_market(code: str) -> str:
278
279
  return json.dumps(data, indent=2, default=str)
279
280
 
280
281
 
281
- @mcp.tool()
282
+ @mcp.tool(title="Get NAICS Positioning", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
282
283
  async def get_naics_positioning(code: str) -> str:
283
284
  """Get the language and set-aside makeup for a NAICS code's SOLICITATION side: the
284
285
  phrase vocabulary contracting officers actually use in notices, the set-aside share
@@ -297,7 +298,7 @@ async def get_naics_positioning(code: str) -> str:
297
298
  return json.dumps(data, indent=2, default=str)
298
299
 
299
300
 
300
- @mcp.tool()
301
+ @mcp.tool(title="Get NAICS Simplified-Acquisition Breakdown", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
301
302
  async def get_naics_simplified_acquisition(code: str) -> str:
302
303
  """Get the award-value breakdown for a NAICS code over the last 12 months of FPDS
303
304
  prime awards: counts of micro / simplified-acquisition / above-SAT awards, and which
@@ -319,7 +320,7 @@ async def get_naics_simplified_acquisition(code: str) -> str:
319
320
  return json.dumps(data, indent=2, default=str)
320
321
 
321
322
 
322
- @mcp.tool()
323
+ @mcp.tool(title="Get NAICS Competition", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
323
324
  async def get_naics_competition(code: str) -> str:
324
325
  """Get how contested a NAICS market is, over the whole FPDS prime-award market: offers
325
326
  received per award, single-bidder share, top place-of-performance states, award-volume
@@ -5,9 +5,10 @@ import json
5
5
  from typing import Optional
6
6
 
7
7
  from ..core import mcp, _get
8
+ from mcp.types import ToolAnnotations
8
9
 
9
10
 
10
- @mcp.tool()
11
+ @mcp.tool(title="Search Opportunities", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
11
12
  async def search_opportunities(
12
13
  naics: Optional[str] = None,
13
14
  psc: Optional[str] = None,
@@ -79,7 +80,7 @@ async def search_opportunities(
79
80
  return json.dumps(data, indent=2, default=str)
80
81
 
81
82
 
82
- @mcp.tool()
83
+ @mcp.tool(title="Get Opportunity", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
83
84
  async def get_opportunity(notice_id: str) -> str:
84
85
  """Fetch a single contract opportunity by its notice_id.
85
86
 
@@ -90,7 +91,7 @@ async def get_opportunity(notice_id: str) -> str:
90
91
  return json.dumps(data, indent=2, default=str)
91
92
 
92
93
 
93
- @mcp.tool()
94
+ @mcp.tool(title="Recent Opportunity Changes", annotations=ToolAnnotations(readOnlyHint=True, destructiveHint=False, idempotentHint=True, openWorldHint=True))
94
95
  async def recent_changes(
95
96
  since: str,
96
97
  limit: int = 100,
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "govconapi-mcp"
7
- version = "0.2.1"
7
+ version = "0.2.2"
8
8
  description = "MCP server for GovCon API: 53 tools spanning the federal contracting lifecycle (market research, opportunity discovery, capture and teaming, bid and proposal, award and compliance) for Claude Desktop, Claude Code, Cursor, Zed, or any local MCP client."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -2,17 +2,18 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.govconapi/govconapi",
4
4
  "title": "GovCon API",
5
- "description": "US federal procurement data for agents: SAM.gov opportunities, FPDS contract awards, SAM entities, FFATA subawards, exclusions, and full-text search inside solicitation attachments.",
5
+ "description": "53 tools over US federal procurement: SAM.gov, FPDS awards, entities, subawards, exclusions.",
6
6
  "repository": {
7
7
  "url": "https://github.com/govconapi/govconapi-mcp",
8
8
  "source": "github"
9
9
  },
10
- "version": "0.2.1",
10
+ "websiteUrl": "https://govconapi.com/mcp",
11
+ "version": "0.2.2",
11
12
  "packages": [
12
13
  {
13
14
  "registryType": "pypi",
14
15
  "identifier": "govconapi-mcp",
15
- "version": "0.2.1",
16
+ "version": "0.2.2",
16
17
  "transport": {
17
18
  "type": "stdio"
18
19
  },
File without changes
File without changes
File without changes
File without changes