mcp-ocr-anagrafica

mcp-ocr-anagrafica

Remote MCP server for extracting structured data from images, PDFs, and XML documents using LLM Vision models. Provides specialized tools for demographic and invoice data extraction with support for multi-part documents.

Category
Visit Server

README

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) -> T

I 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_invoice usa parseInvoiceXml, 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
extract_demographics jpg, jpeg, png, webp, pdf Vision Anagrafica: ragione sociale/nome/cognome, P.IVA, CF, indirizzo, contatti, REA, SDI
extract_invoice jpg, jpeg, png, webp, pdf, xml, xml.p7m Vision (immagine/PDF) · Parser deterministico (XML/.p7m) 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/.p7m viene 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_PARTS viene 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 src/config/env.ts Unica fonte delle variabili d'ambiente, validate con Zod all'avvio (fail-fast)
MCP transport src/mcp/transport/http.transport.ts Endpoint POST /mcp, Streamable HTTP stateless
MCP auth src/mcp/auth/bearer.middleware.ts Verifica del bearer token in tempo costante, interfaccia AuthStrategy
MCP tools src/mcp/tools/*.tool.ts Dichiarazione inputSchema/outputSchema, delega totale la logica a ExtractionService. Nessuna logica di business qui
MCP tools (glue) src/mcp/tools/anagrafica.postprocess.ts Normalizzazione + validator di dominio per un record anagrafico, condivisa da extract_demographics e da cedente/cessionario di extract_invoice
Providers src/providers/*.provider.ts Interfaccia VisionProvider e implementazione AnthropicVisionProvider. Il resto del sistema conosce solo l'interfaccia
Services src/services/extraction.service.ts, merge.service.ts Orchestrazione della pipeline di estrazione (inclusa la scelta della strategy) e strategia di merge multipagina
Services (strategy) src/services/extraction-strategy.ts, vision.strategy.ts, parser.strategy.ts Interfaccia ExtractionStrategy e le sue due implementazioni (Vision, Parser deterministico)
Document src/document/loader.ts, base64.loader.ts, url.loader.ts, pdf.renderer.ts Caricamento documento (base64/URL con protezioni SSRF) e rendering PDF via pdftoppm
Document (XML/P7M) src/document/xml.parser.ts, p7m.unwrapper.ts Parsing FatturaPA deterministico (no DTD/entità esterne, allegati scartati) ed estrazione del contenuto da una busta .p7m (nessuna verifica firma)
Prompts src/prompts/*.system.md Prompt di sistema versionati come file, mai hardcodati nel codice
Schemas src/schemas/*.schema.ts Contratti Zod di input/output
Normalizers src/normalizers/* Normalizzazione dei campi estratti (ragione sociale, P.IVA, indirizzo, telefono, email)
Validators src/validators/* Validatori di dominio (P.IVA, Codice Fiscale, SDI, EAN, Natura IVA, ...): non scartano il valore, segnalano in warnings
Utils src/utils/* Sanitizzazione, retry con backoff, rilevamento MIME/magic bytes (incluso XML/.p7m), hash SHA-256, logger

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)
    end

Questo è 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 &lt;Allegati&gt; 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 unico POST /mcp.
  • Modalità stateless: nessuna gestione di sessione lato server, enableJsonResponse: true per ottenere risposte JSON dirette invece dello streaming SSE opzionale.
  • Non implementati: trasporto stdio, trasporto legacy HTTP+SSE.
  • Entrambi i tool (extract_demographics, extract_invoice) dichiarano inputSchema e outputSchema (derivati da Zod) e restituiscono il risultato tramite structuredContent, 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
ANTHROPIC_API_KEY API key del provider Anthropic — (obbligatoria)
VISION_MODEL Modello Vision Anthropic da utilizzare — (obbligatoria)
MCP_AUTH_TOKEN Bearer token richiesto su /mcp — (obbligatoria)
HTTP_PORT Porta di ascolto HTTP interna 3000
TRUST_PROXY Rispetta X-Forwarded-For (dietro reverse proxy) true
RATE_LIMIT_WINDOW_MS Finestra del rate limit 60000
RATE_LIMIT_MAX_REQUESTS Richieste massime per finestra (per IP e per token) 30
MAX_FILE_SIZE_MB Dimensione massima per parte di documento 10
MAX_PDF_PAGES Numero massimo di pagine PDF elaborabili 10
PDF_RENDER_DPI Risoluzione di rendering PDF (pdftoppm) 150
MAX_DOCUMENT_PARTS Numero massimo di parti immagine per richiesta (fronte/retro) 4
REQUEST_TIMEOUT_MS Timeout richiesta HTTP/provider 60000
MAX_RETRIES Tentativi massimi di retry (backoff esponenziale + jitter) 3
LOG_LEVEL Livello di log Pino (debug/info/warn/error) info

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'interfaccia AuthStrategy per 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_PARTS più l'overhead base64 (una richiesta può contenere fino a MAX_DOCUMENT_PARTS parti immagine, es. fronte/retro, ciascuna fino a MAX_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-For per 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 dichiarazione DOCTYPE viene 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 --build

Il container:

  • è stateless e condivisibile tra più client MCP contemporanei;
  • non pubblica alcuna porta sulla rete pubblica: docker-compose.yml lega la porta esterna (3501, porta interna sempre 3000) esclusivamente all'indirizzo dell'interfaccia WireGuard locale (10.10.0.4, wg0), non a 0.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 /tmp come tmpfs (docker-compose.yml). Motivo: pdftoppm non è 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 /tmp come 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, /tmp sarebbe 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 3501 sull'interfaccia WireGuard locale (wg0, IP 10.10.0.4; porta interna sempre 3000, 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.it

Configurazione 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 --help per 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:

  1. Per ogni campo vince il valore proveniente dalla pagina con confidence più alta.
  2. Se due pagine forniscono valori diversi e non nulli per uno stesso campo identificativo, viene aggiunto un warning esplicito in warnings con entrambi i valori in conflitto (il valore a confidence più alta viene comunque scelto). Per extract_demographics: partita_iva, codice_fiscale, ragione_sociale. Per extract_invoice: numero, cedente.partita_iva, cessionario.partita_iva.
  3. La confidence complessiva del risultato riflette il merge e non è mai superiore alla confidence del valore scelto per ciascun campo.
  4. Il superamento di MAX_PDF_PAGES (per un PDF) o di MAX_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 a ExtractionService.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 via parseStructuredDocument, 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 containerizzato

I 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).

Recommended Servers

playwright-mcp

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.

Official
Featured
TypeScript
Magic Component Platform (MCP)

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.

Official
Featured
Local
TypeScript
Audiense Insights MCP Server

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.

Official
Featured
Local
TypeScript
VeyraX MCP

VeyraX MCP

Single MCP tool to connect all your favorite tools: Gmail, Calendar and 40 more.

Official
Featured
Local
graphlit-mcp-server

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.

Official
Featured
TypeScript
Kagi MCP Server

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.

Official
Featured
Python
E2B

E2B

Using MCP to run code via e2b.

Official
Featured
Neon Database

Neon Database

MCP server for interacting with Neon Management API and databases

Official
Featured
Exa Search

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.

Official
Featured
Qdrant Server

Qdrant Server

This repository is an example of how to create a MCP server for Qdrant, a vector search engine.

Official
Featured