Skip to main content
Glama
bsab

milano-mobility-mcp

by bsab

Milano Mobility MCP

MVP Python funzionante del Nodo Mobilità & Restrizioni: quattro tool MCP stdio per ridurre l'asimmetria informativa su Area B, Area C, MoVe-In e antismog a Milano. Urbanistica esclusa.

Non è un'autorizzazione alla circolazione né consulenza legale. Verificare sempre le disposizioni delle autorità e la propria situazione prima di viaggiare. Nessuna garanzia contro sanzioni.

Cosa funziona davvero

Tool

Supporto nel MVP

check_vehicle_access

Profilo, data e perimetro validati; attualmente sempre non_determinabile, con motivazioni e riferimenti. Nessun divieto abilitato: fonti comunali non verificabili nella consultazione.

get_active_smog_level

Adapter non live: livello, territorio del provvedimento, validità e ultima verifica sconosciuti. Riferimenti ufficiali per verifica esterna.

query_movein_allowance

Calcolo esatto Decimal sui tre valori dichiarati dall'utente. Non legge il saldo personale né determina la soglia normativa.

get_daily_costs

Attualmente sempre non_determinabile: tariffa, totale e scadenza null, non zero. Nessuna tariffa verificata vigente e quindi nessuna abilitata.

SDK ufficiale mcp, modelli Pydantic, Python 3.11+. Nessuna richiesta di rete effettuata dai tool. Risposte con structuredContent e contenuto JSON testuale MCP.

Esiti e prove

  • decision: consentito, vietato, non_determinabile. Questa versione non emette mai consentito: la copertura complessiva è incompleta.

  • Il motore supporta vietato per un divieto ordinario documentato sul profilo dichiarato, condizionato all'assenza di deroghe applicabili dichiarata dall'utente. Questo ramo è esercitato da fixture sintetiche nei test, non da regole distribuite nel catalogo attuale.

  • Un divieto verificato prevale su componenti sconosciute, tutte visibili in assessments.

  • Per budget/costi, decision resta non_determinabile rispetto alla circolazione; calculation_status distingue calcolato, documentato, non_determinabile.

  • Input: user_unverified. Il motore richiede official_public per fonti operative; record synthetic_test o non verificati non sostengono regole. I source_checks restituiti sono riferimenti documentali (reference_only), non prove di regole applicabili.

  • Fonti mancanti, scadute, consultate nel futuro o fuori copertura non producono divieti documentati, costi certi o autorizzazioni. Sconosciuto non significa nessun blocco, ticket gratuito o deroga garantita.

Related MCP server: ECRVSP Documentos: Baixar Veículo

Quickstart

Accesso al repository privato necessario. Nessun token applicativo, account MoVe-In o credenziale da inserire nel progetto.

Windows / PowerShell

gh repo clone bsab/milano-mobility-mcp
Set-Location milano-mobility-mcp
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -e '.[test]'
.\.venv\Scripts\python.exe -m pytest -q
.\.venv\Scripts\python.exe -m milano_mobility_mcp

Entry point alternativo: .\.venv\Scripts\milano-mobility-mcp.exe.

Linux / macOS

gh repo clone bsab/milano-mobility-mcp
cd milano-mobility-mcp
python3 -m venv .venv
.venv/bin/python -m pip install -e '.[test]'
.venv/bin/python -m pytest -q
.venv/bin/python -m milano_mobility_mcp

Entry point alternativo: .venv/bin/milano-mobility-mcp.

Il processo attende messaggi MCP su stdin: non è una REPL né un server HTTP. Nessun banner su stdout; arresto manuale con Ctrl+C. Normalmente lo avvia il client MCP. Installazione senza pytest: pip install -e . con l'interprete dell'ambiente.

Configurazione client MCP

Usare l'interprete assoluto del virtualenv, non python dipendente dal PATH. Sostituire il percorso d'esempio con quello reale. Il package installato non dipende dalla directory corrente del client.

Windows:

