---
title: Docling
description: Optional MCP or CLI Docling providers for DoclingTools, and the PDFBox-first PdfTools fallback.
---

# Docling

Shipped in **0.14.0** ([TAN-3648](https://linear.app/tansuasici-workspace-1/issue/TAN-3648/tnsai-tools-docling-integration-for-advanced-document-parsing-pdf), framework PR #153, commit `eeb16b48`). `DoclingTools` is the 63rd POJO toolkit and contributes one `@Tool` method.

`DoclingTools` converts a **local** document through [Docling](https://github.com/docling-project/docling). TnsAI does **not** bundle Docling, Python, or models. You install one of:

- the official Docling **MCP** server, sharing the same filesystem as the JVM so the admitted source path is resolvable
- a local `docling` **CLI** on `PATH` (or an explicit executable path)

`PdfTools` stays PDFBox-first. Docling page rendering is an **explicit** constructor argument and runs only when PDFBox cannot open the admitted file. The default `PdfTools` constructors use `DoclingProvider.noOp()`.

See the framework [Changelog](../../help/changelog.md) — the 0.14.0 entry (2026-08-18) covers it.

## Register

```java
import com.tnsai.agents.AgentBuilder;
import com.tnsai.enums.BuiltInTool;

agentBuilder.builtInTools(BuiltInTool.DOCLING_TOOLS);
```

The LLM-visible method is `docling_parse`. It returns **Markdown only**, truncated at 100_000 characters. Structured tables, formulas, figures, and lossless Docling JSON require the Java `parse(Path)` API.

The no-arg `DoclingTools` constructor probes a `docling` executable lazily. `DoclingTools.builder()` starts from `DoclingProvider.noOp()` until you call `docling(...)`.

## MCP vs CLI

| | MCP (`DoclingProvider.mcp`) | CLI (`DoclingProvider.subprocess`) |
|---|---|---|
| Runtime | Official Docling MCP server; TnsAI is an MCP client | Local `docling` process via `ProcessBuilder` — **never** a shell |
| Trust | String factory accepts **loopback HTTP(S) only** (`localhost` / `127.0.0.1` / `::1`), no user-info in the URL. Remote servers need a caller-supplied `McpClient` whose server can resolve the same admitted path | Executable name or path only (no `\0`, max 4_096 chars). The source path is an argv element |
| Deploy | MCP server + shared filesystem (or a custom client that can see the file) | `docling` installed; Python/models live in that install |
| Availability | `available()` is true for a validated loopback URL | Cached `docling --version` probe, 5 s timeout. Missing binary → `available() == false` |
| On miss | Conversion throws | `parse` delegates to `noOp()`, which throws `IOException("Docling is unavailable; configure an MCP endpoint or install the docling CLI")` |
| Timeout | 5 minutes per MCP tool call; 200 ms minimum interval between calls | 5 minutes per conversion; temp output capped at 64 MiB |
| Output caps | Exported Markdown capped at 1_000_000 characters | Each `.md` / `.json` / page PNG read capped at 10 MiB |
| MCP tools | `convert_document_into_docling_document`, then `export_docling_document_to_markdown` | `docling convert --to md --to json` |

Both providers go through `FileGuard` and `DoclingTools.DEFAULT_EXTENSIONS` (`pdf`, Office, HTML, Markdown, images, `wav`/`mp3`/`vtt`, `xml`, `latex`/`tex`).

## Configure MCP

```java
import com.tnsai.tools.document.docling.DoclingProvider;
import com.tnsai.tools.document.docling.DoclingTools;
import com.tnsai.tools.document.docling.DocumentResult;

DoclingTools tools = DoclingTools.builder()
    .docling(DoclingProvider.mcp("http://127.0.0.1:9000/mcp"))
    .build();

DocumentResult result = tools.parse(Path.of("/data/paper.pdf"));
String markdown = result.markdown();
boolean structured = result.hasStructuredContent();
```

A pre-connected client is `DoclingProvider.mcp(mcpClient)` when the server is not loopback but can still read the admitted path.

## Configure CLI

```java
DoclingTools tools = DoclingTools.builder()
    .docling(DoclingProvider.subprocess("docling"))
    .build();

DocumentResult result = tools.parse(Path.of("/data/paper.pdf"));
```

Or `new DoclingTools()` — same lazy `docling` probe, `FileGuardConfig.defaults()`.

## `PdfTools` fallback

```java
import com.tnsai.tools.document.docling.DoclingProvider;
import com.tnsai.tools.file.FileGuardConfig;
import com.tnsai.tools.file.PdfTools;

PdfTools pdf = new PdfTools(
    FileGuardConfig.defaults(),
    DoclingProvider.subprocess("docling"));
```

PDFBox still renders first. Docling `renderPages` runs only after PDFBox fails to open the file. No configured provider (`noOp()`) means that failure stays a PDFBox error.

## Not this page

- FILE RAG ingest extractors (`PdfContentExtractor`, office SPI) — [document formats](../rag/index.md)
- Heading-aware analysis split — SmartDocumentSegmenter, not Docling
- Installing Docling itself — upstream [Docling docs](https://github.com/docling-project/docling)
