@okfit/lsp
Language Server Protocol server for okfit. Publishes background diagnostics for an Open Knowledge Format bundle into any LSP client, Claude Code included.
Part of the okfit kit. Most users want @okfit/plugin, which pulls this package in automatically and launches this server through the Claude Code plugin's
lspServers.okfitentry.
What it is
@okfit/lsp speaks the Language Server Protocol over stdio for one or
more discovered OKF bundles. The .md binding is the client's own
registration (the Claude Code plugin manifest's extensionToLanguage);
this server answers for any document under a discovered bundle root. It
discovers a bundle per workspace folder the same way the CLI and MCP
server do, and
publishes okfit validate's diagnostics as documents open, change, save,
or close. It
writes nothing to the bundle on its own -- every edit it computes (setting a
concept's status, appending a verified entry) is sent to the client as a
workspace/applyEdit request, never written directly, so the client remains
the one thing that ever touches disk.
Launching it
Most users never invoke this directly — the Claude Code plugin's
bin/start-lsp.sh loader resolves the project's own
node_modules/.bin/okfit-lsp and falls back to npx --yes @okfit/lsp
when it is not installed. To run it directly, the package's own bin is
okfit-lsp --stdio; --stdio is accepted and silently ignored, since
streams are always stdin/stdout. --node-ipc, --socket, and --pipe
are not supported. --clientProcessId=<pid> is accepted, and the server
still exits on exit. The server's own signal that the client has gone
is its input stream closing; the underlying vscode-languageserver
library separately polls that process and exits with code 1 if it
disappears first.
Publishing behaviour
didOpen/didSavetrigger a full revalidate;didChange/didClosetrigger a cheaper edit-tier revalidate.- The server registers no file watchers. When a client sends
workspace/didChangeWatchedFilesitself, a change triggers a full revalidate on every live session. A config discovery file is the exception: it drops that folder's session, and nothing is republished until the next document event. Claude Code sends no watched-file events, so a config edit during a Claude Code session needs a session restart to take effect. - A folder whose config fails to load is logged once and retried on the
next
didOpenordidSaveunder it, so fixing the config and saving a document recovers it. - Exactly one
textDocument/publishDiagnosticsper file whose diagnostic set changed: an unchanged file is not republished, an emptied file publishes[], and a file not open in the editor still publishes when its diagnostics change. - A bundle-level diagnostic publishes against the bundle root's
index.md. - A non-
file:URI, and any document outside every discovered bundle root, is ignored. - Diagnostics for several files can arrive together in one client
notification batch; in Claude Code that batch reaches the model's
context on the next
EditorWritetool call, not immediately.
Custom methods
Two okfit/-prefixed extensions, for editor front ends (the VS Code
extension's concept explorer); neither is part of the LSP standard, and the
LSP specification reserves $/ for its own extensions, leaving vendor
prefixes to implementations.
okfit/concepts(request, params{}) warms up every workspace folder that has never been resolved and runs a first revalidate for any bundle that has never loaded -- once per bundle root per session; a root whose bundle still fails to load after that first warm-up is not re-scheduled by a later request, only by a rebuild (a config fix, or the root's last workspace folder going away and a new one arriving) -- then answers with every live workspace folder's loaded bundle as{ bundles: [{ root, rootUri, profile, concepts: [{ id, uri, title, type, status, stale }] }] }:statusis"draft" | "stable" | "deprecated" | undefined(absent when the frontmatter has none),staleis computed against the request time, and a bundle that still failed to load (no config, or one that failed) contributes no bundle entry.okfit/bundleChanged(notification, params{ rootUri, reason: "revalidated" | "dropped" }) is sent after every revalidate of a bundle root's diagnostics and after that root's session is dropped, so a client knows when to re-fetchokfit/concepts. The server advertises support withexperimental: { okfitConcepts: true }ininitialize's result.
Commands
workspace/executeCommand (the server advertises executeCommandProvider.commands
in initialize's result) answers three okfit command ids. Arguments are
ExecuteCommandParams.arguments, an array positional by index; a wrong shape
fails naming what was expected. The ids sit under okfit.lsp. so they never
collide with a client extension's own command ids: vscode-languageclient
registers every advertised id as an editor command, and a duplicate stops the
client from starting.
okfit.lsp.setStatus-- args[uri, status](statusone of"draft" | "stable" | "deprecated"). Computes theTextEditthat sets the concept aturi's top-levelstatus, sends it to the client withworkspace/applyEdit, and answers the client's ownApplyWorkspaceEditResultverbatim.okfit.lsp.markVerified-- args[uri]. Computes theTextEditthat appends averifiedentry (the resolved git identity,Derivation.generatedBy) to the concept aturi, sends it the same way, and answers the client's result. Fails when the concept is a draft, already verified by that actor, or no actor resolves.okfit.lsp.revalidate-- args[rootUri?], or no arguments at all. Schedules a full revalidate (republishing diagnostics and sendingokfit/bundleChanged) on the named bundle root, or on every live session when the argument is omitted, and answers{ roots: [rootUri, ...] }with the root URIs it revalidated --{ roots: [] }for a root no live session resolves to.
Both edit commands, and the code actions, compute their edit against the
document's current editor text (or the file on disk when it is not open).
The commands send it as a versioned documentChanges entry over
workspace/applyEdit, so a client whose buffer has changed since refuses
the edit rather than applying it at stale offsets. A code action's edit is
not versioned on the client side -- vscode-languageclient and VS Code
both drop the version when applying a code action -- so it stays safe only
because it is computed from the current buffer at request time, not because
a stale one is refused.
Status
Diagnostics, navigation, hover, code actions and commands, shipped through
phase 5 of the LSP roadmap; see the project's own
okf/roadmaps/lsp-server-and-vscode-extension.md for the full plan.