index4ai-mcp — index4.ai MCP Server
index4ai-mcp is the Model Context Protocol server for index4.ai. It exposes nine tools covering the full loop: ingest (sync, async, resume), search, job status, document listing, attribute and project listing, and a self-describing index4ai_explain. The server is hosted only — connect any MCP client to https://mcp.index4.ai without any local install.
- Repo: github.com/poma-ai/index4ai-mcp
- Hosted endpoint:
https://mcp.index4.ai(OAuth 2.0 browser login, or an API key in a header) - Transport: streamable HTTP. There is no local, stdio, Docker or npx mode.
- License: MPL-2.0
When to use this server
Pick index4ai-mcp when:
- You want prompt-ready RAG context from index4.ai without writing glue code in your agent.
- You want zero-install access: one URL in your MCP client config.
- You want ready-made agent skills for ingesting and searching (see the Agent Plugin).
1. Get credentials
You have two ways to authenticate:
- OAuth 2.0 (default). No key in your config. On first connect your client receives a
401challenge and walks you through a browser login. MCP clients (Claude Code, Claude Desktop, Cursor, …) handle the whole flow automatically. - API key. In the index4.ai console, create a project (Create a project) and copy the project API key shown at creation (prefix
poma_proj_gr_…) — it scopes every call to that project implicitly. Send it asx-api-key: <key>orAuthorization: Bearer <key>.
An account-level key (prefix poma_acc_…) also works with an API-key setup: pass project_id on each tool call to select the project. Use index4ai_projects to list the projects your key can see.
2. Add it to your agent
Two options. Option B (Agent Plugin) is the fastest for Claude Code — one command wires the server and installs two ready-made skills. Use Option A for a plain MCP config without skills.
| What you get | Pick it when | |
|---|---|---|
| A — hosted MCP | Hosted endpoint, plain MCP config | Your client speaks type: "http" and you want zero extras |
| B — Agent Plugin | Hosted endpoint + index4ai-ingest / index4ai-search skills | You use Claude Code (or any Agent-Plugins-capable tool) and want the batteries-included setup |
Option A — hosted MCP
{
"mcpServers": {
"index4ai": {
"type": "http",
"url": "https://mcp.index4.ai"
}
}
}{
"mcpServers": {
"index4ai": {
"url": "https://mcp.index4.ai"
}
}
}Prefer a static key over the browser flow (CI, headless agents)? Pass it as a header instead — "headers": { "x-api-key": "your-api-key" } (or "Authorization": "Bearer your-api-key") — and OAuth is skipped.
The hosted server runs on index4.ai's infrastructure, so it cannot read paths on your laptop. Send small files as
file_base64, and large files byurl(the index4.ai API fetches it) or throughPOST /ingest-upload.
After saving, restart the client. Sample prompt:
"Ingest
https://example.com/contract.pdfinto index4.ai, then search it for 'termination clause'."
Option B — Agent Plugin (one command, adds skills)
The plugin index4ai@index4ai bundles the MCP declaration with two skills, index4ai-ingest and index4ai-search. In Claude Code:
claude plugin marketplace add poma-ai/index4ai-mcp
claude plugin install index4ai@index4aiFor other Agent-Plugins-capable clients: npx plugins add https://github.com/poma-ai/index4ai-mcp.
Tools
index4ai-mcp exposes nine tools covering the full index4.ai loop.
| Tool | What it does |
|---|---|
index4ai_ingest | Starts ingest; returns job_id after upload. Does not wait for indexing to finish. |
index4ai_ingest_sync | Ingest + wait for terminal status. Returns job_id and the status events. |
index4ai_ingest_resume | Reconnect to an in-progress job's status stream and wait until terminal — without re-uploading. |
index4ai_jobs_status | Snapshot the status of up to 50 jobs in one call. No streaming. |
index4ai_search | Hybrid search returning prompt-ready context. Use doc_filter to scope to one doc, attribute_filters to filter on typed attributes. |
index4ai_attributes | List the project's typed attributes (name + type) and the name budget. Call before passing attributes to an ingest tool. |
index4ai_docs_list | List the documents in the project (pages through the API internally). Metadata only. |
index4ai_projects | List the projects your key can access. With a project API key, just the bound project. |
index4ai_explain | Returns a markdown explanation of how index4.ai works. No arguments, no auth — useful for self-documenting agents. |
Every tool accepts an optional token that overrides the credential from the header. All tools except index4ai_explain, index4ai_ingest_resume, index4ai_jobs_status and index4ai_projects accept an optional project_id, needed only with an account-level key.
doc_idvsjob_id: when an ingest reachesdone, the document'sdoc_idequals thejob_idreturned by ingest — unless the result carries agrillobject (see Dedup and re-ingest), in which casegrill.doc_idis the document. Keep that value to feeddoc_filteron subsequent searches.
index4ai_ingest and index4ai_ingest_sync
Both tools accept the same arguments. Provide exactly one of file_base64 or url.
| Argument | Type | Required | Description |
|---|---|---|---|
file_base64 | string | one-of | Standard base64 of the file bytes. Bounded by the MCP request body limit (about 12 MiB of file by default). |
url | string | one-of | Remote URL — the index4.ai API fetches and ingests it; the MCP server does not download it. Use this for large files. |
filename | string | no | Original basename (report.pdf). Inferred from the content when omitted. |
attributes | object | no | Typed document attributes: a name → value map (string, number, boolean, or a non-empty array of one of those), e.g. {"region":"emea","year":2025}. Call index4ai_attributes first and reuse an existing name and type where one fits. Names match ^[a-z0-9_]{1,64}$ and are permanent per project. Sent as X-Attributes (max 64 names, 2048 characters). |
attribute_schema | object | no | Type declarations for names in attributes, shaped {"name": {"type": "…"}}. Needed only where the value cannot say the type: encrypted_text (always), datetime for a new name, and the array type of an empty array. A declaration that conflicts with the type already in force is rejected. |
labels | object | no | Legacy — still accepted; prefer attributes. Each {key: value} pair becomes a "key:value" string in the labels typed attribute. Avoid : in keys. Labels share the attribute limits. |
project_id | string | no | Target project. Needed with account-level keys. |
token | string | no | API key — overrides the header credential for this call. |
Picking index4ai_ingest vs index4ai_ingest_sync
| Use case | Tool |
|---|---|
| Agent will search immediately and needs the doc indexed first | index4ai_ingest_sync |
| Long ingest; agent will check back later; conversation context is precious | index4ai_ingest, then poll with index4ai_jobs_status or reconnect with index4ai_ingest_resume |
index4ai_ingest_resume
Reconnect to an already-running ingest job and wait for it to reach a terminal state. Use this when a previous index4ai_ingest returned a job_id (or the MCP connection dropped mid-ingest) and you don't want to re-upload the file.
| Argument | Type | Required | Description |
|---|---|---|---|
job_id | string | yes | Job ID from a prior ingest. |
token | string | no | API key. |
Returns the same job_id + events payload as index4ai_ingest_sync.
index4ai_jobs_status
Get status snapshots for up to 50 jobs in a single call. Non-streaming — call on an interval if you want progress.
| Argument | Type | Required | Description |
|---|---|---|---|
job_ids | array of string | yes | Up to 50 job IDs to query. |
token | string | no | API key. |
Each result carries the job's status; once the API reports the outcome it also carries the grill object described under Dedup and re-ingest.
index4ai_search
Hybrid search across the project. Result count is bounded server-side by relevance and a token budget — there is no top_k.
| Argument | Type | Required | Description |
|---|---|---|---|
query | string | yes | Natural-language search query. |
doc_filter | string | no | doc_id to restrict search to one document (routes to POST /searchInDoc). |
exclude_doc_ids | array of string | no | Doc IDs to exclude from results (max 100). Useful in agent loops to avoid re-citing docs already shown. |
return_assets | boolean | no | Return cited docs' figures/tables in the response assets field, keyed by doc_id (images are base64 data URIs). |
attribute_filters | array of {attr, op, value} | no | Filter on typed attributes; clauses AND together. Operators depend on the attribute's type (see Retrieval). A name the project has never used matches nothing — check names and types with index4ai_attributes. |
project_id | string | no | Target project (account-level keys). |
token | string | no | API key. |
Response (example):
{ "context": "<context>This is what is relevant […] inside your document.</context>" }The context field is the main payload — drop it straight into your LLM prompt. See RetrievalContext format for the wrapper grammar (<doc>, inline [pN] / […] markers, sandwich ordering, citations).
index4ai_attributes
List the typed attributes already declared in the project — each name with its type — plus max_names, the per-project cap on distinct names (64). Wraps GET /attributes.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | no | Target project (account-level keys). |
token | string | no | API key. |
{
"attributes": [
{ "name": "region", "type": "string" },
{ "name": "year", "type": "int" }
],
"max_names": 64,
"note": "…"
}Agents must call this before declaring a new attribute. Attribute names and their types are permanent per project and count against max_names, so an agent that invents a fresh name per document (year, doc_year, publication_year) burns the budget and splits every later filter across synonyms. The rule:
- Call
index4ai_attributesbefore the first ingest that passesattributes, and before buildingattribute_filtersforindex4ai_search. - Reuse an existing name and type where one fits — send
2025to an existingyear:int, not"2025"and not a newdoc_year. - Declare a new name only when nothing fits.
- If the call fails with
retryable: true(the API's503— the schema is temporarily unreadable), wait and retry. Never treat a failed call as "no attributes exist", and don't declare new names until the list can be read.
Example flow:
index4ai_attributes {}
→ attributes: [region:string, year:int]
index4ai_ingest_sync {
"url": "https://example.com/q3-report.pdf",
"attributes": { "region": "emea", "year": 2025, "review_notes": "…" },
"attribute_schema": { "review_notes": { "type": "encrypted_text" } }
}
→ region, year reused; review_notes is the one new name
index4ai_search {
"query": "operating margin",
"attribute_filters": [ { "attr": "year", "op": "gte", "value": 2024 } ]
}index4ai_docs_list
List the documents in the project. Metadata only; the tool pages through the API internally.
| Argument | Type | Required | Description |
|---|---|---|---|
project_id | string | no | Target project (account-level keys). |
token | string | no | API key. |
index4ai_projects
List the projects your credential can access, each with its project_id. With a project API key it returns just the bound project. Useful with account-level keys to find the project_id to pass to other tools.
index4ai_explain
Self-documentation tool. Takes no arguments and requires no authentication — returns a markdown explanation of how index4.ai works (ingest, search, result format, API key setup). Useful for agents that want to introspect their own capabilities, and for "explain this server" probes.
Output shapes
index4ai_ingest_sync waits for a terminal status and returns the job id with the status events (illustrative):
{
"job_id": "100c65a03a304aa343a1518aa79e8300-20260414T083549Z",
"events": [
{ "status": "pending" },
{ "status": "processing" },
{ "status": "indexing" },
{ "status": "done" }
]
}index4ai_ingest (async variant) returns the job_id immediately, without the trailing events.
Dedup and re-ingest
When the API reports the outcome, index4ai_ingest_sync, index4ai_ingest_resume and each index4ai_jobs_status result carry a grill object:
{ "deduplicated": false, "doc_id": "…", "replaced_doc_ids": [] }- New content:
deduplicated: false, noreplaced_doc_ids;doc_idequalsjob_id. - Unchanged file, same conversion build:
deduplicated: true. Nothing new was stored;doc_idnames the existing document. Use that id asdoc_filter. - Unchanged file, newer conversion build:
deduplicated: false, andreplaced_doc_idslists the documents this job replaces.
Errors
On error, the MCP response sets isError: true and returns a structured error:
{ "error": "job failed: unsupported file type", "code": "job_failed", "retryable": false }code is one of missing_token, invalid_input, auth_expired, payment_required, project_protected, forbidden, too_many_jobs, upstream_error, transport_error, parse_error, job_failed, stream_error. Retry only when retryable is true, after retry_after_seconds.
Large uploads — POST /ingest-upload
For files too big for a tool call, the hosted server has a dedicated upload endpoint that bypasses MCP framing. Same auth as MCP — x-api-key or Authorization: Bearer. An optional X-Project-ID header is forwarded.
- Raw body: send file bytes as the body. Pass the basename via the
?filename=…query string or theX-Filenameheader. - Multipart:
Content-Type: multipart/form-datawith a part namedfile. - Metadata (optional):
X-Attributes(JSON object) andX-Attribute-Schema, validated like the tools'attributes/attribute_schema. Invalid metadata returns400invalid_input. - Response:
201 Createdwith{"job_id": "…"}.
curl -sS -X POST "https://mcp.index4.ai/ingest-upload?filename=report.pdf" \
-H "x-api-key: $INDEX4AI_API_KEY" \
-H "Content-Type: application/octet-stream" \
--data-binary @./report.pdfThe returned job_id is the one you'd pass to index4ai_search's doc_filter once indexing completes (subject to the dedup rule above).
Tips for agent-driven workflows
- For large files, hand the server a URL. Base64 payloads inflate tool calls and are capped at about 12 MiB of file; a
url(or/ingest-upload) avoids both. - Capture the
doc_idafter ingest. The agent can then issue follow-upindex4ai_searchcalls scoped to that document withdoc_filter— this is the difference between "search my whole project" and "chat with this PDF" UX. - Combine ingest + search in one turn. With
index4ai_ingest_syncfollowed byindex4ai_search, the agent can answer a question about a freshly uploaded file in a single response. - Check attributes before tagging. Have the agent call
index4ai_attributesbefore any ingest that setsattributes, reuse existing names, and retry (never assume "none") when the call fails. - Bulk ingest folders by submitting each file with
index4ai_ingestand pollingindex4ai_jobs_statusuntil every job is terminal.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
missing_token | No credential in the header and no token argument | Complete the OAuth login, or add x-api-key / Authorization to the client config. |
forbidden / 401 on index4ai_* | An account-level key was used without selecting a project, or the key does not match the project | Use the project API key (poma_proj_gr_…), or pass project_id with your account key. index4ai_projects lists what the key can see. |
project_protected | The project is protected and rejects account-level credentials | Use a project API key. |
auth_expired | The OAuth session or token expired | Reconnect the server in your client to log in again, or supply a fresh key. |
| Request rejected as too large | file_base64 exceeds the MCP request body limit | Send the file by url, or use POST /ingest-upload. |
| Agent tries to read a local file path | The hosted server cannot read paths on your machine | Send file_base64, url, or /ingest-upload. |