public-tenders-es-eu
Provides normalized search over public-sector tender notices from TED, covering Spain and the rest of the EU. Supports filtering by country, keyword, CPV prefix, and publication recency, returning tender details and related links.
README
Public Tenders ES/EU
Normalized search over public-sector tenders published in TED (Tenders Electronic Daily, the EU's official procurement notice platform) -- covers Spain plus the rest of the EU. NEXUS candidate #16 -- manual build, not FORGE-generated, same manual-Cloud-Run-asset pattern as candidates #3/#4/#6/#8/#9/#13.
POST /search-public-tenders {"country": "ESP", "keyword": null, "cpv_prefix": null, "days_back": 30, "limit": 20}-- $0.01/call.- MCP tool
search_public_tendersat/mcp, same params -- currently free, see "Known limitations". GET /health,GET /.well-known/agent-card.json,GET /openapi.json(hasx-payment-info),GET /.well-known/402index-verify.txt(402index claim verification file).
Source: pivoted from the original brief, with explicit sign-off (2026-08-23)
The task brief named contrataciondelsectorpublico.gob.es/sindicacion/sindicacion_643/ (Spain's national
PLACSP ATOM sindication feed) as "ya grounded de la sesión anterior". Verified live at the start of this
session via two independent fetch paths (curl, WebFetch, with and without a browser User-Agent): 403
Forbidden, and the domain's own robots.txt is Disallow: / for the entire site -- a declared,
site-wide block, not a rate limit. That would very likely also block Cloud Run (a datacenter IP range,
the common target of this kind of WAF rule). Flagged to the user before writing any code (not silently
substituted) -- user chose to pivot to TED.
TED (Tenders Electronic Daily, api.ted.europa.eu) is the EU Publications Office's own official
public-procurement notice search API -- free, keyless, no documented auth. Verified live and field-by-field
this session (a third-party README got query-field names wrong on the first pass -- e.g. claimed
country-buyer where the real API only accepts buyer-country -- so every field/operator below was
confirmed against the real API's own responses, not trusted secondhand):
POST https://api.ted.europa.eu/v3/notices/searchwith a JSON body ofquery(expert-search string),fields(array of eForms field names to return),limit,scope("ACTIVE"or"ALL").- Query fields confirmed live:
publication-date>=YYYYMMDD(8-digit, no dashes),buyer-country=ESP(ISO-3166 alpha-3),FT~"phrase"(full-text),AND,SORT BY publication-date DESC. - Output fields confirmed live:
publication-number,notice-title(multilingual dict, keys likespa/eng),buyer-name(multilingual dict of lists),buyer-country,publication-date,classification-cpv(list of CPV codes),total-value(not present on every notice type -- e.g. qualification-system notices don't carry one, this is normal, not an error),notice-type,links(per-languagehtml/pdf/xmldetail URLs). - Real query tested:
buyer-country=ESPalone matched 26,314 notices in theACTIVEscope at the time of this check; apublication-date+buyer-countrycombo matched 1,058 in an 8-day window -- real, current volume, not a stale/empty endpoint.
TED already covers Spain (any tender above the EU publication threshold appears with buyer-country=ESP)
plus the rest of the EU, so it fits the "España/UE" framing of the original brief at least as well as the
blocked national sub-threshold feed would have -- arguably better, since it's EU-wide by construction and
not blocked. Scope difference from what the blocked source would have given: TED only carries notices
above the EU publication threshold (a real, non-trivial floor -- Spain's own sub-threshold tenders, the
ones PLACSP's national feed alone would have carried, are NOT in TED). This asset does not claim to cover
those; total_matching_notices and each result's source: "TED" field make the actual coverage explicit
to a buyer, not implied to be broader than it is.
State (db_constraint exception)
Every sibling manual asset (url-metadata-api, agent-verification-api, document-conversion-api,
new-x402-listings-feed, onchain-activity-index, x402-receipt-verifier) is fully stateless -- each call
hits its upstream fresh, no NEXUS-owned table beyond the standard traffic_events/revenue_events/
mcp_call_events telemetry trio. This is the first of the 8 manual/FORGE assets to need the case-by-case
exception: a new table, tenders_query_cache (Supabase migration add_tenders_query_cache_candidate16,
project ieduhdgfjdeffvzxvihf), read+written only by this asset.
Why: TED's own docs state there is no documented rate limit but ask callers to be reasonable with
request volume on a shared public resource. Caching identical repeat queries (same country/keyword/
cpv_prefix/days_back/limit, bucketed to the hour) for 1 hour is being a good citizen of that guidance, not
gratuitous state -- a cache hit is marked "cache_hit": true in the response so a buyer can see when this
happened.
This is NOT the state the original PLACSP-scraping design would have needed. That design (walking an
unfilterable ATOM feed, tracking "already seen" tender IDs to detect deltas -- the same shape as
new-x402-listings-feed's catalog-walk cache) doesn't apply here: TED's own server-side
publication-date>= filtering already answers "what's new since X" without this asset needing to remember
anything about individual tenders itself. The only state kept is a short-lived response cache, not a tender
index.
RLS: anon gets SELECT + INSERT on tenders_query_cache only -- no UPDATE/DELETE granted, same
insert-append discipline as every other NEXUS telemetry table. A cache "overwrite" is really just a new row
with a newer cached_at; the lookup query always picks the freshest row within the TTL window. Stale rows
past the 1-hour TTL are never deleted -- unbounded growth over the 7-day probation window is negligible
(one row per distinct query per hour) and accepted for now; a real cleanup would need either a scheduled job
or a DELETE grant this asset doesn't otherwise need.
Deploy target: Cloud Run
Same pipeline as candidates #4/#3/#6/#8/#9/#13 -- see skills/infra-deploy-ops.
# 1. First deploy -- PUBLIC_DOMAIN not known yet, every real request 421s until step 2.
./scripts/deploy_cloud_run.sh public-tenders-es-eu manual_assets/public-tenders-es-eu
# 2. Grab the printed *.run.app URL, then (only if it differs from env-vars.deploy.yaml's guess):
gcloud run services update public-tenders-es-eu --region us-central1 --project nexus-505016 \
--update-env-vars PUBLIC_DOMAIN=<the-real-domain>
Known limitations (left unfixed on purpose -- CLAUDE.md SS3, no gate without evidence it's needed)
- MCP tool calls are not charged. Same in-process-call pattern as every other manual asset in this codebase.
- Only EU-publication-threshold tenders. See "Source" above -- Spain's sub-threshold national tenders are not in TED and not covered by this asset.
cpv_prefixis filtered client-side, after fetching. TED's query syntax doesn't support CPV-prefix matching server-side; this asset over-fetches (3xlimit, capped at 50) whencpv_prefixis set, then filters in-process. A caller with a very narrowcpv_prefixagainst a broaddays_backwindow could get fewer real matches than exist in TED's full result set beyond what was over-fetched.- Cache is a plain 1-hour TTL, not invalidated on new TED publications. A query repeated within the same
hour bucket can return results that are up to ~1 hour stale relative to TED. Disclosed via
cache_hitin every response. - No per-caller rate limiting. Fine for a 7-day disposable measurement window.
tenders_query_cacherows are never deleted. See "State" above.
Quality gate (2026-08-23, from design not retroactive)
Same 2-agent process as candidates #3/#4/#6/#8/#9/#13 (security lens; functional+quality+buyer-experience lens) -- results filled in below once run.
Measurement (candidate #16, 7-day window)
7-day window from 2026-08-23 (real deploy date) -> decision point 2026-08-30. Source of truth:
traffic_events/revenue_events/mcp_call_events (asset_name = 'public-tenders-es-eu'), not Cloud Run
logs. Day 7: if zero real traffic (filtering crawlers), pause/delete the Cloud Run service
(gcloud run services delete public-tenders-es-eu --region us-central1 --project nexus-505016).
推荐服务器
Baidu Map
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。