MCP integration
How the MCP server connects AI agents to your knowledge base — disk-only mode, Hocuspocus-connected mode, and available tools.
The Open Knowledge MCP server gives AI agents structured access to your knowledge base. It runs over stdio and works with any MCP-compatible editor.
Three operating modes
The MCP server adapts its behavior based on whether a collaboration server is running and whether auto-start is enabled. On startup it reads <contentDir>/.ok/server.lock; a live lock tells MCP the real port to connect to. If the lock is absent or stale, ok mcp detach-spawns ok start as a sibling process by default (opt-out restores the pre-Zero-Ceremony disk-only behavior).
Active when a live server.lock is found at startup — ok start or bun run dev is already running.
- All document tools become available — writes go through the CRDT layer for real-time sync with the browser editor
- Agents and humans can co-edit the same document simultaneously
- Undo/redo tracks agent edits separately from human edits
Active when no live server.lock is present and auto-start is not opted out. ok mcp detach-spawns npx @inkeep/open-knowledge start as a sibling process with a kernel-fd stderr redirect to .ok/last-spawn-error.log, then polls the lockfile for port > 0 with a 5-second deadline.
- Zero ceremony — the agent's first tool call resurrects the full stack
- Sibling process (not embedded) so Claude Code's kill-on-session-end does not take the server down with the MCP stdio
- Spawn failures surface the captured stderr (e.g.
EADDRINUSE) in the first tool-result error rather than silently dropping to disk-only - Once the poll succeeds, subsequent behavior is identical to Connected mode
- Transitive auto-git-init: the spawned
ok startrunsensureProjectGiton the project directory, so if there is no parent.git/present it will be created (default branchmain) before the collab server binds. Runningok mcpagainst a directory without.git/therefore creates one transitively — the disclosure line appears in the spawned server's stderr log. To suppress both the auto-spawn and the transitive git-init together, opt out withOK_MCP_AUTOSTART=0(env) ormcp.autoStart: false(config) to fall into Disk-only mode.
Active when auto-start is opted out (OK_MCP_AUTOSTART=0 env or mcp.autoStart: false config) and no live server.lock is found, or when --port=0 is passed explicitly.
- Workflow tools (
init-content,ingest,research) work normally — agents use their native file I/O alongside the instructional workflow tools - Auto-generated catalogs in
.ok/catalogs/are still maintained - Document tools (
write_document,edit_document, etc.) are not available since there is no CRDT layer to route through
Precedence for the startup decision
decideAutoStart is the single function that classifies the MCP session into one of the three modes above. Its precedence:
--port <n>CLI override — bypasses discovery entirely;> 0connects,= 0means disk-only.- Live
server.lockwithport > 0— connects tows://<host>:<port>regardless of auto-start config. A pre-existing live lock always wins; opt-out only suppresses the spawn path. - No live lock + auto-start allowed (default) — detach-spawn
ok start. - No live lock + auto-start opted out — disk-only mode.
Stale locks (PID no longer alive on the same host, or corrupt JSON) are cleaned up automatically when read. The env var OK_MCP_AUTOSTART=0 wins over mcp.autoStart: true in config.
Tools reference
| Tool | Mode | Description |
|---|---|---|
init-content | Both | Bootstrap knowledge articles from the codebase |
ingest | Both | Capture an external source (URL or file) as raw reference material |
research | Both | Gather sources and write provisional research findings |
consolidate | Both | Promote research into a canonical article |
read_document | Both | Read a document with enriched frontmatter + shadow-repo history |
search | Both | Search document contents |
exec | Both | Read-only bash-like commands (cat / ls / grep / find / head / tail / wc / sort / uniq / cut) |
list_documents | Both | List all documents in the knowledge base |
get_backlinks / get_forward_links | Both | Wiki-link graph queries |
get_hubs / get_orphans / get_dead_links | Both | Link-graph diagnostics |
suggest_links | Both | Suggest missing wiki-links for a target page |
get_config | Always | Read the effective merged config (defaults → user → project) |
set_config | Always | Patch config fields on the agent-settable allowlist (auto-scope-routed) |
set_folder_rule | Always | Transactional upsert of one or more folders[] rules |
write_document | Connected | Write content to a document through the CRDT (full document, including frontmatter) |
edit_document | Connected | Find-and-replace on document body through the CRDT (FM edits are rejected — see below) |
rename_document | Connected | Rename a document and rewrite inbound links |
rename_folder | Connected | Rename a folder and rewrite inbound links across every affected doc in one call |
rollback_to_version | Connected | Restore a document to a historical shadow-repo commit |
get_history | Connected | Query the shadow-repo version timeline |
save_version | Connected | Checkpoint the project to a named shadow-repo commit |
Tools marked Always operate directly on the YAML files via the imported Zod schema and so work whether or not ok start is running. When a server IS running, the file watcher detects the write and propagates it into Y.Text, refreshing any open Settings panes within ~500ms.
All 22 docName-producing tools emit previewUrl in their result (single-doc tools top-level, list-producing tools per-row with an additional top-level ui: {baseUrl, port} block). rename_folder emits one previewUrls map keyed by the new docName for every affected doc.
Workflow tools (init-content, ingest, research, consolidate) return instructional text that guides the agent through the workflow. The real work — reading files, editing content, fetching URLs — happens via the agent's native tools.
Frontmatter writes
Frontmatter lives in the YAML region of Y.Text('source') — the same CRDT text the body editor and source-mode editor bind to. Agents edit frontmatter through write_document (full-doc replace, including the ---\n…\n--- block). The browser-side property panel uses an in-process binding to manipulate the same region directly; agent and human edits land on the same Y.Text and merge through Y.js character-level CRDT.
edit_document rejects FM-intersecting find/replace calls with HTTP 400 and a hint pointing at write_document. Body edits stay on edit_document; frontmatter edits route through write_document. Mixing the two in one batch — body edit via edit_document, FM rewrite via write_document — is the supported pattern.
summary on write tools
The five write tools — write_document, edit_document, rename_document, rename_folder, rollback_to_version — accept an optional summary: string describing the intent of the edit in outcome phrasing (e.g. "Fixed token-refresh race", not "Added 3 lines"). Summaries are persisted per-contributor to the shadow-repo attribution journal and render as bullets on the document's [[timeline|Timeline]] row, so readers can scan recent agent activity without opening each diff.
- Cap. 80 characters at the API boundary (49 visible +
…suffix when truncated). The Zod schema caps input at 200 characters as a transport-safety bound; summaries longer than 80 are truncated server-side and the tool response includestruncatedFrom: <original-length>so the agent can adjust. - Optional. Empty strings and whitespace-only values are treated as absent.
rename_documentandrollback_to_versionfill in a default ("Renamed <from> → <to>"or"Restored to <sha-short>") when no summary is provided;write_document,edit_document, andrename_folderproduce a bulletless row in that case. Forrename_folder, one summary is applied to every affected-doc contributor entry — one folder rename is one user intent, not N. - Attribution. Agent calls (those carrying agent identity) attach an
agent-<id>contributor entry. UI-driven renames and rollbacks (noagentIdin the request) attribute to the server-loaded principal (principal-<uuid>) — see [[timeline|Timeline]] for how the rows render. - Privacy. Avoid secrets or PII in summaries — they are persisted to git history.
Config tools
get_config, set_config, and set_folder_rule operate directly on .ok/config.yml (project) and ~/.ok/config.yml (user-global). They do not require a running collab server — the tool resolves cwd to a project root and reads/writes via the imported Zod schema and yaml@2's Document layer (preserving comments, blank lines, and anchors through round-trips). When a server IS running, the file watcher closes the loop: the write lands → file watcher detects → Y.Text updates → any open Settings panes refresh.
get_config
Read the full effective merged config (defaults → user → project), or a sub-path:
get_config() // full config
get_config({ path: ["folders"] }) // folder rules only
get_config({ path: ["mcp", "tools"] }) // mcp.tools sub-treeRead-only and idempotent; no allowlist gating — every field is readable. Returns structuredContent: { value } with the resolved JSON.
set_config
Patch one or more fields on the agent-settable allowlist. The patch is a deep-partial; null at any leaf clears the field; arrays replace wholesale (RFC 7396 spirit, TypeScript-only — no wire format).
The allowlist is exactly three paths:
folders[](whole-array replace; useset_folder_rulefor per-rule upsert)mcp.tools.search.maxResultsmcp.tools.read_document.historyDepth
Anything else returns error.code: NOT_AGENT_SETTABLE. (Content scope is configured via .okignore — not via set_config — see Content filtering.)
There is no scope parameter — the server picks the write target (project vs user) per leaf via the inspect-ladder: existing-at-project → existing-at-user → field's defaultScope → user. If a single patch's leaves resolve to multiple scopes, the call fails with error.code: MIXED_SCOPE and the agent retries per-scope.
Validation runs as a single Zod parse over the merged config before any disk write; on failure the response shape is { ok: false, error: ConfigValidationError } (a discriminated union of YAML_PARSE | SCHEMA_INVALID | SCOPE_VIOLATION | NOT_AGENT_SETTABLE | MIXED_SCOPE | WRITE_ERROR | UNKNOWN) with a human-rendered error in content[].text that primes a retry. On success, the response includes { applied, scope, current } so the agent can confirm the effective state without re-fetching.
set_folder_rule
Transactional upsert of one or more folders[] rules — the schema-aware alternative to set_config({patch: {folders: [...]}}) when you want to add, replace, or rename rules without managing the whole array.
set_folder_rule({
rules: [
{ match: "specs/**", frontmatter: { tags: ["spec"] } },
{ match: "reports/**", frontmatter: { tags: ["report"] } },
]
})Always pass an array, even for a single rule. Each entry is {match, frontmatter, new_match?}; new_match renames an existing rule keyed by match. All-or-nothing: if any rule produces an invalid merged config, no rules land on disk (atomic tmp+rename + Zod parse give the transactional guarantee for free). Removal is read-modify-write through set_config with a filtered array — keeps the tool surface narrow.
previewUrl in tool results
Every docName-producing MCP tool emits a previewUrl in its structuredContent so you can click straight from the agent's response into the React editor at the exact doc it just touched. Single-doc tools emit a top-level previewUrl; list-producing tools emit one per row alongside a top-level ui block. When neither a UI nor a configured base URL is reachable, previewUrl is null and tools still return their primary data — it's an affordance, not a blocker.
Resolution precedence (see config preview.baseUrl for details):
openknowledge://URL scheme — emitted when the server detects it is running inside the Open Knowledge desktop app (viaOK_ELECTRON_PROTOCOL_HOST=1, which the desktop main process injects at utility-fork time). The resulting URL isopenknowledge://open?project=<realpath>&doc=<docName>and routes through the desktop's main-process URL handler to the correct project window. CLI /bunxservers never set the flag, so they fall through to the HTTP sources below.OPEN_KNOWLEDGE_PREVIEW_BASE_URLenv var — explicit per-shell override (tunnels, CI).<contentDir>/.ok/ui.lock— the liveok uiprocess's port.config.preview.baseUrlin.ok/config.yml— config fallback.
The lock branch deliberately reads ui.lock (the React editor) rather than server.lock (the collab server) because preview URLs must point at what the editor pane actually renders.
Desktop app (first-launch consent)
If you installed Open Knowledge from the macOS DMG rather than via npx, MCP wiring can happen automatically on first app launch — no terminal contact required. The first time the packaged app opens, a consent dialog enumerates the AI editors it detects on your machine (Claude Code, Claude Desktop, Cursor, VS Code, Codex, Windsurf). Every detected editor is preselected; click Add and MCP server entries land in each editor's user-level config file. Click Skip and nothing is written.
The dialog is user-scoped — it fires once per Mac per user, not per project.
What gets written
MCP entries written by the desktop app have the shape {"command": "<cliPath>", "args": ["mcp"]} — not npx. The cliPath is resolved via a hybrid strategy at confirm time:
- If
/usr/local/bin/okexists, is a symlink, and its target resolves into the currently-running app bundle (ownership check), the symlink path is written. Stable across auto-updates and drag-to-new-location moves. - Otherwise the bundle-absolute path (
<bundle>/Contents/Resources/cli/bin/ok.sh) is written. Self-contained — works even when theInstall Command-Line Tools…menu item has never been clicked.
CLI-origin ok init continues to produce the {"command": "npx", "args": ["@inkeep/open-knowledge", "mcp"]} shape since CLI users have Node by definition.
Marker file
The dialog records its outcome in ~/.ok/mcp-status.json (next to ~/.ok/config.yml):
{
"configured": true,
"configuredAt": "2026-04-23T15:30:00Z",
"editors": ["claude", "cursor"],
"cliPath": "/Applications/Open Knowledge.app/Contents/Resources/cli/bin/ok.sh"
}Skipped runs write { "configured": false, "skippedAt": "..." } instead. Either shape suppresses the dialog on all subsequent launches.
Re-triggering the dialog
Delete ~/.ok/mcp-status.json. The next time the desktop app launches, the consent dialog fires again with current editor detection.
CLI users
If you installed via npx @inkeep/open-knowledge and ran ok init from the terminal, MCP is already wired — the consent dialog is only relevant for users who arrived via the DMG. Both paths coexist safely; if you later install the DMG, the dialog detects your existing OK entries and overwrites them with the bundle-absolute cliPath shape (foreign customizations are preserved).
Transport
The MCP server uses stdio transport. stdout carries JSON-RPC messages; all diagnostics go to stderr. Each MCP client session spawns its own process:
npx @inkeep/open-knowledge mcpMirrored catalogs
The MCP server maintains auto-generated INDEX.md files in .ok/catalogs/ that mirror the project's directory structure. These are rebuilt when content files change (500ms quiet period, 2s max debounce).
Agents use these catalogs for navigation -- the MCP server's connect instructions tell agents to start with .ok/catalogs/INDEX.md.
Supported clients
The MCP server works with any editor that supports the MCP standard:
- Claude Code
- Claude Desktop
- Cursor
- Windsurf
- VS Code (with MCP extension)
- Codex
- Any editor implementing the MCP protocol