ted-mcp
Provides read-only access to TED (Tenders Electronic Daily), the EU's official public-procurement journal, via its keyless Search API v3. Enables searching and counting EU tender notices using filters such as keywords, countries, CPV codes, date ranges, and notice types, returning notice titles, buyers, countries, CPV codes, values, and links. Also supports raw expert queries and query-syntax validation.
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., "@ted-mcpHow many EU tenders mentioning NIS2 were published since 2026-01-01?"
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.
ted-mcp: EU public tenders for AI agents
Read-only MCP server for TED (Tenders Electronic Daily), the EU's official public-procurement journal. It uses TED's free, keyless Search API v3; no account or API key is needed.
Tools
Tool | What it does |
| Search with simple filters: keywords, countries, CPV codes, date range, notice type. Returns title, buyer, country, CPV, value, link |
| Count matches only (one tiny request). Compare two date windows to see if demand is rising |
| Raw TED expert query, for advanced use |
| Validate expert-query syntax without fetching notices |
All tools are marked read-only. Notice text is returned as third-party data, and the server tells the AI to treat it as data, not instructions.
Example prompts once connected:
"How many EU tenders mentioning NIS2 were published since 2026-01-01, vs the same period last year?"
"List open Romanian IT tenders (CPV 72000000) published this month."
"Find active tenders in DE, AT and NL mentioning SBOM or 'Cyber Resilience Act'."
Related MCP server: LexSocket MCP Server
1. First run: check it against the real API
python3 -m venv .venv && . .venv/bin/activate
pip install -e ".[dev]"
pytest -q # 20 tests, TED is mocked
# Real API smoke test (no key needed):
curl -s -X POST https://api.ted.europa.eu/v3/notices/search \
-H 'Content-Type: application/json' \
-d '{"query":"buyer-country=ROU AND publication-date>=20260901","fields":["publication-number","notice-title"],"limit":2}' | head -c 600Query syntax follows TED's official Expert Search help: single words unquoted (
FT ~ kubernetes), phrases quoted (FT ~ "cyber security"), and lists viaIN(buyer-country IN (ROU DEU)). Usecheck_queryto validate anything custom before running it.
2. Claude Desktop (stdio, local)
Settings → Developer → Edit Config, then add:
{
"mcpServers": {
"ted-tenders": {
"command": "/ABSOLUTE/PATH/ted-mcp/.venv/bin/ted-mcp"
}
}
}Windows path example: C:\\Users\\you\\ted-mcp\\.venv\\Scripts\\ted-mcp.exe. Restart Claude Desktop.
3. Claude Code
claude mcp add ted-tenders -- /ABSOLUTE/PATH/ted-mcp/.venv/bin/ted-mcp4. Remote connector (claude.ai web, mobile)
claude.ai custom connectors need a public HTTPS URL. HTTP mode refuses to start without a token.
export TED_MCP_TOKEN=$(openssl rand -hex 24) # save this in your password manager
docker build -t ted-mcp .
docker run -d --name ted-mcp --restart unless-stopped \
-e TED_MCP_TOKEN=$TED_MCP_TOKEN \
-e TED_MCP_ALLOWED_HOSTS=ted.example.com \
-p 127.0.0.1:8000:8000 ted-mcpPut TLS in front, for example with Caddy (automatic Let's Encrypt):
ted.example.com {
reverse_proxy 127.0.0.1:8000
}Then in claude.ai → Settings → Connectors → Add custom connector:
https://ted.example.com/mcp?token=YOUR_TOKENHealth check (no token needed): https://ted.example.com/healthz
Security notes
The token is compared in constant time; requests without it get
401.A token in a URL can end up in proxy logs. Keep access logs private and rotate the token if exposed. MCP clients that can send headers should use
Authorization: Bearer <token>instead.TED_MCP_ALLOWED_HOSTSenables DNS-rebinding protection. Set it to your domain.The container runs as a non-root user and exposes only port 8000 on localhost. The server makes outbound calls to TED only.
Built-in politeness: at most 2 concurrent requests and 0.4 s between calls, with retries on 429/5xx.
Configuration
Variable | Default | Purpose |
|
|
|
|
| HTTP bind address |
| none | Required for HTTP; minimum 24 characters |
| none | Comma-separated hostnames for DNS-rebinding protection |
| TED v3 search | Override for testing |
Data and licence
TED notice metadata is published by the EU Publications Office for free reuse, including commercial use. Keep "Source: TED (ted.europa.eu)" attribution when you republish results. This project is independent and not affiliated with the EU. Code: MIT.
Available Tools
4 toolscheck_queryBRead-onlyIdempotent
Validate TED expert-search syntax without returning notices.
| Name | Required | Description | Default |
|---|---|---|---|
| query | 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=true, idempotentHint=true, destructiveHint=false, and openWorldHint=true, covering the safety profile. The description adds the useful detail that it validates without returning notices, i.e. a validation-only result rather than data. It does not say what a failure/validity response looks like, but that is partly the output schema's job.
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 compact sentence that front-loads the action and resource, with every clause earning its place (the 'without returning notices' clause carries real information about output).
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 return-value explanation is not required. For a one-parameter validation tool this is close to adequate, but it omits any hint of how to interpret a validation result or what makes a query invalid. Adequate but with clear gaps.
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 schema carries no meaning for 'query'. The description compensates partially by identifying the expected format as 'TED expert-search syntax', which is genuinely useful semantics, but it gives no examples, constraints, or escaping rules for that syntax.
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+resource: validating TED expert-search syntax, with the added scoping note that it does not return notices. This clearly distinguishes it from the search_* and count_tenders siblings, which retrieve data rather than validate a query. It stops short of naming those siblings, but the purpose is unambiguous.
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?
There is no explicit when-to-use guidance or reference to alternatives. The implication that you run this before a search to pre-check syntax is inferable but never stated, and no conditions or exclusions are given.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
count_tendersARead-onlyIdempotent
Count matching notices without listing them (cheap demand signal).
Same filters as search_tenders. Defaults to scope ALL so date ranges count the archive. Tip: compare the same filters across two date windows to see if demand is rising.
| Name | Required | Description | Default |
|---|---|---|---|
| scope | No | ALL | |
| keywords | No | ||
| countries | No | ||
| cpv_codes | No | ||
| notice_types | No | ||
| published_since | No | ||
| published_until | 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 readOnly, idempotent, non-destructive and openWorld, so safety is covered. The description adds real behavioral value beyond that: it returns a count rather than a list, it is cheap to call, and scope defaults to ALL so date ranges span the archive. Return shape is covered by the output schema, so nothing critical is missing.
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-loads the core purpose in the first clause, then adds filter routing and a usage tip in two short sentences. The parenthetical and tip both earn their place, though the tip is slightly advisory rather than definitional.
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 7-parameter count tool with an output schema and full annotation coverage, the description supplies the key non-obvious facts: no listing, cheap, scope default ALL. The main residual gap is parameter-level detail, which it offloads to a sibling rather than stating outright.
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 parameter burden. It explains the scope default and its effect ('ALL so date ranges count the archive') and delegates the rest to search_tenders' filter set, which is a useful pointer but not a substitute for documenting keywords, countries, cpv_codes, notice_types, and the published_since/until format.
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 ('count matching notices') and immediately distinguishes it from the listing behavior of its sibling by saying 'without listing them'. The parenthetical '(cheap demand signal)' clarifies the intent behind the tool, so an agent understands both what it does and why it exists versus search_tenders.
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 routes the agent to search_tenders for the filter set ('Same filters as search_tenders') and gives a concrete usage pattern (compare the same filters across two date windows). It lacks an explicit when-not clause (e.g., 'do not use when you need the actual notices'), but the context is clear.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_noticesARead-onlyIdempotent
Run a raw TED expert-search query (advanced).
Example: 'FT ~ kubernetes AND buyer-country IN (DEU AUT) AND publication-date>=20260801 SORT BY publication-date DESC' fields: TED field names to return (defaults to a standard set). Unknown fields are rejected by TED. Use check_query first if unsure about syntax.
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | ||
| limit | No | ||
| query | Yes | ||
| scope | No | ACTIVE | |
| fields | No | ||
| language | No | ENG |
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 readOnly, idempotent, open-world and non-destructive, so the safety profile is covered. The description adds genuinely useful behavioral context beyond that: fields default to a standard set and unknown fields are rejected by TED, i.e. the query is validated and can fail on bad field names.
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-loads the purpose, then supplies one worked example and two short rules. No filler; the lowercase 'fields:' block is slightly unpolished but every sentence carries information.
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 return values need not be described, and the example query covers the hardest part of the syntax. However, the scope enum (ACTIVE/LATEST/ALL) governs which notices are searched and is left undefined, which is a material gap for a search tool.
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. It documents the required 'query' parameter with a concrete syntax example and explains 'fields' (default set, unknown fields rejected), but says nothing about page, limit, scope (ACTIVE/LATEST/ALL) or language, leaving four of six parameters unexplained anywhere.
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 — running a raw TED expert-search query — and the parenthetical '(advanced)' signals this is the low-level escape hatch rather than a structured search. It does not explicitly name search_tenders as the simpler alternative, so sibling differentiation is only implicit.
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?
"Use check_query first if unsure about syntax" gives an explicit routing rule to a named sibling for a specific condition. It stops short of stating when to use this raw query tool instead of search_tenders, which is the more consequential decision.
Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.
search_tendersARead-onlyIdempotent
Search EU public tenders with simple filters.
keywords: full-text terms, OR-combined (e.g. ["NIS2", "cybersecurity audit"]). countries: buyer countries, ISO alpha-3 or alpha-2 (e.g. ["ROU", "DE"]). cpv_codes: 8-digit CPV codes, e.g. 72000000 (IT services), 48000000 (software packages). published_since / published_until: YYYY-MM-DD. notice_types: e.g. ["cn-standard"] (contract notices) or ["can-standard"] (award notices). scope: ACTIVE (open now), LATEST, or ALL (archive). Returns total match count plus normalised notices (title, buyer, country, CPV, value, URL).
| Name | Required | Description | Default |
|---|---|---|---|
| page | No | ||
| limit | No | ||
| scope | No | ACTIVE | |
| keywords | No | ||
| language | No | ENG | |
| countries | No | ||
| cpv_codes | No | ||
| notice_types | No | ||
| published_since | No | ||
| published_until | 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, idempotentHint, openWorldHint and destructiveHint=false, so the safety profile is covered. The description adds value with the return shape ('total match count plus normalised notices (title, buyer, country, CPV, value, URL)') and per-param semantics, but says nothing about pagination behavior, rate limits, or how scope=ALL affects volume.
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?
One purpose sentence followed by a compact, front-loaded parameter list where every line adds new syntactic or format information. The structure is scannable; the only minor cost is that the list doubles as documentation for parameters the schema already names.
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, yet the description still usefully summarizes the return payload; the filter parameters that drive matching are well covered. The gaps are the unset pagination and language parameters, which an agent may need for multi-page retrieval but which are less critical than the filters.
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 carries the full burden and mostly succeeds: it explains OR-combining for keywords, ISO alpha-3/alpha-2 for countries, 8-digit CPV format with examples, YYYY-MM-DD dates, and concrete notice_type values. It leaves page, limit, and language undocumented, which keeps it short of a 5.
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 first line states a specific verb and resource ('Search EU public tenders') plus a scope qualifier ('simple filters'), which is clear enough for an agent to act on. However, it never distinguishes itself from siblings like search_notices or count_tenders, so an agent cannot tell which search tool is correct without opening the schemas.
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?
The phrase 'simple filters' and the scope enum explanations ('ACTIVE (open now), LATEST, or ALL (archive)') implicitly convey when this tool is appropriate, but there is no explicit when-not or alternative routing despite three closely related siblings (search_notices, count_tenders, check_query). Usage is inferable rather than stated.
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.1.0- First observed
check_query - First observed
count_tenders - First observed
search_notices - First observed
search_tenders
TDQS
Scored across 4 tools
The four tools have mostly distinct purposes: simple search, raw expert search, count-only, and syntax validation. search_tenders and search_notices overlap in domain but are differentiated by simple vs. advanced query modes, so misselection is possible but unlikely.
All tool names follow a consistent snake_case verb_noun pattern: search_tenders, search_notices, count_tenders, check_query. The naming is predictable and readable.
Four tools are well-scoped for a search-focused API: two search modes, a count utility, and a query validator. Each tool earns its place without redundancy or bloat.
The server covers discovery, advanced querying, counting, and query validation for EU tenders, which is strong for a read-only search domain. A minor gap is the absence of a direct notice-detail retrieval tool by ID, though returned URLs partially mitigate this.
Maintenance
Related MCP Connectors
Search EU public tenders across TED and 8 national portals. Monitor, match, and analyse procurement.
Search EU TED procurement notices by CPV, country, keyword, value, or type.
Search public procurement notices from 17 sources across Germany, the EU and the UK. Read-only.
TED MCP Server: Real-time EU public tenders access. https://www.lexsocket.ai/
Related MCP Servers
- AlicenseAqualityBmaintenanceExposes French and EU public procurement data (BOAMP + TED) as MCP tools for AI agents, enabling search for tenders, awards, and winner intelligence via typed filters.462 PyPIMIT
- AlicenseNot gradedqualityDmaintenanceEnables search and analysis of European public procurement tenders, including EU above-threshold (TED) and below-threshold from 11 national sources, with hybrid search and filtering.MIT
- AlicenseAqualityBmaintenanceEnables AI agents to find, score, and monitor government contract opportunities across UK, EU, and US with AI-powered relevance scoring.249 npm1MIT
- FlicenseAqualityCmaintenanceEnables searching and analyzing Polish public procurement tenders from BZP and TED, including filtering, tender details and timelines, offers and contract values, buyer/contractor profiles, CPV code lookup, and regional statistics, with optional AI-summary endpoints.121-