# Analisi tecnica e piano di sviluppo POC - LiteParse in `sophiadeveloper/mcp-servers`

- **Repository target:** `sophiadeveloper/mcp-servers`
- **Branch target:** `rework`
- **Ambito:** `office-node`, tool `pdf_document`, azione `export_text`
- **Data verifica:** 2026-06-05
- **Stato documento:** analisi completata per avvio sviluppo POC
- **Verdetto operativo:** **GO condizionato per PR POC piccola e opt-in. NO-GO per cambio default da PDF.js a LiteParse.**

---

## 1. Sintesi esecutiva

LiteParse puo' essere integrato nel repo come backend sperimentale e opzionale di `office-node`, limitato a:

```text
pdf_document.export_text
```

La strategia corretta e':

```text
default = pdfjs
backend opzionale = liteparse
backend di fallback = auto
```

Il contratto esistente `save_path` / `resource_link` / registry Office / ingestione `docs-node` deve restare invariato. Il POC non deve modificare `docs-node`, non deve cambiare `document_convert`, non deve introdurre refactor cross-server e non deve cambiare il comportamento delle azioni PDF esistenti (`metadata`, `read_all`, `read_page`, `read_range`).

La verifica interna sul tema OCR mostra che non esiste una milestone OCR attiva separata nel backlog futuro, ma esiste un limite operativo gia' documentato: `mcp-office-expert` richiede di segnalare il limite OCR quando un PDF e' scannerizzato o image-only, e l'analisi Pandoc esclude Pandoc come soluzione adeguata per quei PDF. Il POC LiteParse e' quindi il punto corretto per agganciare questa miglioria, trattandola come capability sperimentale misurabile e non come promessa stabile nella prima PR.

La verifica esterna del 2026-06-05 riduce alcune incertezze iniziali:

- il package npm e' `@llamaindex/liteparse`;
- la versione osservata nel `packages/node/package.json` upstream e' `2.0.3`;
- la licenza dichiarata su npm e repository e' `Apache-2.0`;
- il repository ufficiale e' `run-llama/liteparse`;
- il package Node espone una API ESM con `LiteParse`, `new LiteParse(...)`, `parser.parse(...)`, `result.text` e `result.pages`;
- il core e' Rust e i binding Node sono basati su `napi-rs`;
- i binari/prebuild risultano pubblicati per piu' target Windows, Linux e macOS;
- il progetto dichiara uso di PDFium e Tesseract per parsing/OCR.

Restano da verificare localmente prima del merge:

- installazione reale su Windows e Ubuntu/WSL del workspace;
- compatibilita' con Node.js 24.x nel runtime effettivo;
- comportamento su PDF reali del dominio;
- overhead tempo/memoria;
- qualita' Markdown rispetto a PDF.js.

---

## 2. Fonti analizzate

### Fonti locali osservate

| Fonte | Evidenza rilevante |
|---|---|
| `office-node/package.json` | Server ESM, dipendenza attuale `pdfjs-dist`, assenza di `@llamaindex/liteparse`. |
| `office-node/index.js` | `pdf_document.export_text` usa PDF.js, scrive `save_path`, registra artifact con `buildWriteArtifactResponse`. |
| `docs/office-docs-artifact-contract.md` | Contratto Office -> Docs basato su file locale `save_path` e `resource_link` `artifact://office/...`. |
| `tests/smoke/office-docs-bridge.smoke.mjs` | Smoke gia' disponibile per PDF -> Markdown -> registry Office -> `docs-node`. |
| `docs/server-capability-matrix.md` | `office-node` espone artifact testuali tramite `pdf_document.export_text` e `document_convert.convert`. |
| `skills/mcp-office-expert/SKILL.md` | Documenta il limite attuale: per PDF scannerizzati o image-only bisogna segnalare il limite OCR/estrazione prima della pipeline Docs. |
| `docs/pandoc/analisi-tecnica-supporto-pandoc-mcp-skills.md` | Valuta Pandoc non adatto a PDF scannerizzati o image-only; conferma che il problema resta nell'area `pdf_document`. |
| `docs/future-backlog-mcp-skills.md` | Non contiene una voce OCR attiva separata; il backlog futuro attivo riguarda solo Web UI installer/control panel. |
| `AGENTS.md` | Compatibilita' legacy, schema strict, array con `items`, setup/restart se cambiano dipendenze/runtime. |
| `docs/mcp-skills-agents-development-guide.md` | Cambi incrementali, schema check MCP, smoke, documentazione coerente. |

