file-analyzer-mcp
<p align="center">
<img src="./assets/readme/hero.svg" width="100%" alt="file-analyzer-mcp — personal read-only MCP server. Point it at a folder and pdf, docx, pptx, svg, png come back as text, tables, and images.">
</p>
## Proof
Point it at a folder and it hands back a real tree, not a guess:
```text
$ analyze_folder_structure({ "folder_path": "tests/fixtures" })
```
```json
{
"status": "OK",
"file_count": 6,
"supported_file_count": 6,
"extension_stats": [
{ "extension": ".pdf", "count": 2, "bytes": 723995 },
{ "extension": ".docx", "count": 1, "bytes": 35505 },
{ "extension": ".pptx", "count": 1, "bytes": 33067 },
{ "extension": ".svg", "count": 1, "bytes": 319 },
{ "extension": ".png", "count": 1, "bytes": 74 }
],
"tree_text": "fixtures/\n├── sample.docx (34.7KB)\n├── sample.pdf (431B)\n├── sample.png (74B)\n├── sample.pptx (32.3KB)\n├── sample.svg (319B)\n└── sample_scanned.pdf (706.6KB)"
}
```
Then read one of them — headings, paragraphs, and tables come back structured, not flattened:
```text
$ read_docx({ "file_path": "tests/fixtures/sample.docx" })
```
```json
{
"status": "OK",
"headings": ["테스트 문서"],
"text": "테스트 문서\n본문 첫 문단입니다.",
"tables": [{ "index": 0, "rows": [["A", "B"], ["1", "2"]] }]
}
```
Both calls are reproducible — clone this repo, run `uv sync --extra dev`, and call them
against `tests/fixtures/` yourself.
## What it is
A personal MCP server that reads whatever's in a folder — PDF, Word, PowerPoint, SVG,
PNG — and hands the structure and raw content back to whichever agent called it
(Claude Code, Claude Desktop, Codex). It doesn't summarize anything itself.
<p align="center">
<img src="./assets/readme/flow.svg" width="100%" alt="Request flow: your folder goes into file-analyzer-mcp, which extracts text, tables, and image content, which Claude or Codex then reads and summarizes.">
</p>
That's the whole design: the server extracts, the host interprets. No LLM API key
lives in this server. A PNG comes back as base64 image content, not a caption —
your host's own vision reads it. A scanned PDF only gets OCR'd when you ask for it.
## Tools
| Tool | Role |
|---|---|
| `analyze_folder_structure` | Recursive tree + per-extension stats for a folder |
| `list_supported_files` | Just the pdf/docx/pptx/svg/png paths, filtered |
| `read_pdf` | Per-page text. `ocr=True` runs Tesseract on pages with no text layer |
| `read_docx` | Paragraphs, headings, tables (.doc not supported) |
| `read_pptx` | Per-slide title, body, speaker notes (.ppt not supported) |
| `read_svg` | Size, tag counts, `<text>` content — no rasterizing |
| `read_image` | PNG as metadata + image content, for the host to look at directly |
Large documents paginate: `page_start`/`page_end` for PDF, `slide_start`/`slide_end`
for PPTX. Default caps are 30 pages / 60 slides — past that, the response's
`next_actions` tells you the next range to ask for.
## Install
```bash
uv sync --extra dev
```
Register with Claude Code:
```bash
claude mcp add -s user file-analyzer -- "<uv.exe path>" --directory "<this folder>" run python src/file_analyzer_mcp/server.py
```
On Windows, if `uv` was installed via pip it won't be on Claude's PATH — use `uv.exe`'s
full path (`pip show uv` to find it). Claude Desktop and Codex examples are in
[config/](config/): [claude_code.example.md](config/claude_code.example.md),
[claude_desktop_config.example.json](config/claude_desktop_config.example.json),
[codex-config.example.toml](config/codex-config.example.toml).
## Security
Sensitive paths are refused deterministically — this doesn't depend on the model
deciding not to read them. See [paths.py](src/file_analyzer_mcp/paths.py).
| Pattern | What it protects |
|---|---|
| `.ssh`, `.aws`, `.gnupg`, `.azure`, `.kube`, `.docker` | Credential and cloud-config directories |
| Browser profile roots (e.g. `User Data`) | Saved logins and cookies |
| `.env*`, `*.pem`, `*.key`, `*.pfx`, `*.p12` | Secret files, matched by name pattern |
| `id_rsa`, `id_ed25519`, `known_hosts`, `.netrc`, `credentials`, `credentials.json`, `login data`, `cookies`, `web data` | Specific credential filenames |
Every call is also audited — tool name, arguments, and outcome (success or blocked)
get appended to `logs/audit.jsonl`. See [audit.py](src/file_analyzer_mcp/audit.py).
A 50MB file size cap applies on top of all of this.
Conventions for anyone extending this server are in [AGENTS.md](AGENTS.md).
## Errors
Every failure raises `ToolFailure` with a code, a plain-language reason, and how
to recover — the message is written for the calling model to read and act on,
not just for a human.
| Code | Raised when |
|---|---|
| `PATH_NOT_FOUND` | The folder or file path doesn't exist |
| `NOT_A_DIRECTORY` / `NOT_A_FILE` | A tool got the wrong kind of path |
| `UNSUPPORTED_EXTENSION` | The file isn't pdf/docx/pptx/svg/png |
| `WRONG_TOOL_FOR_EXTENSION` | e.g. `read_pdf` called on a `.docx` |
| `FILE_TOO_LARGE` | File exceeds the 50MB cap |
| `SENSITIVE_PATH_BLOCKED` | Path matches the security table above |
| `OCR_ENGINE_NOT_FOUND` | `ocr=True` but Tesseract isn't installed/configured |
| `SVG_PARSE_ERROR` | The `.svg` file isn't valid XML |
## Scanned-PDF OCR
`read_pdf(ocr=True)` needs Tesseract:
```bash
winget install UB-Mannheim.TesseractOCR
uv run python scripts/setup_ocr.py # copies eng/osd, downloads kor.traineddata
```
`ocr_lang` defaults to `"kor+eng"`. Wrong Tesseract path? Set `TESSERACT_CMD`.
## Testing
```bash
uv run pytest -q # parser / path / audit unit tests
uv run python scripts/smoke_stdio.py # real stdio round-trip against the server
```
Both should pass before a change counts as done — pytest checks modules in
isolation, the smoke test is the only thing that exercises the actual MCP
protocol and catches schema-level breakage.
## Limits
| Limit | Why / what to do |
|---|---|
| `.doc` / `.ppt` not supported | Legacy binary formats — save as `.docx`/`.pptx` first |
| Scanned PDFs return empty text by default | Pass `ocr=True` (off by default — it's slower) |
| SVGs aren't rasterized | Parsed as XML for structure, not rendered as an image |
TDQS
Scored across 7 tools
Each read_* tool targets a distinct file format (pptx, svg, image, pdf, docx) with clear boundaries; list_supported_files and analyze_folder_structure have separate discovery roles. No overlap or ambiguity exists.
All tools follow a consistent verb_noun pattern: read_<format> for content extraction, plus list_supported_files and analyze_folder_structure. Conventions are uniform and predictable.
Seven tools is well-scoped for a file analyzer server: five format readers plus two discovery/analysis helpers. Each tool has a clear purpose and none feel redundant or missing.
The server covers all explicitly supported formats (pdf, docx, pptx, svg, png) with read operations, and provides folder-level discovery through list_supported_files and analyze_folder_structure. The surface is complete for its stated analysis purpose.