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.
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"]
}
}
}
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.
| Tool | What it does |
|---|---|
apply_edits | Apply 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_unit | Approve 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_file | Check 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_text | Check 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_search | Ask 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_format | Detect the file format from a file path based on its extension |
extract_content | Parse 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). |
redact | Replace sensitive spans with protected placeholders before processing |
reject_unit | Reject 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_queue | List 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_unit | Fetch 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_unit | Sign off one review-queue unit (→ signed-off, the top ladder rung). Recorded with identity "agent/<client>". |
stats | Size 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-check | Terminology Check |
translate | Translate content with an LLM or machine-translation provider (select an engine, then a provider) |
up | Bring 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_plan | Dry-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_check | Score text against a voice profile using deterministic vocabulary rules; returns a 0-100 compliance score and findings |
voice_rewrite | Rewrite 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-toolsadds 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-flowsadds the flow-running verbs:list_flows,run_flow.kapi mcp --alladds 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:
| Address | Renders as | What it answers |
|---|---|---|
context://profile/{name}{?format,project} | text/markdown | What 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/markdown | What 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
context_search
| Parameter | Type | Required | Description |
|---|---|---|---|
query | string | yes | the word or phrase to ask about |
limit | integer | no | max results per group (default 10) |
locale | string | no | narrow results to one language (e.g. en, fr) |
memory | string | no | path to a standalone content memory (default: the project's own store) |
project | string | no | the 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) |
terms | string | no | path to a standalone terms store (default: the project's own store) |
extract_content
| Parameter | Type | Required | Description |
|---|---|---|---|
path | string | yes | File path to extract content from |
format | string | no | Override format detection |
project | string | no | the 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_lang | string | no | Source 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.
| Parameter | Type | Required | Description |
|---|---|---|---|
changeset | array | yes | the typed change-set entries to apply |
project | string | no | the 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.
kind | What the entry changes |
|---|---|
content | A block's text, through the faithful writer |
comment | A code comment's prose, keeping every other byte |
term | An entry in the project's terms store |
memory | A source/target pair in the content memory |
voice | A rule in a voice profile |
recipe | A field of kapi.yaml |
review | A 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
| Parameter | Type | Required | Description |
|---|---|---|---|
diff | string | no | unified diff text (git diff output); only the content blocks it touches are checked |
diff_against | string | no | git revision to diff the working tree against, read-only, with untracked files as added; only the content blocks changed are checked |
diff_range | string | no | two 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 |
dnt | array | no | do-not-translate terms that must survive verbatim into the target |
file | string | no | path 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 |
forbid | array | no | regex that must NOT appear in the content |
max_chars | integer | no | flag content longer than this many characters (0 = off) |
max_words | integer | no | flag content with more than this many words (0 = off) |
profile_file | string | no | explicit voice override loaded from YAML; bypasses the file-scoped project voice and channel; omit to use project guidance |
profile_pack | string | no | explicit voice override; omitting profile_pack and profile_file preserves the file-scoped project voice and channel |
project | string | no | the 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) |
require | array | no | regex that MUST appear in the content |
staged | boolean | no | check 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 |
target | string | no | translated target file to check against the source (enables the bilingual source-against-target checks) |
target_lang | string | no | locale of the target file (e.g. de) |
validate | string | no | reader 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
| Parameter | Type | Required | Description |
|---|---|---|---|
text | string | yes | the text to verify |
context_path | string | no | project-relative destination whose voice and terms govern this draft; may not exist yet; requires a project; cannot combine with profile_pack or profile_file |
forbid | array | no | regex that must NOT appear in the content |
max_chars | integer | no | flag content longer than this many characters (0 = off) |
max_words | integer | no | flag content with more than this many words (0 = off) |
profile_file | string | no | path to a voice profile YAML |
profile_pack | string | no | built-in profile pack to check vocabulary against (e.g. marketing-blog) |
project | string | no | the 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) |
require | array | no | regex that MUST appear in the content |
stats
| Parameter | Type | Required | Description |
|---|---|---|---|
files | array | yes | paths of the files to summarize |
format | string | no | input format override applied to every file (default: auto-detect by extension/content) |
up_plan
| Parameter | Type | Required | Description |
|---|---|---|---|
project | string | no | the 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
| Parameter | Type | Required | Description |
|---|---|---|---|
jobs | integer | no | how many languages to catch up concurrently per pass (0 = project default, else 4) |
local | boolean | no | in a server-connected project, run the loop on this machine and push the results, instead of running it on the server |
materialize | boolean | no | after the loop, write the target-language files for every shippable locale (overrides the recipe's materialize policy) |
no_checks | boolean | no | skip the project's bound checks inside the loop (failing units then count as translated) |
passes | integer | no | maximum reconciliation passes (0 = loop until up to date or parked; 1 = single pass) |
project | string | no | the 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
| Parameter | Type | Required | Description |
|---|---|---|---|
path | string | yes | File 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.
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).
Related
- Use the Kapi Agent Skills: the agent-skills path.
- Kapi CLI guides
- Context retrieval: why one question reaches every store.
- Command reference