Python SDK and MCP server for self-hosted PageIndex RAG service
Project description
PageServe
Python SDK and MCP server for PageIndex — the self-hosted, reasoning-based RAG engine.
Installation · Quick Start · MCP Server · CLI · Documentation
PageServe is a typed, batteries-included client for PageIndex, a reasoning-based RAG engine that navigates document structure instead of doing vector similarity search. It reads the table of contents, picks the right sections, and synthesizes answers with page-level citations — no embeddings, no vector database, no chunking heuristics.
One install gives you three ways to talk to your PageIndex deployment:
- 🐍 A Python SDK — synchronous and asynchronous clients, fully typed with Pydantic models.
- 🤖 An MCP server — drop your documents into Claude Desktop, Cursor, or any MCP-compatible agent.
- ⌨️ A CLI — query, upload, and manage documents straight from the terminal.
Features
- Vector-free retrieval — answers come with exact page references (
Employment Contract p.5, 6), not opaque similarity scores. - Query or retrieve —
query()synthesizes an answer;retrieve()returns the raw section content (cheaper, ideal for your own prompts). - Sync & async —
PageServeClientandAsyncPageServeClient, including concurrentquery_many. - Streaming — token-by-token responses over Server-Sent Events.
- Model Context Protocol — expose your corpus to LLM agents with keys kept out of the model's context.
- Fully typed — Pydantic v2 models and strict, descriptive exceptions for every failure mode.
- Minimal core — only
httpxandpydanticrequired; MCP and CLI are opt-in extras.
Installation
pip install pageserve # SDK only (sync + async)
pip install "pageserve[mcp]" # + MCP server for agent frameworks
pip install "pageserve[cli]" # + CLI commands
pip install "pageserve[all]" # everything
Requires Python 3.10+.
Quick Start
from pageserve import PageServeClient
client = PageServeClient(
base_url = "https://pageindex.company.com",
public_key = "<your-public-key>",
secret_key = "<your-secret-key>",
)
# List indexed documents
docs = client.list_documents()
# Ask a question
result = client.query(docs[0].doc_id, "What are the probation terms?")
print(result.answer)
print(result.citation) # "Employment Contract p.5, 6"
print(result.page_refs) # [5, 6]
# Or retrieve the raw source sections without synthesizing an answer (cheaper)
retrieved = client.retrieve(docs[0].doc_id, "What are the probation terms?")
print(retrieved.text) # all relevant section text, ready to drop into a prompt
# Upload and wait for indexing to complete
upload = client.upload("./contract.pdf", wait=True)
# Read specific pages (no LLM, instant)
pages = client.get_pages(docs[0].doc_id, "22-24")
Async Usage
import asyncio
from pageserve import AsyncPageServeClient
async def main():
async with AsyncPageServeClient(
base_url = "https://pageindex.company.com",
public_key = "<your-public-key>",
secret_key = "<your-secret-key>",
) as client:
docs = await client.list_documents()
# Query multiple documents concurrently
results = await client.query_many([
(docs[0].doc_id, "What are the probation terms?"),
(docs[1].doc_id, "What does the law say about probation?"),
])
for r in results:
print(r.answer)
asyncio.run(main())
Streaming
for event in client.query_stream(doc_id, "What are the key clauses?"):
if event.type == "token":
print(event.content, end="", flush=True)
elif event.type == "sources":
for src in event.sources:
print(f"\nSource: {src.citation}")
elif event.type == "done":
break
MCP Server
Run the MCP server so Claude Desktop, Cursor, or any MCP-compatible agent can query your documents:
{
"mcpServers": {
"pageindex": {
"command": "pageserve",
"args": ["mcp"],
"env": {
"PAGESERVE_URL": "https://pageindex.company.com",
"PAGESERVE_PUBLIC_KEY": "<your-public-key>",
"PAGESERVE_SECRET_KEY": "<your-secret-key>"
}
}
}
}
The server exposes seven tools:
| Tool | Description |
|---|---|
list_documents |
See what documents are available |
query_document |
Ask a question against one document |
query_multiple_documents |
Cross-reference multiple documents |
retrieve_document |
Return raw section content without synthesizing an answer |
get_page_content |
Read raw page text (no LLM, instant) |
get_document_structure |
Browse the table of contents |
get_service_health |
Check service status and queue |
Keys live in environment variables and never appear in tool arguments or the model's context window.
CLI
export PAGESERVE_URL=https://pageindex.company.com
export PAGESERVE_PUBLIC_KEY=<your-public-key>
export PAGESERVE_SECRET_KEY=<your-secret-key>
pageserve list # list documents
pageserve query <doc_id> "question" # ask a question
pageserve query <doc_id> "question" --stream # streaming output
pageserve retrieve <doc_id> "question" # raw sections, no answer
pageserve upload ./report.pdf --watch # upload + progress bar
pageserve health # service status
pageserve keys list # list API keys
pageserve keys create "My App" # create a key
pageserve mcp # run MCP server (stdio)
pageserve mcp --transport sse --port 3000 # MCP over SSE
Error Handling
Every failure mode maps to a specific, catchable exception:
from pageserve import (
AuthError,
NotFoundError,
DocumentNotReadyError,
FileTooLargeError,
RateLimitError,
ServiceUnavailableError,
ServiceError,
)
try:
result = client.query(doc_id, "question")
except AuthError:
print("Invalid or expired API key")
except NotFoundError:
print("Document not found")
except RateLimitError as e:
import time; time.sleep(e.retry_after)
except ServiceError as e:
print(f"Server error [{e.status_code}]")
Documentation
| Guide | |
|---|---|
| Getting Started | Install, configure, and run your first query |
| Authentication | API keys and credential handling |
| Sync Client Reference | PageServeClient API |
| Async Client Reference | AsyncPageServeClient API |
| Streaming (SSE) | Token-by-token responses |
| MCP Server | Tools, transports, and agent setup |
| CLI Reference | All commands and flags |
| Data Models | Pydantic response models |
| Error Handling | Exception hierarchy |
Contributing
Contributions are welcome! To set up a development environment:
git clone https://github.com/pageserve/pageserve.git
cd pageserve
pip install -e ".[all,dev]"
pytest # run the test suite
ruff check . # lint
mypy pageserve # type-check
Please open an issue to discuss substantial changes before submitting a pull request.
License
Licensed under the Apache License 2.0. © 2026 PageServe.
Related
- PageIndex OSS — the self-hosted service this SDK wraps
- PageIndex Cloud — hosted version
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 pageserve-0.1.1.tar.gz.
File metadata
- Download URL: pageserve-0.1.1.tar.gz
- Upload date:
- Size: 26.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
55273e0eb794e1729ea3ae3105b38da4eaeca41e286b490cf6a6624d2ac1a01c
|
|
| MD5 |
23d5f299ad93cdfe14ae1c14b4c9207d
|
|
| BLAKE2b-256 |
cceb3619d8e9bec70a9359dc41632017196c37af3ec00b62cfdcc691d23bea0f
|
Provenance
The following attestation bundles were made for pageserve-0.1.1.tar.gz:
Publisher:
publish.yml on pageserve/pageserve
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pageserve-0.1.1.tar.gz -
Subject digest:
55273e0eb794e1729ea3ae3105b38da4eaeca41e286b490cf6a6624d2ac1a01c - Sigstore transparency entry: 1810941619
- Sigstore integration time:
-
Permalink:
pageserve/pageserve@033566f8432bed730d0d39795b93a2e34e7a2211 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/pageserve
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@033566f8432bed730d0d39795b93a2e34e7a2211 -
Trigger Event:
push
-
Statement type:
File details
Details for the file pageserve-0.1.1-py3-none-any.whl.
File metadata
- Download URL: pageserve-0.1.1-py3-none-any.whl
- Upload date:
- Size: 31.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d74a8984c200fee9061126f0ed4053cf2591fa20117f50435a15d55880896225
|
|
| MD5 |
4085b247e49049c82f6dc6f130c4bb0d
|
|
| BLAKE2b-256 |
274504c666e574d6d9985b55ac49f5ba9448c5dfe15f39b5c4ed21463aef340f
|
Provenance
The following attestation bundles were made for pageserve-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on pageserve/pageserve
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
pageserve-0.1.1-py3-none-any.whl -
Subject digest:
d74a8984c200fee9061126f0ed4053cf2591fa20117f50435a15d55880896225 - Sigstore transparency entry: 1810941629
- Sigstore integration time:
-
Permalink:
pageserve/pageserve@033566f8432bed730d0d39795b93a2e34e7a2211 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/pageserve
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@033566f8432bed730d0d39795b93a2e34e7a2211 -
Trigger Event:
push
-
Statement type: