Skip to main content

MCP server

kapi exposes its format-aware content engine as an MCP (Model Context Protocol) server. An assistant connected to it can read the content inside a .docx, a JSON catalog or a Markdown file, ask what the project's own context says about a phrase, edit the content, verify it against the project's checks, and write it back byte-for-byte, through structured tool calls rather than by guessing at the file's bytes.

For the agent-skills path (Claude Code calling the kapi CLI), see Use the Kapi Agent Skills. The two can be used together.

Generated from the server

The tables on this page are generated by connecting to a real kapi mcp server and asking it for its tools and its resources. A CI drift gate fails the build if they stop matching the binary, so nothing here can describe a tool the server does not answer to, or an address it does not serve.

Quick Start

Start the MCP server:

kapi mcp

This launches a JSON-RPC server on stdio. You don't run it manually; your AI tool starts it as a subprocess.

Setup

Claude Desktop

Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS, %APPDATA%\Claude\claude_desktop_config.json on Windows):

{
"mcpServers": {
"kapi": {
"command": "kapi",
"args": ["mcp"]
}
}
}

Restart Claude Desktop. Kapi tools will appear in the tool picker.

Claude Code

Add to your project's .mcp.json file (or create it at the repository root):

{
"mcpServers": {
"kapi": {
"command": "kapi",
"args": ["mcp"]
}
}
}

Claude Code will automatically discover and connect to the kapi MCP server.

VS Code (GitHub Copilot / Copilot Chat)

Add to your VS Code settings (.vscode/settings.json or user settings):

{
"mcp": {
"servers": {
"kapi": {
"command": "kapi",
"args": ["mcp"]
}
}
}
}

Or add to .vscode/mcp.json in your project:

{
"servers": {
"kapi": {
"command": "kapi",
"args": ["mcp"]
}
}
}

Cursor

Add to your Cursor MCP config (~/.cursor/mcp.json):

{
"mcpServers": {
"kapi": {
"command": "kapi",
"args": ["mcp"]
}
}
}

Windsurf

Add to your Windsurf MCP config (~/.windsurf/mcp.json):

{
"mcpServers": {
"kapi": {
"command": "kapi",
"args": ["mcp"]
}
}
}
tip

If kapi is not in your $PATH, use the full path to the binary (e.g. /usr/local/bin/kapi or $HOME/go/bin/kapi).

Available tools

kapi mcp serves a curated set of 19 tools. Each tool is exposed by a named decision: an assistant that is handed every pipeline step picks the wrong one about half the time, so the steps the loop runs for you are deliberately absent.

