MCP Server
Model Context Protocol surface — stdio + HTTP/SSE, tools / resources / prompts.
Status: Draft v0.1 · 2026-05-03
Transports: stdio (indx mcp serve --stdio), HTTP/SSE (POST /mcp on the main server).
SDK: @modelcontextprotocol/sdk
AI runtime: see AI.md. The ai_* tools below are the MCP transport for those operations.
1. Why MCP first
Section titled “1. Why MCP first”For a frontier-model agent, an MCP server is the most native way to talk to indx:
- One handshake, many tools. The agent loads the tool catalog once and gets self-describing JSON Schemas for every operation.
- Resources are first-class. Notes, search results, and the link graph all surface as readable resources — agents can browse, not just call.
- Prompts come along for free. indx ships a few high-leverage prompts (daily-note, link-orphans, vault-summary) so a fresh agent can be productive immediately.
Everything in the API surface is reachable via MCP. The naming is tuned for tool calling (snake-case, verb-first, atomic).
2. Server identity
Section titled “2. Server identity”{ "name": "indx", "version": "0.1.0", "instructions": "You are connected to an indx vault. Notes are markdown files identified by vault-relative posix paths. Prefer note_patch with structured ops over note_write whenever you only need to change a section. Search before you read; read before you write; pass the etag back on writes."}The instructions block is the agent onboarding — it nudges good habits without prescriptive prompts.
3. Tools
Section titled “3. Tools”All tools follow these conventions:
- Snake-case names, verb-first.
- Inputs are flat JSON objects with required fields up front.
- Outputs are structured (no walls of prose).
contentblocks are used for human-readable summaries; structured payloads sit instructuredContent. - Errors return a tool call with
isError: trueand a JSON{ code, message, details }body. - Idempotency is automatic: the server treats identical
(tool, input)within 60s as a replay if the tool issafe.
3.1 Read tools (safe)
Section titled “3.1 Read tools (safe)”| Tool | Purpose | Required | Optional |
|---|---|---|---|
vault_status | health + counts | — | — |
note_read | fetch a note | path | include[] |
note_list | list notes | — | path_glob, tag, frontmatter, limit, cursor |
note_outline | headings of a note | path | — |
note_exists | boolean existence | path | — |
vault_search | full-text/semantic/hybrid | q | mode, tag[], path_glob, limit |
link_backlinks | inbound links | path | — |
link_forward | outbound links | path | — |
link_unresolved | broken [[…]] | — | limit |
link_orphans | notes with no links | — | limit |
tag_list | all tags + counts | — | — |
tag_notes | notes for a tag | tag | limit |
canvas_read | full JSON Canvas | path | — |
base_query | run a .base | path | limit, cursor |
ai_status | AI runtime probe (provider, model, cache, spend) | — | — |
ai_summarize | summary of a scope, with citations | scope | style, length, include, language, max_tokens, cite, seed, cache |
ai_ask | grounded Q&A over the vault | question | scope, retrieve, style, history, max_tokens, cite, stream |
ai_toc | TOC (single note) or MOC (folder/glob/tag) | mode, path | scope | depth, include_descriptions, group_by, max_groups, max_per_group, include_summaries, title |
ai_relate | related notes + relation typing | source | candidates, top_k, retrieve_mode, classify, relations, threshold, propose_links |
ai_tag (suggest) | suggest tags biased to existing vocabulary | scope | vocabulary (use_existing, allow_new, candidates, forbid, namespace, max_per_note, min_confidence), language, seed, cache |
ai_metadata (suggest) | typed frontmatter generation | scope, fields[] | language, seed, cache |
ai_extract (suggest) | structured extraction into a JSON Schema | scope, schema | schema_id, destination (= "json" for read-only), language, seed, cache |
3.2 Write tools (require vault:write scope)
Section titled “3.2 Write tools (require vault:write scope)”| Tool | Purpose | Required | Optional |
|---|---|---|---|
note_write | create or replace | path, body | frontmatter, if_match, if_not_exists |
note_patch | structured edit | path, ops[] | if_match |
note_delete | delete a note | path | if_match |
note_move | rename + rewrite links | from, to | update_links (default true) |
tag_rename | bulk rename | from, to | dry_run |
canvas_write | create/replace canvas | path, canvas | if_match |
canvas_patch | structured canvas edit | path, ops[] | if_match |
ai_toc (with write) | materialize a generated TOC/MOC as a note | mode, write.path | if_match, if_not_exists, frontmatter |
ai_tag (with apply: true) | apply tag suggestions to frontmatter | scope, apply: true | apply_mode, if_match, vocabulary fields |
ai_metadata (with apply: true) | populate typed frontmatter fields | scope, fields[], apply: true | apply_mode, if_match |
ai_extract (with apply: true + destination: "frontmatter") | persist extracted data to frontmatter | scope, schema, destination: "frontmatter", destination_key, apply: true | apply_mode, if_match |
3.3 Admin tools (require vault:admin scope)
Section titled “3.3 Admin tools (require vault:admin scope)”| Tool | Purpose |
|---|---|
vault_reindex | rebuild the index |
vault_config_get | read merged config |
vault_config_set | write config |
3.4 Tool schema example — note_patch
Section titled “3.4 Tool schema example — note_patch”{ "name": "note_patch", "title": "Edit a note with structured ops", "description": "Apply one or more AST-aware patches to a markdown note. Prefer this over note_write when changing a single section. Returns the full updated note (read-after-write) with new etag.", "inputSchema": { "type": "object", "required": ["path", "ops"], "properties": { "path": { "type": "string", "description": "Vault-relative posix path, e.g. 'Projects/Foo.md'" }, "ops": { "type": "array", "minItems": 1, "items": { "$ref": "#/definitions/Patch" } }, "if_match": { "type": "string", "description": "Optional ETag for optimistic concurrency. Pass the etag returned from note_read." } }, "definitions": { "Patch": { "oneOf": [ { "type": "object", "required": ["op", "key", "value"], "properties": { "op": { "const": "set_frontmatter" }, "key": { "type": "string" }, "value": {} } }, { "type": "object", "required": ["op", "key"], "properties": { "op": { "const": "delete_frontmatter" }, "key": { "type": "string" } } }, { "type": "object", "required": ["op", "markdown"], "properties": { "op": { "const": "append_body" }, "markdown": { "type": "string" } } }, { "type": "object", "required": ["op", "markdown"], "properties": { "op": { "const": "prepend_body" }, "markdown": { "type": "string" } } }, { "type": "object", "required": ["op", "heading", "markdown"], "properties": { "op": { "const": "insert_before_heading" }, "heading": { "type": "string" }, "markdown": { "type": "string" } } }, { "type": "object", "required": ["op", "heading", "markdown"], "properties": { "op": { "const": "insert_after_heading" }, "heading": { "type": "string" }, "markdown": { "type": "string" } } }, { "type": "object", "required": ["op", "heading", "markdown"], "properties": { "op": { "const": "replace_section" }, "heading": { "type": "string" }, "markdown": { "type": "string" } } }, { "type": "object", "required": ["op", "block_id", "markdown"], "properties": { "op": { "const": "replace_block" }, "block_id": { "type": "string" }, "markdown": { "type": "string" } } }, { "type": "object", "required": ["op", "from", "to"], "properties": { "op": { "const": "rename_heading" }, "from": { "type": "string" }, "to": { "type": "string" } } } ] } } }, "outputSchema": { "type": "object", "properties": { "path": { "type": "string" }, "etag": { "type": "string" }, "frontmatter": {}, "outline": { "type": "array" }, "body": { "type": "string" } } }}3.5 Tool result example — vault_search
Section titled “3.5 Tool result example — vault_search”{ "isError": false, "structuredContent": { "results": [ { "path": "Ops/Runbook.md", "title": "Runbook", "score": 0.81, "snippet": "…incident **runbook**…", "tags": ["ops"], "matched_in": ["title", "body"] } ], "next_cursor": null, "mode_used": "hybrid" }, "content": [ { "type": "text", "text": "Found 1 result for 'runbook' (hybrid). Top hit: Ops/Runbook.md (score 0.81)." } ]}The text block is a short, deterministic summary — useful for an agent that wants to display progress without re-rendering the structured payload.
3.6 AI tools
Section titled “3.6 AI tools”ai_* tools are advertised iff a provider is configured (AI.md §3).
On a vault without AI configured, the tool catalog simply does not list them
— calling them via tools/call returns ai_unavailable. The same scope
gating applies as for non-AI tools: vault:read is sufficient for all AI
reads (including suggest mode of ai_tag, ai_metadata, ai_extract).
Apply-mode invocations — ai_toc with write, ai_tag / ai_metadata /
ai_extract with apply: true — require vault:write and are filtered out
of a vault:read-only catalog. Inputs and outputs match
AI.md §5 verbatim — the schemas come from the same
Zod tree as the HTTP API.
Streaming. ai_ask (and ai_summarize over very large scopes) emit
notifications/progress with partial, citation, and usage payloads
during execution; the final tool_result carries the complete
structuredContent. Agents that don’t subscribe to progress still receive
the full structured result — streaming is purely a latency optimization.
Resources. When AI is enabled, two extra read-only URI patterns join
MCP §4:
ai://summary/<url-encoded AiScope>ai://moc/<url-encoded AiScope>resources/subscribe on these URIs fires notifications/resources/updated
when the underlying scope’s content changes (any cited path’s etag moves) —
the agent re-reads to get a freshly-derived summary or MOC without polling.
4. Resources
Section titled “4. Resources”Resources let an agent browse the vault as if it were a filesystem.
4.1 URI scheme
Section titled “4.1 URI scheme”vault://<path> a single note or attachmentvault://search?q=…&mode=… a live search result setvault://graph the link graph as JSONvault://graph/backlinks/<path> backlinks for one notevault://graph/forward/<path> forward links for one notevault://tags tag indexvault://canvas/<path> a JSON Canvasvault://base/<path> a Bases query result4.2 Capabilities
Section titled “4.2 Capabilities”resources/listreturns a paginated tree starting atvault://.resources/readreturns the file bytes (for.md,.canvas,.base) or a JSON projection (for the syntheticvault://search/...,vault://graph/...URIs).resources/subscribewatches a path glob and emitsnotifications/resources/updatedon change. The agent can subscribe tovault://Projects/**to track a focused area.
4.3 Why resources and tools
Section titled “4.3 Why resources and tools”- Tools = action verbs (“do this thing now”).
- Resources = browsable, subscribable, paginatable views.
A common agent pattern is subscribe to vault://search?q=todo&mode=lexical, then react to the change stream by calling note_patch to mark items done. This is awkward with tools alone.
5. Prompts
Section titled “5. Prompts”Indx ships a few prompts as starting points; the user/agent can ignore them.
| Prompt | Args | Purpose |
|---|---|---|
vault_summary | — | Summarize vault state (counts, recent activity, top tags) |
daily_note | date? | Create or open today’s daily note in Daily/YYYY-MM-DD.md |
link_orphans | limit? | Walk orphan notes and propose links |
cleanup_unresolved_links | dry_run? | Find and propose fixes for broken [[…]] |
weekly_review | week_of? | Aggregate the last 7 days of changes into a review note |
A prompt definition includes both messages and a recommended tools allow-list, so the prompt feels self-contained.
6. Transport details
Section titled “6. Transport details”6.1 stdio (local agents)
Section titled “6.1 stdio (local agents)”INDX_VAULT=$HOME/MyVault indx mcp serve --stdioUsed by Claude Code, Cursor, etc. via their mcpServers config. Auth is implicit (the agent owns the process). Scopes default to vault:read,vault:write unless overridden.
6.2 HTTP / SSE (remote agents)
Section titled “6.2 HTTP / SSE (remote agents)”POST /mcpAuthorization: Bearer indx_…Content-Type: application/jsonAccept: text/event-streamStandard MCP HTTP transport. Token scopes (§2 of API.md) gate which tools are advertised in the catalog — an agent with only vault:read literally does not see the write tools.
7. Mapping to API and CLI
Section titled “7. Mapping to API and CLI”MCP tool ↔ HTTP ↔ CLI──────────────────────────────────────────────────────────────────────────────note_read GET /v1/notes/{path} indx note getnote_write PUT /v1/notes/{path} indx note updatenote_patch PATCH /v1/notes/{path} indx note patchnote_delete DELETE /v1/notes/{path} indx note deletenote_move POST /v1/notes/{path}/move indx note movenote_list GET /v1/notes indx note listnote_outline GET /v1/notes/{path}?include=outline indx note outlinenote_exists HEAD /v1/notes/{path} indx note existsvault_search GET /v1/search indx searchlink_backlinks GET /v1/links/{path}/backlinks indx link backlinkslink_forward GET /v1/links/{path}/forward indx link forwardlink_unresolved GET /v1/links/unresolved indx link unresolvedlink_orphans GET /v1/links/orphans indx link orphanstag_list GET /v1/tags indx tag listtag_notes GET /v1/tags/{tag}/notes indx tag notestag_rename POST /v1/tags/rename indx tag renamecanvas_read GET /v1/canvas/{path} indx canvas getcanvas_write PUT /v1/canvas/{path} indx canvas createcanvas_patch PATCH /v1/canvas/{path} indx canvas patchbase_query GET /v1/bases/{path}/query indx base queryvault_status GET /v1/vault/status indx vault statusvault_reindex POST /v1/vault/reindex indx vault index --rebuildvault_config_get GET /v1/vault/config indx vault config getvault_config_set PUT /v1/vault/config indx vault config setai_status GET /v1/ai/status indx ai statusai_summarize POST /v1/ai/summarize indx ai summarizeai_ask POST /v1/ai/ask indx ai askai_toc POST /v1/ai/toc indx ai tocai_relate POST /v1/ai/relate indx ai relateai_tag POST /v1/ai/tag indx ai tagai_metadata POST /v1/ai/metadata indx ai metadataai_extract POST /v1/ai/extract indx ai extract8. Worked examples
Section titled “8. Worked examples”8.1 “Read a note, edit it, write it back safely”
Section titled “8.1 “Read a note, edit it, write it back safely””1. note_read { path: "Spec.md" } ← { etag: "ab12…", body: "...", outline: [...] }
2. note_patch { path: "Spec.md", if_match: "ab12…", ops: [{ op: "insert_after_heading", heading: "## Open questions", markdown: "- [ ] decide on cursor scheme\n" }] } ← { etag: "cd34…", body: "...", outline: [...] }8.2 “Search, then fan out reads”
Section titled “8.2 “Search, then fan out reads””1. vault_search { q: "deployment runbook", mode: "hybrid", limit: 5 } ← { results: [{path,...}, ...] }
2. for each path: note_read { path }8.3 “Watch a folder, react to changes”
Section titled “8.3 “Watch a folder, react to changes””1. resources/subscribe { uri: "vault://Daily/**" }2. on notifications/resources/updated: → note_read on the changed path → note_patch to add a TOC link from "Index.md"9. What this design buys the agent
Section titled “9. What this design buys the agent”- No screen-scraping. Everything is a tool with a schema.
- No hidden state. ETags, idempotency keys, and structured outputs make retries safe.
- No glue parsers.
note_outline,link_backlinks, etc. give pre-parsed structure. - Fast feedback. Resources subscriptions push deltas; the agent doesn’t poll.
- Discovery. A new agent connecting to a strange indx server can call
tools/list,resources/list, andprompts/listand be productive in one round trip.