MCP server
Khiip ships a Model Context Protocol server
(khiip-mcp-server) that exposes the substrate’s core operations as MCP tools.
Claude Desktop, Cursor, or any other MCP-aware client can connect over stdio and
capture/recall through Khiip without writing any HTTP code.
Tools
| Tool | Maps to |
|---|---|
capture_url(url, force_new=False) | POST /api/v1/captures?mode=async + polls GET /api/v1/jobs/{id} |
recall(query, limit=10, min_score=None) | GET /api/v1/recall?view=lean |
list_captures(source=None, limit=50, offset=0) | GET /api/v1/captures?view=lean |
get_capture(capture_id, format="json") | GET /api/v1/captures/{id} |
refetch_capture(capture_id, dimension="extraction", force=False) | POST /api/v1/captures/{id}/refetch |
daemon_status() | GET /health + GET /api/v1/meta |
recall and list_captures return lean rows — a flat, token-frugal
projection (id, url, source, title, author, recorded_at,
vault_path, description, excerpt, excerpt_from, payload_chars, and —
on recall — score) rather than the full nested payload, which would blow
past an MCP client’s tool-result budget. excerpt is the head of the source’s
primary text (capped at 500 characters, with a trailing … only when it was
actually truncated); payload_chars is the exact character count of the
format="json" drill-down body, so an agent can size get_capture(id) before
calling it. There is no full/verbose flag on these two tools by design —
get_capture(id) is the drill-down.
recall’s optional min_score is a cosine floor in [-1, 1] — see
score interpretation. refetch_capture’s
dimension takes extraction (default) / re-extract / re-render / media /
wayback, and force=True overrides the daemon’s hand-edit guard on the
in-place dimensions — see the refetch guide.
capture_url runs asynchronously
capture_url submits the capture as a daemon-side job and polls it to
completion, forwarding per-stage progress (extract → enrich → media →
preserve → wayback → render → write → embed; the media stage reports item
counts) as MCP progress notifications when your client asks for them. The
returned value is the finished capture record — the same shape a synchronous
capture returns — so nothing about the result changes; you just get a progress
bar on long captures.
The poll loop has a client-side wall-clock deadline (300 seconds). If a capture
job is still running after that — a stuck worker, not a slow one — the tool
returns a structured capture-job-timeout result (with the job_id) instead of
hanging; the job keeps running on the daemon and can be polled directly at
GET /api/v1/jobs/{id}.
Every tool reports failures as a structured {"error": ..., "detail": ...}
dict rather than raising, so the agent always has something readable to act on
(for example a daemon-unreachable error when the daemon is down, or
daemon-409 when a refetch would clobber a hand-edited note).
How it works
The MCP server is a thin HTTP proxy that connects to your running Khiip daemon —
by default at 127.0.0.1:8478, or wherever KHIIP_DAEMON_URL, else
[daemon] host/port in ~/.config/khiip/config.toml, points it (in that precedence;
prefer config.toml, since GUI-launched MCP servers don’t reliably see shell env
vars). It reads the Bearer auth token from ~/.config/khiip/auth.toml — the same file
the CLI and Obsidian plugin use. Since 0.2.3, the MCP initialize handshake reports
khiip’s own package version, so your client shows which khiip it launched.
The daemon must be running before MCP tool calls land. To start it
automatically at login — so a reboot never leaves your MCP client pointing at a dead
daemon — use the ready-made launchd/systemd templates on the
Autostart (launchd / systemd) page.
Claude Desktop config
On macOS, edit ~/Library/Application Support/Claude/claude_desktop_config.json
(analogous paths on other platforms):
{ "mcpServers": { "khiip": { "command": "khiip-mcp-server" } }}If khiip-mcp-server isn’t on Claude Desktop’s PATH (common when you installed
with pip install -e inside a venv), point at the full path:
{ "mcpServers": { "khiip": { "command": "/Users/you/projects/khiip/.venv/bin/khiip-mcp-server" } }}Verify the wiring
# Start the daemon in one terminalkhiipd serve
# In another terminal, send an initialize ping to the MCP serverpython -c "import asynciofrom mcp.client.session import ClientSessionfrom mcp.client.stdio import stdio_client, StdioServerParameters
async def main(): params = StdioServerParameters(command='khiip-mcp-server', args=[]) async with stdio_client(params) as (read, write): async with ClientSession(read, write) as session: await session.initialize() tools = await session.list_tools() for t in tools.tools: print(t.name)
asyncio.run(main())"You should see all six tool names listed.