ToolWhat it does
apply_editsApply a typed change-set: the one write verb. For document wording, each entry uses kind=content, file, id, content_hash and text (the new wording). Read block IDs and hashes with extract_content. The replacement field is for voice rules. Content edits land through the byte-faithful round-trip (structure and inline codes preserved, drift-guarded by content_hash); asset edits (terms entry, content memory pair, voice rule, recipe field) are written to their committed source and compiled into the cache. No AI provider is used. Read the context://<project-relative-path> resource before editing content, then run check_file on each changed file to review findings and analyzer coverage. For a code comment, an entry uses kind=comment, file, id and lines (as check_file reports them, such as func/Parse), comment_sha256 (the fingerprint check_file reports for the comment; a comment whose bytes differ is refused as changed, and current_text may carry the prose as read instead) and text (the comment's prose without comment markers, or a /* */ comment's delimiters and the * opening each line). Every byte outside the comment is kept, a /* */ comment keeps its layout, the result must parse and the language's formatter must agree; a directive, a generated file's comment, a changed comment, text holding */ in a /* */ comment and text that drops a code block or reference are refused with a reason and write nothing. A comment in a language whose plugin or formatter is not installed, or whose formatter does not format the file, did not run and is not written. A project's formatter runs code that project controls, and an agent that can write files can write the configuration it loads, so apply_edits never runs it: such a comment did not run, with the reason formatter, and a person applies it with kapi apply in a terminal. Each written file's result carries a check scoped to the change.
approve_unitApprove one review-queue unit (→ reviewed). The unit state is recorded in the project store, bound to the current translation's content hash, with identity "agent/<client>".
check_fileCheck the actual content inside a file (Word, PowerPoint, JSON, XLIFF, Markdown, …) or only the content blocks a change touched: pass diff (unified diff text), diff_against (a git revision), staged (the changes staged for commit, read from the index) or diff_range (A..B or A...B, read from B) to check each touched block whole, with the lines it spans, and read report.scope for every changed file and what became of it. with format-aware extraction and the applicable project voice and terms. Before editing, read the context://<project-relative-path> resource; after saving edits (including apply_edits), run check_file and review its per-block findings and analyzer coverage. Returns a kapi.check/v1 Report with effective scope in execution.contexts and configuration warnings, which never change pass; pass is not semantic approval. Omit profile_file/profile_pack to use the file’s project profile and channel. Supplying either replaces that voice selection with an explicit override; project terms still apply. Pass target/target_lang to also run bilingual checks.
check_textCheck a draft snippet with deterministic content rules. Before drafting, read the context://<project-relative-path> resource for the applicable guidance. Supply context_path to check with that destination's voice and terms; without it, project guidance is not resolved. Explicit profile_pack/profile_file are available only without context_path. Returns a kapi.check/v1 Report with findings, analyzer coverage and configuration warnings, which never change pass; pass is not semantic approval. After saving edits, use check_file to verify the actual file.
context_searchAsk what this project's content context says about a word or phrase: what it is called here, whether it is discouraged and what to say instead, and wording the project has already approved. One question across every store the project binds; you do not need to know which one holds the answer. Search before writing; read the context://<project-relative-path> resource for the full guidance at your destination. After saving edits, use check_file on the changed files. Results are grouped by kind, and say what could not be reached. Each term carries how often the project's extracted content uses it, as of the last extraction (the last `kapi up`) rather than of the working tree.
detect_formatDetect the file format from a file path based on its extension
extract_contentParse a file into translatable content blocks: each block's id, content_hash, source text (inline codes rendered as <x id="…"/> placeholders), and word count. The read leg of the edit loop: edit a block's text keeping the placeholders, then send it back via apply_edits (or kapi apply).
redactReplace sensitive spans with protected placeholders before processing
reject_unitReject one review-queue unit (→ draft, back to the work queue) with a note explaining why. Recorded with identity "agent/<client>"; retranslating the unit re-enters it in review.
review_queueList the review queue: every unit awaiting a person, addressed by (file, key, locale). One queue holds every language, the project's source language among them: a translated unit not yet approved is one row, and a source unit the project's source gate is waiting on is another, marked `isSource`. The result also carries `languages`, the pending count per language. Filter with language, locale and/or collection. Read-only, derived from the content files and the project state store; units annotated by an AI pre-review carry their score. Lean by design: call review_unit for a unit's context (the point governing it, its neighbourhood, its prior version, its findings).
review_unitFetch one review-queue unit's full picture: source and target text, ladder status, the last recorded state (with identity), and the context the decision is made in: the point governing the file (voice guidance, term rules, coordinates), the blocks before and after it as run sequences, the prior approved version and the content-memory match with its wording, the check findings with their run anchors, and the AI pre-review score. A unit in the project's source language is read the same way, from its source file, and returns its authoring rung with no target half. The read leg before approve_unit / reject_unit / sign_off_unit.
sign_off_unitSign off one review-queue unit (→ signed-off, the top ladder rung). Recorded with identity "agent/<client>".
statsSize files before processing them. Reports per-file and total content metrics: blocks (translatable and not), words, characters (with and without spaces, plus the unique-character inventory), segments when available, and a by-role breakdown. Works on any supported format (Word, PowerPoint, JSON, XLIFF, Markdown, HTML, …) and returns the same JSON `kapi stats --json` emits.
term-checkTerminology Check
translateTranslate content with an LLM or machine-translation provider (select an engine, then a provider)
upBring a kapi project up to date against its ship gates: re-extract drifted sources, run the default flow (content memory reuse then AI translate) over every target language, concurrently per language, loop until every gated scope is shippable or parks for a human, and run the project's bound checks each pass. In a project connected to a Bowrain server the run happens there (push, converge on the org's keys and shared content memory, pull the results); pass local to run the loop on this machine instead. Never fails on pending target work: parked units are reported, not thrown. Returns the structured result (per-locale standing, parked scopes, materialized files). Use up_plan first to see the pending work and token estimate.
up_planDry-run the catch-up work for a kapi project: per (collection, locale), the units missing a target, exact-content-memory leverage, the remaining AI work, and a rough token estimate. No provider calls, nothing written. The pre-flight for the up tool.
voice_checkScore text against a voice profile using deterministic vocabulary rules; returns a 0-100 compliance score and findings
voice_rewriteRewrite text to comply with a voice profile by substituting forbidden/competitor terms (deterministic, offline). A rule that names no replacement, and a match on an inflected form of a term, are left in place and listed under skipped with the term, its list, severity and reason; rewrite those by hand and verify with voice_check.

Project scoping applies on top. Started inside a kapi project (discovered by a git-style upward walk, exactly as the CLI discovers it), the tool set is narrowed to the sources the recipe declares and the project's first target language becomes the default for the translating tools.

Tools from installed plugins

An installed plugin adds its own tools to this surface. kapi mcp starts each plugin's server once per session and passes calls through, so the tools a plugin brings answer on the same connection that serves the loop verbs. Configure one MCP server, not one per plugin.

A plugin contributes the tools its manifest declares. A plugin that is not installed adds nothing, one that fails to start is left off with a note on stderr while the rest of the surface keeps working, and a plugin tool whose name kapi already uses is skipped so the kapi tool keeps the name.

Widening the surface

Two flags on kapi mcp widen what the server offers, for debugging and for callers who really are assembling a pipeline by hand:

  • kapi mcp --all-tools adds every CLI-visible processing tool, plus the listing verbs: case-transform, create-target, diff-leverage, dnt-check, encoding-detect, entity-extract, inline-codes-remove, list_formats, list_tools, media-refine, placeholder-check, pseudo-translate, pseudo_translate, qa, recycle, remove-target, review, search-replace, segmentation, source-gate, term-extract, unredact, voice-check, voice-infer, whitespace-correct, xml-validation.
  • kapi mcp --all-flows adds the flow-running verbs: list_flows, run_flow.
  • kapi mcp --all adds both.

Two tools are withheld from every surface, including the widened ones: external-command and script run arbitrary commands and JavaScript, which is a different class of decision from "show me every tool". Both remain available through kapi exec.

Available resources

Retrieval comes in two shapes, a tool and a resource. Asking what a word means is a call with arguments, so it is the context_search tool. Asking what applies at a location is reading something that already exists at an address, so it is a resource:

AddressRenders asWhat it answers
context://profile/{name}{?format,project}text/markdownWhat this project's context says applies at one place: the voice profile in force with its full guidance, the terms bound there, and the governance windows around them. Read this BEFORE writing or editing content at that location. Returns markdown by default; append `?format=json` for the structured shape. Addresses a governance profile by name, for a caller with no file in hand, e.g. `context://profile/marketing`.
context://{+path}{?format,project}text/markdownWhat this project's context says applies at one place: the voice profile in force with its full guidance, the terms bound there, and the governance windows around them. Read this BEFORE writing or editing content at that location. Returns markdown by default; append `?format=json` for the structured shape. The path is project-relative, e.g. `context://docs/guide.md`. Add `?project=<path>` to read a project other than the one the server started in; the path names its kapi.yaml, its root directory, or anything inside it.

The two addresses are one primitive. A path answers what applies here; a profile name answers the same question for a caller with no file in hand (an ad-hoc run, or a starter pack). Both return the point the location resolved to, the voice profile in force with its full guidance, the terms bound there, and the governance windows around them.

The rendering is a property of the read. ?format=json returns the same answer as application/json (the document kapi context <path> --json prints) and everything else returns text/markdown. An unrecognised format is refused rather than answered with prose a program cannot parse.

context://docs/guide.md # markdown, for a model to read
context://docs/guide.md?format=json # the same answer, for a program
context://profile/marketing # a named profile, no location needed

The CLI half is kapi context <path>, which calls the same host function and prints the same markdown. A conformance test holds the two surfaces to the same answer.

Example conversations

"How many words need translating?"

How many translatable words are in src/locales/en.json?

The assistant calls stats with the file path and returns a structured answer with word, block, character, and segment counts.

"What do we call this here?"

Is "sign-in" the term we use, and how do we say it in French?

The assistant calls context_search. One question reaches every store the project binds (its terms, its content memory, its voice profile) and the answer says what the project calls the thing, whether it is discouraged and what to say instead, and any wording already approved. Asking before writing is cheaper than learning the same fact from a failing check afterwards.

