mcp-ocr-anagrafica
mcp-ocr-anagrafica
Server MCP remoto (Streamable HTTP), stateless e containerizzato, che espone
strumenti di estrazione dati strutturati da immagini, PDF e documenti XML
tramite modelli LLM con capacità Vision. extract_demographics ed
extract_invoice sono entrambi specializzazioni di un motore di estrazione
generico, pensati per l'inserimento di dati anagrafici e di fatture/note di
credito in un gestionale fiscale.
Documento vincolante: l'architettura completa e i requisiti sono definiti in
docs/SPEC.md. Questo README ne è la documentazione operativa; in caso di conflitto prevale la specifica.
Indice
Architettura
Il sistema non è progettato esclusivamente per l'anagrafica. Il nucleo è un motore generico di estrazione strutturata:
ExtractionService.extractStructuredData(document, prompt, schema) -> TI tool MCP extract_demographics ed extract_invoice sono entrambi
specializzazioni di questo motore: ciascuno fornisce solo un prompt di
sistema (src/prompts/*.system.md) e uno schema Zod
(src/schemas/*.schema.ts). In futuro, nuovi tool (extract_receipt,
extract_identity_card, extract_shipping_label, ...) potranno essere
aggiunti allo stesso modo, senza modificare il core.
Il layer di trasporto (HTTP) è isolato dal core: né i servizi, né i provider, né gli schemi conoscono l'esistenza di HTTP, Express o del protocollo MCP.
Il motore non è "sempre Vision": internamente sceglie tra due strategie di
estrazione in base al MIME type reale del documento, entrambe dietro la
stessa interfaccia ExtractionStrategy.
VisionExtractionStrategy(immagine/PDF): la pipeline OCR classica — rendering PDF se necessario, chiamata al provider Vision con retry, validazione Zod dell'output del modello.ParserExtractionStrategy(XML, incluso XML firmato.p7m): lettura deterministica, senza alcuna chiamata LLM. Non esiste un mapper XML generico (a differenza del ramo Vision, dove prompt + schema bastano per qualunque dominio): ogni tool che vuole accettare XML fornisce il proprio mapper (extract_invoiceusaparseInvoiceXml, il parser FatturaPA). Un tool che non lo fornisce (es.extract_demographics) riceve un rifiuto esplicito (unsupported_mime_type_for_tool) se gli viene passato un documento XML/.p7m, mai un comportamento silenzioso o un tentativo di interpretarlo come immagine.
ExtractionService.selectStrategy() è l'unico punto che conosce
l'esistenza di entrambe le strategie; nessun'altra parte del sistema sa
quale sia stata usata per produrre un dato risultato.
flowchart TB
subgraph transport["Transport layer (src/mcp)"]
HTTP["http.transport.ts<br/>Streamable HTTP · POST /mcp"]
AUTH["bearer.middleware.ts<br/>AuthStrategy"]
TOOL1["demographics.tool.ts"]
TOOL2["invoice.tool.ts"]
POST["anagrafica.postprocess.ts<br/>(condiviso dai due tool)"]
end
subgraph core["Core (nessuna dipendenza da HTTP)"]
EXT["ExtractionService<br/>extractStructuredData() · selectStrategy()"]
MERGE["MergeService"]
subgraph strategies["ExtractionStrategy"]
VSTRAT["VisionExtractionStrategy<br/>immagine / PDF"]
PSTRAT["ParserExtractionStrategy<br/>XML / .p7m"]
end
PROV["VisionProvider (interfaccia)<br/>AnthropicVisionProvider"]
DOC["Document Loader<br/>base64 / url + SSRF guard"]
PDF["PDF Renderer<br/>(pdftoppm)"]
XMLP["xml.parser.ts<br/>FatturaPA -> schema, no DTD/entità esterne"]
P7M["p7m.unwrapper.ts<br/>solo estrazione, nessuna verifica firma"]
SCHEMA["Zod Schemas<br/>input / output"]
NORM["Normalizers"]
VALID["Domain Validators<br/>P.IVA · CF · SDI · EAN · Natura IVA"]
end
CLIENT["Client MCP remoto<br/>(Claude Desktop / Claude Code)"] -->|Bearer token| HTTP
HTTP --> AUTH --> TOOL1
AUTH --> TOOL2
TOOL1 --> POST
TOOL2 --> POST
TOOL1 --> EXT
TOOL2 --> EXT
EXT --> DOC
EXT --> MERGE
EXT --> strategies
VSTRAT --> PDF
VSTRAT --> PROV
PSTRAT --> P7M
PSTRAT --> XMLP
EXT --> SCHEMA
POST --> NORM
POST --> VALID
TOOL2 --> VALID
PROV -.implementa.-> AI["Anthropic Vision API"]Tool disponibili
Tool | Formati accettati | Strategia | Output |
|
| Vision | Anagrafica: ragione sociale/nome/cognome, P.IVA, CF, indirizzo, contatti, REA, SDI |
|
| Vision (immagine/PDF) · Parser deterministico (XML/ | Fattura/nota di credito/nota di debito: cedente, cessionario, righe (codice, EAN, descrizione, quantità, prezzo, aliquota IVA), riepiloghi IVA |
Entrambi restituiscono confidence, confidence_fields (mappa
campo → confidence) e warnings (array di stringhe), mai valori inventati:
un campo assente nel documento è sempre null, mai una stringa vuota.
Per extract_invoice su XML/.p7m, confidence è sempre 1 per ogni
campo estratto: la lettura è deterministica, non c'è ambiguità visiva da
esprimere (a differenza dell'OCR, dove la confidence riflette l'incertezza
del modello).
Documenti multi-parte (fronte/retro)
Per i formati immagine (non PDF, non XML/.p7m), il parametro document
accetta un singolo oggetto oppure un array non vuoto di 1..
MAX_DOCUMENT_PARTS oggetti — pensato per i casi in cui un unico documento
fisico è catturato in più foto: fronte/retro di una carta d'identità, di
una patente, di un biglietto da visita.
{
"document": [
{ "type": "base64", "data": "<fronte>", "mimeType": "image/jpeg" },
{ "type": "base64", "data": "<retro>", "mimeType": "image/jpeg" }
]
}Ogni parte viene inviata al provider Vision separatamente (come se fosse
una pagina PDF) e i risultati vengono unificati da MergeService con la
stessa identica strategia campo-per-campo già usata per il PDF
multipagina — vedi Strategia di merge multipagina.
Non esiste una nozione di "questa è la faccia anteriore": il merge
semplicemente prende, per ogni campo, il valore con confidence più alta tra
tutte le parti fornite.
Vincoli:
Ammesso solo per parti tutte di tipo immagine (
jpg/jpeg/png/webp). Un array contenente un PDF o un documento XML/.p7mviene rifiutato esplicitamente (mixed_or_unsupported_document_parts): un PDF ha già il proprio meccanismo multipagina, un documento strutturato è per natura un'unica unità.Un array che supera
MAX_DOCUMENT_PARTSviene rifiutato esplicitamente (too_many_document_parts), prima di caricare o elaborare qualunque parte.Un singolo oggetto (senza array) continua a funzionare esattamente come prima — nessuna modifica per i client esistenti.
Questa capacità vive interamente in ExtractionService/documentInputOrPartsSchema,
non nei singoli tool: sia extract_demographics che extract_invoice la
ereditano senza alcuna modifica al proprio file, e un futuro
extract_identity_card la avrebbe fin dal primo giorno.
Responsabilità dei layer
Layer | Percorso | Responsabilità |
Config |
| Unica fonte delle variabili d'ambiente, validate con Zod all'avvio (fail-fast) |
MCP transport |
| Endpoint |
MCP auth |
| Verifica del bearer token in tempo costante, interfaccia |
MCP tools |
| Dichiarazione |
MCP tools (glue) |
| Normalizzazione + validator di dominio per un record anagrafico, condivisa da |
Providers |
| Interfaccia |
Services |
| Orchestrazione della pipeline di estrazione (inclusa la scelta della strategy) e strategia di merge multipagina |
Services (strategy) |
| Interfaccia |
Document |
| Caricamento documento (base64/URL con protezioni SSRF) e rendering PDF via |
Document (XML/P7M) |
| Parsing FatturaPA deterministico (no DTD/entità esterne, allegati scartati) ed estrazione del contenuto da una busta |
Prompts |
| Prompt di sistema versionati come file, mai hardcodati nel codice |
Schemas |
| Contratti Zod di input/output |
Normalizers |
| Normalizzazione dei campi estratti (ragione sociale, P.IVA, indirizzo, telefono, email) |
Validators |
| Validatori di dominio (P.IVA, Codice Fiscale, SDI, EAN, Natura IVA, ...): non scartano il valore, segnalano in |
Utils |
| Sanitizzazione, retry con backoff, rilevamento MIME/magic bytes (incluso XML/ |
Flusso completo di una richiesta
sequenceDiagram
participant C as Client MCP
participant T as HTTP Transport (/mcp)
participant A as Auth (Bearer)
participant X as ExtractionService
participant L as Document Loader
participant R as PDF Renderer
participant V as VisionProvider
participant M as MergeService
C->>T: POST /mcp (tools/call extract_demographics)
T->>A: verifica Authorization: Bearer <token>
alt token assente/non valido
A-->>C: 401 (nessun dettaglio)
else token valido
A->>X: invoca extractStructuredData(document, prompt, schema)
X->>L: carica ogni parte (una o più, es. fronte/retro)
Note over L: SSRF guard se URL,<br/>verifica magic bytes vs MIME dichiarato,<br/>per ciascuna parte
L-->>X: buffer per ogni parte
opt una parte è un PDF
X->>R: pdftoppm (rendering per pagina, limite MAX_PDF_PAGES)
R-->>X: immagini per pagina
end
loop per ogni pagina
X->>V: chiamata Vision (con retry/backoff su timeout, 429, 503)
V-->>X: output grezzo
X->>X: sanitizzazione + validazione Zod
end
X->>M: merge multipagina (campo per campo, confidence)
M-->>X: risultato unificato + warnings
X-->>T: structuredContent
T-->>C: 200 JSON (structuredContent)
endQuesto è il flusso quando il documento è un'immagine o un PDF (entrambi i
tool). Se document è un array (fronte/retro), ogni parte viene caricata e
poi trattata esattamente come una pagina: il "loop per ogni pagina" scorre
sull'unione di tutte le pagine di tutte le parti (vedi Documenti
multi-parte (fronte/retro)). Per
extract_invoice con un documento XML o .p7m, la pipeline è diversa a
valle del Document Loader: nessun rendering PDF, nessuna chiamata al
provider Vision, nessun retry.
sequenceDiagram
participant X as ExtractionService
participant L as Document Loader
participant P as ParserExtractionStrategy
participant U as p7m.unwrapper.ts
participant XP as xml.parser.ts (parseInvoiceXml)
X->>L: carica documento (base64 o URL)
Note over L: verifica magic bytes:<br/>dichiarazione XML oppure struttura ASN.1/OID PKCS#7
L-->>X: buffer + mimeType (application/xml | application/pkcs7-mime)
X->>P: selectStrategy() ha scelto ParserExtractionStrategy
opt mimeType è application/pkcs7-mime
P->>U: estrae il contenuto dalla busta CMS SignedData
Note over U: nessuna verifica di firma/certificato:<br/>fuori scope, fuori dal perimetro OCR
U-->>P: XML originale
end
P->>XP: parseInvoiceXml(xml)
Note over XP: DOCTYPE rifiutato, blocco <Allegati> scartato<br/>prima di ogni elaborazione, mai loggato/restituito
XP-->>P: dati mappati (confidence 1 per ogni campo)
P->>P: validazione Zod (stesso schema del ramo Vision)
P-->>X: risultato (un solo "page", MergeService lo restituisce invariato)Protocollo MCP
SDK ufficiale
@modelcontextprotocol/sdk, nessuna implementazione manuale del protocollo.Trasporto: solo Streamable HTTP (
StreamableHTTPServerTransport), endpoint unicoPOST /mcp.Modalità stateless: nessuna gestione di sessione lato server,
enableJsonResponse: trueper ottenere risposte JSON dirette invece dello streaming SSE opzionale.Non implementati: trasporto
stdio, trasporto legacy HTTP+SSE.Entrambi i tool (
extract_demographics,extract_invoice) dichiaranoinputSchemaeoutputSchema(derivati da Zod) e restituiscono il risultato tramitestructuredContent, così il client riceve JSON tipizzato nativamente senza dover ri-parsare testo libero.
Configurazione
Tutte le variabili sono validate con Zod all'avvio (src/config/env.ts):
configurazione invalida ⇒ il processo termina immediatamente con un errore
chiaro (fail-fast). Nessun process.env.X è utilizzato fuori da questo
modulo.
Copiare .env.example in .env e valorizzare:
Variabile | Descrizione | Default |
| API key del provider Anthropic | — (obbligatoria) |
| Modello Vision Anthropic da utilizzare | — (obbligatoria) |
| Bearer token richiesto su | — (obbligatoria) |
| Porta di ascolto HTTP interna |
|
| Rispetta |
|
| Finestra del rate limit |
|
| Richieste massime per finestra (per IP e per token) |
|
| Dimensione massima per parte di documento |
|
| Numero massimo di pagine PDF elaborabili |
|
| Risoluzione di rendering PDF ( |
|
| Numero massimo di parti immagine per richiesta (fronte/retro) |
|
| Timeout richiesta HTTP/provider |
|
| Tentativi massimi di retry (backoff esponenziale + jitter) |
|
| Livello di log Pino ( |
|
Sicurezza dell'endpoint remoto
Il server è esposto su internet e processa dati personali: la sicurezza del confine HTTP è un requisito primario.
Autenticazione: header
Authorization: Bearer <token>obbligatorio su/mcp, confrontato in tempo costante (crypto.timingSafeEqual). Richieste senza token o con token errato ⇒401, senza dettagli interni. L'architettura predispone un'interfacciaAuthStrategyper una futura migrazione a OAuth 2.1 (non implementata in questa versione).Rate limiting: per IP e per token, configurabile via env.
Limite dimensione body: applicato prima del parsing JSON, coerente con
MAX_FILE_SIZE_MB × MAX_DOCUMENT_PARTSpiù l'overhead base64 (una richiesta può contenere fino aMAX_DOCUMENT_PARTSparti immagine, es. fronte/retro, ciascuna fino aMAX_FILE_SIZE_MB).Timeout di richiesta a livello HTTP.
Header di sicurezza standard (helmet).
Nessuna informazione interna (stack trace, path, versioni) negli errori restituiti al client.
TLS: il container espone solo HTTP in chiaro sulla rete interna; la terminazione TLS è demandata a un reverse proxy esterno (vedi sezione nginx). Il server rispetta
X-Forwarded-Forper il rate limiting dietro proxy (TRUST_PROXY).SSRF (caricamento documento da URL): solo schema
https, risoluzione DNS con blocco di IP privati/loopback/link-local e metadata endpoint (169.254.0.0/16,10/8,172.16/12,192.168/16,::1), blocco dei redirect verso IP privati, timeout e limite dimensione sul download.XML (
extract_invoice): qualunque documento con dichiarazioneDOCTYPEviene rifiutato esplicitamente, prima di qualunque parsing (protezione XXE/entity-expansion in profondità — il parser XML usato [fast-xml-parser] non risolve comunque entità esterne per progettazione, ma il rifiuto esplicito non dipende da questa garanzia implicita). Il blocco<Allegati>di una FatturaPA viene scartato dalla stringa XML prima di qualunque elaborazione: il suo contenuto (spesso un allegato base64 di dimensioni significative) non diventa mai un valore in memoria, non compare nell'output, non viene mai loggato..p7m(busta CMS/PKCS#7): viene solo estratto il contenuto originale firmato. Non viene eseguita alcuna verifica della firma (certificato, catena di fiducia, scadenza, revoca): è una scelta di design, non una lacuna — la validità legale di una firma digitale è una valutazione che spetta al gestionale/operatore a valle, non a un servizio di estrazione OCR. Solo le buste CMS SignedData con contenuto incorporato sono supportate; le firme distaccate (detached) sono rifiutate esplicitamente.
Docker
Build multi-stage: stage di build (dipendenze complete, compilazione
TypeScript) e stage runtime minimale (node:20-bookworm-slim +
poppler-utils per pdftoppm), eseguito come utente non-root, con
HEALTHCHECK su GET /health (non autenticato, senza informazioni
interne).
docker compose up -d --buildIl container:
è stateless e condivisibile tra più client MCP contemporanei;
non pubblica alcuna porta sulla rete pubblica:
docker-compose.ymllega la porta esterna (3501, porta interna sempre3000) esclusivamente all'indirizzo dell'interfaccia WireGuard locale (10.10.0.4,wg0), non a0.0.0.0;è raggiungibile solo dal reverse proxy (nginx) che sta dall'altro capo del tunnel WireGuard, su una VPS separata — vedi Reverse proxy nginx con TLS (certbot);
monta
/tmpcome tmpfs (docker-compose.yml). Motivo:pdftoppmnon è in grado di scrivere l'immagine renderizzata su stdout (l'argomento-come prefisso di output viene trattato come nome di file letterale, non come richiesta di stream), quindi ogni pagina viene scritta in un file temporaneo e riletta in memoria. Montando/tmpcome tmpfs quei byte restano esclusivamente in RAM e non toccano mai uno storage persistente, in coerenza con il vincolo "documenti mai persistiti su disco". Questo mount è obbligatorio in produzione: senza tmpfs,/tmpsarebbe disco reale del filesystem del container.
Reverse proxy nginx con TLS (certbot)
Nota sul deployment reale di questa istanza: nginx e la gestione del certificato (certbot) non fanno parte di questo progetto e non vengono installati/gestiti da esso. Nel deployment attuale il reverse proxy e il TLS sono già gestiti su una VPS separata, indipendente da questo repository. Il container qui pubblica solo la porta
3501sull'interfaccia WireGuard locale (wg0, IP10.10.0.4; porta interna sempre3000, numero esterno diverso perché su questo host convivono più server MCP) — irraggiungibile da chiunque non sia dall'altro capo del tunnel. La configurazione che segue è mantenuta come esempio di riferimento generico (richiesto da docs/SPEC.md) per chi clona questo progetto e non dispone già di un proprio reverse proxy con TLS.
Più server MCP possono condividere lo stesso dominio, instradati per
prefisso di path (/ocr, /rag, ...) verso upstream diversi, ciascuno sulla
propria porta wg0. Il backend espone sempre e solo l'endpoint fisso
POST /mcp (vincolo di docs/SPEC.md, non configurabile), quindi nginx deve
riscrivere il path esterno su /mcp in fase di proxy_pass, non limitarsi a
un passthrough:
upstream mcp_ocr_anagrafica {
server 10.10.0.4:3501;
}
# Esempio: un futuro secondo server MCP (es. RAG) sulla stessa VPS, sulla
# propria porta wg0 dedicata, seguirebbe lo stesso schema:
# upstream mcp_rag {
# server 10.10.0.4:3502;
# }
server {
listen 80;
server_name mcp.baldoweb.it;
# Redirect verso HTTPS, eccetto la sfida ACME di certbot
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
server {
listen 443 ssl http2;
server_name mcp.baldoweb.it;
ssl_certificate /etc/letsencrypt/live/mcp.baldoweb.it/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mcp.baldoweb.it/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
# Coerente con MAX_FILE_SIZE_MB × MAX_DOCUMENT_PARTS (10MB × 4) +
# overhead base64 (~1.37x) - una richiesta può contenere più parti
# immagine (fronte/retro)
client_max_body_size 60m;
# /ocr esterno -> /mcp interno (endpoint fisso del server OCR anagrafica)
location = /ocr {
proxy_pass http://mcp_ocr_anagrafica/mcp;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Allineati a REQUEST_TIMEOUT_MS (60s di default)
proxy_connect_timeout 10s;
proxy_send_timeout 70s;
proxy_read_timeout 70s;
}
location = /ocr/health {
proxy_pass http://mcp_ocr_anagrafica/health;
proxy_set_header Host $host;
}
# Esempio: un futuro MCP "rag" seguirebbe lo stesso schema, sul proprio
# upstream/porta:
# location = /rag {
# proxy_pass http://mcp_rag/mcp;
# ... (stessi header/timeout di cui sopra)
# }
}Rinnovo certificati con certbot (esempio, container dedicato o cron host):
certbot certonly --webroot -w /var/www/certbot -d mcp.baldoweb.itConfigurazione client MCP remoti
Il server espone internamente un unico endpoint fisso POST /mcp (vincolo
di docs/SPEC.md). In questo deployment, il reverse proxy lo pubblica
all'esterno come POST https://mcp.baldoweb.it/ocr (vedi Reverse proxy
nginx con TLS (certbot) per lo schema
di path-routing multi-MCP); richiede comunque l'header
Authorization: Bearer <MCP_AUTH_TOKEN>.
Claude Desktop (claude_desktop_config.json):
{
"mcpServers": {
"ocr-anagrafica": {
"url": "https://mcp.baldoweb.it/ocr",
"headers": {
"Authorization": "Bearer <MCP_AUTH_TOKEN>"
}
}
}
}Claude Code (CLI):
claude mcp add --transport http ocr-anagrafica \
https://mcp.baldoweb.it/ocr \
--header "Authorization: Bearer <MCP_AUTH_TOKEN>"La sintassi esatta del flag può variare in base alla versione della CLI: fare riferimento a
claude mcp add --helpper la sintassi corrente di registrazione di un server HTTP remoto con header custom.
Il client MCP non deve contenere alcuna logica di business: si limita a
inoltrare la chiamata al tool con il documento (base64 o URL) e a ricevere
structuredContent già validato.
Strategia di merge multipagina
Si applica solo al ramo Vision (immagine/PDF): un documento XML o .p7m
viene letto come un unico documento, non ha un concetto di "pagina", quindi
MergeService riceve un solo risultato e lo restituisce invariato.
Le "pagine" che MergeService unifica possono avere due origini, trattate
in modo identico a valle: il rendering di un PDF multipagina, oppure le
parti di un documento multi-parte fornite direttamente dal client (vedi
Documenti multi-parte (fronte/retro)).
Un fronte/retro produce quindi esattamente due "pagine", elaborate ed
unificate con le stesse regole di seguito:
Per ogni campo vince il valore proveniente dalla pagina con
confidencepiù alta.Se due pagine forniscono valori diversi e non nulli per uno stesso campo identificativo, viene aggiunto un warning esplicito in
warningscon entrambi i valori in conflitto (il valore a confidence più alta viene comunque scelto). Perextract_demographics:partita_iva,codice_fiscale,ragione_sociale. Perextract_invoice:numero,cedente.partita_iva,cessionario.partita_iva.La
confidencecomplessiva del risultato riflette il merge e non è mai superiore alla confidence del valore scelto per ciascun campo.Il superamento di
MAX_PDF_PAGES(per un PDF) o diMAX_DOCUMENT_PARTS(per un documento multi-parte) produce un errore esplicito, senza elaborazione parziale silenziosa.
Considerazioni GDPR
I documenti non vengono mai persistiti su disco: l'elaborazione avviene esclusivamente in memoria, per l'intera pipeline. Unica eccezione tecnica: il rendering delle pagine PDF (
pdftoppm) richiede un file di output reale (non supporta lo streaming su stdout); per questo motivo/tmpè montato come tmpfs (RAM) nel container — vedi sezione Docker — così anche questo passaggio intermedio non tocca mai storage persistente.Nessun dato personale (documenti, contenuto estratto, base64, URL completi) viene scritto nei log. È loggabile solo l'hash SHA-256 del documento come identificativo tecnico, insieme a request-id, tool invocato, esito, durata e numero di retry.
I documenti caricati transitano verso il provider AI configurato (Anthropic). Questo comporta implicazioni di titolarità/responsabilità del trattamento (titolare/responsabile, eventuale subresponsabile) che devono essere valutate a livello contrattuale con il fornitore del provider, in base al caso d'uso specifico del gestionale che integra questo server.
Il codice è predisposto per un futuro caching basato sull'hash SHA-256 del documento, non implementato in questa versione. Un eventuale caching futuro conterrebbe dati personali e dovrà prevedere una politica di retention esplicita prima di essere attivato.
Per
extract_invoice, il blocco<Allegati>di una FatturaPA XML (spesso un secondo documento in base64, es. il DDT) viene scartato prima di qualunque elaborazione e non transita mai verso il provider Vision, non compare nell'output, non viene loggato — vedi Sicurezza dell'endpoint remoto.
Estendibilità futura
Nuovi tool di estrazione:
extract_receipt,extract_identity_card,extract_shipping_label, ... — ciascuno richiede solo un nuovo prompt (src/prompts/*.system.md), un nuovo schema Zod (src/schemas/*.schema.ts) e un nuovo tool MCP che delega aExtractionService.extractStructuredData(). Il core non viene modificato. Il supporto multi-parte (fronte/retro, vedi Documenti multi-parte (fronte/retro)) è già incluso fin dal primo giorno, senza bisogno di scriverlo di nuovo — è una proprietà del motore, non del singolo tool.Nuovi formati strutturati (non-Vision):
ParserExtractionStrategyè già generica rispetto al formato — non conosce FatturaPA, conosce solo "un mapper produce un oggetto, Zod lo valida". Un futuro formato strutturato diverso da XML (es. un tracciato a larghezza fissa, un CSV normato) richiederebbe solo un nuovo mapper passato viaparseStructuredDocument, non una nuova strategy.Nuovi provider Vision: OpenAI, Gemini, Ollama, Mistral — ciascuno richiede solo una nuova implementazione dell'interfaccia
VisionProvider. Il resto del sistema continua a conoscere solo l'interfaccia.Nuovi trasporti: l'aggiunta di trasporti futuri non richiede modifiche a servizi, provider o schemi, perché il core non conosce l'esistenza di HTTP.
OAuth 2.1: l'interfaccia
AuthStrategyè predisposta per accogliere una futura strategia OAuth 2.1 accanto a quella bearer-token attuale.
Il server MCP costituisce il confine di sicurezza sia verso internet sia verso i sistemi legacy. Tutti i guardrail (autenticazione, rate limiting, validazione, limiti, timeout, sanitizzazione, retry, logging e controllo dell'output) risiedono nel server MCP e non nel client.
Comandi di sviluppo
npm run dev # avvio locale con watch (tsx)
npm run build # compilazione TypeScript (dist/)
npm run lint # ESLint
npm run typecheck # tsc --noEmit
npm run test # Vitest (unit + integration senza provider live)
npm run format # Prettier (write)
docker compose up -d --build # avvio containerizzatoI test di integrazione con provider AI reale sono opt-in tramite
RUN_LIVE_TESTS=1 e non fanno parte del gate standard (npm run test).