### Fonti esterne verificate

| Fonte | Evidenza rilevante |
|---|---|
| `https://www.npmjs.com/package/@llamaindex/liteparse` | Package `@llamaindex/liteparse`; metadata da riverificare in PR con `npm view`. |
| `https://github.com/run-llama/liteparse` | Repository ufficiale, licenza `Apache-2.0`, descrizione Rust parser per documenti. |
| `https://github.com/run-llama/liteparse/tree/main/packages/node` | Binding Node, install `npm install @llamaindex/liteparse`, esempio API con `LiteParse`. |
| `https://github.com/run-llama/liteparse/blob/main/packages/node/README.md` | API Node: `import { LiteParse }`, `new LiteParse(...)`, `await parser.parse(path)`, `result.text`, `result.pages`. |
| `https://raw.githubusercontent.com/run-llama/liteparse/main/packages/node/package.json` | `type: "module"`, versione `2.0.3`, engines `node >=18.0.0`, build `napi-rs`, optional native packages, licenza `Apache-2.0`. |

Nota: le fonti esterne sono instabili per natura. In PR va ripetuta la verifica con `npm view @llamaindex/liteparse` e install locale, usando la versione bloccata nel lockfile.

---

## 3. Fatti accertati

### 3.1 Il punto di integrazione corretto e' `office-node`

`office-node` e' il server MCP gia' responsabile di Word, Excel, PDF e conversioni opzionali. Il tool `pdf_document` gestisce oggi:

```text
metadata
read_all
read_page
read_range
export_text
```

L'azione `export_text` e' l'unica da estendere nel POC perche':

- produce artifact testuali su `save_path`;
- emette `resource_link`;
- e' gia' collegata al workflow `docs-node`;
- ha smoke end-to-end esistente.

### 3.2 Il backend attuale e' PDF.js

Il codice importa PDF.js da:

```js
import { getDocument } from "pdfjs-dist/legacy/build/pdf.mjs";
```

L'estrazione usa `page.getTextContent()` e normalizza gli item testuali con `normalizePdfTextItems`. Questo e' adatto a PDF testuali semplici, ma non e' un parser di layout strutturato.

### 3.3 LiteParse ha API Node ESM verificata

La documentazione Node ufficiale mostra l'uso:

```js
import { LiteParse } from "@llamaindex/liteparse";

const parser = new LiteParse();
const result = await parser.parse(filePath);
console.log(result.text);
```

La README Node documenta anche accesso a dati strutturati per pagina:

```js
for (const page of result.pages) {
  console.log(`Page ${page.pageNum}: ${page.textItems.length} text items`);
}
```

La conversione `format="md"` non va quindi basata su un presunto output Markdown nativo della API Node. Per il POC:

```text
format=txt -> result.text normalizzato
format=md  -> wrapper Markdown locale costruito da result.text/result.pages
```

### 3.4 Il package e' nativo ma ha prebuild

Il package Node usa `napi-rs` e pubblica pacchetti/prebuild per target come Windows x64, Linux x64/arm64, macOS x64/arm64. Questo riduce il rischio di installazione, ma non lo elimina.

Il POC deve comunque verificare:

- installazione senza Rust toolchain;
- installazione con Node.js 24.x;
- caricamento runtime su Windows;
- caricamento runtime su Ubuntu/WSL.

### 3.5 PDFium e Tesseract sono parte del profilo tecnico

LiteParse dichiara uso di PDFium e Tesseract. Questo e' positivo per OCR/layout, ma aumenta il profilo di rischio:

- licenze delle componenti native/binarie da confermare nel contesto di distribuzione;
- dimensione package;
- possibili differenze OS;
- consumi CPU/memoria su PDF scannerizzati;
- aspettative OCR da non promettere come feature stabile nel POC.

---

## 4. Decisioni architetturali

### D1 - Backend default invariato

```text
Decisione: default resta pdfjs.
Motivo: compatibilita' legacy e rischio nativo/qualita' non ancora misurato.
```

