You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
La revisione 2026-07-28 della
specifica MCP è la più invasiva dalla nascita del protocollo: MCP diventa stateless (via
l'handshake initialize e le sessioni), arriva server/discover obbligatorio, gli header Mcp-Method/Mcp-Name diventano requisiti di conformità, e Roots/Sampling/Logging entrano in
deprecazione. In parallelo l'SDK Python mcp2.0.0 (rilasciato lo stesso giorno) implementa la
revisione e rinomina FastMCP → MCPServer, spostando la configurazione di trasporto dal
costruttore a run().
Questa issue è il tracking dell'allineamento dei 13 server MCP del repo, più una serie di
miglioramenti di qualità dei tool che oggi sono a metà strada.
Tipo: tracking / piano realizzativo. Deliverable: 5 PR indipendenti (Fase 0–3 + 5) con
sotto-issue collegate. La Fase 4 è bloccata da upstream.
Stato attuale (fotografia del repo)
13 pacchetti MCP, ~80 tool, tutti sullo stesso stampo:
pacchetto
tool
ToolAnnotations
tipo di ritorno
ckan-mcp-server
12
❌
dict
istat-mcp-server
10
❌
dict
opencoesione-mcp-server
8
❌
dict
openpnrr-mcp-server
6
❌
dict
maturity-mcp-server
5
❌
dict
opendata-mcp-server
5
❌
dict
centriditalia-mcp-server
5
✅
dict
ods-mcp-server
3
❌
dict
socrata-mcp-server
3
❌
dict
web-mcp
2
❌
dict
bdap-mcp-server
1
✅
dict
ispra-mcp-server
1
✅
dict
osm-mcp
19
❌
str (JSON serializzato a mano)
Punti strutturali:
Pin identico in 14 pyproject.toml: mcp>=1.2.0,<2.0 (mcp[cli] per osm-mcp).
server.py duplicato 13 volte: il diff fra ckan-mcp-server/src/ckan_mcp/server.py e ods-mcp-server/src/ods_mcp/server.py differisce solo per stringhe di log/nome/istruzioni.
Stesso build_server(), stesso /healthz, stesso dispatch stdio|streamable-http|sse.
Nessun server espone resources o prompts → capability annunciate = solo tools.
Nessuna autenticazione su nessun endpoint MCP HTTP; HOST=0.0.0.0, porte pubblicate su tutte
le interfacce in docker-compose.yml, e nessun TransportSecuritySettings (allowlist
Host/Origin) configurato da nessuna parte.
Ramo TRANSPORT=sse ancora presente in tutti i server.py (transport deprecato dal 2025-03-26, ora classificato Deprecated sotto la nuova feature-lifecycle policy).
Gli smoke test make mcp-stdio-* inviano {"jsonrpc":"2.0","id":1,"method":"tools/list"} senza initialize e senza _meta → non validi in era moderna.
osm-mcp usa MCP_TRANSPORT mentre gli altri 12 usano TRANSPORT.
Zero test di conformità protocollo: i test coprono i client HTTP (test_ckan_client.py, test_sdmx_client.py), non il livello MCP.
⚠️ Problema di sicurezza indipendente dalla spec
Il floor mcp>=1.2.0 lascia installabili versioni con 6 advisory HIGH già pubblicate. Nulla nel
repo impedisce a un build di risolvere mcp==1.2.0:
range vulnerabile
patch
advisory
< 1.9.4
1.9.4
DoS via validation error in FastMCP
< 1.10.0
1.10.0
unhandled exception in Streamable HTTP → DoS
< 1.23.0
1.23.0
DNS rebinding protection non attiva per default
>= 1.23.0, <= 1.27.1
1.27.2
task handler: un client accede/cancella i task di altri
<= 1.27.1
1.27.2
HTTP transport serve richieste di sessione senza verificare il principal autenticato
< 1.28.1
1.28.1
WebSocket transport senza validazione Host/Origin
Va chiuso anche se il resto del piano slitta (→ Fase 0).
Cosa cambia con 2026-07-28
Le parti che ci toccano:
MCP diventa stateless. Spariscono initialize / notifications/initialized e l'header Mcp-Session-Id. Ogni richiesta porta in _meta la propria io.modelcontextprotocol/protocolVersion + clientCapabilities; il server timbra serverInfo
in ogni risultato.
server/discover è obbligatorio (MUST): versioni supportate, capability, identità, instructions.
Ogni result ha resultType ("complete" | "input_required").
Header obbligatori sui POST: MCP-Protocol-Version, Mcp-Method, e Mcp-Name per tools/call/resources/read/prompts/get. Il server MUST validare header vs body →
altrimenti 400 + -32020 HeaderMismatch.
Rimossi: endpoint GET, resources/subscribe, ping, logging/setLevel, resumability SSE
(Last-Event-ID). Le notifiche di cambiamento passano da subscriptions/listen.
MRTR sostituisce ogni richiesta server→client (sampling, elicitation, roots): il server ritornaInputRequiredResult, il client ritenta con inputResponses.
ttlMs + cacheScope obbligatori su tools/list, prompts/list, resources/list, resources/read, resources/templates/list.
Deprecati: Roots, Sampling, Logging MCP-level, transport HTTP+SSE, OAuth DCR (→ Client ID
Metadata Documents).
tools/listSHOULD avere ordine deterministico (prompt cache); inputSchema/outputSchema
accettano qualsiasi keyword JSON Schema 2020-12.
Breaking changes dell'SDK Python v2 che ci colpiscono
Config trasporto spostata dal costruttore a run(): host, port, streamable_http_path, stateless_http, json_response, transport_security non sono più
argomenti di MCPServer(...) → MCPServer("x", port=9000) è un TypeError.
Tutti i campi wire in snake_case (tool.input_schema, result.is_error).
McpError → MCPError; sollevarlo dentro un tool ora è un errore di protocollo, non is_error=True.
httpx → httpx2, con TLS verificato via truststore (trust store del sistema) invece di certifi. mcp-types nuova dipendenza pin-esatta, opentelemetry-api obbligatoria.
Le funzioni def sincrone girano su worker thread; il lifespan Streamable HTTP gira una volta
all'avvio (non più per sessione/richiesta).
@mcp.tool() / @mcp.resource() / @mcp.prompt()restano invariati come firma → per server
decorator-based come i nostri il porting è per il 90% i due punti sopra.
Il vincolo che decide la strategia
agent-framework-core[all] 1.12.1 → mcp<2,>=1.24.0
opendata-backend usa agent_framework.MCPStreamableHTTPTool (factory.py:30,324) come client
MCP: non può installare mcp 2.x finché agent-framework non alza il cap.
Questo non blocca i server. L'SDK v2 serve entrambe le ere sulla stessa app: streamable_http_app() risponde sia all'initialize di un client 2025 sia alle richieste
per-request-_meta di un client 2026, senza flag né deployment separati. Quindi:
Server → mcp 2.x subito. Client (backend) → resta legacy e continua a funzionare.
Le due migrazioni sono disaccoppiate: è il cardine del piano.
Fase 0 — Hardening del pin e della rete
~0.5 gg · nessuna dipendenza · nessun cambio di comportamento. Chiude le 6 advisory.
14 pyproject.toml: mcp>=1.28.1,<2.0 (mcp[cli]>=1.28.1,<2.0 per osm-mcp) — compatibile
con il floor >=1.24.0 di agent-framework.
Attivare la DNS rebinding protection esplicitamente: TransportSecuritySettings(allowed_hosts=..., allowed_origins=...) da mcp.server.transport_security, valorizzata da env (MCP_ALLOWED_HOSTS/MCP_ALLOWED_ORIGINS).
Con HOST=0.0.0.0 la protezione automatica non scatta.
docker-compose.yml: porte MCP su 127.0.0.1:1808x:808x (oggi 13 endpoint MCP non
autenticati sono esposti su tutte le interfacce in dev). In prod l'overlay aes-infra le
tiene interne, ma il compose locale no.
pip-audit (o Dependabot) nel job ci.yml, per non riaprire il buco.
Fase 1 — Runtime MCP condiviso
~2 gg · prerequisito per fare le Fasi 2–3 una volta invece di tredici.
Nuovo pacchetto opendata-mcp-runtime/ — non in opendata_core/: l'invariante di CLAUDE.md
vieta FastMCP/FastAPI lì.
osm-mcp: l'istanza FastMCP a livello di modulo (osm-mcp/src/osm_mcp/server.py:12) passa a build_server() come gli altri — necessario per la Fase 3, dove host/port/ streamable_http_path non sono più argomenti del costruttore.
Dockerfile: copiare opendata-mcp-runtime/ accanto a opendata_core/, context = root (R1).
Fase 2 — Qualità dei tool
~2–3 gg · dopo la Fase 1. Paga subito sull'agente e prepara la spec nuova.
ToolAnnotations su tutti i ~80 tool. Nessun tool del repo muta stato (nessun create_*/update_*/delete_*): readOnlyHint=True, idempotentHint=True, openWorldHint=True (leggono portali esterni). Oggi ce l'hanno 3 server su 13.
title leggibile per tool e server (nuovo parametro posizionale in MCPServer), più icons / website_url.
osm-mcp: -> str → -> dict. 19 tool su 19 ritornano JSON dentro una stringa: il client
non riceve né outputSchema né structuredContent e il modello deve fare un parse. È l'unico
server fuori standard nel repo.
Ordine deterministico di tools/list (nuovo SHOULD): migliora l'hit rate della prompt
cache Anthropic, rilevante dato che resolve_provider di default è claude (R11).
outputSchema espliciti → budget di contesto. Il commento in istat-mcp-server/src/istat_mcp/tools.py:28 documenta che FastMCP duplica il payload in contentestructuredContent, raddoppiando il prompt, e per questo _MAX_PAYLOAD_CHARS
è tenuto a 80 KB. Con outputSchema dichiarato e structured_output controllato si serve solo
la forma strutturata e si alza il budget utile — guadagno diretto sui codelist tipo CL_ITTER107.
Paginazione a cursore dove oggi c'è solo limit (ckan_search_datasets, istat_list_dataflows, …): la spec ha nextCursor, noi tronchiamo.
Fase 3 — Migrazione SDK v2 / protocollo 2026-07-28
~3–4 gg · dopo la Fase 1. Con il runtime condiviso, il lavoro è concentrato in un punto.
Nel runtime (una volta):
from mcp.server import MCPServer; args di trasporto → run(transport=..., host=..., port=..., streamable_http_path=..., transport_security=...).
cache_hints= per popolare ttlMs/cacheScope, mappati sui TTL che già gestiamo (catalogo
SDMX 24h via SDMX_PERSISTENT_CACHE_TTL_SECONDS, classify 24h): cacheScope="public" per i
cataloghi, "private" per tutto ciò che dipende dall'utente.
server/discover e subscriptions/listen: serviti out-of-the-box da MCPServer, nulla da
scrivere — solo da verificare in test.
Nei 13 pacchetti (meccanico):
pyproject.toml: mcp>=2,<3; sse-starlette>=3 dove importato; starlette/uvicorn
restano.
httpx → httpx2solo dove l'oggetto viene passato all'SDK. opendata_core è condiviso
col backend e usa httpx: non toccarlo; i due pacchetti si installano affiancati.
Rinomina snake_case sui campi wire; McpError → MCPError.
⚠️Verificare ca-certificates nelle immagini (python:3.x-slim): httpx2 valida il TLS
contro il trust store di sistema via truststore, non contro certifi. Senza CA store usabile tutte le chiamate a ISTAT/CKAN/Overpass fallirebbero l'handshake. Fallback: SSL_CERT_FILE/SSL_CERT_DIR o verify=ssl_context esplicito. Rischio operativo n.1.
Audit di except httpx. e isinstance(..., httpx...): se httpx resta installato, un except httpx.ConnectError importa ancora ma non matcha più nulla (fallimento silenzioso).
Test e tooling:
Aggiornare i payload make mcp-stdio-*: tools/list nudo non è più valido in era moderna
(serve _meta con protocolVersion + clientCapabilities).
Nuovo target make mcp-discover-* che chiama server/discover.
Test di conformità per pacchetto usando il Client in-memory di v2 (Client(server),
nessun transport): asserisce server/discover, resultType, presenza di ttlMs/cacheScope,
annotations, ordine di tools/list.
CI: doppia era verificata — un client mode="legacy" e uno moderno contro la stessa app.
Fase 4 — Client-side (backend) — 🔒 BLOCCATA
~1–2 gg · bloccata da agent-framework-core[all] → mcp<2.
Aprire/seguire una issue upstream su agent-framework-core per il cap mcp<2.
Quando si sblocca: MCPStreamableHTTPTool → nuovo Client dell'SDK v2.
Benefici attesi: niente sticky session (13 server scalabili dietro round-robin), cache
client-side che onora ttlMs, e Mcp-Method/Mcp-Name come header ⇒ Traefik può fare
rate-limit e routing per-tool senza leggere il body.
Alternativa se serve prima: client MCP diretto su mcp 2.x per i path che non passano da
agent-framework (es. osm_map.py:109, che chiama osm-mcp).
Nel frattempo: nessuna azione richiesta — i server v2 servono il client legacy.
Fase 5 — Capability nuove (opzionale, alto valore prodotto)
~3–5 gg · dopo la Fase 3. Oggi il progetto usa solotools.
prompts: i workflow che oggi vivono come istruzioni hardcoded in config.py
(CKAN_INSTRUCTIONS, ISTAT_INSTRUCTIONS, …) sono di fatto prompt template. Esporli come prompts renderebbe opendata-mcp-server usabile da Claude Desktop con i percorsi
Esplora/Territorio/Maturità/Qualità pronti, e ridurrebbe la duplicazione che R5 ci
costringe a mantenere a mano su 5 template.
MRTR / elicitation per i tool che oggi indovinano: territory_resolve con comune ambiguo
può chiedere disambiguazione invece di scegliere.
⏸️ Estensione ufficiale Tasks (io.modelcontextprotocol/tasks) per harvest CKAN e batch
lunghi: l'SDK Python v2 non la implementa ancora → da rinviare.
Ordine e stima
#
Fase
Effort
Blocca / bloccata da
0
Pin + hardening rete
0.5 gg
—
1
Runtime MCP condiviso
2 gg
abilita 2, 3
2
Qualità tool
2–3 gg
dopo 1
3
SDK v2 + 2026-07-28
3–4 gg
dopo 1 · rischio TLS/httpx2
4
Client (backend)
1–2 gg
🔒 agent-framework mcp<2
5
resources / prompts
3–5 gg
dopo 3
Le Fasi 0, 1 e 2 hanno valore anche se la 3 slitta, e nessuna delle tre richiede coordinamento col
backend.
Rischi
TLS via truststore (Fase 3): il rischio più concreto. Da validare su un solo server prima di
propagare — web-mcp (2 tool) è il candidato per il PoC.
httpx/httpx2 non interscambiabili a runtime: un httpx.AsyncClient passato come http_client degrada in modo silenzioso invece di sollevare.
Doppia era in produzione: fino alla Fase 4 gireranno server 2026 con client 2025. Copertura
in CI obbligatoria (Fase 3).
Validazione più severa in v2: i risultati sono validati in uscita, quindi uno schema {} scritto a mano fallirebbe tools/list. I nostri tool sono tutti decorator-based → basso
rischio, ma va verificato.
Vincoli di processo (regole del repo)
R1 — Docker build context = root del repo: i Dockerfile della Fase 1 devono copiare opendata-mcp-runtime/ accanto a opendata_core/.
R3 — test via /tmp/oda-venv, per pacchetto durante l'iterazione.
R11 — la Fase 2 (ordine deterministico) interagisce con la prompt cache del provider risolto.
R12 — make lint && make test prima di ogni commit; un PR per fase.
R14 — le Fasi 2 e 5 cambiano capability visibili → aggiornare nello stesso PR la sezione
README e la copy della pagina UI interessata.
Invariante CLAUDE.md: no FastMCP in opendata_core/ → da cui il pacchetto runtime separato.
Contesto
La revisione
2026-07-28dellaspecifica MCP è la più invasiva dalla nascita del protocollo: MCP diventa stateless (via
l'handshake
initializee le sessioni), arrivaserver/discoverobbligatorio, gli headerMcp-Method/Mcp-Namediventano requisiti di conformità, e Roots/Sampling/Logging entrano indeprecazione. In parallelo l'SDK Python
mcp2.0.0 (rilasciato lo stesso giorno) implementa larevisione e rinomina
FastMCP→MCPServer, spostando la configurazione di trasporto dalcostruttore a
run().Questa issue è il tracking dell'allineamento dei 13 server MCP del repo, più una serie di
miglioramenti di qualità dei tool che oggi sono a metà strada.
Stato attuale (fotografia del repo)
13 pacchetti MCP, ~80 tool, tutti sullo stesso stampo:
ToolAnnotationsckan-mcp-serverdictistat-mcp-serverdictopencoesione-mcp-serverdictopenpnrr-mcp-serverdictmaturity-mcp-serverdictopendata-mcp-serverdictcentriditalia-mcp-serverdictods-mcp-serverdictsocrata-mcp-serverdictweb-mcpdictbdap-mcp-serverdictispra-mcp-serverdictosm-mcpstr(JSON serializzato a mano)Punti strutturali:
pyproject.toml:mcp>=1.2.0,<2.0(mcp[cli]perosm-mcp).server.pyduplicato 13 volte: ildifffrackan-mcp-server/src/ckan_mcp/server.pyeods-mcp-server/src/ods_mcp/server.pydifferisce solo per stringhe di log/nome/istruzioni.Stesso
build_server(), stesso/healthz, stesso dispatchstdio|streamable-http|sse.resourcesoprompts→ capability annunciate = solotools.HOST=0.0.0.0, porte pubblicate su tuttele interfacce in
docker-compose.yml, e nessunTransportSecuritySettings(allowlistHost/Origin) configurato da nessuna parte.
TRANSPORT=sseancora presente in tutti iserver.py(transport deprecato dal2025-03-26, ora classificato Deprecated sotto la nuova feature-lifecycle policy).make mcp-stdio-*inviano{"jsonrpc":"2.0","id":1,"method":"tools/list"}senza
initializee senza_meta→ non validi in era moderna.osm-mcpusaMCP_TRANSPORTmentre gli altri 12 usanoTRANSPORT.test_ckan_client.py,test_sdmx_client.py), non il livello MCP.Il floor
mcp>=1.2.0lascia installabili versioni con 6 advisory HIGH già pubblicate. Nulla nelrepo impedisce a un build di risolvere
mcp==1.2.0:< 1.9.4< 1.10.0< 1.23.0>= 1.23.0, <= 1.27.1<= 1.27.1< 1.28.1Va chiuso anche se il resto del piano slitta (→ Fase 0).
Cosa cambia con
2026-07-28Le parti che ci toccano:
initialize/notifications/initializede l'headerMcp-Session-Id. Ogni richiesta porta in_metala propriaio.modelcontextprotocol/protocolVersion+clientCapabilities; il server timbraserverInfoin ogni risultato.
server/discoverè obbligatorio (MUST): versioni supportate, capability, identità,instructions.resultType("complete"|"input_required").MCP-Protocol-Version,Mcp-Method, eMcp-Namepertools/call/resources/read/prompts/get. Il server MUST validare header vs body →altrimenti
400+-32020 HeaderMismatch.resources/subscribe,ping,logging/setLevel, resumability SSE(
Last-Event-ID). Le notifiche di cambiamento passano dasubscriptions/listen.ritorna
InputRequiredResult, il client ritenta coninputResponses.ttlMs+cacheScopeobbligatori sutools/list,prompts/list,resources/list,resources/read,resources/templates/list.Metadata Documents).
tools/listSHOULD avere ordine deterministico (prompt cache);inputSchema/outputSchemaaccettano qualsiasi keyword JSON Schema 2020-12.
Breaking changes dell'SDK Python v2 che ci colpiscono
FastMCP→MCPServer,mcp.server.fastmcp→mcp.server.mcpserver(import pathrimosso, non deprecato).
run():host,port,streamable_http_path,stateless_http,json_response,transport_securitynon sono piùargomenti di
MCPServer(...)→MCPServer("x", port=9000)è unTypeError.tool.input_schema,result.is_error).McpError→MCPError; sollevarlo dentro un tool ora è un errore di protocollo, nonis_error=True.httpx→httpx2, con TLS verificato viatruststore(trust store del sistema) invece dicertifi.mcp-typesnuova dipendenza pin-esatta,opentelemetry-apiobbligatoria.defsincrone girano su worker thread; il lifespan Streamable HTTP gira una voltaall'avvio (non più per sessione/richiesta).
@mcp.tool()/@mcp.resource()/@mcp.prompt()restano invariati come firma → per serverdecorator-based come i nostri il porting è per il 90% i due punti sopra.
Il vincolo che decide la strategia
opendata-backendusaagent_framework.MCPStreamableHTTPTool(factory.py:30,324) come clientMCP: non può installare
mcp2.x finché agent-framework non alza il cap.Questo non blocca i server. L'SDK v2 serve entrambe le ere sulla stessa app:
streamable_http_app()risponde sia all'initializedi un client 2025 sia alle richiesteper-request-
_metadi un client 2026, senza flag né deployment separati. Quindi:Le due migrazioni sono disaccoppiate: è il cardine del piano.
Fase 0 — Hardening del pin e della rete
~0.5 gg · nessuna dipendenza · nessun cambio di comportamento. Chiude le 6 advisory.
pyproject.toml:mcp>=1.28.1,<2.0(mcp[cli]>=1.28.1,<2.0perosm-mcp) — compatibilecon il floor
>=1.24.0di agent-framework.TransportSecuritySettings(allowed_hosts=..., allowed_origins=...)damcp.server.transport_security, valorizzata da env (MCP_ALLOWED_HOSTS/MCP_ALLOWED_ORIGINS).Con
HOST=0.0.0.0la protezione automatica non scatta.docker-compose.yml: porte MCP su127.0.0.1:1808x:808x(oggi 13 endpoint MCP nonautenticati sono esposti su tutte le interfacce in dev). In prod l'overlay
aes-infraletiene interne, ma il compose locale no.
pip-audit(o Dependabot) nel jobci.yml, per non riaprire il buco.Fase 1 — Runtime MCP condiviso
~2 gg · prerequisito per fare le Fasi 2–3 una volta invece di tredici.
Nuovo pacchetto
opendata-mcp-runtime/— non inopendata_core/: l'invariante diCLAUDE.mdvieta FastMCP/FastAPI lì.
build_mcp_server(*, name, title, instructions, version, register, log_name) -> MCPServerrun_from_env(server): leggeTRANSPORT/HOST/PORT/MCP_PATH, monta/healthz, applicatransport_security.server.pysopra il runtime (~15 righe ciascuno).MCP_TRANSPORT(osm) →TRANSPORT, aggiornandoMakefile:225.sse(un solo punto).osm-mcp: l'istanzaFastMCPa livello di modulo (osm-mcp/src/osm_mcp/server.py:12) passa abuild_server()come gli altri — necessario per la Fase 3, dovehost/port/streamable_http_pathnon sono più argomenti del costruttore.opendata-mcp-runtime/accanto aopendata_core/, context = root (R1).Fase 2 — Qualità dei tool
~2–3 gg · dopo la Fase 1. Paga subito sull'agente e prepara la spec nuova.
ToolAnnotationssu tutti i ~80 tool. Nessun tool del repo muta stato (nessuncreate_*/update_*/delete_*):readOnlyHint=True, idempotentHint=True, openWorldHint=True(leggono portali esterni). Oggi ce l'hanno 3 server su 13.titleleggibile per tool e server (nuovo parametro posizionale inMCPServer), piùicons/website_url.osm-mcp:-> str→-> dict. 19 tool su 19 ritornano JSON dentro una stringa: il clientnon riceve né
outputSchemanéstructuredContente il modello deve fare un parse. È l'unicoserver fuori standard nel repo.
tools/list(nuovoSHOULD): migliora l'hit rate della promptcache Anthropic, rilevante dato che
resolve_providerdi default èclaude(R11).outputSchemaespliciti → budget di contesto. Il commento inistat-mcp-server/src/istat_mcp/tools.py:28documenta che FastMCP duplica il payload incontentestructuredContent, raddoppiando il prompt, e per questo_MAX_PAYLOAD_CHARSè tenuto a 80 KB. Con
outputSchemadichiarato estructured_outputcontrollato si serve solola forma strutturata e si alza il budget utile — guadagno diretto sui codelist tipo
CL_ITTER107.limit(ckan_search_datasets,istat_list_dataflows, …): la spec hanextCursor, noi tronchiamo.Fase 3 — Migrazione SDK v2 / protocollo
2026-07-28~3–4 gg · dopo la Fase 1. Con il runtime condiviso, il lavoro è concentrato in un punto.
Nel runtime (una volta):
from mcp.server import MCPServer; args di trasporto →run(transport=..., host=..., port=..., streamable_http_path=..., transport_security=...).cache_hints=per popolarettlMs/cacheScope, mappati sui TTL che già gestiamo (catalogoSDMX 24h via
SDMX_PERSISTENT_CACHE_TTL_SECONDS, classify 24h):cacheScope="public"per icataloghi,
"private"per tutto ciò che dipende dall'utente.server/discoveresubscriptions/listen: serviti out-of-the-box daMCPServer, nulla dascrivere — solo da verificare in test.
Nei 13 pacchetti (meccanico):
pyproject.toml:mcp>=2,<3;sse-starlette>=3dove importato;starlette/uvicornrestano.
httpx→httpx2solo dove l'oggetto viene passato all'SDK.opendata_coreè condivisocol backend e usa
httpx: non toccarlo; i due pacchetti si installano affiancati.McpError→MCPError.ca-certificatesnelle immagini (python:3.x-slim):httpx2valida il TLScontro il trust store di sistema via
truststore, non controcertifi. Senza CA store usabiletutte le chiamate a ISTAT/CKAN/Overpass fallirebbero l'handshake. Fallback:
SSL_CERT_FILE/SSL_CERT_DIRoverify=ssl_contextesplicito. Rischio operativo n.1.except httpx.eisinstance(..., httpx...): sehttpxresta installato, unexcept httpx.ConnectErrorimporta ancora ma non matcha più nulla (fallimento silenzioso).Test e tooling:
make mcp-stdio-*:tools/listnudo non è più valido in era moderna(serve
_metaconprotocolVersion+clientCapabilities).make mcp-discover-*che chiamaserver/discover.Clientin-memory di v2 (Client(server),nessun transport): asserisce
server/discover,resultType, presenza dittlMs/cacheScope,annotations, ordine di
tools/list.mode="legacy"e uno moderno contro la stessa app.Fase 4 — Client-side (backend) — 🔒 BLOCCATA
~1–2 gg · bloccata da
agent-framework-core[all] → mcp<2.agent-framework-coreper il capmcp<2.MCPStreamableHTTPTool→ nuovoClientdell'SDK v2.client-side che onora
ttlMs, eMcp-Method/Mcp-Namecome header ⇒ Traefik può farerate-limit e routing per-tool senza leggere il body.
mcp2.x per i path che non passano daagent-framework (es.
osm_map.py:109, che chiamaosm-mcp).Nel frattempo: nessuna azione richiesta — i server v2 servono il client legacy.
Fase 5 — Capability nuove (opzionale, alto valore prodotto)
~3–5 gg · dopo la Fase 3. Oggi il progetto usa solo
tools.resources: cataloghi e report come risorse indirizzabili (opendata://portals,opendata://territory/{istat}/report,opendata://maturity/{entity}/scorecard), conttlMs/cacheScopeesubscriptions/listenper notificare i cambi rilevati daopendata-monitor— incastro esatto con l'agente di monitoraggio (Agente di monitoraggio schedulato (cron): freshness / qualità / link + notifica #88, Maturità: avvisi nel tempo (trend, freshness, link rotti) #103).prompts: i workflow che oggi vivono come istruzioni hardcoded inconfig.py(
CKAN_INSTRUCTIONS,ISTAT_INSTRUCTIONS, …) sono di fatto prompt template. Esporli comepromptsrenderebbeopendata-mcp-serverusabile da Claude Desktop con i percorsiEsplora/Territorio/Maturità/Qualità pronti, e ridurrebbe la duplicazione che R5 ci
costringe a mantenere a mano su 5 template.
territory_resolvecon comune ambiguopuò chiedere disambiguazione invece di scegliere.
io.modelcontextprotocol/tasks) per harvest CKAN e batchlunghi: l'SDK Python v2 non la implementa ancora → da rinviare.
Ordine e stima
2026-07-28httpx2mcp<2resources/promptsLe Fasi 0, 1 e 2 hanno valore anche se la 3 slitta, e nessuna delle tre richiede coordinamento col
backend.
Rischi
truststore(Fase 3): il rischio più concreto. Da validare su un solo server prima dipropagare —
web-mcp(2 tool) è il candidato per il PoC.httpx/httpx2non interscambiabili a runtime: unhttpx.AsyncClientpassato comehttp_clientdegrada in modo silenzioso invece di sollevare.in CI obbligatoria (Fase 3).
{}scritto a mano fallirebbetools/list. I nostri tool sono tutti decorator-based → bassorischio, ma va verificato.
Vincoli di processo (regole del repo)
opendata-mcp-runtime/accanto aopendata_core/./tmp/oda-venv, per pacchetto durante l'iterazione.make lint && make testprima di ogni commit; un PR per fase.README e la copy della pagina UI interessata.
CLAUDE.md: no FastMCP inopendata_core/→ da cui il pacchetto runtime separato.Riferimenti
2026-07-28·changelog ·
versioning ·
Streamable HTTP ·
server/discoverSEP-2549 (
ttlMs/cacheScope), SEP-2243 (header), SEP-2577 (deprecazioni), SEP-2663 (Tasks).what's new in v2
*/src/*/server.py(13 copie),*/src/*/tools.py,opendata-backend/src/opendata_backend/factory.py:30,324,opendata-backend/src/opendata_backend/osm_map.py:109,Makefile:166-227,docker-compose.ymlsubscriptions/listen)🤖 Generated with Claude Code