Sezzlee PDF

io.github.sezzlee由 GitHub 账号 sezzlee 发布v0.1.2更新于 Oct 24, 2026

Extract text from local PDF documents with optional pluggable OCR. Read-only, no network.

已验证STDIO仅桌面文件与存储知识与记忆

概览

AI 生成的概览

只读的本地 MCP 服务器,让助手在沙箱目录内列出、描述、读取和搜索 PDF 文档,可选接入 OCR。

可以做什么
  • 在沙箱根目录下列出可读取的 PDF 候选文件,支持子目录、匹配模式和结果数量限制
  • 描述文档:页数、文档类型、各页是否需要 OCR 以及服务器能力
  • 以 Markdown 读取指定页面,页码从 1 开始,支持游标续读
  • 对提取出的文本做字面搜索,返回页码和匹配上下文
  • 报告搜索覆盖情况,标出仍需 OCR 的页面
适用场景
适合助手需要基于本地 PDF 集合(如报告、论文、扫描件)回答问题,又不希望把文件上传到外部服务的场景。它面向只读的文档查阅与文本搜索,而非编辑或格式转换。
运行要求
通过 npx 从 npm 包 @sezzlee/pdf-mcp 以 stdio 在本地运行,必须传入 PDF 源根目录路径作为参数。OCR 默认关闭,需用 --ocr 指定提供光栅化器和识别提供方的模块。平台支持范围较窄:缺少对应二进制的主机会在启动时以退出码 3 停止。
安装前请注意
启用 OCR 会把页面图像发送给所注入模块使用的提供方,像素数据会离开本进程,因此 OCR 默认关闭且需在每次调用时显式请求。若提供方一直不返回,其占用的槽位会保持到服务器重启。服务器本身不访问网络,且为只读。

安装

在 SourceWeft 中

  1. 打开 控制台中的 Sezzlee PDF,将其添加到工作区。
  2. 为需要使用其工具的对话启用该服务。

Desktop only,通过 STDIO。 STDIO 服务会启动本地进程,因此需要 SourceWeft 桌面宿主。

其他 MCP 客户端

参照 仓库 中的启动说明。

README

@sezzlee/pdf-mcp

A read-only, sandboxed MCP server for local PDF documents. It builds on @sezzlee/file-core, which supplies sandbox path resolution, the response budget, the error envelope, cursors and the document cache. Text extraction uses @firecrawl/pdf-inspector. The server never reaches the network: OCR is optional, and when it is enabled, the connection decision belongs entirely to whoever supplies the two injected ports.

Quick start

json
{  "mcpServers": {    "pdf": {      "command": "npx",      "args": ["-y", "@sezzlee/pdf-mcp", "/path/to/documents"]    }  }}

To enable OCR, point --ocr at a module whose default export is an OcrBinding (see Configuration and OCR):

json
{  "mcpServers": {    "pdf": {      "command": "npx",      "args": [        "-y",        "@sezzlee/pdf-mcp",        "/path/to/documents",        "--ocr",        "/path/to/ollama-binding.js"      ]    }  }}

Tools

ToolWhat it doesKey arguments
list_documentsLists readable PDF candidates under the root; never opens a filesubdirectory, pattern, maxResults
describe_documentPage count, document type, per-page OCR need, server capabilitiesfilePath
read_pagesReads selected pages as Markdown, page numbers 1-basedfilePath, pages, maxPages, ocr, cursor
find_in_documentLiteral search over extracted text, with page number and match contextfilePath, query, matchMode, caseSensitive, ocr, cursor

If the file path is already known, describe_document is optional: read_pages and find_in_document work from filePath alone.

Configuration

CLI (sezzlee-pdf <pdf-source-root> [--ocr <module>]):

ArgumentDefaultMeaning
<pdf-source-root>requiredSandbox root; every readable path is resolved and checked against it.
--ocr <module>nonePath or package specifier to a module whose default export is { rasterizer, provider }. Absent means no OCR: read_pages/find_in_document refuse ocr: true and describe_document reports capabilities.ocr: false.

Programmatic (createPdfMcpServer(root, options)):

OptionDefaultMeaning
documentCacheSize2Extracted documents held in memory at once, 1–16. A budget, not a guard: each cached document holds every page's Markdown on the heap.
maxConcurrentListings4Concurrent list_documents calls before a request is refused.
maxConcurrentExtractions2Concurrent PDF extractions before a request is refused.
ocrnoneAn OcrBinding ({ rasterizer, provider, dpi?, maxPagesPerCall?, timeoutMs? }). Neither port is a dependency of this package.

OCR

The server does not perform OCR; it supports it. Two ports are injected — neither the raster library nor the model client is a dependency of this package.

ts
interface PageRasterizer {  render(job: {    bytes: Buffer;    pages: readonly number[];    dpi: number;    signal?: AbortSignal;  }): Promise<readonly RenderedPage[]>; // { page, image, mediaType }}
interface OcrProvider {  readonly name: string;  recognize(job: {    pages: readonly RenderedPage[];    signal?: AbortSignal;  }): Promise<readonly RecognizedPage[]>; // { page, markdown, confidence? }}

The server does exactly this: it renders the pages the text layer could not answer, recognizes the resulting images, substitutes the result for that page, and marks it source: "ocr".

Programmatic binding:

ts
createPdfMcpServer(root, {  ocr: { rasterizer: pdfjsRasterizer, provider: ollamaProvider, dpi: 200 },});

CLI binding — a module whose default export matches OcrBinding, loaded with --ocr <module> (see examples/ollama-binding.ts in this package for a working example built from @sezzlee/pdf-raster-pdfjs and @sezzlee/ocr-ollama):

ts
import { createPdfjsRasterizer } from "@sezzlee/pdf-raster-pdfjs";import { createOllamaOcrProvider } from "@sezzlee/ocr-ollama";import type { OcrBinding } from "@sezzlee/pdf-mcp";
const binding: OcrBinding = {  rasterizer: createPdfjsRasterizer(),  provider: createOllamaOcrProvider({    baseUrl: process.env["SEZZLEE_PDF_OCR_URL"] ?? "http://127.0.0.1:11434",    model: process.env["SEZZLEE_PDF_OCR_MODEL"] ?? "deepseek-ocr:3b",  }),  dpi: 200,};
export default binding;

OCR rules