Chiamate esistenti senza `backend` devono produrre output equivalente all'attuale.

### D2 - LiteParse solo opt-in

```text
Decisione: backend="liteparse" abilita LiteParse esplicitamente.
Motivo: POC controllato, reviewabile e senza rischio su flussi esistenti.
```

Se LiteParse non e' installato o fallisce, `backend="liteparse"` deve restituire errore esplicito. Non deve fare fallback silenzioso.

### D3 - Modalita' `auto` ammessa solo come fallback esplicito

```text
Decisione: backend="auto" prova LiteParse e poi PDF.js su errore recuperabile.
Motivo: consente sperimentazione controllata preservando artifact.
```

Il fallback deve essere visibile in `structuredContent.warnings`.

### D4 - Nessun cambio a `docs-node`

```text
Decisione: docs-node resta invariato.
Motivo: il contratto file-based via save_path e' sufficiente.
```

### D5 - Packaging consigliato: `optionalDependencies`

```text
Decisione consigliata: aggiungere @llamaindex/liteparse in optionalDependencies.
Motivo: backend opzionale, package nativo, default PDF.js da proteggere.
```

Alternativa ammessa dopo verifica install matrix positiva:

```text
dependencies
```

Scelta sconsigliata per la PR POC:

```text
nessuna dipendenza stabile
```

perche' impedirebbe smoke riproducibili in CI/locale.

### D6 - Collegamento con miglioria OCR

```text
Decisione: collegare il POC LiteParse al limite OCR documentato in mcp-office-expert.
Motivo: LiteParse dichiara OCR/Tesseract e Pandoc non copre PDF scannerizzati o image-only.
```

Questo collegamento non cambia il perimetro della prima PR:

```text
PR POC = backend LiteParse opt-in + metriche OCR
non = OCR stabile general-purpose
```

La prima PR deve quindi:

- mantenere `ocrEnabled` configurato in modo esplicito nel wrapper LiteParse;
- riportare metadata minimi OCR quando disponibili o inferibili;
- includere nel benchmark almeno un PDF scannerizzato/image-only;
- aggiornare `mcp-office-expert` solo se il POC dimostra che il limite puo' essere attenuato in modo riproducibile.

Non introdurre invece un nuovo tool OCR dedicato finche' non sono chiari output, performance, licenze e casi reali.

---

## 5. Contratto MCP proposto

### Schema `pdf_document`

Aggiungere solo a `pdf_document.inputSchema.properties`:

```js
backend: {
  type: "string",
  enum: ["pdfjs", "liteparse", "auto"],
  description: "Backend export PDF. Default: pdfjs.",
}
```

Non aggiungere array. Se in futuro si aggiungono array, ogni nodo `type: "array"` deve dichiarare `items`.

### Semantica

| `backend` | Comportamento |
|---|---|
| assente | Alias di `pdfjs`. |
| `pdfjs` | Comportamento attuale. |
| `liteparse` | Usa LiteParse; errore se import o parse falliscono. |
| `auto` | Prova LiteParse; su errore recuperabile usa PDF.js e aggiunge warning. |

### `structuredContent` minimo

Per tutti i backend:

```json
{
  "ok": true,
  "tool": "pdf_document",
  "action": "export_text",
  "save_path": "...",
  "resource_link": "artifact://office/...",
  "mime_type": "text/markdown",
  "pages": 1,
  "format": "md",
  "backend_requested": "pdfjs",
  "backend_used": "pdfjs",
  "ocr_requested": false,
  "ocr_used": false,
  "warnings": []
}
```

Per fallback:

```json
{
  "backend_requested": "auto",
  "backend_used": "pdfjs",
  "warnings": ["LiteParse failed; fallback to PDF.js: <sanitized detail>"]
}
```

### Errori

Per `backend="liteparse"` e import non disponibile:

```json
{
  "ok": false,
  "error_code": "LITEPARSE_UNAVAILABLE",
  "recoverable": true,
  "detail": "LiteParse non disponibile o non caricabile."
}
```

Per parse fallito:

```json
{
  "ok": false,
  "error_code": "LITEPARSE_ERROR",
  "recoverable": true,
  "detail": "<messaggio sanitizzato>"
}
```

