bandiradar-mcp
Enables monitoring Italian public funding opportunities, normalizing them into a canonical model, and ranking them against a company profile with a two-stage matcher.
README
BandiRadar
Open-source engine that monitors Italian public funding opportunities (public tenders, grants, incentives), normalizes them into one canonical model, and ranks them against a company profile with a two-stage matcher.
Runs offline, zero secrets · 3 live key-less sources · optional LLM Stage-2 · MIT
Features
- Two-stage matcher — a deterministic prefilter + LLM relevance scoring, with a zero-secrets offline heuristic fallback (the LLM is optional).
- 3 live, key-less sources — TED (EU), incentivi.gov.it (national), Regione Lombardia (regional); plus a bundled ANAC OCDS mapper.
- ANAC historical-benchmark enrichment — value/volume/seasonality context per CPV division, optionally attached to matches.
watchmonitor loop (new/amended deltas) + JSON/RSS export.- CLI + MCP server — drive it from a shell or from an AI agent.
- Fully offline on
--sample— every demo and the whole test suite run with no network and no secrets.
Table of contents
- Quickstart
- Works across company types
- How it works
- Stage 2: LLM scoring
- Sources
- Intelligence and benchmarks
- Watch and export
- AI agents (MCP)
- Status
- Open core vs Pro
- Roadmap
- Contributing
- License · Data and licenses
Quickstart
30 seconds, offline, no keys:
uv sync
uv run bandiradar match --profile data/profiles/mayai.yaml --sample
Real output on the bundled sample data:
3 matching opportunities for 'MayAI':
#1 score 76 [closing_soon] Fornitura di licenze software e servizi cloud GDPR-compliant
issuer: Regione Lazio (Lazio) deadline: 2026-06-08
why: CPV prefix match (depth 2); capability overlap: cloud, digitalizzazione, gdpr, processi, software; within profile value range
https://example.invalid/anac/notice/ocds-bandi-0002
#2 score 72 [open] Servizi di analisi dati e machine learning per la PA centrale
issuer: Ministero dell'Economia e delle Finanze (Lazio) deadline: 2026-12-01
why: CPV prefix match (depth 2); capability overlap: automazione, data, dati, learning, machine; within profile value range
https://example.invalid/anac/notice/ocds-bandi-0004
#3 score 66 [open] Servizi di sviluppo e manutenzione software gestionale comunale
issuer: Comune di Roma Capitale (Lazio) deadline: 2026-09-15
why: CPV prefix match (depth 2); capability overlap: dati, software; within profile value range
https://example.invalid/anac/notice/ocds-bandi-0001
Add --json for machine-readable output. These ANAC sample URLs are synthetic
(example.invalid) — see Status.
Works across company types
BandiRadar isn't tuned to one company — it runs any profile against every source.
bandiradar batch runs the bundled profile suite and compares results. Real
output on --sample (offline heuristic):
PROFILE # TOP MATCH (score) BY SOURCE
------------------------------------------------------------------------------------
Consulenza Strategica S.r.l. 8 Servizi di consulenza organizza… (72) anac:2 incentivi:5 ted:1
Costruzioni Lombarde S.r.l. 2 LAVORI DI FORMAZIONE MANUTENZIO… (56) lombardia:1 ted:1
Trattoria & Bottega S.r.l. 2 Manifestazione d'interesse per … (55) incentivi:2
Manifattura Esempio S.r.l. 3 Fornitura di macchinari industr… (76) anac:1 incentivi:2
MayAI 5 Fornitura di licenze software e… (76) anac:3 incentivi:1 ted:1
MedForniture Lombardia S.r.l. 3 FORNITURA DI DISPOSITIVI PER EN… (76) lombardia:3
Studio Associato Commercialis… 4 Fornitura di licenze software e… (50) anac:1 incentivi:2 ted:1
The suite spans distinct Italian SME segments — AI/software (MayAI),
manufacturing, medical-devices (Lombardy), accounting, construction,
hospitality/retail (keyword-driven, no CPV), and consultancy. Counts are real
matches on the tiny bundled sample; a segment can legitimately show few hits when
the sample doesn't cover it. Keyword/capability overlap ignores a curated list of
generic procurement filler (lavori, servizi, fornitura, manutenzione, …),
so matches reflect sector-bearing terms rather than boilerplate.
uv run bandiradar batch --sample # human comparison table
uv run bandiradar batch --sample --json # machine-readable
uv run bandiradar batch --sample --csv out.csv
With an LLM key the same table gets sharper scores and ranking — see Stage 2: LLM scoring.
How it works
┌─────────┐ ┌───────────┐ ┌────────┐ ┌────────┐ ┌──────────┐
sources│ INGEST │──▶│ NORMALIZE │──▶│ STORE │──▶│ MATCH │──▶│ DELIVER │
└─────────┘ └───────────┘ └────────┘ └────────┘ └──────────┘
fetch raw→canonical sqlite 2 stages cli/mcp
+ dedupe (dashboard=pro)
Italian public funding is scattered across dozens of fragmented sources.
BandiRadar pulls opportunities from those sources, maps each into a single
canonical Opportunity model, and surfaces the few that matter for a given
company — with reasons and deadlines. Matching is two stages:
- Deterministic prefilter — a pure, explainable function (region/geo, value
range, deadline, exclusions, and a relevance signal: the opportunity's CPV
codes prefix-matched against the profile's
cpv_interests, or a keyword overlap). Cuts thousands of rows to dozens. No LLM, no network. - LLM relevance — scores the survivors
0–100with reasons, matched capabilities, eligibility flags, and risk notes. It ships with a zero-secrets offline fallback (a deterministic heuristic), so the whole thing runs in CI and in agent dev loops without any API key.
A thin core service layer orchestrates the pipeline; the CLI and MCP server are
shells over it with no business logic. Storage is stdlib SQLite with change
detection: a changed content_hash bumps the version, marks the row
amended, and makes it re-notifiable (a tender rettifica should re-notify).
See ARCHITECTURE.md for the full design.
Stage 2: LLM scoring
Stage 2 is off by default (zero secrets → deterministic offline heuristic). To enable real LLM relevance scoring:
uv sync --extra anthropic # or: --extra openai (optional SDKs)
# in .env (gitignored):
# BANDIRADAR_LLM_PROVIDER=anthropic # or openai
# ANTHROPIC_API_KEY=sk-ant-... # or OPENAI_API_KEY=...
# BANDIRADAR_LLM_MODEL=... # optional; defaults to a cheap Haiku-class model
.env is auto-loaded — no manual export. With no key (or no SDK), the
engine falls back to the heuristic, so CI and offline runs need nothing. When the
LLM path is active you'll see a one-time scoring via anthropic:<model> on stderr.
The LLM is more discriminating than the heuristic — same prefiltered set, sharper
scores/ranking (bandiradar batch --sample):
heuristic LLM (anthropic, Haiku)
Costruzioni (real construction) 56 92 ← genuine match promoted
Costruzioni (IT doc-digitization) (kept ~36) 15 ← cross-sector match demoted
MayAI top match software-licenses ML/data tender (88)
Studio comm. software-licenses 50 25 ← weak fit penalized
MedForniture medical devices 76 92 ← strong sector fit held
Sources
| Source | What it delivers | Live fetch |
|---|---|---|
incentivi |
incentivi.gov.it (MIMIT) — the national catalogue of business incentives / grants (kind="incentive"), national and regional. The grant side, and the source a digital SME profile actually matches. |
✅ Wired — the official IODL open-data export, no API key. |
ted |
TED — Tenders Electronic Daily, the EU's portal for above-threshold, OPEN, biddable tenders (includes large Italian public tenders). | ✅ Wired — anonymous, no API key. |
lombardia |
Regione Lombardia — regional / sub-threshold public tenders (kind="tender"), from the Osservatorio Regionale (Socrata SODA). The first regional source; carries CPV, value, and province. |
✅ Wired — Socrata SODA, no API key. |
anac |
ANAC / PNCP open-contracting (OCDS) data — primarily historical / award records, a separate analytics track rather than open calls. | ⏳ Mapper + fixture done; live fetch() not wired. |
uv run bandiradar fetch --source incentivi --sample # offline, bundled real capture
uv run bandiradar match --profile data/profiles/mayai.yaml --source incentivi --sample
uv run bandiradar match --profile data/profiles/mayai.yaml --source ted --sample
The --sample fixtures are real captures (data/fixtures/*.json).
incentivi exercises the canonical superset on the grant side — no CPV, a funding
range, and an eligibility text the matcher reads. TED carries above-threshold
contracts often far larger than a micro-SME's range, so a small profile matches
only the few that fit — which is why incentive/national/regional sources matter too.
A regional example (data/profiles/medtech_lombardia.yaml, a Lombardy
medical-devices distributor) matches open Lombardy tenders, while the Lazio-only
MayAI profile correctly drops them — regional filtering in action:
uv run bandiradar match --profile data/profiles/medtech_lombardia.yaml --source lombardia --sample
# -> 3 open medical-device tenders (region match, CPV 33*, within value range)
uv run bandiradar match --profile data/profiles/mayai.yaml --source lombardia --sample
# -> No matching opportunities (Lazio profile, Lombardy bandi dropped on region)
Source data licensing is consolidated under Data and licenses.
Intelligence and benchmarks
A separate track (not the matcher) ingests ANAC historical OCDS data — awarded public contracts — and computes compact benchmarks per CPV-division × region: award value distribution (median, p25/p75, min/max), volume, seasonality (by year), and distinct-supplier counts.
uv run bandiradar benchmarks build --sample # offline, bundled real capture
uv run bandiradar benchmarks show --cpv 45 # region falls back to national
Real output on the bundled sample:
CPV division 45 [national]
awards (count): 22 distinct suppliers: 21
value EUR: median 470,768 p25 121,649 p75 1,594,879
range: 68,117 – 11,369,083
by year: 2022:22
Honest data caveats:
- The dataset is retrospective — awarded contracts (> €40k), not open calls.
- It has awards + suppliers but no tenderers list, so we cannot derive a "number of bidders". We derive value/volume/seasonality/supplier counts only.
- The release addresses carry city + postal code but no region/NUTS, so
benchmarks are national-only for now (
regionstaysNone); the model and aggregation already support regional buckets for when a region-bearing source arrives.
Enrichment: benchmarks in the matcher
The benchmarks are optional matcher enrichment (injected like the score
cache — the matcher works fine without them). Add --with-benchmarks to match
and each scored opportunity gets, for its CPV division, a historical-context
reason plus a value-sanity risk note when it declares an estimated value:
uv run bandiradar benchmarks build --sample
uv run bandiradar match --profile data/profiles/mayai.yaml --source ted --sample --with-benchmarks
#1 score 44 [open] Italia – Servizi di gestione dati – SERVIZIO DI GESTIONE ... ROCCA IMPERIALE (CS)
why: CPV prefix match (depth 2); capability overlap: dati; eu scope; ANAC history (CPV 72, national): 8 awards, median EUR 104,326, p25-p75 EUR 71,619-183,410
https://ted.europa.eu/en/notice/-/detail/376324-2026
Value-sanity triggers when the opportunity declares a value — e.g. on the ANAC
sample (--source anac --with-benchmarks):
# Servizi di analisi dati e machine learning per la PA centrale
why: ...; ANAC history (CPV 72, national): 8 awards, median EUR 104,326, p25-p75 EUR 71,619-183,410
risk: estimated value EUR 250,000 is above the historical p75 EUR 183,410 for this category
Enrichment is append-only on a copy of the cached match: the cache always
stores the bare match, so repeated runs never double-append. The
search_opportunities MCP tool takes the same with_benchmarks flag. ANAC data
licensing is under Data and licenses.
Watch and export
watch is a monitor loop: it fetches, applies the storage change-detection, and
reports only matches whose opportunity is new or amended since the last
watch run (a per-profile marker is persisted; --since overrides it).
# 1st run: all current matches are "new"; a 2nd run reports nothing new
uv run bandiradar watch --profile data/profiles/mayai.yaml --source incentivi --sample
# write a feed instead of printing
uv run bandiradar watch --profile data/profiles/mayai.yaml --source incentivi --sample --rss ~/feed.xml
export is the full, non-delta dump of current matches (--json or --rss PATH).
Scheduling is your cron — this is open-core (single-user/local). For example:
0 8 * * * cd /path/to/bandiradar && uv run bandiradar watch --profile mine.yaml --rss ~/feed.xml
Managed delivery (WhatsApp/email/alerts), scheduling SaaS, and multi-tenant
hosting live in bandiradar-pro.
AI agents (MCP)
BandiRadar ships a thin MCP server (FastMCP), so you can drive it from Claude. Six tools:
list_sources · fetch_opportunities · search_opportunities ·
score_opportunity · get_matches · get_profile
uv run bandiradar mcp
Registration and an offline example session are in docs/MCP.md.
Status
- ✅ Offline, zero-secret — every demo above and the whole test suite run with no network and no API key.
- ✅ 3 live key-less sources —
incentivi,ted,lombardia.--samplekeeps them offline against recorded real captures. - ✅ Stage-2 LLM scoring is wired and working (optional); with no key it transparently uses the offline heuristic — a proxy, not real semantic relevance.
- ⏳ Live ANAC/PNCP fetch is pending — the mapper + fixture are done and
tested, but
fetch()raisesNotImplementedErroruntil the open-data endpoint is confirmed against current docs. The ANAC sample URLs are synthetic.
Open core vs Pro
Anything a single user can run locally is open. Anything managed,
multi-client, or a delivery channel lives in the private bandiradar-pro,
which depends on this package — never the reverse.
bandiradar (this repo, MIT) |
bandiradar-pro (private) |
|
|---|---|---|
| Engine (ingest/normalize/match) | ✅ | imports it |
Source framework (Source interface + registry) |
✅ | |
| Reference adapters (ANAC, incentivi.gov.it) | ✅ | |
| Two-stage matcher (incl. offline fallback) | ✅ | |
| CLI + MCP server | ✅ | |
| Dashboard (web UI) | ✅ | |
| Premium / regional source adapters | ✅ | |
| Delivery channels (WhatsApp, email, alerts) | ✅ | |
| Multi-tenant, managed hosting, scheduling SaaS | ✅ |
Roadmap
Shipped
- Canonical model +
Sourceframework + two-stage matcher (deterministic prefilter + LLM relevance with a zero-secrets offline fallback) + SQLite with change-detection + CLI + MCP server. - Live sources: TED (EU open tenders), incentivi.gov.it (national incentives), and Regione Lombardia (first regional source), all key-less; ANAC OCDS mapper bundled.
- Intelligence track: ANAC historical benchmarks + optional matcher
enrichment (
--with-benchmarks). watchmonitor loop (new/amended deltas) + JSON/RSS export.
Upcoming
- Live ANAC/PNCP fetch (confirm the open-data endpoint first).
- Embeddings-based prefilter; more community/regional source adapters (via the
Sourceframework — Lombardy is the first; other regions welcome). bandiradar-pro(private): dashboard, WhatsApp/email delivery, scheduling SaaS, multi-tenant hosting.
Contributing
Every source is fetch + a pure to_opportunities, plus a recorded fixture and
a test — adding one is a new file, no core changes. See
CONTRIBUTING.md and the add-a-source skill
(skills/add-a-source/) for the full copy-pasteable template; the playbook also
lives in CLAUDE.md ("How to add a new Source").
License
MIT © MayAI — see LICENSE.
Data and licenses
BandiRadar consumes public open data; each source keeps its own licence, which its operator requires you to honor:
- incentivi.gov.it (IODL 2.0) — published by the Ministero delle Imprese e del
Made in Italy under the
Italian Open Data License v2.0 (attribution
required). The live
incentivifetch hits the same open-data export endpoint the portal's own "Scarica dataset" button uses (no separate static file; the download is built client-side from that endpoint). - Regione Lombardia (CC0 1.0) — dataset
k6cb-4hbm(Bandi di gara — Osservatorio Regionale), via the dati.lombardia.it Socrata SODA API. - ANAC public contracts (CC BY 4.0) — via the Open Contracting Data mirror; the intelligence track streams the gzipped JSONL memory-safely.
Recommended Servers
playwright-mcp
A Model Context Protocol server that enables LLMs to interact with web pages through structured accessibility snapshots without requiring vision models or screenshots.
Magic Component Platform (MCP)
An AI-powered tool that generates modern UI components from natural language descriptions, integrating with popular IDEs to streamline UI development workflow.
Audiense Insights MCP Server
Enables interaction with Audiense Insights accounts via the Model Context Protocol, facilitating the extraction and analysis of marketing insights and audience data including demographics, behavior, and influencer engagement.
VeyraX MCP
Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.
graphlit-mcp-server
The Model Context Protocol (MCP) Server enables integration between MCP clients and the Graphlit service. Ingest anything from Slack to Gmail to podcast feeds, in addition to web crawling, into a Graphlit project - and then retrieve relevant contents from the MCP client.
Kagi MCP Server
An MCP server that integrates Kagi search capabilities with Claude AI, enabling Claude to perform real-time web searches when answering questions that require up-to-date information.
E2B
Using MCP to run code via e2b.
Neon Database
MCP server for interacting with Neon Management API and databases
Qdrant Server
This repository is an example of how to create a MCP server for Qdrant, a vector search engine.
Exa Search
A Model Context Protocol (MCP) server lets AI assistants like Claude use the Exa AI Search API for web searches. This setup allows AI models to get real-time web information in a safe and controlled way.