Every answer carries notes, and the first of them says whether the context, the terms or the decisions moved since this session last read them. The comparison is against what the project last observed of its server, held on disk, so it costs a small file read and no round trip, and it reports rather than resolves: an assistant told the terms moved asks again before continuing, because the wording it had settled on was chosen against a context that has since changed.

"Extract the content from this file"

Show me the translatable strings in messages.json

The assistant calls extract_content, parses the file, and returns each translatable block with its ID, content hash, source text (inline codes as <x id="…"/> placeholders), and word count.

"Edit the content, then check it"

Tighten the intro paragraph in report.docx, keeping it on brand

The assistant reads the blocks with extract_content, rewrites the text itself (no second model), sends the change back through apply_edits (the byte-faithful round-trip, drift- and inline-code-guarded), then calls check_file to confirm the gate passes, looping until it does.

"Catch the project up"

What translation work is outstanding, and how much would it cost?

The assistant calls up_plan, a dry run reporting, per collection and locale, the units missing a target, the content-memory leverage, the remaining AI work and a rough token estimate. Nothing is written and no provider is called. up then does the work: re-extract what drifted, reuse the content memory, translate the rest, run the project's checks, and loop until every gated scope is shippable or parks for a human.

"Review this translation"

Show me the French units waiting for review, and approve the ones that look right

The assistant calls review_queue, reads each candidate with review_unit, and records its decision with approve_unit, reject_unit, or sign_off_unit. Every decision is bound to the translation's content hash and recorded under the identity agent/<client>, so a later edit re-opens the unit rather than inheriting the approval.

review_queue returns one queue across the project's languages. Each row carries language and, when that language is the project's source, isSource: true and a status on the authoring ladder; languages counts the pending units per language, so an assistant can narrow with the language argument to one that has work. review_unit reads a source-language unit the same way it reads a translation, from the source file, and returns its point and neighbourhood with no target half. The three decision tools record target-language decisions.

Tool reference