Nel codice attuale molti errori tool sono convertiti nel catch generale in `content: Errore: ...`. Il POC puo' restare coerente con questo pattern, ma deve usare messaggi sanitizzati e distinguibili.

---

## 6. Design implementativo

### 6.1 Refactor minimo PDF.js

Estrarre il comportamento attuale senza cambiarlo:

```js
async function extractPdfTextWithPdfJs(filePath, format) {
  return withPdfDocument(filePath, async (pdfDocument) => {
    const metadata = await readPdfMetadata(pdfDocument);
    const pages = await readPdfPages(pdfDocument, 1, pdfDocument.numPages);
    const output =
      format === "md"
        ? formatPdfPagesAsMarkdown(filePath, metadata, pages)
        : formatPdfPagesAsText(pages);

    return {
      output,
      pageCount: pages.length,
      backend_used: "pdfjs",
      warnings: [],
    };
  });
}
```

### 6.2 Loader LiteParse

Usare import dinamico per non caricare il modulo nativo quando non richiesto:

```js
async function loadLiteParse() {
  try {
    return await import("@llamaindex/liteparse");
  } catch (error) {
    const detail = normalizeErrorMessage(error);
    const wrapped = new Error(`LiteParse non disponibile: ${detail}`);
    wrapped.code = "LITEPARSE_UNAVAILABLE";
    wrapped.recoverable = true;
    throw wrapped;
  }
}
```

### 6.3 Wrapper LiteParse

```js
async function extractPdfTextWithLiteParse(filePath, format) {
  const { LiteParse } = await loadLiteParse();
  if (typeof LiteParse !== "function") {
    const error = new Error("Export LiteParse non valido: LiteParse non e' una funzione.");
    error.code = "LITEPARSE_UNAVAILABLE";
    error.recoverable = true;
    throw error;
  }

  const parser = new LiteParse({
    ocrEnabled: true,
    quiet: true,
  });
  const result = await parser.parse(filePath);
  const output =
    format === "md"
      ? formatLiteParseResultAsMarkdown(filePath, result)
      : normalizeLiteParseText(result);

  return {
    output,
    pageCount: extractLiteParsePageCount(result),
    backend_used: "liteparse",
    ocr_requested: true,
    ocr_used: inferLiteParseOcrUsed(result),
    warnings: [],
  };
}
```

La shape precisa di `result` va verificata nello spike locale. In base alla README Node, il wrapper deve supportare almeno:

```text
result.text
result.pages
result.pages[].pageNum
result.pages[].textItems
```

Se `result.text` non e' una stringa utilizzabile, errore `LITEPARSE_ERROR`.

Nel POC `inferLiteParseOcrUsed(result)` deve essere conservativo:

```text
true  -> solo se LiteParse espone un segnale esplicito o se il test controllato dimostra OCR su fixture image-only
false -> backend pdfjs o LiteParse senza segnale OCR
null  -> LiteParse usato, OCR richiesto, ma uso effettivo non inferibile
```

Non inventare metadata OCR se la API non li espone.

### 6.4 Dispatch backend

```js
async function extractPdfText({ filePath, format, backend }) {
  if (backend === "pdfjs") {
    return extractPdfTextWithPdfJs(filePath, format);
  }

  if (backend === "liteparse") {
    return extractPdfTextWithLiteParse(filePath, format);
  }

  if (backend === "auto") {
    try {
      return await extractPdfTextWithLiteParse(filePath, format);
    } catch (error) {
      if (error?.recoverable === false) throw error;
      const fallback = await extractPdfTextWithPdfJs(filePath, format);
      return {
        ...fallback,
        warnings: [
          ...fallback.warnings,
          `LiteParse failed; fallback to PDF.js: ${normalizeErrorMessage(error)}`,
        ],
      };
    }
  }

  throw new Error(`Backend PDF non supportato: ${backend}`);
}
```

### 6.5 Handler `export_text`

La logica di scrittura deve restare unica:

