Skip to content

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.


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

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

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). content blocks are used for human-readable summaries; structured payloads sit in structuredContent.
  • Errors return a tool call with isError: true and a JSON { code, message, details } body.
  • Idempotency is automatic: the server treats identical (tool, input) within 60s as a replay if the tool is safe.
ToolPurposeRequiredOptional
vault_statushealth + counts
note_readfetch a notepathinclude[]
note_listlist notespath_glob, tag, frontmatter, limit, cursor
note_outlineheadings of a notepath
note_existsboolean existencepath
vault_searchfull-text/semantic/hybridqmode, tag[], path_glob, limit
link_backlinksinbound linkspath
link_forwardoutbound linkspath
link_unresolvedbroken [[…]]limit
link_orphansnotes with no linkslimit
tag_listall tags + counts
tag_notesnotes for a tagtaglimit
canvas_readfull JSON Canvaspath
base_queryrun a .basepathlimit, cursor
ai_statusAI runtime probe (provider, model, cache, spend)
ai_summarizesummary of a scope, with citationsscopestyle, length, include, language, max_tokens, cite, seed, cache
ai_askgrounded Q&A over the vaultquestionscope, retrieve, style, history, max_tokens, cite, stream
ai_tocTOC (single note) or MOC (folder/glob/tag)mode, path | scopedepth, include_descriptions, group_by, max_groups, max_per_group, include_summaries, title
ai_relaterelated notes + relation typingsourcecandidates, top_k, retrieve_mode, classify, relations, threshold, propose_links
ai_tag (suggest)suggest tags biased to existing vocabularyscopevocabulary (use_existing, allow_new, candidates, forbid, namespace, max_per_note, min_confidence), language, seed, cache
ai_metadata (suggest)typed frontmatter generationscope, fields[]language, seed, cache
ai_extract (suggest)structured extraction into a JSON Schemascope, schemaschema_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)”
ToolPurposeRequiredOptional
note_writecreate or replacepath, bodyfrontmatter, if_match, if_not_exists
note_patchstructured editpath, ops[]if_match
note_deletedelete a notepathif_match
note_moverename + rewrite linksfrom, toupdate_links (default true)
tag_renamebulk renamefrom, todry_run
canvas_writecreate/replace canvaspath, canvasif_match
canvas_patchstructured canvas editpath, ops[]if_match
ai_toc (with write)materialize a generated TOC/MOC as a notemode, write.pathif_match, if_not_exists, frontmatter
ai_tag (with apply: true)apply tag suggestions to frontmatterscope, apply: trueapply_mode, if_match, vocabulary fields
ai_metadata (with apply: true)populate typed frontmatter fieldsscope, fields[], apply: trueapply_mode, if_match
ai_extract (with apply: true + destination: "frontmatter")persist extracted data to frontmatterscope, schema, destination: "frontmatter", destination_key, apply: trueapply_mode, if_match

3.3 Admin tools (require vault:admin scope)

Section titled “3.3 Admin tools (require vault:admin scope)”
ToolPurpose
vault_reindexrebuild the index
vault_config_getread merged config
vault_config_setwrite config
{
"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" }
}
}
}
{
"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.

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.

Resources let an agent browse the vault as if it were a filesystem.

vault://<path> a single note or attachment
vault://search?q=…&mode=… a live search result set
vault://graph the link graph as JSON
vault://graph/backlinks/<path> backlinks for one note
vault://graph/forward/<path> forward links for one note
vault://tags tag index
vault://canvas/<path> a JSON Canvas
vault://base/<path> a Bases query result
  • resources/list returns a paginated tree starting at vault://.
  • resources/read returns the file bytes (for .md, .canvas, .base) or a JSON projection (for the synthetic vault://search/..., vault://graph/... URIs).
  • resources/subscribe watches a path glob and emits notifications/resources/updated on change. The agent can subscribe to vault://Projects/** to track a focused area.
  • 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.

Indx ships a few prompts as starting points; the user/agent can ignore them.

PromptArgsPurpose
vault_summarySummarize vault state (counts, recent activity, top tags)
daily_notedate?Create or open today’s daily note in Daily/YYYY-MM-DD.md
link_orphanslimit?Walk orphan notes and propose links
cleanup_unresolved_linksdry_run?Find and propose fixes for broken [[…]]
weekly_reviewweek_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.

Terminal window
INDX_VAULT=$HOME/MyVault indx mcp serve --stdio

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

POST /mcp
Authorization: Bearer indx_…
Content-Type: application/json
Accept: text/event-stream

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

MCP tool ↔ HTTP ↔ CLI
──────────────────────────────────────────────────────────────────────────────
note_read GET /v1/notes/{path} indx note get
note_write PUT /v1/notes/{path} indx note update
note_patch PATCH /v1/notes/{path} indx note patch
note_delete DELETE /v1/notes/{path} indx note delete
note_move POST /v1/notes/{path}/move indx note move
note_list GET /v1/notes indx note list
note_outline GET /v1/notes/{path}?include=outline indx note outline
note_exists HEAD /v1/notes/{path} indx note exists
vault_search GET /v1/search indx search
link_backlinks GET /v1/links/{path}/backlinks indx link backlinks
link_forward GET /v1/links/{path}/forward indx link forward
link_unresolved GET /v1/links/unresolved indx link unresolved
link_orphans GET /v1/links/orphans indx link orphans
tag_list GET /v1/tags indx tag list
tag_notes GET /v1/tags/{tag}/notes indx tag notes
tag_rename POST /v1/tags/rename indx tag rename
canvas_read GET /v1/canvas/{path} indx canvas get
canvas_write PUT /v1/canvas/{path} indx canvas create
canvas_patch PATCH /v1/canvas/{path} indx canvas patch
base_query GET /v1/bases/{path}/query indx base query
vault_status GET /v1/vault/status indx vault status
vault_reindex POST /v1/vault/reindex indx vault index --rebuild
vault_config_get GET /v1/vault/config indx vault config get
vault_config_set PUT /v1/vault/config indx vault config set
ai_status GET /v1/ai/status indx ai status
ai_summarize POST /v1/ai/summarize indx ai summarize
ai_ask POST /v1/ai/ask indx ai ask
ai_toc POST /v1/ai/toc indx ai toc
ai_relate POST /v1/ai/relate indx ai relate
ai_tag POST /v1/ai/tag indx ai tag
ai_metadata POST /v1/ai/metadata indx ai metadata
ai_extract POST /v1/ai/extract indx ai extract

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: [...] }
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"
  • 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, and prompts/list and be productive in one round trip.