Operate it from your agent (MCP)
i18n-keyless ships an MCP server. Your agent — Claude Code, Cursor, Windsurf, Codex, or a
client you wrote — can do anything you can do in the dashboard: list missing translations,
fix a bad one, change the language list, invite a teammate, create a project. Same rule
everywhere: if a human can do it on i18n-keyless.com, an agent can do it with a prompt.
There is no key to paste and no secret to keep in a config file. The server uses OAuth: the agent gets its own login, bound to you and to one project.
Connect
Claude Code
claude mcp add --transport http i18n-keyless https://api.i18n-keyless.com/mcp
Cursor, Windsurf, and any other client that reads an mcpServers block
{
"mcpServers": {
"i18n-keyless": {
"type": "http",
"url": "https://api.i18n-keyless.com/mcp"
}
}
}
The URL is the same for every client. Nothing in the flow is specific to one agent.
What happens on the first call
- The agent calls the server. The server answers
401with OAuth metadata, and the agent registers itself — no pre-registration, no allow-list. - Your browser opens once, on
i18n-keyless.com/oauth/consent. Sign in, or sign up on the same page: app name, email, password, plan, then the Stripe payment page, and back. - Pick the project, read the requested scopes, approve. The agent now holds a token bound
to you and that one project. It refreshes itself and can be revoked from the agent at any
time (
/revoke).
One token, one project. A token never reaches more than the project you approved it for, so a tool call never has to guess which app it is editing. If you have several apps, add the server once per app under a different name:
claude mcp add --transport http i18n-keyless-web https://api.i18n-keyless.com/mcp
claude mcp add --transport http i18n-keyless-mobile https://api.i18n-keyless.com/mcp
Then ask your agent for whatever you would have clicked for: "list the strings that have no German yet", "the French for 'Sign in' is wrong, make it 'Se connecter'", "ship Japanese too".
The tools
Three scopes. You approve each one on the consent page, and a tool is only offered to the agent when its scope was granted — a read-only grant cannot be talked into a delete or a purchase.
i18n:read
| Tool | What it does |
|---|---|
get_started { framework } | The install and init instructions with the project's public key, primary language and supported languages already filled in. |
search_docs { query, limit? } | Searches this documentation (llms.txt) by section and returns the matching ones. |
list_languages { query? } | The 48 supported codes with names and App Store locales. With query, answers which supported code a BCP-47 tag maps to. |
get_project {} | app_name, primary language, supported languages, row count, current-month usage, subscription status, and the public key. Never the private key, the email, or the Stripe ids. |
list_projects {} | Every project of your account, flagging the one the token is bound to. |
list_translations { namespace?, language?, search?, missingOnly?, limit?, cursor? } | The project's translations, paginated. missingOnly keeps the rows where a supported language is still empty. |
get_translation { sourceText, context?, namespace? } | One row, looked up by its source text. |
i18n:write
| Tool | What it does |
|---|---|
translate { sourceText, context?, namespace?, languages?, originLanguage? } | Translates a string into the project's languages — the same path as the SDK's POST /translate, billed the same way. |
override_translation { id, language, value } | Replaces one translation. Every client cache is invalidated on its next refresh. |
delete_translation { id, confirm: true } | Deletes a row. Destructive, so confirm: true is required. |
set_project_languages { supported, primary? } | Changes the language list. The primary language is only settable while the project has no translation, exactly as in the dashboard. Note: the next POST /translate from an SDK overwrites the supported list with whatever that client sends. |
i18n:account
These spend money or send email, so they get their own consent line.
| Tool | What it does |
|---|---|
create_project { app_name, plan? } | A new project on your account. With a saved payment method the subscription is created in-band and the project is returned; without one, a checkout_url is returned for the browser. |
invite_member { email, confirm: true } | Adds a teammate to the project and emails them. Owner only. |
remove_member { email, confirm: true } | Removes a teammate from the project. Owner only. |
What is deliberately not a tool
- Code scanning. A remote server cannot read your filesystem, and the product's pitch is that no extraction step exists — your agent already has the code in its context.
- The free trial. Not reachable from an agent. An agent that wants a project pays for it.
- Plan changes and the billing portal. The dashboard has no plan-change screen today, so they are not parity; when the dashboard grows them, the tools follow.
Anything else a human can do in the dashboard and an agent cannot is a bug — report it.
For AI agents
Machine-checkable summary of the server:
- Transport: streamable HTTP at
https://api.i18n-keyless.com/mcp, stateless — no sessions, no SSE stream;GETandDELETEon/mcpanswer405. - Auth: OAuth 2.1 with PKCE and dynamic client registration. Discovery starts from the
WWW-Authenticateheader of the first401; metadata at/.well-known/oauth-authorization-serverand/.well-known/oauth-protected-resource/mcp. Access token 1 hour, refresh token 30 days, rotated on refresh. Scopes:i18n:read,i18n:write,i18n:account. Tools outside the granted scopes are not listed. - One token is bound to one user and one project;
list_projectstells which. - Language codes are v3 only (
zh-Hans,cs) — the v2 spellings are never emitted. list_translationsreturns at most 100 rows per call and acursorfor the next page.delete_translation,invite_memberandremove_memberrequireconfirm: true.- MCP traffic never counts as a monthly active user; only
translatecosts anything, and it costs what the same string would cost from the SDK. - Rate limit on
/mcp: 60 requests per 10 seconds per token.