Skip to content

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 401 challenge 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 as x-api-key: <key> or Authorization: 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 getPick it when
A — hosted MCPHosted endpoint, plain MCP configYour client speaks type: "http" and you want zero extras
B — Agent PluginHosted endpoint + index4ai-ingest / index4ai-search skillsYou use Claude Code (or any Agent-Plugins-capable tool) and want the batteries-included setup

Option A — hosted MCP ​

json
{
  "mcpServers": {
    "index4ai": {
      "type": "http",
      "url": "https://mcp.index4.ai"
    }
  }
}
json
{
  "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 by url (the index4.ai API fetches it) or through POST /ingest-upload.

After saving, restart the client. Sample prompt:

"Ingest https://example.com/contract.pdf into 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:

bash
claude plugin marketplace add poma-ai/index4ai-mcp
claude plugin install index4ai@index4ai

For 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.

ToolWhat it does
index4ai_ingestStarts ingest; returns job_id after upload. Does not wait for indexing to finish.
index4ai_ingest_syncIngest + wait for terminal status. Returns job_id and the status events.
index4ai_ingest_resumeReconnect to an in-progress job's status stream and wait until terminal — without re-uploading.
index4ai_jobs_statusSnapshot the status of up to 50 jobs in one call. No streaming.
index4ai_searchHybrid search returning prompt-ready context. Use doc_filter to scope to one doc, attribute_filters to filter on typed attributes.
index4ai_attributesList the project's typed attributes (name + type) and the name budget. Call before passing attributes to an ingest tool.
index4ai_docs_listList the documents in the project (pages through the API internally). Metadata only.
index4ai_projectsList the projects your key can access. With a project API key, just the bound project.
index4ai_explainReturns 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_id vs job_id: when an ingest reaches done, the document's doc_id equals the job_id returned by ingest — unless the result carries a grill object (see Dedup and re-ingest), in which case grill.doc_id is the document. Keep that value to feed doc_filter on subsequent searches.

index4ai_ingest and index4ai_ingest_sync ​

Both tools accept the same arguments. Provide exactly one of file_base64 or url.

ArgumentTypeRequiredDescription
file_base64stringone-ofStandard base64 of the file bytes. Bounded by the MCP request body limit (about 12 MiB of file by default).
urlstringone-ofRemote URL — the index4.ai API fetches and ingests it; the MCP server does not download it. Use this for large files.
filenamestringnoOriginal basename (report.pdf). Inferred from the content when omitted.
attributesobjectnoTyped 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_schemaobjectnoType 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.
labelsobjectnoLegacy — 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_idstringnoTarget project. Needed with account-level keys.
tokenstringnoAPI key — overrides the header credential for this call.

Picking index4ai_ingest vs index4ai_ingest_sync

Use caseTool
Agent will search immediately and needs the doc indexed firstindex4ai_ingest_sync
Long ingest; agent will check back later; conversation context is preciousindex4ai_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.

ArgumentTypeRequiredDescription
job_idstringyesJob ID from a prior ingest.
tokenstringnoAPI 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.

ArgumentTypeRequiredDescription
job_idsarray of stringyesUp to 50 job IDs to query.
tokenstringnoAPI 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.

Hybrid search across the project. Result count is bounded server-side by relevance and a token budget — there is no top_k.

ArgumentTypeRequiredDescription
querystringyesNatural-language search query.
doc_filterstringnodoc_id to restrict search to one document (routes to POST /searchInDoc).
exclude_doc_idsarray of stringnoDoc IDs to exclude from results (max 100). Useful in agent loops to avoid re-citing docs already shown.
return_assetsbooleannoReturn cited docs' figures/tables in the response assets field, keyed by doc_id (images are base64 data URIs).
attribute_filtersarray of {attr, op, value}noFilter 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_idstringnoTarget project (account-level keys).
tokenstringnoAPI key.

Response (example):

json
{ "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.

ArgumentTypeRequiredDescription
project_idstringnoTarget project (account-level keys).
tokenstringnoAPI key.
json
{
  "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:

  1. Call index4ai_attributes before the first ingest that passes attributes, and before building attribute_filters for index4ai_search.
  2. Reuse an existing name and type where one fits — send 2025 to an existing year:int, not "2025" and not a new doc_year.
  3. Declare a new name only when nothing fits.
  4. If the call fails with retryable: true (the API's 503 — 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:

text
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.

ArgumentTypeRequiredDescription
project_idstringnoTarget project (account-level keys).
tokenstringnoAPI 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):

json
{
  "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:

json
{ "deduplicated": false, "doc_id": "…", "replaced_doc_ids": [] }
  • New content: deduplicated: false, no replaced_doc_ids; doc_id equals job_id.
  • Unchanged file, same conversion build: deduplicated: true. Nothing new was stored; doc_id names the existing document. Use that id as doc_filter.
  • Unchanged file, newer conversion build: deduplicated: false, and replaced_doc_ids lists the documents this job replaces.

Errors ​

On error, the MCP response sets isError: true and returns a structured error:

json
{ "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 the X-Filename header.
  • Multipart: Content-Type: multipart/form-data with a part named file.
  • Metadata (optional): X-Attributes (JSON object) and X-Attribute-Schema, validated like the tools' attributes / attribute_schema. Invalid metadata returns 400 invalid_input.
  • Response: 201 Created with {"job_id": "…"}.
bash
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.pdf

The 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_id after ingest. The agent can then issue follow-up index4ai_search calls scoped to that document with doc_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_sync followed by index4ai_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_attributes before any ingest that sets attributes, reuse existing names, and retry (never assume "none") when the call fails.
  • Bulk ingest folders by submitting each file with index4ai_ingest and polling index4ai_jobs_status until every job is terminal.

Troubleshooting ​

SymptomLikely causeFix
missing_tokenNo credential in the header and no token argumentComplete 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 projectUse the project API key (poma_proj_gr_…), or pass project_id with your account key. index4ai_projects lists what the key can see.
project_protectedThe project is protected and rejects account-level credentialsUse a project API key.
auth_expiredThe OAuth session or token expiredReconnect the server in your client to log in again, or supply a fresh key.
Request rejected as too largefile_base64 exceeds the MCP request body limitSend the file by url, or use POST /ingest-upload.
Agent tries to read a local file pathThe hosted server cannot read paths on your machineSend file_base64, url, or /ingest-upload.

See also ​