跳到正文
DEV Community· Italo Armenti·· 3 小時前AI 評分59

如何用 MCP、LangChain 和 n8n 按 CPV 代碼為 AI 智能體設定 TED 招標提醒

EU tender alerts by CPV code for your AI agent (MCP, LangChain, n8n)

AI 導讀

作者展示如何用其開源 MCP 伺服器,配合 LangGraph 智能體或 n8n 工作流程,按 CPV 代碼搜尋 TED 招標並建立新公告提醒。本地伺服器無需 API key、免費運行並提供 36 個工具;在 30 October 2026 的一次查詢中,24 條公告符合條件(展示 5 條),端到端耗時 0.75 s。

正文

If you sell IT services, construction, consulting or anything else to the public sector in Europe, the tenders worth knowing about are published in TED (Tenders Electronic Daily), the EU's official journal for public procurement. TED has a free search API, but turning it into "tell me every morning about new Finnish IT tenders" still means:

  • learning the eForms field names,
  • handling multilingual fields,
  • paginating,
  • dropping award notices that have no deadline,
  • remembering which notices you've already seen.

This post shows one way to do it with a small open-source MCP server. You can use it from Claude Desktop or Cursor, from a LangGraph agent, or from an n8n workflow. All numbers below come from runs on 30 October 2026.

Disclosure: I wrote the tools used here. The server is MIT-licensed and runs locally for free. A hosted version also exists on Apify, priced per event (details below).

1. Run the MCP server locally (no API key)

git clone https://github.com/itulo/eu-nordic-open-data-mcp.git
cd eu-nordic-open-data-mcp
npm install

Add it to your client config (claude_desktop_config.json, .cursor/mcp.json, Cline, …):

{
  "mcpServers": {
    "eu-nordic-open-data": {
      "command": "node",
      "args": ["/absolute/path/to/eu-nordic-open-data-mcp/src/index.js"]
    }
  }
}

The server exposes 36 tools. For tenders, the relevant ones are ted_search_tenders (search) and ted_new_tenders_alert (only notices you haven't seen yet). Now ask your assistant:

"Find open TED contract notices for CPV 72000000 (IT services) from Finnish buyers published in the last week, with deadlines."

The tool call behind that question is:

{ "cpvCodes": ["72000000"], "countries": ["FI"], "maxResults": 5 }

On 30 Oct 2026 this returned 24 matching notices (5 shown), and the call took 0.75 s end to end on my machine. One record, trimmed:

{
  "publicationNumber": "756345-2026",
  "noticeTypeLabel": "Contract notice",
  "title": "Finland – Software package and information systems – HRM- ja palkkajärjestelmän hankinta",
  "buyerNames": ["Espoon kaupunki"],
  "cpvCodes": ["48000000", "72000000"],
  "procedureType": "open",
  "tenderDeadline": "2026-11-06",
  "estimatedValue": { "amount": 8000000, "currency": "EUR" },
  "xmlUrl": "https://ted.europa.eu/en/notice/756345-2026/xml",
  "pdfUrl": "https://ted.europa.eu/en/notice/756345-2026/pdf",
  "attribution": "Source: TED - Tenders Electronic Daily (ted.europa.eu), © European Union, 1998-2026. Reused under the European Commission reuse policy (Decision 2011/833/EU); changes: fields selected, normalised and deduplicated."
}

A few details matter if you build this yourself:

  • The CPV filter is hierarchical. Pass the division (72000000) and TED matches notices tagged with any code under it.
  • Filter by notice type. By default the tool keeps contract notices and calls for competition, so award notices (can-*, no deadline) don't clutter an alert.
  • Not every contract notice has a deadline. Restricted procedures and dynamic purchasing systems often have no single one. Above, 2 of the 5 had tenderDeadline: null, so don't treat null as an error.
  • Use the API's own xml/pdf links. The HTML notice pages sit behind a bot challenge for automated clients. The /xml and /pdf links returned plain 200s in my tests.
  • Keep the attribution. Each record carries an attribution and licence field. TED notices are freely reusable under Commission Decision 2011/833/EU, provided you credit the source and say what you changed.

2. The same tools in a LangGraph agent

langchain-mcp-adapters turns the stdio server into LangChain tools:

import asyncio
from langchain_mcp_adapters.client import MultiServerMCPClient
from langchain.agents import create_agent

client = MultiServerMCPClient({
    "eu_nordic_open_data": {
        "command": "node",
        "args": ["/absolute/path/to/eu-nordic-open-data-mcp/src/index.js"],
        "transport": "stdio",
    }
})

async def main():
    tools = await client.get_tools()  # 36 tools
    agent = create_agent(
        model="openai:gpt-4.1-mini",  # any tool-calling model
        tools=tools,
        system_prompt="Answer with EU/Finnish open data. Always quote the attribution field.",
    )
    result = await agent.ainvoke({"messages": [
        {"role": "user", "content": "Which Finnish IT tenders (CPV 72000000) close in the next 14 days?"}
    ]})
    print(result["messages"][-1].content)

asyncio.run(main())

Runnable versions are in examples/langchain/:

  • mcp_tools_smoke.py needs no keys at all and calls the TED tool directly.
  • mcp_langgraph_agent.py is the agent above.
  • apify_actors_tool.py uses ApifyActorsTool with the hosted actor, if you'd rather not run Node.js.

3. A daily alert in n8n

The repo also ships an importable n8n workflow, eu-tender-alert-by-cpv-email-slack.json. It works like this:

  1. A Schedule trigger fires at 07:00 on weekdays.
  2. The Apify node runs the itulo/ted-tender-feed actor with alertMode: true. The actor keeps state between runs, so each run returns only notices it hasn't returned before.
  3. A Code node builds an HTML table and a Slack message that include the TED attribution. If nothing is new, it returns no items, so you get no empty emails.
  4. A Send Email node delivers the digest. A Slack node is included but disabled.

Input for the actor node:

{
  "cpvCodes": ["72000000", "48000000"],
  "countries": ["FI", "SE", "EE"],
  "alertMode": true,
  "alertStateKey": "n8n-daily-it-tenders",
  "collapseByProcedure": true
}

I tested it on n8n 2.45.7 with the Apify community node 0.6.4. One test run (without the email step) fetched 5 notices and built the digest in about 6.5 s, most of which was the actor starting up. The repo has two more workflows: Finnish company register events (PRH) into Google Sheets, and screening a sheet of names against the EU financial sanctions list.

Cost of the hosted path: the actor is pay-per-event, $0.01 per run start plus $0.003 per notice returned. A separate test run returned 20 notices in 3.7 s of actor runtime, which works out to about $0.07. The local MCP server costs nothing beyond your own machine.

What else is in the box

The same server covers other official sources, each with its licence attached to every record:

  • EU and ECB: EU consolidated financial sanctions screening (fuzzy name matching, an informational aid, not legal advice), EU VIES VAT validation, and ECB reference FX rates and macro series.
  • Finland: PRH company register events (company-level data only), Fingrid grid data (free key needed), FMI weather warnings and Digitraffic port calls (CC BY 4.0).

Feedback, bug reports and PRs are welcome: https://github.com/itulo/eu-nordic-open-data-mcp.

Data sources: TED, © European Union, 1998-2026, reused under Commission Decision 2011/833/EU; changes: fields selected, normalised and deduplicated.

來源:DEV Community · dev.to