{
  "mcpServers": {
    "milano-mobility": {
      "command": "C:\\progetti\\milano-mobility-mcp\\.venv\\Scripts\\python.exe",
      "args": ["-m", "milano_mobility_mcp"]
    }
  }
}

Linux/macOS:

{
  "mcpServers": {
    "milano-mobility": {
      "command": "/home/utente/milano-mobility-mcp/.venv/bin/python",
      "args": ["-m", "milano_mobility_mcp"]
    }
  }
}

La posizione del file di configurazione dipende dal client. Nessuna variabile segreta richiesta. Non abilitare log degli argomenti nel client se contengono dati personali: il server non registra input e non accetta targhe.

Esempi dei quattro tool

Argomenti MCP sempre nella forma {"request": {...}}. Risposte sotto come estratti; motivazioni e metadati completi vengono restituiti.

1. Profilo veicolo esplicito

check_vehicle_access:

{
  "request": {
    "vehicle": {
      "category": "M1", "fuel": "elettrico", "euro_class": null,
      "exemptions": "da_verificare", "resident_in_milan": true
    },
    "at": "2026-09-08T12:00:00+02:00",
    "area": "area_b",
    "inside_area_confirmed_by_user": true,
    "destination": "Destinazione dichiarata in Area B, non geocodificata"
  }
}

Risultato decision: "non_determinabile", anche per elettrico: restano situazione personale, geografia e misure non coperte. Residenza non significa esenzione. exemptions: "nessuna_applicabile_dichiarata" è solo una dichiarazione; usare da_verificare se accessi gratuiti, deroghe o MoVe-In potrebbero essere applicabili.

Obbligatori: categoria, alimentazione, classe Euro (null esplicito ammesso solo per elettrico), stato deroghe, area e conferma perimetro. Valori enumerati esposti da list_tools; categorie/alimentazioni non coperte non vengono assimilate ad altre.

Data/ora ISO 8601 con offset di Europe/Rome: +01:00 in inverno, +02:00 in estate. Date naive, offset errati, ore locali inesistenti e timestamp numerici rifiutati. Le due occorrenze dell'ora ambigua autunnale si distinguono con l'offset. Nessun divieto prospettico per istanti futuri. Nessuna geocodifica di civici, confini, itinerari o verifica varchi/telecamere.

2. Antismog: sconosciuto non è livello zero

get_active_smog_level:

{"request": {"territory": "Comune di Milano"}}

Risposta: decision: "non_determinabile", active_level: null, authority_status: "non_verificato", authoritative_territory: null, valid_from: null, valid_until: null, last_checked: null, freshness: "missing".

evaluated_at è l'ora della chiamata, non la consultazione di un bollettino. Il territorio richiesto non è presentato come ambito di un provvedimento. Le misurazioni ARPA non provano attivazione/revoca; il MVP non converte PM10 in uno stato amministrativo.

3. MoVe-In: solo aritmetica dichiarata

query_movein_allowance:

{"request": {"km_percorsi": "100.1", "soglia_annuale": "100.4", "distanza_viaggio": "0.2"}}

Estratto:

{
  "decision": "non_determinabile", "calculation_status": "calcolato",
  "input_provenance": "user_unverified",
  "km_residui": "0.3", "km_eccedenti": "0",
  "viaggio_nel_budget_dichiarato": true,
  "km_residui_dopo_viaggio": "0.1", "km_eccedenti_dopo_viaggio": "0",
  "authenticated_balance": false
}

Numeri dimostrativi, non soglie normative. Decimal serializzati come stringhe: preferirle anche negli input. Ammessi 0–1.000.000.000 km, massimo sei decimali (limiti tecnici). Negativi, NaN, infiniti e booleani rifiutati. Sopra soglia il residuo è zero, l'eccedenza esplicita e neppure un viaggio nullo rientra nel budget. Nessun saldo autenticato, rinnovo, soglia spettante o autorizzazione dedotti.

4. Costi non coperti

get_daily_costs:

{"request": {"area": "area_b", "on_date": "2026-09-08", "tariff": "ordinaria"}}

Risposta: calculation_status: "non_determinabile", ordinary_ticket_eur: null, total_due_eur: null, activation_deadline_exclusive: null. Non afferma che Area B sia a pagamento: non è calcolato un costo totale per quell'area.

Anche per Area C il risultato attuale è sconosciuto: prove mancanti. Il motore è predisposto per tariffa ordinaria/scadenza solo con fonte verificata per la data; nessuna tariffa reale è abilitata. Tariffe agevolate/sconosciute non diventano ordinarie. Totale dovuto sempre null: non si verifica l'obbligo individuale. Il campo scadenza esclusiva, quando sarà supportato, indica attivazione prima di quell'istante, non una scadenza universale di pagamento.

Fonti, aggiornamento e limiti

Registro fonti: URL, consultazione, regole, periodo e limiti. Catalogo in src/milano_mobility_mcp/sources.py, incluso nel package; nessun aggiornamento automatico o parametro client per sovrascrivere fonti/orologio.

Consultazione dei riferimenti: 8 settembre 2026; nessuna data operativa coperta da regole/tariffe distribuite. Le pagine comunali necessarie hanno restituito HTTP 403; InfoAria soltanto una shell JavaScript. Le pagine Regione/ARPA leggibili documentano cautele e ruoli, non un bollettino attivo. Il motore impone alle future fonti operative una freschezza massima di 24 ore, non una scadenza normativa. Un timestamp nuovo non basta: occorre verifica documentale e temporale. Nessun calendario generale di festivi/sospensioni. Il calcolo MoVe-In è già utilizzabile senza queste fonti mancanti.

Non supportati: storico completo, regole future, tutte le classi/categorie, deroghe e accessi residui, targa, importo personale dovuto, geocodifica, API varchi, funzionamento telecamere, saldo privato MoVe-In, antismog live, pagamento o scraping autenticato. Nessun deploy/acquisto. Repository privato, nessuna licenza aggiunta.

Test e sviluppo

.\.venv\Scripts\python.exe -m pytest -q
.\.venv\Scripts\python.exe -m pytest -q tests\test_mcp_integration.py
.\.venv\Scripts\python.exe -m pip check

Su Unix usare .venv/bin/python e separatori /. Test offline deterministici: calcoli, input invalidi, timezone/DST, confini, fonti mancanti/scadute/periodi, divieto con unknown, costi sconosciuti non zero. Lo smoke avvia veri processi da modulo ed entry point, usa il client SDK per initialize, list_tools, call_tool sui quattro tool, verifica JSON strutturato e sopravvivenza agli input invalidi. CI Windows/Linux, Python 3.11/3.13.

Validazione locale dell'8 settembre 2026: Python 3.13.14, SDK mcp 1.30.0; python -m pytest -q 122 passati, inclusi due smoke stdio reali; python -m pip check senza problemi. Wheel costruita con python -m pip wheel . --no-deps --wheel-dir <directory-artifact> e verificata per package, fonti ed entry point, senza fixture di test. La matrice CI è configurata separatamente: questi risultati locali non attestano da soli gli altri sistemi/interpreti.

Architettura: models.py contratti; catalog.py provenienza/tempo; sources.py snapshot; service.py valutazioni con clock interno; adapters.py indisponibilità antismog; server.py stdio. Nessun log applicativo degli input; stdout solo protocollo MCP.

Prossimi passi: roadmap.

Available Tools

4 tools
check_vehicle_accessD
Read-onlyIdempotent

Profilo/perimetro dichiarati, ora Europe/Rome: nessun divieto abilitato nel catalogo attuale.

ParametersJSON Schema
NameRequiredDescriptionDefault
requestYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
areaYes
reasonsYes
decisionYes
assessmentsYes
evaluated_atYes
source_checksNo
decision_scopeNo
input_provenanceNo
requested_at_romeYes
geographic_limitationsNo

TDQS