```js
const backend = args.backend || "pdfjs";
if (!["pdfjs", "liteparse", "auto"].includes(backend)) {
  throw new Error(`Backend PDF non supportato: ${backend}. Usa 'pdfjs', 'liteparse' o 'auto'.`);
}

const extraction = await extractPdfText({
  filePath: resolvedPath,
  format,
  backend,
});

fs.mkdirSync(path.dirname(savePath), { recursive: true });
fs.writeFileSync(savePath, extraction.output, "utf8");

const response = buildWriteArtifactResponse(...);
response.structuredContent.pages = extraction.pageCount;
response.structuredContent.format = format;
response.structuredContent.backend_requested = backend;
response.structuredContent.backend_used = extraction.backend_used;
response.structuredContent.ocr_requested = extraction.ocr_requested ?? false;
response.structuredContent.ocr_used = extraction.ocr_used ?? false;
response.structuredContent.warnings = extraction.warnings;
return response;
```

### 6.6 Wrapper Markdown compatibile

Problema: la README Node corrente documenta `result.text` e dati strutturati per pagina, non un output Markdown nativo. Il POC deve quindi generare localmente il Markdown compatibile.

Decisione POC:

- per `format="txt"` scrivere `result.text` normalizzato;
- per `format="md"` costruire un wrapper Markdown locale con header compatibile.

Formato consigliato:

```md
# <nome-file>

- Source: <file_path>
- Backend: liteparse
- Pages: <n|unknown>

<result.text normalizzato o testo pagina-per-pagina ricostruito da result.pages>
```

Questo evita regressioni nei consumer che si aspettano titolo/source e rende visibile il backend.

---

## 7. File da modificare nella PR POC

```text
office-node/package.json
office-node/package-lock.json
office-node/index.js
tests/smoke/office-node-liteparse.smoke.mjs
tests/smoke/office-docs-bridge.smoke.mjs
docs/office-docs-artifact-contract.md
docs/server-capability-matrix.md
README.md
skills/mcp-office-expert/SKILL.md
```

Note:

- `package-lock.json` va aggiornato solo insieme a `package.json`.
- Se `@llamaindex/liteparse` entra in `optionalDependencies`, documentare che `backend="liteparse"` puo' fallire se il package non e' stato installato.
- `skills/mcp-office-expert/SKILL.md` va aggiornato solo se il POC conferma un comportamento OCR utile e riproducibile; fino ad allora deve continuare a segnalare il limite dei PDF scannerizzati/image-only.
- Dopo cambio dipendenze, documentare:

```text
node scripts/install-user-runtime.js
riavvio server MCP office-node nel client in uso
```

---

## 8. Piano PR eseguibile

### Step 1 - Dipendenza e install matrix

1. Aggiungere `@llamaindex/liteparse` in `office-node/optionalDependencies`.
2. Aggiornare lockfile.
3. Verificare installazione con Node.js 24.x:

```text
npm install
```

4. Annotare esito su:

```text
Windows
Ubuntu/WSL
```

### Step 2 - Refactor PDF.js senza cambio comportamento

1. Estrarre `extractPdfTextWithPdfJs`.
2. Usare la nuova funzione in `export_text`.
3. Eseguire lo smoke esistente:

```text
node tests/smoke/office-docs-bridge.smoke.mjs
```

Questo step deve passare prima di introdurre LiteParse.

### Step 3 - Backend schema e dispatch

1. Aggiungere `backend` nello schema.
2. Validare `backend` nel handler.
3. Aggiungere `backend_requested`, `backend_used`, `warnings`.
4. Verificare che `backend` assente equivalga a `pdfjs`.

### Step 4 - LiteParse opt-in

1. Aggiungere import dinamico.
2. Implementare `extractPdfTextWithLiteParse`.
3. Normalizzare output e page count.
4. Scrivere artifact tramite la stessa logica di PDF.js.

### Step 5 - `auto` fallback

1. Implementare fallback LiteParse -> PDF.js.
2. Aggiungere warning esplicito.
3. Testare fallback simulando import/parse fallito.

### Step 6 - Documentazione

Aggiornare:

- contratto Office -> Docs;
- capability matrix;
- README;
- nota setup/restart per dipendenze runtime.

---

## 9. Smoke e test richiesti

### 9.1 Nuovo smoke `tests/smoke/office-node-liteparse.smoke.mjs`

Scenario minimo:

