Skip to content

MCP server

An MCP server is a small program that gives an AI agent a set of tools it can call — MCP, the Model Context Protocol, is the open standard those tools speak. The Kenly MCP server hands your agent tools for translating books, so you can drive a whole translation in plain English instead of writing code against the API yourself.

With it, you can run a full Kenly book translation from your own AI agent — Claude Code, Claude Desktop, Cursor, or any MCP client — with only an API key and no website visit. Point the agent at files on your own computer and say "translate these to English": it creates the project, uploads the local files, prices the job, tops up credit if needed, runs the fully-automatic translation, watches its status, and writes the finished book back to your disk.

The server is a thin wrapper over this same versioned /v1 API — every guard (owner-scoping, idempotency, affordability, the automatic batch ladder, the never-exceed charge cap) stays server-side. Anything you can do through the tools, you could do with raw HTTP; the connector just makes it one natural-language instruction to your agent.

Fully automatic — there is no approve/reject step

A run auto-advances every batch (1 → 5 → 25 → 100 → rest) with no human gate. The agent starts and monitors a run; it never approves individual pages — there is deliberately no approve/reject tool. The only thing that pauses a run is affordability, and that self-heals the moment you add credit.

Every tool runs as your user, scoped to your wallet and quotas. A leaked or shared key still can't exceed your wallet or touch another account's data — but treat it like a password all the same (see Security).

Prerequisites

  • Node.js ≥ 18.17 — the connector runs straight from npm with npx, no clone or build.
  • A Kenly API key (kenly_sk_live_…) in the KENLY_API_KEY environment variable. If you don't have one yet, jump to Get an API key — it's a two-minute, one-time step.

The package is published to npm as @kenly/mcp-server and talks to the production API (https://api.getkenly.com) by default.

Install

Add the server to your MCP client with one of the copy-paste blocks below — replace kenly_sk_live_… with your own key. Every client launches the same stdio command (npx -y @kenly/mcp-server) with KENLY_API_KEY in its environment.

Claude Code

One command registers it:

bash
claude mcp add kenly --env KENLY_API_KEY=kenly_sk_live_… -- npx -y @kenly/mcp-server

The --env FLAG is repeatable (add --env KENLY_API_BASE_URL=… to point at a non-production API), and the -- separates Claude Code's own flags from the server command. Scope it with --scope user (all your projects) or --scope project (checked into the repo's .mcp.json for your team); the default is the current project only. The project-file form is equivalent:

jsonc
// .mcp.json  (project root)
{
  "mcpServers": {
    "kenly": {
      "type": "stdio",
      "command": "npx",
      "args": ["-y", "@kenly/mcp-server"],
      "env": { "KENLY_API_KEY": "kenly_sk_live_…" }
    }
  }
}

Confirm it loaded with claude mcp listkenly should report its tools.

Claude Desktop

Edit the config via Settings → Developer → Edit Config, or open the file directly:

  • macOS: ~/Library/Application Support/Claude/claude_desktop_config.json
  • Windows: %APPDATA%\Claude\claude_desktop_config.json
json
{
  "mcpServers": {
    "kenly": {
      "command": "npx",
      "args": ["-y", "@kenly/mcp-server"],
      "env": {
        "KENLY_API_KEY": "kenly_sk_live_…"
      }
    }
  }
}

Restart Claude Desktop; kenly appears in the tools (🔌) menu.

Cursor

Create the config globally (all projects) at ~/.cursor/mcp.json, or per-repo at .cursor/mcp.json:

json
{
  "mcpServers": {
    "kenly": {
      "command": "npx",
      "args": ["-y", "@kenly/mcp-server"],
      "env": {
        "KENLY_API_KEY": "kenly_sk_live_…"
      }
    }
  }
}

Enable it under Cursor Settings → MCP.

Any other MCP client

Launch a stdio server the same way — command: "npx", args: ["-y", "@kenly/mcp-server"], and KENLY_API_KEY in the environment. The server speaks standard MCP over stdio, so any compliant client can host it.

Get an API key