  • ocr: true with no provider bound is an error (ocr_unavailable), never a silent fall back to the text layer. Otherwise an agent that asked for OCR could mistake "nobody transcribed this document" for "no match". describe_document → capabilities.ocr reports whether one is bound.
  • An empty transcription does not count as an answer. If the provider returns empty text, the page stays needsOcr — "the model returned nothing" is not the same as "this page is blank".
  • An unrequested page is ignored. The ports may reorder, skip, or invent pages; results are matched to the page number that was requested, so a provider cannot overwrite a page the text layer already trusted.
  • Bytes leave the process. Binding a provider means those pages' pixels go somewhere else. OCR therefore defaults to off and is requested explicitly on every call.
  • A page is not re-transcribed on every call. Transcriptions are cached by document fingerprint plus page number. The cache is not a progress log: a search sends only the pages after the cursor to OCR and carries coverage forward in the cursor, so even a large document is eventually walked to the end from cache.
  • An expired job does not release its slot. Neither the engine nor an injected port can cancel work that has already started; the slot stays held until the work actually finishes. A provider that never returns can only be recovered by restarting the server — reclaiming the slot on a timer would only postpone the limit.

Rules

  • Every page number on the public surface is 1-based. The underlying library returns some fields 0-based and others 1-based within the same result object. The whole conversion lives in src/engine/, and lint bans importing @firecrawl/pdf-inspector outside that folder — a second import site would reintroduce the bug (measured on 1.23.0: PageMarkdownResult.page is 0-based, sibling pagesNeedingOcr is 1-based).
  • Page selection is validated before it reaches the library. An out-of-range index otherwise comes back as a phantom page marked needsOcr; a negative index wraps through u32. Both would look like a real page to the agent.
  • describe_document never builds its OCR page list from the classifier. classifyPdf marks every page of a document that contains a single scanned page. The list comes from the per-page extraction instead. classificationConfidence is confidence in the classifier's own verdict, not in any extracted text's accuracy.
  • An unreadable page is never presented as an empty one. A scanned page returns needsOcr: true; empty is true only for a page the engine trusted and found genuinely blank.
  • Search coverage is reported on every response. When any page needs OCR, coverageComplete is false, and an empty match list is not proof the query is absent from the whole document.
  • A document is extracted once, in full; the response is paginated. Measurement showed that requesting a single page cuts extraction cost by a third, not 200-fold — the fixed cost dominates. Cursor identity depends on document content and OCR mode, not page size. An explicit pages selection is preserved across a cursor: it carries forward the rest of that selection and never wanders into unselected pages. A cursor resuming inside a page carries that page's text fingerprint; if the text changed since transcription, the response is stale_cursor rather than silently skipping content.
  • The server never reaches the network. fetch, node:http, node:net and their siblings are lint-banned inside src/, and the ban is repeated in every layer's override — a later oxlint override replaces no-restricted-imports rather than merging with it, so a single top-level ban would not apply everywhere.
  • Platform support is narrow. The engine ships no darwin-x64 binary and its wasm fallback is unpublished. On an unsupported host the server stops at startup with a one-line message (exit code 3).

Development

sh
pnpm turbo run build --filter=@sezzlee/pdf-mcpnode packages/servers/pdf-mcp/dist/cli.js /path/to/documents

Tests: pnpm turbo run test --filter=@sezzlee/pdf-mcp (Vitest). A live-Ollama suite (test/ocr-live.spec.ts) is skipped unless SEZZLEE_PDF_OCR_URL is set; it also reads SEZZLEE_PDF_OCR_MODEL (default deepseek-ocr:3b). Everything else in test/ocr.spec.ts runs against fake ports; the live suite only proves the two real adapters compose.

来源:packages/servers/pdf-mcp/README.md,提交 37f6849

工具

0
SourceWeft 尚未在沙箱中启动过该软件包,因此其工具未知。

版本历史

1
  1. v0.1.2最新Oct 11, 2026

评分

已满足适用检查项的 50%

该评分衡量此收录条目的完整程度,依据注册中心条目、端点与公开来源计算得出。它不是对服务器本身的评价,也不表示服务器安全或当前可用:可连通性是带日期的检查结果,评分也不包含安装量或用户评价。

  • 近 30 天内可连通25 分

    请保持端点能够响应 MCP 握手;SourceWeft 每 30 天探测一次。

  • 已声明认证方式10 分

    请在注册中心条目中声明凭据请求头,或保持端点能够响应探测,以便判定其认证方式。

  • 已列出工具15 分

    请让服务器在无凭据时响应 tools/list,或发布可在沙箱中无凭据启动的软件包。

  • 描述必需15 分
  • 图标10 分
  • 文档15 分
  • 许可证10 分