D1.3/5.0
Behavior2/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and non-destructive. The description adds a little context ('now Europe/Rome', 'current catalog'), but it does not explain that the request is evaluated, how the result is conveyed, or what 'nessun divieto' implies for the caller.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness2/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The text is short, but it is under-specified rather than concise. It front-loads a status-like assertion instead of a purpose, so the single sentence does not earn its place.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness1/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

This tool takes a required nested request with multiple semantic fields, yet the description gives no usable context for constructing or validating that request. An agent cannot infer when to call it, what inputs matter, or how to interpret the result.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the sole parameter `request` embeds a complex AccessRequest. The phrase 'Profilo/perimetro dichiarati' gives only a vague hint and does not explain `vehicle`, `area`, `at`, or `inside_area_confirmed_by_user`.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose1/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description reports a state ('nessun divieto abilito') rather than stating what the tool does. It never says it checks vehicle access against a request, so it could be mistaken for an output or catalog status message.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines1/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

No guidance is given for when to use this tool versus the siblings query_movein_allowance, get_active_smog_level, or get_daily_costs. There are no conditions, exclusions, or alternative selection criteria.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_active_smog_levelB
Read-onlyIdempotent

Stato antismog: adapter non live, restituisce onestamente livello e validità sconosciuti.

ParametersJSON Schema
NameRequiredDescriptionDefault
requestYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
reasonsYes
decisionNo
authorityYes
freshnessNo
valid_fromNo
valid_untilNo
active_levelNo
evaluated_atYes
last_checkedNo
source_checksNo
reference_urlsYes
authority_statusNo
requested_territoryYes
authoritative_territoryNo

TDQS

B3.3/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already declare readOnlyHint, idempotentHint, and non-destructive behavior. The description adds meaningful behavioral context beyond those annotations: the adapter is not live and the tool deliberately returns unknown level and validity values. This is a useful and honest disclosure of data freshness limitations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single concise sentence that front-loads the key caveat about the non-live adapter and the honest unknown result. There is no filler or redundant restatement of the tool name.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a simple one-parameter read-only tool with an output schema, the description is mostly adequate: it explains the main behavioral caveat and return semantics. However, it omits any guidance on how the territory parameter should be supplied and does not clarify how the 'unknown' result should be handled by the agent.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the description does not mention the 'request.territory' parameter at all. The parameter name is reasonably self-explanatory, but no format, examples, or allowed values are provided, and the description makes no effort to compensate for the schema's lack of documentation.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The name 'get_active_smog_level' and the Italian description 'Stato antismog' make it clear the tool returns an anti-smog level status. It also states it returns level and validity honestly as unknown. However, the description is a noun phrase rather than a clear verb+resource statement and does not differentiate it from sibling tools.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives no guidance on when to use this tool versus the sibling tools check_vehicle_access, query_movein_allowance, or get_daily_costs. The non-live adapter caveat implies limited reliability, but no explicit when-to-use or when-not-to-use conditions are provided.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

get_daily_costsC
Read-onlyIdempotent

Nessuna tariffa/scadenza abilitata nel catalogo attuale: importi null, mai zero implicito.

ParametersJSON Schema
NameRequiredDescriptionDefault
requestYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
areaYes
on_dateYes
reasonsYes
currencyNo
decisionNo
evidenceYes
evaluated_atYes
source_checksNo
total_due_eurNo
calculation_statusYes
ordinary_ticket_eurNo
activation_deadline_exclusiveNo

TDQS