```text
1. crea fixture PDF testuale minimale
2. avvia office-node
3. chiama pdf_document.export_text con backend="liteparse", format="md"
4. verifica save_path
5. verifica resource_link artifact://office/...
6. verifica registry Office
7. verifica structuredContent.backend_requested === "liteparse"
8. verifica structuredContent.backend_used === "liteparse"
9. verifica structuredContent.ocr_requested === true
10. verifica contenuto atteso nel Markdown
11. ripete con format="txt"
```

Se LiteParse non e' installabile nell'ambiente, lo smoke deve fallire in modo chiaro, non essere silenziato.

### 9.2 Regressione default

Estendere `tests/smoke/office-docs-bridge.smoke.mjs` o aggiungere controllo dedicato:

```text
export_text senza backend -> backend_used=pdfjs
contenuto Bridge Smoke ancora ricercabile in docs-node
resource_link invariato
registry invariato
```

### 9.3 Fallback `auto`

Scenario:

```text
1. forzare errore LiteParse con hook interno/test flag o path controllato
2. chiamare backend="auto"
3. verificare backend_used="pdfjs"
4. verificare warning non vuoto
5. verificare save_path scritto
6. verificare resource_link e registry
```

Non introdurre flag pubblici MCP solo per test se non servono. Preferire helper testabile o simulazione locale.

### 9.4 Schema check MCP

Validare:

```text
tools/list
schema pdf_document
tools/call export_text backend assente
tools/call export_text backend=pdfjs
tools/call export_text backend=liteparse
tools/call export_text backend=auto
tools/call export_text backend invalido
```

Requisito strict:

```text
nessun nodo type:"array" senza items
```

### 9.5 Test negativi

```text
file non esistente
file non .pdf
save_path assente
format invalido
backend invalido
PDF protetto
PDF corrotto
LiteParse non disponibile
LiteParse import fallito
LiteParse parse fallito con backend=liteparse
LiteParse parse fallito con backend=auto
```

### 9.6 OCR smoke/fixture sperimentale

Lo smoke OCR non deve essere gate bloccante della prima PR se manca una fixture affidabile. Deve pero' essere pianificato come verifica separata, preferibilmente con fixture piccola e deterministica:

```text
1. crea o usa un PDF image-only con testo noto
2. chiama pdf_document.export_text con backend="pdfjs"
3. verifica che PDF.js non estragga il testo oppure estragga EMPTY_EXTRACTED_TEXT
4. chiama pdf_document.export_text con backend="liteparse"
5. verifica che LiteParse estragga il testo noto
6. registra backend_used="liteparse"
7. registra ocr_requested=true
8. registra ocr_used=true se inferibile; altrimenti null con warning documentato
```

Se la fixture richiede asset binari o generazione immagini non banale, tenerla fuori dalla prima PR e usarla nel benchmark valore backend.

---

## 10. Benchmark valore backend

Il benchmark non e' uno smoke. Serve a decidere se LiteParse merita evoluzioni oltre il POC.

Dataset minimo:

```text
1. PDF testuale semplice
2. PDF multi-colonna
3. PDF con tabella
4. PDF scannerizzato
5. PDF misto immagini/testo
6. PDF lungo 100+ pagine
7. PDF protetto/corrotto
8. documento reale anonimizzato
```

Metriche:

```text
tempo parsing
picco memoria
dimensione output
ordine di lettura
qualita' Markdown
resa tabelle
qualita' OCR, se presente
comportamento su errore
compatibilita' docs-node
```

Criterio per valutare un futuro default `auto`:

```text
LiteParse migliora in modo misurabile PDF complessi senza regressioni install/runtime e senza overhead eccessivo.
```

Fino a quel benchmark:

```text
default resta pdfjs
```

---

## 11. Rischi e mitigazioni

| Rischio | Impatto | Mitigazione |
|---|---:|---|
| Package nativo non installabile in alcuni ambienti | Alto | `optionalDependencies`, import dinamico, default PDF.js. |
| Node.js 24.x non supportato da prebuild | Alto | Install matrix prima del merge; stop se fallisce. |
| OCR lento o pesante | Medio/Alto | Non promettere OCR stabile nel POC; benchmark separato. |
| Markdown generato dal wrapper incompatibile con consumer attuali | Medio | Header compatibile e contratto artifact invariato. |
| Fallback `auto` nasconde problemi | Medio | Warning obbligatorio e `backend_used` esplicito. |
| Licenze componenti native non accettabili | Alto | Verifica licenza package, repo e dipendenze native/binarie in PR. |
| Aumento lockfile/setup | Medio | Documentare setup/restart e mantenere PR piccola. |
| Regressione host MCP strict | Alto | Schema check `tools/list`; nessun array senza `items`. |

