Open KnowledgeOpen Knowledge
Guides

MCP integration

How the HTTP MCP server and stdio shim connect AI agents to your knowledge base.

The Open Knowledge MCP server gives AI agents structured access to your knowledge base. The server implementation lives in the running ok start process at /mcp; editors still launch ok mcp over stdio, but that command is only a transport shim that proxies JSON-RPC frames to the HTTP MCP endpoint.

Startup and connection model

On startup, ok mcp resolves the HTTP MCP endpoint for the current project. It reads <contentDir>/.ok/server.lock; a live lock tells the shim which port to proxy to. If the lock is absent or stale, the shim detach-spawns ok start as a sibling process by default, then polls the lockfile for port > 0 with a 5-second deadline.

Active when a live server.lock is found at startup — ok start or bun run dev is already running.

  • ok mcp proxies stdio JSON-RPC to http://localhost:<port>/mcp
  • Tool registration, schemas, identity, CRDT writes, and HTTP API access all run inside ok start
  • Agents and humans can co-edit the same document simultaneously

Active when no live server.lock is present and auto-start is not opted out. ok mcp detach-spawns ok start as a sibling process with a kernel-fd stderr redirect to .ok/last-spawn-error.log, then proxies to the spawned server's /mcp endpoint once the lock advertises a port.

  • Zero ceremony — the agent's first tool call resurrects the full stack
  • Sibling process (not embedded) so closing the stdio shim does not take down sibling editors' sessions
  • Spawn failures surface the captured stderr (e.g. EADDRINUSE) to the editor rather than silently downgrading behavior
  • Transitive auto-git-init: the spawned ok start runs ensureProjectGit on the project directory, so if there is no parent .git/ present it will be created (default branch main) before the collab server binds. Running ok mcp against 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 with OK_MCP_AUTOSTART=0 (env) or mcp.autoStart: false (config).

Active when auto-start is opted out (OK_MCP_AUTOSTART=0 env or mcp.autoStart: false config) and no live server.lock is found.

  • ok mcp exits with a diagnostic telling you the Open Knowledge server is not running
  • Start the project server with open-knowledge start / ok start, or re-enable MCP auto-start
  • There is no separate disk-only MCP server; the HTTP MCP endpoint is the single implementation

Precedence for endpoint resolution

resolveMcpHttpUrl is the resolver that runs before the stdio bridge starts:

  1. --port <n> CLI override with n > 0 — proxy to http://<host>:<port>/mcp.
  2. Live server.lock with port > 0 — proxy to http://localhost:<port>/mcp regardless of auto-start config. A pre-existing live lock always wins; opt-out only suppresses the spawn path.
  3. No live lock + auto-start allowed (default) — detach-spawn ok start, then proxy to its /mcp endpoint.
  4. No live lock + auto-start opted out — exit 1 with a diagnostic.

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

Once the shim reaches /mcp, the running ok start process registers the full tool set:

ToolModeDescription
init-contentBothBootstrap knowledge articles from the codebase
ingestBothCapture an external source (URL or file) as raw reference material
researchBothGather sources and write provisional research findings
consolidateBothPromote research into a canonical article
read_documentBothRead a document with enriched frontmatter + shadow-repo history
searchBothSearch document contents
execBothRead-only bash-like commands (cat / ls / grep / find / head / tail / wc / sort / uniq / cut)
list_documentsBothList all documents in the knowledge base
get_backlinks / get_forward_linksBothWiki-link graph queries
get_hubs / get_orphans / get_dead_linksBothLink-graph diagnostics
suggest_linksBothSuggest missing wiki-links for a target page
get_configAlwaysRead the effective merged config (defaults → user → project)
set_configAlwaysPatch config fields on the agent-settable allowlist (auto-scope-routed)
set_folder_ruleAlwaysTransactional upsert of one or more folders[] rules
write_documentConnectedWrite content to a document through the CRDT (full document, including frontmatter)
edit_documentConnectedFind-and-replace on document body through the CRDT (FM edits are rejected — see below)
delete_documentConnectedDelete a document through the managed delete flow — closes agent sessions, unloads from Hocuspocus, removes from disk
rename_documentConnectedRename a document and rewrite inbound links
rename_folderConnectedRename a folder and rewrite inbound links across every affected doc in one call
rollback_to_versionConnectedRestore a document to a historical shadow-repo commit
get_historyConnectedQuery the shadow-repo version timeline
save_versionConnectedCheckpoint 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 23 docName-producing tools emit a preview URL 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. delete_document emits previousPreviewUrl instead of previewUrl since the doc is gone — agents use it to close any stale preview tab.

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 includes truncatedFrom: <original-length> so the agent can adjust.
  • Optional. Empty strings and whitespace-only values are treated as absent. rename_document and rollback_to_version fill in a default ("Renamed <from> → <to>" or "Restored to <sha-short>") when no summary is provided; write_document, edit_document, and rename_folder produce a bulletless row in that case. For rename_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 (no agentId in 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-tree

Read-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; use set_folder_rule for per-rule upsert)
  • mcp.tools.search.maxResults
  • mcp.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 defaultScopeuser. 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):

  1. openknowledge:// URL scheme — emitted when the server detects it is running inside the Open Knowledge desktop app (via OK_ELECTRON_PROTOCOL_HOST=1, which the desktop main process injects at utility-fork time). The resulting URL is openknowledge://open?project=<realpath>&doc=<docName> and routes through the desktop's main-process URL handler to the correct project window. CLI / bunx servers never set the flag, so they fall through to the HTTP sources below.
  2. OPEN_KNOWLEDGE_PREVIEW_BASE_URL env var — explicit per-shell override (tunnels, CI).
  3. <contentDir>/.ok/ui.lock — the live ok ui process's port.
  4. config.preview.baseUrl in .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.

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:

  1. If /usr/local/bin/ok exists, 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.
  2. Otherwise the bundle-absolute path (<bundle>/Contents/Resources/cli/bin/ok.sh) is written. Self-contained — works even when the Install 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.

Resetting older development entries

Current desktop wiring overwrites entries only when they match today's canonical Open Knowledge shape. If an older development build left an entry that is now treated as foreign-customized, reset it manually: remove that editor's open-knowledge MCP entry, delete ~/.open-knowledge/mcp-status.json, then relaunch the desktop app or run ok init from the terminal.

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 updates canonical OK-managed entries to the bundle cliPath shape and preserves foreign customizations.

Transport

Editors still speak MCP over stdio by launching a command such as:

npx @inkeep/open-knowledge mcp

That process is a shim. stdout carries JSON-RPC messages from the editor, stderr carries diagnostics, and the shim proxies each JSON-RPC frame to the running ok start process's Streamable HTTP endpoint at /mcp. The shim does not register tools or schemas; the HTTP endpoint owns the MCP runtime.

Mirrored 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