ParameterTypeRequiredDescription
querystringyesthe word or phrase to ask about
limitintegernomax results per group (default 10)
localestringnonarrow results to one language (e.g. en, fr)
memorystringnopath to a standalone content memory (default: the project's own store)
projectstringnothe project this call acts on: its kapi.yaml recipe, its root directory, or any path inside it (default: the project the MCP server started in)
termsstringnopath to a standalone terms store (default: the project's own store)

extract_content

ParameterTypeRequiredDescription
pathstringyesFile path to extract content from
formatstringnoOverride format detection
projectstringnothe project this call acts on: its kapi.yaml recipe, its root directory, or any path inside it (default: the project the MCP server started in); its declared formats scope detection
source_langstringnoSource language (default: en)

apply_edits

The single write verb. One entry of the change-set is one deliberate change: a content edit, or an edit to an asset the project owns.

ParameterTypeRequiredDescription
changesetarrayyesthe typed change-set entries to apply
projectstringnothe project this call acts on: its kapi.yaml recipe, its root directory, or any path inside it (default: the project the MCP server started in)

Each change-set entry carries a kind. Content edits address a block by file, id, and content_hash, the drift guard: if the block's source has changed since it was read, the edit is refused rather than applied to different text. Asset edits carry an op and the fields for their kind.

A comment edit addresses one code comment by file and the id that check_file reports, is guarded by the comment_sha256 reported with it, and carries the comment's prose in text. kapi keeps every byte outside the comment, and writes a /* */ comment in the layout it already has. It refuses the edit with a reason when the comment has changed since the check, the text drops or adds a code block or reference, the text holds */ inside a /* */ comment, the result would not parse, or the formatter would rewrite it. A comment in a language whose formatter is not installed, or does not format the file, is not written, and the edit reports that it did not run. A formatter runs code its project controls, such as its configuration and plugins, and an agent that may write files can write that configuration, so apply_edits never runs one. Such an edit reports that it did not run, with the reason formatter; a person applies it with kapi apply in a terminal, where the formatter runs under execution trust. A Go comment is held to go/format inside the kapi binary, so a Go edit starts no formatter process. Each written file's result carries a check scoped to what was written.

kindWhat the entry changes
contentA block's text, through the faithful writer
commentA code comment's prose, keeping every other byte
termAn entry in the project's terms store
memoryA source/target pair in the content memory
voiceA rule in a voice profile
recipeA field of kapi.yaml
reviewA unit's position on the review ladder
kapi apply <<<'{"kind":"memory","source":"Save","target":"Lagre","source_locale":"en","target_locale":"nb","status":"signed-off"}'

check_file

ParameterTypeRequiredDescription
diffstringnounified diff text (git diff output); only the content blocks it touches are checked
diff_againststringnogit revision to diff the working tree against, read-only, with untracked files as added; only the content blocks changed are checked
diff_rangestringnotwo commits as A..B, or A...B for the change B made since its merge base with A; each file is read from B and nothing from the working tree; only the content blocks changed are checked
dntarraynodo-not-translate terms that must survive verbatim into the target
filestringnopath to the file whose content should be checked; with diff, diff_against, staged or diff_range it narrows the scope to this file, and may be omitted
forbidarraynoregex that must NOT appear in the content
max_charsintegernoflag content longer than this many characters (0 = off)
max_wordsintegernoflag content with more than this many words (0 = off)
profile_filestringnoexplicit voice override loaded from YAML; bypasses the file-scoped project voice and channel; omit to use project guidance
profile_packstringnoexplicit voice override; omitting profile_pack and profile_file preserves the file-scoped project voice and channel
projectstringnothe project this call acts on: its kapi.yaml recipe, its root directory, or any path inside it (default: the project the MCP server started in)
requirearraynoregex that MUST appear in the content
stagedbooleannocheck the changes staged for commit: the index diffed against HEAD, with each file read from the index, leaving out unstaged edits and untracked files; only the content blocks changed are checked
targetstringnotranslated target file to check against the source (enables the bilingual source-against-target checks)
target_langstringnolocale of the target file (e.g. de)
validatestringnoreader structure/encoding validation: off|report|strict (report folds structure.*/encoding.* findings into the report; strict also fails on a Major+ structure/encoding problem). Default off.

check_text

ParameterTypeRequiredDescription
textstringyesthe text to verify
context_pathstringnoproject-relative destination whose voice and terms govern this draft; may not exist yet; requires a project; cannot combine with profile_pack or profile_file
forbidarraynoregex that must NOT appear in the content
max_charsintegernoflag content longer than this many characters (0 = off)
max_wordsintegernoflag content with more than this many words (0 = off)
profile_filestringnopath to a voice profile YAML
profile_packstringnobuilt-in profile pack to check vocabulary against (e.g. marketing-blog)
projectstringnothe project this call acts on: its kapi.yaml recipe, its root directory, or any path inside it (default: the project the MCP server started in)
requirearraynoregex that MUST appear in the content

stats

ParameterTypeRequiredDescription
filesarrayyespaths of the files to summarize
formatstringnoinput format override applied to every file (default: auto-detect by extension/content)

up_plan

ParameterTypeRequiredDescription
projectstringnothe project this call acts on: its kapi.yaml recipe, its root directory, or any path inside it (default: the project the MCP server started in)

up

ParameterTypeRequiredDescription
jobsintegernohow many languages to catch up concurrently per pass (0 = project default, else 4)
localbooleannoin a server-connected project, run the loop on this machine and push the results, instead of running it on the server
materializebooleannoafter the loop, write the target-language files for every shippable locale (overrides the recipe's materialize policy)
no_checksbooleannoskip the project's bound checks inside the loop (failing units then count as translated)
passesintegernomaximum reconciliation passes (0 = loop until up to date or parked; 1 = single pass)
projectstringnothe project this call acts on: its kapi.yaml recipe, its root directory, or any path inside it (default: the project the MCP server started in)

detect_format

ParameterTypeRequiredDescription
pathstringyesFile path to detect format from

How it works

Kapi MCP uses the same machinery as the CLI commands: the format registry for detection, the executor for flow orchestration, and the same built-in tools. The MCP server exposes them as typed, discoverable tools over the Model Context Protocol stdio transport.

No server process, ports, or authentication needed. Your AI tool launches kapi mcp as a child process, communicates over stdin/stdout, and shuts it down when the session ends.

note

Most tools here are rule-based and need no API key: context_search, check_text, check_file, voice_check, voice_rewrite, stats, extract_content, apply_edits, and the review verbs all run offline. The ones that reach a language model (translate, and up when there is work the content memory cannot cover) need a provider credential, which they read from the same credential store the CLI uses (kapi credentials add).