---

## 12. Criteri GO/NO-GO

### GO per merge POC

```text
[ ] default pdfjs invariato
[ ] @llamaindex/liteparse installabile nell'ambiente target verificato
[ ] Node.js 24.x verificato
[ ] backend assente/pdfjs/liteparse/auto validati
[ ] save_path invariato
[ ] resource_link invariato
[ ] registry Office invariato
[ ] office-docs-bridge smoke verde
[ ] nuovo smoke LiteParse verde
[ ] fallback auto testato
[ ] ocr_requested/ocr_used documentati e coerenti
[ ] limite OCR attuale di mcp-office-expert rivalutato con evidenza
[ ] schema check MCP verde
[ ] docs aggiornate
[ ] setup/restart documentati
```

### NO-GO

```text
[ ] installazione fallisce su Windows o Ubuntu/WSL senza workaround accettabile
[ ] import Node ESM non funziona in office-node
[ ] Node.js 24.x non supportato
[ ] LiteParse non produce output md/txt normalizzabile
[ ] impossibile preservare save_path/resource_link
[ ] licenza package o dipendenze non chiara/incompatibile
[ ] overhead memoria/tempo non accettabile gia' su PDF semplici
[ ] regressione dello smoke office-docs-bridge
```

Nota: OCR non riuscito su fixture scannerizzata non e' da solo NO-GO per il backend opt-in LiteParse, se il parsing testuale non-OCR funziona e il limite viene documentato. Diventa NO-GO solo se l'obiettivo dichiarato della PR viene promosso a "supporto OCR stabile".

---

## 13. Checklist pronta per sviluppo

```text
[ ] Verificare npm metadata: versione, licenza, engines, optional/native packages.
[ ] Aggiungere @llamaindex/liteparse a office-node/optionalDependencies.
[ ] Aggiornare package-lock.
[ ] Refactor export_text PDF.js in helper dedicato.
[ ] Aggiungere parametro backend nello schema pdf_document.
[ ] Implementare dispatch pdfjs/liteparse/auto.
[ ] Implementare loader dinamico LiteParse.
[ ] Implementare normalizzazione output LiteParse.
[ ] Aggiungere backend_requested/backend_used/warnings a structuredContent.
[ ] Aggiungere ocr_requested/ocr_used a structuredContent con semantica conservativa.
[ ] Aggiungere smoke office-node-liteparse.
[ ] Pianificare o aggiungere fixture OCR image-only, separando smoke base e benchmark OCR.
[ ] Estendere/verificare office-docs-bridge per default pdfjs.
[ ] Eseguire schema check MCP.
[ ] Aggiornare docs/office-docs-artifact-contract.md.
[ ] Aggiornare docs/server-capability-matrix.md.
[ ] Aggiornare README.md con setup/restart.
[ ] Aggiornare skills/mcp-office-expert/SKILL.md solo dopo evidenza OCR positiva.
[ ] Documentare risultati install matrix.
```

---

## 14. Raccomandazione finale

Procedere con una PR POC incrementale.

Implementazione autorizzata:

```text
office-node only
pdf_document.export_text only
LiteParse opt-in
default PDF.js invariato
optionalDependencies consigliato
docs-node invariato
contratto artifact invariato
OCR collegato come verifica sperimentale del limite PDF scannerizzati/image-only
smoke + schema check obbligatori
```

Implementazione non autorizzata in questa PR:

```text
cambio default a LiteParse o auto
OCR come feature stabile
nuovo tool OCR dedicato
modifiche a docs-node
modifiche a document_convert
refactor cross-server
benchmark come prerequisito runtime del tool
```

Il valore atteso del POC e' verificare se LiteParse migliora l'estrazione da PDF complessi mantenendo la compatibilita' dell'ecosistema MCP esistente. La decisione di adozione stabile va presa solo dopo install matrix, smoke, schema check e benchmark su PDF reali.
