Local MCP server for querying Excel files (heavy merged cells, multi-language text) via DuckDB and substring search. No LLM/embedding required.
Project description
search-excel-mcp
Local MCP server that lets any MCP client (Claude Code, Cursor, Claude Desktop, Cline, …) query complex Excel files (heavy merged cells, large size, multi-language text including CJK) via SQL and substring search — no API keys, no embeddings, no GPU.
Built with FastMCP + DuckDB + openpyxl + pyarrow.
Why this exists
Documentation-style Excel files (external design docs, report specs, requirements sheets) typically have thousands of merged cells per sheet, 20–100 MB per file, and multi-language text where most FTS tokenizers fail.
Standard RAG (embeddings + vector store) needs an LLM or embedding model that you can't always run locally. Your MCP client is the reasoning layer; this server just gives it accurate structured access to cells via DuckDB.
Install
pip install search-excel-mcp
# or run without install:
uvx search-excel-mcp
Configure your MCP client
Add to the client's MCP config file. Common locations:
| Client | Config path |
|---|---|
| Claude Code | ~/.claude/mcp.json (global) or <project>/.mcp.json (project-local) |
| Cursor | ~/.cursor/mcp.json (global) or <project>/.cursor/mcp.json (project-local) |
| Claude Desktop (macOS) | ~/Library/Application Support/Claude/claude_desktop_config.json |
| Cline / other | see your client's docs |
Config entry (same JSON shape for all):
{
"mcpServers": {
"search-excel": {
"command": "uvx",
"args": ["search-excel-mcp"],
"env": {
"SEARCH_EXCEL_DATA_DIR": "/absolute/path/to/your/excel/folder"
}
}
}
}
Only SEARCH_EXCEL_DATA_DIR is required. The DuckDB cache is placed under your OS-standard app data folder (e.g. ~/Library/Application Support/search-excel-mcp/data.duckdb on macOS) — override with SEARCH_EXCEL_DB if you want it elsewhere.
Restart your MCP client → the search-excel server appears.
Use
Drop .xlsx files into your data folder, then ask your agent:
"ingest all the files in the data folder" "find rows that mention '保存ボタン' in any file" "list all sheets in report-2024.xlsx" "show me sheet 5"
The agent routes to the right MCP tool. DuckDB is built incrementally — already-ingested files are skipped unless you re-ingest with reset=true.
CLI (optional)
The same package installs CLI tools for batch use:
# Ingest everything under $SEARCH_EXCEL_DATA_DIR
search-excel-ingest
# Ingest a single file or folder
search-excel-ingest --input /path/to/file.xlsx
search-excel-ingest --input /path/to/folder
# Filter / reset
search-excel-ingest --only "report"
search-excel-ingest --reset
# Validate ingest accuracy (samples random cells against the source xlsx)
search-excel-validate --samples 20
Environment variables
| Variable | Default | Purpose |
|---|---|---|
SEARCH_EXCEL_DATA_DIR |
./data (cwd-relative) |
Where your .xlsx files live — set this for MCP usage |
SEARCH_EXCEL_DB |
OS user data dir | DuckDB cache file |
SEARCH_EXCEL_REPORTS_DIR |
OS user data dir / reports | Ingest/validation log output |
SEARCH_EXCEL_WORKERS |
min(4, cpu_count) |
Parallel ingest worker count |
<OS user data dir> resolves per platform via platformdirs:
- macOS:
~/Library/Application Support/search-excel-mcp/ - Linux:
~/.local/share/search-excel-mcp/(or$XDG_DATA_HOME) - Windows:
%LOCALAPPDATA%\search-excel-mcp\
MCP tools
| Tool | Purpose |
|---|---|
ingest_file(path, reset?) |
Ingest a single .xlsx file |
ingest_folder(path?, reset?) |
Ingest every .xlsx under a folder (recursive) |
list_files() |
All ingested files |
list_sheets(file?) |
Sheets, optionally filtered |
search_text(keyword, file?, sheet?, limit?) |
Substring search across cells (CJK-friendly) |
search_sheets(keyword, limit?) |
Which sheets discuss a topic, ranked by hit count |
get_sheet_cells(sheet_id, row/col range, ...) |
Read a slice of a sheet |
get_sheet_text(sheet_id, max_chars?) |
Deduplicated text overview of a sheet |
query_sql(sql, limit?) |
Read-only SELECT against DuckDB |
delete_file(file_id) |
Remove one file's data |
clear_db(confirm) |
Wipe all data (preview unless confirm=true) |
db_stats() |
Counts and overall stats |
Accuracy notes
- No LLM in the pipeline → deterministic.
- Cell formatting (font color, bold, fill) is not extracted — only text values. Files that mark changes with red font lose that signal here.
- Read-only MCP for queries; ingest tools mutate DuckDB only.
search-excel-validatesamples cells from the source xlsx and checks them against the DB.search_textuses ILIKE (substring) instead of DuckDB FTS so CJK works reliably.
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file search_excel_mcp-0.1.0.tar.gz.
File metadata
- Download URL: search_excel_mcp-0.1.0.tar.gz
- Upload date:
- Size: 14.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2a9f35d4b0c3a45086dd9ee8866399cb128d35892b27523e2dd2398e912f6e2d
|
|
| MD5 |
6149e30d341eddea084482c0839ab253
|
|
| BLAKE2b-256 |
58f2931463a9617b09b3d0df56d386659d0fd7b9086e1cf24da0cd42a36dfaf9
|
File details
Details for the file search_excel_mcp-0.1.0-py3-none-any.whl.
File metadata
- Download URL: search_excel_mcp-0.1.0-py3-none-any.whl
- Upload date:
- Size: 19.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.11.5
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
156a74f29e9f416859e395315a155a1079d98e198b6a5cdad567c503d14bfa24
|
|
| MD5 |
48ee2088fd367a882aacff022abf8929
|
|
| BLAKE2b-256 |
2425d8680f331d7938e37c53c6c5202668885878daa8a8993c2e2825ce4d43db
|