TED MCP — Tenders Electronic Daily (EU public procurement)
SaferSkills independently audited Mcp Ted Eu (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
TED (Tenders Electronic Daily) MCP — EU public procurement notices from national, regional and municipal buyers in every member state. ~700k notices a year; 1.3M construction notices on file. Keyless.
Part of Pipeworx — an MCP gateway connecting AI agents to 1857+ live data sources. This is an independent, unofficial integration — not affiliated with, endorsed by, or published by the upstream provider.
search_notices(query?, country?, category?, cpv?, date_from?, date_to?, open_only?, value_min?, value_max?, notice_type?, sort?, limit?, page?)— newest first by default. category takes plain English ("construction", "IT services", "medical equipment") and resolves to CPV; cpv takes the code if you have it.
get_notice(publication_number) — one notice with its full buyer-written description.notice_id / id accepted as aliases.
cpv_lookup(term) — plain-English category → CPV code, or what a CPV code covers.ted_search_awards(query?, winner?, buyer?, country?, winner_country?, category?, cpv?, date_from?, date_to?, value_min?, value_max?, sort?, limit?, page?)— contract-AWARD notices only: who won, for how much. Every winner on the notice is returned (lots and consortium members), with winner_countries and decision_date where published.
ted_supplier_history(supplier, country?, category?, cpv?, date_from?, date_to?, limit?, page?)— everything a named economic operator has won, newest first, with a summary: awarded value by currency, top buyers, top CPV categories. Name matching is partial ("Siemens" covers every Siemens entity).
ted_buyer_profile(buyer, country?, category?, cpv?, date_from?, date_to?, limit?, page?)— everything a named contracting authority has awarded, with top winners and top categories. Use the authority's own-language name ("Ville de Paris", not "City of Paris").
None. The public search endpoint answers unauthenticated.
Note that GET /notices/{id} is NOT keyless — it 400s with "Missing Authorization header" — so get_notice fetches through the public search endpoint filtered on publication-number instead. Same data, no key.
Each notice is flattened out of TED's raw shape:
| field | notes |
|---|---|
title / title_language | English where TED has it (it usually does — all 24 languages are published), otherwise the notice's own language, and the language is always stated |
title_original | the buyer's own title, in their language, when it differs |
buyer, buyer_country, buyer_city | contracting authority |
value, currency | buyer-entered and unvalidated by TED. Not always EUR — Czech awards come back in CZK, Hungarian in HUF. Sorting by value surfaces the typos first |
deadline | deadline for receipt of requests |
winner | on award notices (can-standard) |
notice_type | cn-standard call for tenders · can-standard award · pin-only prior information |
url | the notice on ted.europa.eu |
The envelope carries total, partial and sorted_by. `partial: true` means TED timed out and returned an incomplete result set — the count and total are lower bounds, not an answer.
query searches the full notice via TED's FT operator, not just the title. It escalates:
It stops at the first attempt that finds anything and reports which in match_strategy. This matters because ~ phrase-matches: the old title-or-description phrase search returned zero for every ordinary multi-word query real callers sent — "backup disaster recovery business continuity", "data center colocation IaaS PaaS", "GRC risk management compliance governance" were all 0 while the AND-ed forms return real notices.
Write OR between two words to hedge a spelling — fuze OR fuse artillery becomes (fuze OR fuse) AND artillery, because AND-ing both spellings asks for the one notice that contains both.
There is deliberately no any-word fallback. It was measured and cut: it answered "fuze OR fuse artillery ammunition" with 42 notices led by printed matter. A zero with an explanation beats a page of things you did not ask for.
The award tools search notice-type IN (can-standard can-social can-desg can-tran) — the four contract-award subtypes. Legacy pre-eForms award forms are mapped into these by TED, so coverage reaches back years (2022 alone has ~296k can-standard notices), 2.8M+ award notices in total. can-modif (modification of an existing contract) and veat (intent to award) are outside the default set — filter search_notices by notice_type if you want those.
Two caveats every award response states, because they are the two things a caller will otherwise get wrong:
published on national portals and never appear — a supplier with no TED awards may still win plenty of smaller public work.
unvalidated by TED. Summaries sum by currency — there is no cross-currency grand total, because that would be a made-up number.
winner-name upstream is a per-language map of arrays carrying every winner (one per lot, consortium members, duplicates included). The award tools dedupe and return the full list — taking only the first, as a single winner string would, silently drops co-winners.
GET /v3/notices/search is 405.reads like an auth wall but isn't.
DESC`. Without it the result order is not recency, and a 2016 prior-information notice was the top hit for German construction tenders.
notice-title carries all 24 official languages andbuyer-name a map of arrays. Handing them back raw gives a caller a Latvian title for a German tender, and costs ~4KB of duplicated title per notice.
notice-title is TED's generated"Country – CPV label – buyer title" summary. Asking for a field that doesn't exist returns nothing rather than erroring, so a typo'd field name looks like missing data.
with QUERY_UNSUPPORTED_FIELD_VALUE, which reads as "found nothing". This pack validates the division first and names the valid ones.
YYYY-MM-DD) and come back stamped with a UTC offset.https://api.ted.europa.eu/v3/notices/search — POST, JSON expert-query body. Docs: https://docs.ted.europa.eu/api/
The endpoint enumerates all 1,830 supported field names in its 400 message when you ask for an unsupported one — the cheapest way to find a field without guessing.
Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
{
"mcpServers": {
"ted-eu": {
"url": "https://gateway.pipeworx.io/ted-eu/mcp"
}
}
}tools/list at https://gateway.pipeworx.io/ted-eu/mcp returns the tools in the table above plus the shared Pipeworx meta-tools — ask_pipeworx, discover_tools, search_within, remember/recall and the rest of the gateway-wide set. So the tool count you see is larger than this table: a single-pack endpoint currently lists roughly 30 shared tools alongside the pack's own. The connection's initialize response states its exact scope, and is the authoritative answer for a given day.
This is deliberate, not multiplexing by accident. The meta-tools are what let a scoped connection answer a question this pack does not cover — via ask_pipeworx, which routes across the whole catalog — without you adding a second MCP server. There is currently no way to mount a pack endpoint without them; if the extra schemas cost you more context than the routing is worth, connect to the full gateway once rather than to several pack endpoints.
Or connect to the full Pipeworx gateway to get every pack's tools listed directly, instead of just this one's:
{
"mcpServers": {
"pipeworx": {
"url": "https://gateway.pipeworx.io/mcp"
}
}
}Both URLs reach the same gateway and the same 1857+ data sources. The only difference is which pack's tools are listed directly; ask_pipeworx reaches all of them from either one.
curl -X POST https://gateway.pipeworx.io/v1/tools/ted_eu_search_notices \
-H 'Content-Type: application/json' \
-d '{"category":"construction","country":"DEU","limit":25}'No account needed for the first calls. Inspect any tool: GET https://gateway.pipeworx.io/v1/tools/ted_eu_search_notices. Find one: POST https://gateway.pipeworx.io/v1/tools/search_packs with {"query":"..."}.
@pipeworx/mcp-ted-eu is a local stdio bridge to the Pipeworx gateway: your MCP client starts it with npx, and it forwards every call to the same https://gateway.pipeworx.io/ted-eu/mcp endpoint shown above. It needs network access; there is no offline mode.
{
"mcpServers": {
"ted-eu": {
"command": "npx",
"args": ["-y", "@pipeworx/mcp-ted-eu"]
}
}
}Without a key it calls anonymously, capped at 50 calls a day. A free account raises that to 200 a day: get a key at https://pipeworx.io/signup and add "env": { "PIPEWORX_API_KEY": "<your key>" } to the server entry. The key is sent as a Bearer token, the same way a hosted connection sends it, so calls are metered and billed exactly as they are there.
It lists only this pack's tools by default. Set PIPEWORX_META_TOOLS=1 to also list ask_pipeworx and the other gateway-wide tools.
Behind an HTTPS proxy: Node's built-in fetch does not read HTTPS_PROXY by default. Newer Node releases honour it when NODE_USE_ENV_PROXY=1 is set; check your Node version's documentation.
Instead of calling tools directly, you can ask questions in plain English — this works on the pack endpoint above as well as on the full gateway:
ask_pipeworx({ question: "your question about Ted Eu data" })The gateway picks the right tool and fills the arguments automatically.
MIT
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.