C2.4/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already cover read-only/idempotent/non-destructve safety, so the description only needs to add behavioral context. It does so by warning that missing amounts are null, not zero, which prevents a common misinterpretation. This is useful, though it could be more explicit about what triggers the condition.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness3/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is a single sentence with no padding, so it is concise. However, it is structurally mislead: it front-loads a niche response caveat and omits the primary operation entirely, which makes the terseness a defect rather than a virtue.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness2/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Rich annotations and a self-describing input schema cover safety and parameter names, but the description leaves out the tool's core purpose and usage context. The null caveat is valuable but cannot make the overall definition complete for an agent deciding whether to call this tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters1/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0% and the description contains no information about request, area, on_date, or tariff. The input schema's names and enums carry all meaning; the description adds nothing and does not compensate for the low coverage.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose2/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description never states that this tool retrieves daily costs; it only says that with no enabled tariff/expiration in the current catalog, amounts are null and never implicit zero. The action and resource are left entirely to the tool name. This is not a pure tautology, but it is missing a clear purpose statement.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines2/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

There is no guidance on when to call get_daily_costs rather than check_vehicle_access, query_movein_allownce, or get_active_smog_level. The single sentence is a catalog-state caveat, not a usage condition or alternative-routing rule.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

query_movein_allowanceA
Read-onlyIdempotent

Sottrae km dichiarati con Decimal; NON saldo autenticato né autorizzazione alla circolazione.

ParametersJSON Schema
NameRequiredDescriptionDefault
requestYes

Output Schema

ParametersJSON Schema
NameRequiredDescription
ruleNo
reasonsYes
decisionNo
km_residuiYes
km_percorsiYes
km_eccedentiYes
source_checksNo
soglia_annualeYes
distanza_viaggioYes
input_provenanceNo
calculation_statusNo
authenticated_balanceNo
km_residui_dopo_viaggioYes
km_eccedenti_dopo_viaggioYes
viaggio_nel_budget_dichiaratoYes

TDQS

A3.5/5.0
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already mark the tool read-only and non-destructive; the description adds useful behavioral context beyond that: Decimal-based arithmetic rather than floating point, and a clear disclaimer that this is not an authenticated balance nor an authorization check. No contradiction with the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is two short sentences with the core computation front-loaded and the important misuse caveat isolated in the second sentence. Every word earns its place; there is no filler or unnecessary repetition.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness3/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a small read-only computational query with an output schema, the main caveat is present. However, the role of all three request fields and the exact subtraction source are not fully specified, so the agent may still have to infer the formula from parameter names.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters2/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema description coverage is 0%, and the description only loosely references 'km dichiarati' and Decimal precision. It does not explain soglia_annuale or distanza_viaggio, nor how they relate to the subtraction, so an agent cannot fully determine parameter semantics from the description alone.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose4/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description states a specific operation: subtract declared km using Decimal precision, and explicitly rules out authenticated balance and circulation authorization. This distinguishes it from access/authorization tools, though the exact meaning of 'move-in allowance' is still partly left to the tool name.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines3/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description gives implied usage guidance by saying it is NOT an authenticated balance or a circulation authorization, which helps prevent misuse for access decisions. However, it does not explicitly state when to use this tool or name sibling tools as alternatives.

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.

  1. 4 tool updatesv0.1.0
    • First observedcheck_vehicle_access
    • First observedget_active_smog_level
    • First observedget_daily_costs
    • First observedquery_movein_allowance

TDQS

C2.5/5.0

Scored across 4 tools

Disambiguation4/5

Each tool targets a different mobility concept—vehicle access, Move-In allowance, smog level, and daily costs—so an agent can generally select correctly. The only mild ambiguity is between check_vehicle_access and query_movein_allowance, since both concern circulation restrictions.

Naming Consistency3/5

All names use snake_case and a verb-like prefix, but the verbs are inconsistent: check, query, get, get. This is readable but does not follow a single uniform verb_noun convention.

Tool Count4/5

Four tools is on the lean side but reasonable for a focused Milan mobility server. The count is not bloated, though the domain could justify a few more tools if live functionality were present.

Completeness2/5

The tools cover four distinct query areas, but the descriptions repeatedly admit to non-live adapters, empty catalogs, and non-authenticated data. This leaves obvious gaps for agents needing real smog levels, actual Move-In balances, enabled restrictions, or applicable fees.

Maintenance

ActivityMaintained
ResponsivenessNo issues

Related MCP Connectors

Related MCP Servers