The tools authenticate with a scoped, revocable kenly_sk_live_… key — never your password. Create one in the web app (there's a full walkthrough in Authentication):

  1. Sign in and open Settings → Developerhttps://app.getkenly.com/settings/developer.
  2. Click Create key, give it a descriptive name (e.g. claude-cli or my-laptop), and choose its scopes. The default projects:write scope can do everything the connector exposes (create/upload/estimate/translate/export and wallet top-up); a read-only projects:read key can only inspect status, quotes, and exports.
  3. Copy the kenly_sk_live_… secret now — it's shown exactly once. Put it in the MCP client's env block above (or an environment variable); never commit it.

Prefer the command line? Mint a key over the API instead — see Authentication & API keys → Manage keys programmatically. Revoke any key instantly from the same Settings → Developer screen; mint one key per agent/machine so you can revoke just that one.

Configuration

Two environment variables (nothing else to configure):

VariableRequiredDefaultMeaning
KENLY_API_KEYYour kenly_sk_live_… key. Never hardcode it; keep it out of source control.
KENLY_API_BASE_URLhttps://api.getkenly.comThe API origin. Override only for local dev, e.g. http://localhost:5080 (the Aspire-pinned API port).

The server refuses to start without KENLY_API_KEY, printing an actionable message (never a stack trace) so a bad config fails loudly at launch rather than 401-ing on the first tool call. All diagnostics go to stderr; stdout is reserved for the MCP JSON-RPC stream.

Tool catalog

The connector exposes 15 tools, grouped by what they do. Retry-safety is server-side — the API deduplicates a double-submit of a billable or state-changing call itself, so an agent retry can't double-charge or double-enqueue (nothing to send). Errors surface the API's ProblemDetails message — a 402 tells you to top_up, a 409 explains the wrong state, a 403 names the missing scope.

Each tool below lists its arguments, what it returns, and an example of the natural-language ask that triggers it (you type these to your agent — the agent picks the tool).

Translate local files

create_project

Reads file(s) off local disk, uploads them, creates the project, and splits it into pages — ready to translate, no website visit.

ArgumentTypeRequiredMeaning
namestringA title for the book/project.
filesstring[]Local file path(s) on this machine (PDF/PNG/JPEG/TIFF/BMP). Several files are combined into one book.
targetLanguageCodestringDestination language ISO code (e.g. "en"). Omit for your account default.

Returns { projectId, name, pagesTotal, targetLanguageCode, sourceFiles, merged } — the projectId is what every other tool takes.

One source document per project

A book is one source document in the Kenly model. Passing several files combines them into a single PDF client-side (each PDF's pages copied, each PNG/JPEG one page) before upload — so a folder of scans becomes one book. When combining, each file must be a PDF/PNG/JPEG (convert TIFF/BMP first).

Example"Create a Kenly project called 'Gesner 1551' from /scans/gesner.pdf and translate it to English."

Estimate before spending

get_estimate

A free whole-book cost/time projection (page/word analysis, no GPU spend).

ArgumentTypeRequiredMeaning
projectIdstringThe project id from create_project.

Returns the whole-book estimate (projected pages, words, cost, and time).

Example"Roughly how much will the whole book cost to translate?"

get_batch_quote

The next batch's expected "about $X" and never-exceed "$Y", plus whether your wallet covers it. Use it before start_translation and whenever a run pauses for credit.

ArgumentTypeRequiredMeaning
projectIdstringThe project id.

Returns the batch quote{ nextBatchSize, expectedUsd, estimatedUsd, balanceUsd, affordable, unlimited, … }.

Example"What will the next batch cost, and can I afford it?"

Billing self-service

get_balance

Your current prepaid credit balance (or "included" for an unlimited account).

ArgumentTypeRequiredMeaning
(none)

Returns the wallet view{ balanceUsd, unlimited, lowBalance, currency }.

Example"What's my Kenly balance?"

top_up

Add prepaid credit. The default manual method credits immediately; a card/crypto method returns a checkout link to open in a browser. After credit lands, a paused run auto-resumes.

ArgumentTypeRequiredMeaning
amountUsdnumberAmount to add, in USD.
methodmanual | card | cryptoPayment method (default manual, credits immediately).

Returns the top-up view — { reference, status, method, amountUsd, balanceUsd, checkoutUrl, … } (checkoutUrl is set for card/crypto).

Example"Add $10 of credit to my wallet."

redeem_code

Redeem an operator-issued credit code — a browser-free way to add credit so a paused run resumes.

ArgumentTypeRequiredMeaning
codestringThe credit code to redeem.

Returns the top-up view with the credited amount and new balance.

Example"Redeem the code KENLY-XXXX-YYYY."

get_transactions

Your top-ups and processing charges, most recent first, with lifetime totals.

ArgumentTypeRequiredMeaning
skipnumberHow many to skip (paging).
takenumberHow many to return (default 20, max 100).

Returns the transaction history with lifetime totals.

Example"Show my last 10 Kenly transactions."

Run (automatic)

start_translation

Kicks off the fully-automatic ladder (1 → 5 → 25 → 100 → rest). Each batch auto-advances with no human approval; the only pause is affordability. On a credit pause, call top_up and it auto-resumes.

ArgumentTypeRequiredMeaning
projectIdstringThe project id.

Returns the job handle { jobId, status } — monitor it with get_status / wait_for_status.

Example"Start translating the project."

Monitor

get_status

One described status for a book: where it is, whether it's waiting (self-heals — just poll) or blocked (needs you, e.g. add credit), progress, the active job, and the next-batch quote.

ArgumentTypeRequiredMeaning
projectIdstringThe project id.

Returns the status view — state, isWaiting/isBlocked flags, progress { pagesTranslated, pagesTotal }, active job, and quote.

Example"How's the translation going?"

wait_for_status

Blocks until the book's status meaningfully changes, becomes blocked (needs you), or reaches a terminal state — then returns the new status. The efficient "tell me when it changes" alternative to polling get_status yourself.

ArgumentTypeRequiredMeaning
projectIdstringThe project id.
timeoutSecondsnumberMax seconds to wait before returning the latest status (default 120, max 600).
pollSecondsnumberPoll interval in seconds (default 5, 2–60).
waitForCompletebooleanIf true, keep waiting through intermediate changes until the run completes or blocks.

Returns { reason, status } where reason is changed | blocked | terminal | timeout.

Example"Wait until the book finishes (or needs me), then tell me."

Recover

retry_failed

Re-process only a project's failed pages. Returns a job handle to monitor.

ArgumentTypeRequiredMeaning
projectIdstringThe project id.

Returns the job handle { jobId, status }.

Example"Retry the pages that failed."

Results

list_exports

The export files already generated for a project, re-downloadable with download_result.

ArgumentTypeRequiredMeaning
projectIdstringThe project id.

Returns the list of export files (name, size, content type, download name).

Example"What exports are available for this book?"

download_result

Generate (if needed) and write the finished book to local disk.

ArgumentTypeRequiredMeaning
projectIdstringThe project id.
formatstringMarkdown, PlainText, Html, Epub, Docx, Pdf, or Archive (aliases like md/txt/pdf/word/zip accepted).
outputPathstringLocal file path or directory to save into. Defaults to ./<generated-name> in the current directory.
matchSourcePagesbooleanPDF only: one output sheet per source page (page-for-page layout).

Returns { path, bytes, contentType, format } — and the file is written to path on your disk.

Example"Download the finished book as EPUB to ~/books/gesner.epub."

Convenience

list_projects

Your projects (paged, newest first) — to find a projectId across sessions.

ArgumentTypeRequiredMeaning
searchstringFilter by name/text.
statusstringFilter by lifecycle status.
pageSizenumberHow many to return (default 25, max 100).

Returns a paged list of your projects.

Example"List my Kenly projects."

get_meta

API version, the batch ladder, export formats, and supported destination languages.

ArgumentTypeRequiredMeaning
(none)

Returns the capability document{ apiVersion, batchLadder, finalBatch, exportFormats, … }.

Example"What languages and export formats does Kenly support?"

A full self-service run

The whole point: translate a PDF on your disk from the Claude CLI without opening the website. You give your agent one instruction — "Translate /scans/gesner.pdf to English and save it as EPUB" — and it walks the tools end to end. Under the hood that's:

text
create_project { name:"Gesner 1551", files:["/scans/gesner.pdf"] }
   -> { projectId, pagesTotal }                    # local file uploaded + split into pages

get_batch_quote { projectId }
   -> about $X, never more than $Y, affordable?     # pre-flight the cost

top_up { amountUsd: 10 }                            # only if the quote says NOT affordable
   -> balance topped up (manual = immediate)

start_translation { projectId }
   -> { jobId }                                     # ladder auto-advances 1 -> 5 -> 25 -> 100 -> rest

wait_for_status { projectId, waitForComplete: true }
   -> on "blocked" (needs credit): top_up, then wait_for_status again  # auto-resumes
   -> on "terminal": done

download_result { projectId, format:"epub", outputPath:"/books/gesner.epub" }
   -> saved to disk

No website was opened, no page was manually approved, and everything ran against a file on your own machine. The run charges your wallet as each batch settles, never exceeding the quoted ceiling, and pauses cleanly (rather than failing) if a batch would outrun your balance.

Security

  • Treat the key like a password. It authenticates every call as you and can spend from your wallet. Put it in your MCP client's env block (or a .env you never commit) — never hardcode it in source, and never paste it into a shared/project config that lands in git.
  • Scope down. Use a projects:read key for agents that should only inspect status/quotes/exports; reserve projects:write (create/translate/spend) for agents you trust to run and pay for jobs.
  • Rotate & revoke freely. Keys are revocable in the web app (Settings → Developer); mint one key per agent/machine so you can revoke just that one. Revocation is immediate.
  • Least privilege by design. Every guard — owner-scoping, affordability, the never-exceed charge cap, idempotency — is enforced server-side. A leaked key still cannot exceed your wallet or touch another user's data, but you should still revoke it.

How it relates to the HTTP API

The MCP server is a convenience layer, not a separate product: each tool maps to one or more /v1 operations. If you'd rather integrate over raw HTTP (a backend service, a non-MCP agent), the Quickstart and the API reference cover the same flow — create_projectPOST /v1/projects, start_translationPOST /v1/projects/{id}/process, get_statusGET /v1/projects/{id}/status, and so on. The tools and the API stay in lockstep because both speak the same versioned /v1 contract.