uk-tenders-mcp
You can find, filter, score, and research UK public-sector tenders and companies using read-only MCP tools.
Search open UK public-sector tenders across Find a Tender and Contracts Finder, with optional filters for days, limit, source, regions, and keywords; results are deduplicated, exclude expired deadlines, and include buyer, value, deadline, days_left, and source_url.
List available bid profiles and the value ranges each profile covers.
Score tenders as GO, MAYBE, or NO-GO against a bid profile, returning score reasons, buyer, value, deadline, days_left, and source_url; NO-GO results can be included on request.
Look up a UK company on Companies House by company number, returning status, incorporation date, SIC codes, insolvency history, overdue-accounts status, and source_url.
Click on "Deploy Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@uk-tenders-mcpscore this week's tenders for rail and civils, show GOs with links"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
uk-tenders-mcp
Read-only MCP tools that let an AI agent find this week's UK public-sector tenders across Find a Tender and Contracts Finder, score them against a bid profile, and check a company on Companies House. Every result has a source link, a buyer, a value and a deadline.
Built by Ahmed Osman as a public, cleaned-up version of the tender-intelligence work I run in production. It uses public data only.
What a run looks like
A live run on 4 Nov 2026, last 7 days, rail_civils profile:
Sources: Find a Tender 445 open tenders, Contracts Finder 41 (cross-listed duplicates merged)
Scored: 471 GO 13 MAYBE 3 NO-GO 455 in about 6 seconds
GO 13 Roads North of the Thames - Bridge Bearings £500,000 8 days left
GO 13 Samlesbury Enterprise Zone - Foul Water Drainage £2,000,000 22 days left
GO 12 HS2 Saltley Viaduct gabion wall £1,000,000 8 days left
GO 11 Lime Tree Wood landslip repair works £1,440,000 29 days left
...Version 0.1 read Contracts Finder alone and found 21 tenders in 3 days, none of them a GO. Since the Procurement Act 2023, most notices go to Find a Tender, which carries about ten times as many open tenders in a typical week.
Related MCP server: koko-call-mcp
Tools
Tool | What it's for | Returns |
| "What tenders are out for bridge works in London this week?" "What else has this buyer got out?" | Open notices from both sources, soonest deadline first, filtered by keyword, region and buyer |
| "Which of this week's tenders are worth bidding for?" | GO and MAYBE first with score, reasons, buyer, value, deadline and days left; NO-GO counted but hidden unless asked |
| "What can you score against?" | Bid profiles and the value range each one bids in |
| "Is this subcontractor still active? Any insolvency history?" | Status, SIC codes, insolvency history, overdue accounts |
Every result carries source_url, and every tender result says which sources answered.
Design decisions
Two sources, one list. Both sites publish OCDS. Notices on both are merged into one record, and the Find a Tender record is kept because it carries more fields. It is marked
also_listed_on: ["Contracts Finder"].Fast despite a slow API. Find a Tender pages with a cursor, so one long window is about 25 sequential calls a week. The window is split into one-day slices fetched in parallel (4 at a time), which brings a week down from about 40 seconds to about 6. Results are cached for 10 minutes, so a search followed by a score costs one fetch.
Only live tenders. Find a Tender mixes every notice type: awards, plans, cancellations. Only tender and tender-update notices are kept, the newest release per procurement wins, cancelled procurements are dropped, and so are tenders whose deadline has passed.
Partial results say so. If one source fails, the other still answers and the result says which one failed. Find a Tender rate-limits heavy use (HTTP 429), so requests back off and retry, honouring
Retry-After; if some day slices still fail, the days that answered are kept and the result reportsmissing_days. A partial result is cached for 1 minute rather than 10, so the next call tries again.Task-shaped, read-only and capped. Four tools that match what people ask.
daysandlimitare clamped, and descriptions are trimmed, so one call cannot flood the model's context.Grounded by design. A missing company returns
found: falserather than a guess. The server tells the agent to cite sources and never fill gaps from memory.Explainable scoring. Rules, not a black box: keywords, anchor terms, exclusions, works CPV codes, the buyer's own works/services/goods label, value range, SME suitability and time left to bid. Every verdict lists its reasons.
How scoring decides
Rule | Effect |
Excluded term (rolling stock, catering, cleaning services…) | NO-GO |
No anchor term anywhere | NO-GO |
Anchor only in passing: not in the title, no works CPV, fewer than two keywords | NO-GO |
Value more than 10x the profile's ceiling | NO-GO: programme-scale, worth a look only as a subcontractor |
Study, system or supply signals with no works CPV or works category | NO-GO |
Keywords | +2 each, up to +6 |
Works CPV (4520, 4522-4524, 45111) / related services CPV (71311, 71312) | +4 / +2 |
Buyer classes it as works / goods | +2 / -3 |
Value in range / out of range | +2 / -3 |
Suitable for SMEs | +1 |
Closes in under 7 days | -2 |
GO at 7 or more, MAYBE at 4 or more.
Bugs the live data caught
Each of these came from a real run, became a labelled eval case and a regression test, and cannot come back silently:
"Platform" (3 Nov). "Candidate Assessment Services" scored MAYBE because "platform" matched an online assessment platform. Now only rail-specific phrases count.
"Station" (4 Nov). An ambulance station refurbishment and a bus station cleaning contract came through, because bare "station" was an anchor term.
Passing mentions (4 Nov). A town festival scored GO on "track record" and a note about site drainage. Now an anchor must be in the title, or backed by a works CPV or two keywords, and "track" only counts in rail phrases.
Programme scale (4 Nov). A £450m rail works framework scored GO on keywords and CPV alone. Notices above 10x the profile ceiling are now NO-GO with a subcontracting note.
Regions (4 Nov). National frameworks showed as "North East" because buyers tick every region and the first was taken. Now all regions are kept, and labelled "England-wide" or "UK-wide" where that applies.
Evaluation
eval/run_eval.py scores a labelled set and prints a confusion matrix. It fails if accuracy drops below 80%, or if any NO-GO case is scored GO, because a false GO wastes bid effort.
Profile: rail_civils Cases: 33 Accuracy: 94% False GO: 0
expected \ got GO MAYBE NO-GO
GO 8 0 0
MAYBE 1 5 0
NO-GO 0 1 18How to read it honestly:
13 cases were written alongside the first rules. 18 are real Find a Tender notices that I labelled by hand and used while tuning this version, so they show the rules behave as intended, not that they generalise.
The 2 misses are held-out: real notices scored after the rules were frozen, which I disagreed with on review. An asbestos refurbishment of a market scores GO (I'd say MAYBE), and a materials-and-tools supply framework scores MAYBE (I'd say NO-GO). They stay in the file as open misses rather than being tuned away.
On the fresh 4 Nov run, I agreed with 14 of the 16 tenders the tool flagged GO or MAYBE.
The next step is labels from real bid and pass decisions, with this rules version as the baseline any learned model has to beat.
Quick start
git clone https://github.com/atz1man/uk-tenders-mcp
cd uk-tenders-mcp
uv sync --extra dev # or: pip install -e ".[dev]"
uv run pytest # 34 tests, no network needed
uv run python eval/run_eval.py # scoring evaluationCompanies House lookups need a free API key from the Companies House developer hub. Copy .env.example to .env, or set COMPANIES_HOUSE_API_KEY in your MCP client config. The tender sources need no key.
Use it in Claude Desktop
Add this to claude_desktop_config.json:
{
"mcpServers": {
"uk-tenders": {
"command": "uv",
"args": ["--directory", "/absolute/path/to/uk-tenders-mcp", "run", "uk-tenders-mcp"],
"env": { "COMPANIES_HOUSE_API_KEY": "your-key" }
}
}
}Then ask: "Score this week's tenders for rail and civils in the North West and show me the GOs with deadlines and links."
Project layout
src/uk_tenders_mcp/
server.py MCP server, the four tools, caching and filters
sources.py Find a Tender and Contracts Finder (OCDS), merging, Companies House
scoring.py bid profiles and GO / MAYBE / NO-GO rules
models.py typed records (Pydantic)
tests/ 34 tests, all offline via a mocked HTTP transport
eval/ labelled set (including real notices) and evaluation script
.github/ CI: tests and evaluation on every pushLimits and next steps
Two profiles (
rail_civils,fire_safety). Profiles are plain data, so adding one is a few lines plus labelled cases.Cross-source matching uses title and buyer, so a notice retitled between sites will appear twice.
Notices with no deadline are kept, and marked with
days_left: null.No write tools, by design. Anything that acts, such as registering interest, would be a separate server, off by default, with human approval.
Built with
Python 3.11+, the MCP Python SDK (v2), httpx, Pydantic, pytest. Licensed MIT.
Available Tools
4 toolsget_companyARead-only
Look up a UK company on Companies House by its company number (for example 00445790).
Returns status, incorporation date, SIC codes, insolvency history and whether accounts are overdue, with a source_url. Use before stating anything about a company's status; never infer it.
| Name | Required | Description | Default |
|---|---|---|---|
| company_number | Yes |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint, and an output schema exists, yet the description still adds value: it identifies the live external authority, advertises the substantive fields (status, incorporation date, SIC codes, insolvency history, overdue accounts) and the source_url that enables citation. It omits edge-case behavior such as what happens for an unknown or malformed company number, which keeps it below a 5.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Two tight sentences: the lookup definition with example comes first, then the returned fields, then the usage constraint. Every clause carries information and nothing is repeated from the schema or annotations.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
Annotations cover the safety profile and the output schema covers return structure, so the description only needs to add selection and input-format guidance, which it does. The one gap is failure behavior for invalid or not-found company numbers, which an agent might reasonably need before committing to a call.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0% — the schema only says 'Company Number' as a bare string — so the description must compensate, and it does by giving the concrete example '00445790', conveying zero-padded numeric format. It does not state whether the leading zeros or any prefix are mandatory, so it falls short of complete semantics.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource (look up a UK company), names the authoritative source (Companies House), and the lookup key (company number). This is clearly distinct from the sibling tools list_profiles, search_tenders and score_tenders, so an agent can select it without opening the schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Explicitly instructs 'Use before stating anything about a company's status; never infer it', which is a concrete when-to-use rule plus a prohibition on guessing. It does not name an alternative tool for the case where the company number is unknown, so it stops just short of full routing guidance.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
list_profilesARead-only
List the bid profiles that score_tenders can use, with what each one covers.
| Name | Required | Description | Default |
|---|---|---|---|
No parameters | |||
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint=true and openWorldHint=true, so the safety profile is covered. The description adds a hint about result content ("with what each one covers"), but says nothing about ordering, caching, or whether the profile set is fixed — left to the output schema, which exists.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
A single sentence with no filler. The core action leads, and the qualifying clause about score_tenders follows immediately, so nothing needs trimming or reordering.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a no-parameter, read-only listing tool with an output schema and readOnly annotations, the description covers what it does and why it matters in the score_tenders workflow. The only mild gap is that it does not say what a "profile" contains or whether invoking it has side effects on profiling state.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
There are zero parameters, so there is no parameter meaning to convey; the empty schema is self-explanatory. No misdirection about inputs, hence the baseline 4 rather than a 5, which would require the description to add something about input handling.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
The description states a specific verb and resource ("List the bid profiles") and immediately ties the resource to the sibling tool that consumes it ("that score_tenders can use"), so an agent can tell it apart from search_tenders and score_tenders without opening any schema.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
It gives clear usage context: call this to discover the valid profile inputs before/instead of guessing at score_tenders. It stops short of explicit exclusions or step ordering (e.g., "call this first if you don't know a profile id"), so it is strong context rather than a full when/when-not rule set.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
score_tendersARead-only
Score this week's open tenders GO / MAYBE / NO-GO against a bid profile, with reasons.
Use when the user asks which tenders are worth bidding for. Call list_profiles for valid names.
Searches Find a Tender and Contracts Finder. Returns GO then MAYBE, highest score first, at most
limit (1-50), each with buyer, value, deadline, days_left, reasons and a source_url. NO-GO
results are counted but left out unless include_no_go is true.
| Name | Required | Description | Default |
|---|---|---|---|
| days | No | ||
| limit | No | ||
| profile | No | rail_civils | |
| regions | No | ||
| include_no_go | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations only declare readOnlyHint and openWorldHint; the description adds real context beyond that — it names the two external corpora searched (Find a Tender and Contracts Finder), the result ordering (GO then MAYBE, highest score first), the hard cap of 50, and the non-obvious behavior that NO-GO results are counted but omitted unless include_no_go is set.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Front-loaded with purpose, then trigger, then source/return behavior — a sensible progression with no filler sentences. Slightly dense in the final sentence, where return fields and the NO-GO exception are packed together.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
An output schema exists, so enumerating GO/MAYBE fields is a bonus rather than a requirement. However, for a five-parameter, zero-coverage schema the description should account for every input, and `regions` is never mentioned anywhere, leaving an agent unable to use it confidently.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema coverage is 0%, so the description must carry the load. It documents limit's range (1-50), the exact effect of include_no_go, and how to obtain valid profile values, but leaves `regions` completely unexplained and only implies `days` through the phrase "this week's" despite a default of 7.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ("Score this week's open tenders") plus the exact output classification (GO / MAYBE / NO-GO against a bid profile, with reasons). The scoring/ranking framing makes it clearly distinct from search_tenders, and it names list_profiles as a dependency.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives an explicit trigger ("Use when the user asks which tenders are worth bidding for") and a prerequisite workflow step ("Call list_profiles for valid names"). It does not explicitly say when to prefer search_tenders instead, so the exclusion half of the guidance is missing.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_tendersARead-only
Find open UK public-sector tenders published or updated in the last days days (1-30).
Searches Find a Tender and Contracts Finder together, removes notices listed on both, and drops
tenders whose deadline has passed. Optional filters: keywords (any match in title or description)
and regions (for example ["London", "South East"]). Returns at most limit (1-50), soonest
deadline first, each with buyer, value, deadline, days_left and a source_url to cite.
| Name | Required | Description | Default |
|---|---|---|---|
| days | No | ||
| limit | No | ||
| source | No | all | |
| regions | No | ||
| keywords | No |
Output Schema
| Name | Required | Description |
|---|---|---|
No output parameters | ||
TDQS
Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?
Annotations already declare readOnlyHint and openWorldHint, and the description adds real behavior: it queries Find a Tender and Contracts Finder together, de-duplicates notices listed on both, drops expired tenders, and returns results soonest-deadline-first. The main omission is that the `source` parameter can narrow to a single registry, which contradicts the flat claim that it always searches both.
Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.
Is the description appropriately sized, front-loaded, and free of redundancy?
Three compact sentences, front-loaded with scope, then filters, then return shape — no filler. The closing clause listing returned fields is mildly redundant given an output schema exists, but it is short and orients the agent before the call.
Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.
Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?
For a five-parameter read-only search with an output schema and safety annotations already in place, the description covers scope, filtering, de-duplication, expiry handling, ordering, and result caps. Only the `source` filter and default values are left unstated, which is a small gap relative to everything else it supplies.
Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.
Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?
Schema description coverage is 0%, so the description must carry the load, and it does add value by supplying ranges the schema lacks (`days` 1-30, `limit` 1-50) and by giving a concrete regions example. However, it never explains the `source` enum (all/find_a_tender/contracts_finder) and does not mention that `days` defaults to 7 and `limit` to 20, leaving one of five parameters and all defaults undocumented.
Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.
Does the description clearly state what the tool does and how it differs from similar tools?
States a specific verb and resource ('Find open UK public-sector tenders') plus the freshness window and both underlying sources, so an agent knows exactly what corpus is being queried. It is unmistakably distinct from the sibling tools (list_profiles, get_company, score_tenders), which operate on different objects.
Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.
Does the description explain when to use this tool, when not to, or what alternatives exist?
Gives clear context for when this applies: open tenders only, published or updated within `days`, with expired deadlines dropped. It marks keywords and regions as optional filters, but never states when to prefer this over score_tenders or what to do if zero results come back.
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.
4 tool updates
v0.2.0- First observed
get_company - First observed
list_profiles - First observed
score_tenders - First observed
search_tenders
TDQS
Scored across 4 tools
Each tool targets a distinct step in the workflow: browsing profiles, finding tenders, ranking them, and checking a company. The only soft overlap is between search_tenders and score_tenders, since both query the same sources and return similar fields, but the descriptions clearly separate 'find tenders' from 'which are worth bidding for'.
All four tools follow a clean verb_noun snake_case pattern (list_profiles, search_tenders, score_tenders, get_company). The verb choice is also semantically accurate (list vs search vs score vs get), so names are both consistent and predictable.
Four tools is lean but each has a clear role in a coherent pipeline: profiles -> search -> score, plus an external company lookup. It sits at the low end of the comfortable range; a tender-detail or source-listing tool would round out the surface without bloat.
Core discovery and qualification are covered, but there is no way to fetch a full tender notice (only summary fields with a source_url), no profile creation/editing, and get_company requires a company number with no name-based lookup. These gaps force agents to leave the server or ask the user for data mid-workflow.
Maintenance
Related MCP Connectors
UK public procurement data for AI agents: tenders, contracts, buyer and supplier profiles.
Government tender search for AI agents. UK, EU and US procurement opportunities.
B2B, GovCon & LegalTech MCP server for live RFPs, contract parsing, compliance & proposal tools.
Open UK and EU public tenders (Find a Tender, TED) with go/no-go screening. Paid tier via L402.
Related MCP Servers
- AlicenseAqualityBmaintenanceUK due diligence MCP server — Companies House, corporate research, compliance checks18169 PyPI3MIT
- AlicenseNot gradedqualityAmaintenanceRead-only MCP server for searching Japanese government procurement bid notices (官公需) from the SME Agency's KKJ portal. Includes AI ranking, PDF requirement extraction, and CSV/calendar export.49 npmApache 2.0

oro-intel-mcpofficial
AlicenseNot gradedqualityDmaintenanceUK public procurement data for AI agents. Tenders, contracts, buyer and supplier profiles over MCP and REST. 250 free credits.MIT- AlicenseAqualityCmaintenanceEnables AI agents to find, score, and monitor government contract opportunities across UK, EU, and US with AI-powered relevance scoring.2103 npm1MIT