pageindex-local-mcp
by jamesbubenik
README.md
# pageindex-local-mcp
A local-first MCP (Model Context Protocol) server for [PageIndex](https://github.com/VectifyAI/PageIndex) — the vectorless, reasoning-based RAG framework.
This server lets local AI agents (Claude Desktop, Cursor, Claude Code, Cline, Continue, OpenAI Agents SDK, LangChain, or any MCP-compatible client) index and query local PDF and Markdown documents through a self-hosted PageIndex installation, **without requiring any PageIndex cloud API key**.
---
> **Security Warning:** This MCP server exposes local file indexing and tree-query capabilities to MCP clients. Only connect trusted clients. Review `PAGEINDEX_ALLOWED_ROOTS` before deploying in shared environments.
---
## What This Project Does
- Wraps a locally installed PageIndex repository and exposes its capabilities as MCP tools.
- Indexes local PDF and Markdown files by calling `run_pageindex.py` from the PageIndex repo.
- Builds and stores a hierarchical PageIndex tree structure for each document.
- Performs vectorless, reasoning-based document search over those trees using a local OpenAI-compatible LLM endpoint (LM Studio, Ollama, vLLM, etc.).
- Returns traceable results: document ID, node ID, title, summary, page/line range, reasoning path.
- Maintains a local document registry with full metadata.
## What This Project Does Not Do
- Does **not** call `https://api.pageindex.ai` or any PageIndex cloud API.
- Does **not** require a `PAGEINDEX_API_KEY`.
- Does **not** use vector databases or embeddings.
- Does **not** provide a web UI.
- Does **not** perform cloud OCR. Local PDF parsing quality depends on your PageIndex installation and the underlying Python PDF library (PyPDF2). Complex scanned PDFs may parse poorly compared to the cloud pipeline.
## How It Differs from the Official PageIndex MCP
| Feature | Official pageindex-mcp | This project |
|---|---|---|
| Backend | PageIndex Cloud API | Local PageIndex repo |
| API key required | Yes | No |
| Runs locally | No | Yes |
| Vector DB | No (tree-based) | No (tree-based) |
| LLM for indexing | Cloud models | Configurable local/remote |
| LLM for querying | Cloud models | Local OpenAI-compatible endpoint |
| OCR quality | Cloud (best) | Local (depends on PageIndex/PyPDF2) |
---
## Prerequisites
- **Node.js 18+** (for this MCP server)
- **Python 3.9+** (for the PageIndex repo)
- A **local clone of [VectifyAI/PageIndex](https://github.com/VectifyAI/PageIndex)** with dependencies installed
- A **local OpenAI-compatible LLM endpoint** (LM Studio, Ollama, vLLM) — required for both indexing (if PageIndex is configured to use it) and querying
---
## 1. Install the Local PageIndex Repository
```bash
git clone https://github.com/VectifyAI/PageIndex.git
cd PageIndex
pip install -r requirements.txt
```
PageIndex needs an LLM to generate tree structures. Configure it to use your local endpoint by editing `pageindex/config.yaml`:
```yaml
model: local-model # must match what your local server loads
```
Or set the model via the `--model` argument at indexing time.
> **Note:** PageIndex's indexing currently calls LLM APIs. Point its config at your local endpoint (LM Studio, Ollama, vLLM) so no internet calls are made during indexing.
---
## 2. Install the MCP Server
```bash
git clone https://github.com/jamesbubenik/pageindex-local-mcp.git
cd pageindex-local-mcp
npm install
npm run build
```
---
## 3. Configure Environment Variables
Copy the example and edit:
```bash
cp examples/sample.env .env
# or: cp .env.example .env
```
Edit `.env`:
```dotenv
PAGEINDEX_REPO_PATH=/home/user/PageIndex
PAGEINDEX_PYTHON=python3
PAGEINDEX_WORKSPACE=/home/user/.pageindex-local-mcp
PAGEINDEX_LLM_BASE_URL=http://127.0.0.1:1234/v1
PAGEINDEX_LLM_API_KEY=lm-studio
PAGEINDEX_MODEL=local-model
```
### All Configuration Options
| Variable | Required | Default | Description |
|---|---|---|---|
| `PAGEINDEX_REPO_PATH` | **Yes** | — | Absolute path to cloned PageIndex repo |
| `PAGEINDEX_PYTHON` | No | `python3` | Python executable with PageIndex deps |
| `PAGEINDEX_WORKSPACE` | No | `~/.pageindex-local-mcp` | Where the MCP server stores artifacts |
| `PAGEINDEX_MODEL` | No | `local-model` | Default model name for indexing/querying |
| `PAGEINDEX_LLM_BASE_URL` | No | `http://127.0.0.1:1234/v1` | OpenAI-compatible endpoint for queries |
| `PAGEINDEX_LLM_API_KEY` | No | `local` | API key (any non-empty value for local servers) |
| `PAGEINDEX_LLM_TIMEOUT_MS` | No | `120000` | LLM request timeout (ms) |
| `PAGEINDEX_TOOL_TIMEOUT_MS` | No | `600000` | Max ms for a PageIndex Python subprocess. Raise for large PDFs or slow machines. |
| `PAGEINDEX_TOC_CHECK_PAGES` | No | `20` | Pages scanned for TOC (PDF only) |
| `PAGEINDEX_MAX_PAGES_PER_NODE` | No | `10` | Max pages per tree node (PDF only) |
| `PAGEINDEX_MAX_TOKENS_PER_NODE` | No | `20000` | Max tokens per tree node |
| `PAGEINDEX_ALLOWED_ROOTS` | No | `""` (all) | Semicolon (Win) or colon (Unix) separated allowed dirs |
| `PAGEINDEX_REGISTRY_BACKEND` | No | `json` | `json` (supported) or `sqlite` (future) |
| `PAGEINDEX_LOG_LEVEL` | No | `info` | `debug`, `info`, `warn`, `error` |
---
## 4. Configure Your MCP Client
### Claude Desktop
**macOS/Linux** — config file: `~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) or `~/.config/Claude/claude_desktop_config.json` (Linux)
```json
{
"mcpServers": {
"pageindex-local": {
"command": "node",
"args": ["/home/user/pageindex-local-mcp/dist/index.js"],
"env": {
"PAGEINDEX_REPO_PATH": "/home/user/PageIndex",
"PAGEINDEX_PYTHON": "python3",
"PAGEINDEX_WORKSPACE": "/home/user/.pageindex-local-mcp",
"PAGEINDEX_LLM_BASE_URL": "http://127.0.0.1:1234/v1",
"PAGEINDEX_LLM_API_KEY": "lm-studio",
"PAGEINDEX_MODEL": "local-model",
"PAGEINDEX_ALLOWED_ROOTS": "/home/user/Documents:/home/user/Downloads"
}
}
}
}
```
**Windows** — config file: `%APPDATA%\Claude\claude_desktop_config.json`
```json
{
"mcpServers": {
"pageindex-local": {
"command": "node",
"args": ["C:\\Users\\user\\pageindex-local-mcp\\dist\\index.js"],
"env": {
"PAGEINDEX_REPO_PATH": "C:\\Users\\user\\PageIndex",
"PAGEINDEX_PYTHON": "C:\\Users\\user\\miniconda3\\envs\\pageindex\\python.exe",
"PAGEINDEX_WORKSPACE": "C:\\Users\\user\\.pageindex-local-mcp",
"PAGEINDEX_LLM_BASE_URL": "http://127.0.0.1:1234/v1",
"PAGEINDEX_LLM_API_KEY": "lm-studio",
"PAGEINDEX_MODEL": "local-model",
"PAGEINDEX_ALLOWED_ROOTS": "C:\\Users\\user\\Documents;C:\\Users\\user\\Downloads"
}
}
}
}
```
### Cursor
Add to `.cursor/mcp.json` in your project root:
```json
{
"mcpServers": {
"pageindex-local": {
"command": "node",
"args": ["/home/user/pageindex-local-mcp/dist/index.js"],
"env": {
"PAGEINDEX_REPO_PATH": "/home/user/PageIndex",
"PAGEINDEX_PYTHON": "python3",
"PAGEINDEX_WORKSPACE": "/home/user/.pageindex-local-mcp",
"PAGEINDEX_LLM_BASE_URL": "http://127.0.0.1:1234/v1",
"PAGEINDEX_LLM_API_KEY": "lm-studio",
"PAGEINDEX_MODEL": "local-model"
}
}
}
}
```
### Claude Code
Add to your project's `.claude/settings.json` under `mcpServers`, using the same format as Cursor above.
### LM Studio (as MCP client)
LM Studio 0.3.17+ can act as an MCP host, meaning it can call this server's tools directly from its chat UI — no separate MCP client needed.
> **Note:** This section is about using LM Studio as the **MCP client**. For using LM Studio as the **LLM backend** for indexing and querying, see [Section 5](#5-lm-studio-setup) below.
**Requirements:**
- LM Studio 0.3.17 or later
- A tool-use-capable model loaded in LM Studio (e.g., Mistral Nemo Instruct, Qwen2.5 Instruct, LLaMA 3.1 Instruct, Gemma 3). Pure base models will not invoke tools reliably.
**Step 1 — Edit `mcp.json`**
Open LM Studio, switch to the **Program** tab in the right sidebar, then click **Install → Edit mcp.json**. This opens the config file in LM Studio's built-in editor.
The file lives at:
- **macOS / Linux:** `~/.lmstudio/mcp.json`
- **Windows:** `%USERPROFILE%\.lmstudio\mcp.json`
**Step 2 — Add the server**
Paste the following, adjusting paths for your system:
**macOS / Linux:**
```json
{
"mcpServers": {
"pageindex-local": {
"command": "node",
"args": ["/home/user/pageindex-local-mcp/dist/index.js"],
"timeout": 600,
"env": {
"PAGEINDEX_REPO_PATH": "/home/user/PageIndex",
"PAGEINDEX_PYTHON": "python3",
"PAGEINDEX_WORKSPACE": "/home/user/.pageindex-local-mcp",
"PAGEINDEX_LLM_BASE_URL": "http://127.0.0.1:1234/v1",
"PAGEINDEX_LLM_API_KEY": "lm-studio",
"PAGEINDEX_MODEL": "your-loaded-model-name",
"PAGEINDEX_TOOL_TIMEOUT_MS": "600000",
"PAGEINDEX_LOG_LEVEL": "info"
}
}
}
}
```
**Windows:**
```json
{
"mcpServers": {
"pageindex-local": {
"command": "node",
"args": ["C:\\Users\\user\\pageindex-local-mcp\\dist\\index.js"],
"timeout": 600,
"env": {
"PAGEINDEX_REPO_PATH": "C:\\Users\\user\\PageIndex",
"PAGEINDEX_PYTHON": "C:\\Users\\user\\miniconda3\\envs\\pageindex\\python.exe",
"PAGEINDEX_WORKSPACE": "C:\\Users\\user\\.pageindex-local-mcp",
"PAGEINDEX_LLM_BASE_URL": "http://127.0.0.1:1234/v1",
"PAGEINDEX_LLM_API_KEY": "lm-studio",
"PAGEINDEX_MODEL": "your-loaded-model-name",
"PAGEINDEX_TOOL_TIMEOUT_MS": "600000",
"PAGEINDEX_LOG_LEVEL": "info"
}
}
}
}
```
Set `PAGEINDEX_MODEL` to the exact model name shown in LM Studio's server status bar (e.g., `mistral-nemo-instruct-2407`). Save the file — LM Studio picks up changes immediately.
**Timeout configuration — required for large PDFs**
Indexing a PDF can take several minutes because PageIndex makes multiple LLM calls. LM Studio's default MCP request timeout is 60 seconds, which is not long enough. You **must** set two values or you will see `MCP error -32001: Request timed out`:
| Setting | Where | What it does |
|---|---|---|
| `"timeout": 600` | `mcp.json` server entry | Tells LM Studio to wait up to 600 seconds (10 min) for a tool response |
| `PAGEINDEX_TOOL_TIMEOUT_MS=600000` | `env` block or `.env` | Tells the server how long to let the Python subprocess run before killing it |
Both values are already included in the example configs above. **Make sure they are present in your actual `mcp.json`** — LM Studio does not have a default that is long enough.
The server also sends heartbeat notifications every 5 seconds while indexing or searching. Clients that support `resetTimeoutOnProgress` (Claude Desktop, Cursor, Claude Code) will reset their timer on each one. LM Studio will additionally receive supplemental log notifications that may reset its connection timer depending on version.
**Step 3 — Enable tool use**
Go to **App Settings → Tools & Integrations** and ensure tool calling is enabled. You can allow individual tools once or permanently when the confirmation dialog appears.
**Step 4 — Start the LM Studio local server**
The MCP server's query engine calls LM Studio's OpenAI-compatible endpoint (`http://127.0.0.1:1234/v1`) to reason over document trees. Make sure the local server is running: **Developer tab → Start Server** (default port 1234).
**Step 5 — Chat with your documents**
Load a tool-capable model, open a new chat, and ask naturally:
```
Index the file at /home/user/Documents/research-paper.pdf
```
```
Search my indexed documents for information about climate feedback loops
```
```
List all my indexed documents
```
When the model decides to call a tool, LM Studio will show a confirmation dialog with the tool name and arguments. Review and approve. Results are returned inline in the chat.
> **Tip:** Run `pageindex_local_health` first to confirm the server, PageIndex repo, and Python environment are all reachable before attempting to index.
---
## 5. LM Studio Setup
1. Download and install [LM Studio](https://lmstudio.ai/).
2. Load a model (e.g., Mistral 7B Instruct, LLaMA 3, Qwen 2.5).
3. Start the local server: **Server tab → Start Server** (default port 1234).
4. Set:
```dotenv
PAGEINDEX_LLM_BASE_URL=http://127.0.0.1:1234/v1
PAGEINDEX_LLM_API_KEY=lm-studio
PAGEINDEX_MODEL=<model-name-from-lm-studio>
```
### Ollama Setup
```bash
ollama serve
ollama pull llama3
```
```dotenv
PAGEINDEX_LLM_BASE_URL=http://127.0.0.1:11434/v1
PAGEINDEX_LLM_API_KEY=ollama
PAGEINDEX_MODEL=llama3
```
---
## 6. Using the MCP Tools
### Check Health
```
pageindex_local_health
```
Verifies the PageIndex repo, Python, workspace, and LLM config. Run this first.
### Index a PDF
```json
{
"tool": "pageindex_local_index_document",
"arguments": {
"path": "/home/user/Documents/research-paper.pdf",
"addNodeSummary": true,
"addNodeId": true,
"addDocDescription": true
}
}
```
Index with node text (larger output, enables source text in search results):
```json
{
"path": "/home/user/Documents/research-paper.pdf",
"addNodeText": true
}
```
### Index a Markdown File
```json
{
"tool": "pageindex_local_index_document",
"arguments": {
"path": "/home/user/notes/project-spec.md"
}
}
```
### List Indexed Documents
```json
{
"tool": "pageindex_local_list_documents",
"arguments": { "status": "indexed", "limit": 20 }
}
```
### Get Tree Structure
```json
{
"tool": "pageindex_local_get_tree",
"arguments": {
"documentId": "550e8400-e29b-41d4-a716-446655440000",
"maxDepth": 3
}
}
```
### Query (Vectorless Search)
```json
{
"tool": "pageindex_local_search",
"arguments": {
"query": "What are the main conclusions about climate change?",
"maxResults": 5,
"includeReasoningPath": true
}
}
```
Search across specific documents:
```json
{
"query": "What is the recommended dosage?",
"documentIds": ["doc-id-1", "doc-id-2"],
"includeSourceText": true
}
```
### Remove a Document
```json
{
"tool": "pageindex_local_remove_document",
"arguments": {
"documentId": "550e8400-e29b-41d4-a716-446655440000",
"deleteFiles": true
}
}
```
### Re-index a Document
```json
{
"tool": "pageindex_local_reindex_document",
"arguments": {
"documentId": "550e8400-e29b-41d4-a716-446655440000",
"addNodeText": true
}
}
```
---
## 7. Workspace Layout
The server stores all artifacts under `PAGEINDEX_WORKSPACE`:
```
~/.pageindex-local-mcp/
registry.json ← document registry
documents/
<document-id>/
original/
source.pdf ← copy of original file
index/
tree.json ← PageIndex tree structure
metadata.json ← indexing metadata
stdout.log ← PageIndex stdout
stderr.log ← PageIndex stderr
queries/
<query-id>.json ← query results (future)
```
---
## 8. Development and Testing
```bash
# Type-check only
npm run typecheck
# Run tests
npm test
# Run smoke tests (requires configured .env and PageIndex repo)
npm run smoke:health
npm run smoke:index -- /absolute/path/to/document.pdf
npm run smoke:list
npm run smoke:query -- "What is this document about?"
# Dev mode (runs from TypeScript source, no build needed)
npm run dev
```
---
## 9. Troubleshooting
**`run_pageindex.py` not found**
Verify `PAGEINDEX_REPO_PATH` points to the root of the cloned PageIndex repository and that `run_pageindex.py` exists there.
**Python import errors during indexing**
Make sure the PageIndex Python dependencies are installed in the Python environment pointed to by `PAGEINDEX_PYTHON`:
```bash
pip install -r /path/to/PageIndex/requirements.txt
```
**Tree file not found after indexing**
PageIndex saves output to `<PAGEINDEX_REPO_PATH>/results/<filename>_structure.json`. If your version saves elsewhere, check `stdout.log` in the document workspace for the actual output path and open an issue.
**LLM connection failed during search**
Verify your local LLM server is running and that `PAGEINDEX_LLM_BASE_URL` is correct. Test manually:
```bash
curl http://127.0.0.1:1234/v1/models
```
**File outside allowed roots**
Add the file's parent directory to `PAGEINDEX_ALLOWED_ROOTS` in your environment config.
**Low-quality indexing results on scanned PDFs**
PageIndex uses PyPDF2 for local PDF parsing, which does not perform OCR. Scanned PDFs without embedded text will produce poor results. For scanned documents, consider pre-processing with an OCR tool or using the PageIndex cloud service.
**`MCP error -32001: Request timed out` in LM Studio (or other clients)**
The timeout is enforced by the MCP **client**, not this server. LM Studio's default is 60 seconds — not long enough for PDF indexing.
Checklist (do all three):
1. **`"timeout": 600` must be present in your `mcp.json`** under the server entry. This raises LM Studio's per-request timeout to 10 minutes. Without this field, LM Studio uses 60 seconds regardless of how fast the server is.
2. **`PAGEINDEX_TOOL_TIMEOUT_MS=600000`** in the `env` block (or `.env`) — keeps the server-side Python subprocess limit in sync.
3. **Restart LM Studio** after editing `mcp.json` — changes are not always picked up without a restart.
The server sends heartbeat notifications every 5 seconds (progress + log) while indexing and searching. If you are still seeing `-32001` after adding `"timeout": 600`, set `PAGEINDEX_LOG_LEVEL=debug` and check the stderr output to confirm whether `hasProgressToken: true` appears — if it does, LM Studio is sending progress tokens and the heartbeats are active. If `hasProgressToken: false`, the heartbeats are log-only and you must rely on the `"timeout"` field.
**MCP server logs**
All logs go to stderr (not stdout, which is reserved for the MCP protocol). Check your MCP client's stderr console or increase log level:
```dotenv
PAGEINDEX_LOG_LEVEL=debug
```
---
## 10. Security Notes
- **`PAGEINDEX_ALLOWED_ROOTS`**: When set, only files within these directories can be indexed. Always configure this in shared or multi-user environments.
- **No shell interpolation**: All Python subprocess calls use argument arrays (`shell: false`). Path arguments are never interpolated into shell strings.
- **No cloud calls**: This server never contacts `api.pageindex.ai`, `chat.pageindex.ai`, or any PageIndex cloud endpoint.
- **Secrets**: Never place API keys in document paths or document IDs. All config comes from environment variables.
- **Trusted clients only**: The MCP protocol grants tool invocation to any connected client. Run this server only in trusted local environments.
---
## 11. Known Limitations
- **SQLite registry backend**: The `sqlite` option for `PAGEINDEX_REGISTRY_BACKEND` is planned but not yet implemented. Use the default `json` backend.
- **Concurrent indexing**: Only one indexing job should run per server instance at a time. Concurrent calls are not prevented but may produce race conditions in the registry.
- **Source text extraction**: Full source text in search results (`includeSourceText: true`) only works when the document was indexed with `addNodeText: true`. Otherwise, results include node summaries only.
- **Markdown line references**: PageIndex uses line numbers (not pages) for Markdown files. Search results will show line ranges instead of page numbers.
- **Large documents**: Indexing very large PDFs may exceed LLM context windows. Adjust `maxPagesPerNode` and `maxTokensPerNode` to reduce node size.
- **Model compatibility**: The query engine uses a simple JSON-structured prompt. Some smaller local models may not reliably output valid JSON. Use instruction-tuned models (Mistral Instruct, LLaMA Instruct, Qwen Instruct, etc.).
---
## 12. Using with an AI Agent
[`AGENT_SYSTEM_PROMPT.md`](./AGENT_SYSTEM_PROMPT.md) contains a ready-to-use system prompt for any AI agent that will drive this MCP server. It covers all 8 tools, every parameter and response field, typical workflows, error handling, and usage constraints.
**How to use it:**
1. Copy the full contents of `AGENT_SYSTEM_PROMPT.md`.
2. Paste it into your agent's system prompt (or include it as a context file if your framework supports file injection).
3. The agent will know how to index documents, search them, handle failures, and avoid common mistakes — without needing further instruction.
This is useful when building automated pipelines, custom agents, or assistants that need to interact with local documents through this server.
---
## MCP Tools Reference
| Tool | Description |
|---|---|
| `pageindex_local_health` | Check configuration and connectivity |
| `pageindex_local_index_document` | Index a local PDF or Markdown file |
| `pageindex_local_list_documents` | List all registered documents |
| `pageindex_local_get_document` | Get full metadata for one document |
| `pageindex_local_get_tree` | Retrieve the PageIndex tree structure |
| `pageindex_local_search` | Vectorless reasoning-based search |
| `pageindex_local_remove_document` | Remove a document from the registry |
| `pageindex_local_reindex_document` | Re-run indexing for an existing document |
---
## License
MIT
TDQS
B3.2/5.0
Scored across 8 tools
Disambiguation5/5
Each tool has a clearly distinct purpose: get vs list, index vs reindex, health vs search, etc. No overlapping functionality.
Naming Consistency4/5
All tools use the pageindex_local_ prefix and snake_case, with verb_noun pattern for most. The health tool is a minor exception as it's a noun-only verb, but still fits the style.
Tool Count5/5
8 tools is well-scoped for a document indexing server covering CRUD, search, and health checks. Each tool earns its place without redundancy.
Completeness4/5
Covers all core operations: add, list, get, reindex, remove, search, and tree structure. Minor gap might be content retrieval, but the focus is on metadata and indexed search.
Maintenance
ActivityInactive
ResponsivenessNo issues