Outside of my usual 9-5 mobile dev work (but due to issues found at it), I've decided to build verba.dev, a light and fully automated translation management system for small businesses and indie devs. As usual, one idea and project, once started, made my brain generate tons of new ideas and potential projects.
One of them being: what if developers could manage their translations without ever leaving their AI coding assistant?
No dashboard tabs. No copy-pasting keys. Just tell Claude Code or Cursor: "Add a French translation for the checkout button" and it happens.
That's what MCP lets you do. And I'm open-sourcing the server.
Repo: github.com/verbadev/mcp-server
What is MCP, quickly
Model Context Protocol is an open standard (originally from Anthropic, now under the Linux Foundation) that lets AI assistants connect to external tools.
If you've used Claude Desktop, Claude Code, Cursor, or Windsurf, you've probably seen MCP servers in action. The GitHub MCP server lets Claude read your repos. The Slack MCP server lets it search messages. The filesystem MCP server lets it read and write files.
The pattern is simple: you build a server that exposes "tools" (functions the AI can call), and any MCP-compatible client can use them.
Why I built one for a TMS
With most translation tools, the developer workflow looks like this:
- Write code
- Switch to browser
- Open translation dashboard
- Find the right project
- Add a key
- Type the default value
- Click "translate"
- Switch back to editor
- Repeat 47 times
Verba already made this much better. With our SDK, you just write verba.t('checkout.success', 'Payment successful!') in your code and the key gets created automatically with AI translations to all your configured languages. No dashboard, no context switching. You stay in your editor.
But there's still a gap. What about checking which keys are missing translations? Reviewing what's been translated? Adding a new language to the project? Cleaning up unused keys? For those tasks, you still had to open the dashboard.
The MCP server closes that gap entirely. Now the workflow is:
- Write code
- When you need anything translation-related, just ask your AI assistant
"Show me all untranslated French keys." "Add Japanese to the project." "Delete all keys that start with old." - all from your editor, zero context switching, zero dashboard.
The architecture
The MCP server is intentionally simple - it's a thin TypeScript wrapper around the Verba REST API. No database access, no business logic, no secrets baked in. Just HTTP calls.
Developer <-> AI Assistant (Claude/Cursor) <-> MCP Server <-> Verba API
The server runs locally via stdio transport. Your AI assistant spawns it as a subprocess and communicates over stdin/stdout using JSON-RPC. Your Verba API key stays in your local environment.
Building it
Setup
mkdir verba-mcp-server && cd verba-mcp-server
npm init -y
npm install @modelcontextprotocol/sdk
npm install -D @types/node typescript
The whole server lives in a single file: src/index.ts.
The API helper
Every tool needs to call the Verba API, so I wrote a small wrapper:
const API_KEY = process.env.VERBA_API_KEY;
const API_URL = (process.env.VERBA_API_URL || "https://verba.dev").replace(
/\/$/,
""
);
if (!API_KEY) {
console.error("VERBA_API_KEY environment variable is required");
process.exit(1);
}
async function verbaApi(
method: string,
path: string,
body?: unknown
): Promise<{ ok: boolean; status: number; data: unknown }> {
const url = `${API_URL}${path}`;
const options: RequestInit = {
method,
headers: {
Authorization: `Bearer ${API_KEY}`,
"Content-Type": "application/json",
},
};
if (body) {
options.body = JSON.stringify(body);
}
const response = await fetch(url, options);
const data = await response.json();
return { ok: response.ok, status: response.status, data };
}
Instead of throwing on errors, the helper always returns { ok, status, data } so each tool can decide how to surface failures to the LLM.
Registering tools
The SDK makes this clean. Each tool gets a name, description (this is what the LLM reads to decide when to use it), a Zod schema for inputs, and a handler:
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
import { z } from "zod";
const server = new McpServer({
name: "verba",
version: "0.1.0",
});
server.tool(
"list_keys",
"List translation keys with optional search, locale filter, and pagination",
{
projectId: z.string().describe("The project ID"),
search: z.string().optional().describe("Search in key names and values"),
locale: z.string().optional().describe("Filter by locale"),
untranslated: z
.boolean()
.optional()
.describe("Only show keys with missing translations"),
page: z.number().optional().describe("Page number (default 1)"),
pageSize: z
.number()
.optional()
.describe("Results per page (default 50, max 100)"),
},
async ({ projectId, search, locale, untranslated, page, pageSize }) => {
try {
const params = new URLSearchParams();
if (search) params.set("search", search);
if (locale) params.set("locale", locale);
if (untranslated) params.set("untranslated", "true");
if (page) params.set("page", String(page));
if (pageSize) params.set("pageSize", String(pageSize));
const qs = params.toString();
const { ok, data } = await verbaApi(
"GET",
`/api/v1/projects/${projectId}/keys${qs ? `?${qs}` : ""}`
);
if (!ok) {
return {
content: [{ type: "text", text: `Error: ${JSON.stringify(data)}` }],
isError: true,
};
}
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
} catch (error) {
return {
content: [
{ type: "text", text: `Error: ${(error as Error).message}` },
],
isError: true,
};
}
}
);
The pattern is identical for every tool. Here's the one that triggers AI translation - probably the most powerful one:
server.tool(
"translate",
"AI-translate one or more keys to target locales. Max 20 keys per request.",
{
projectId: z.string().describe("The project ID"),
keys: z.array(z.string()).describe("Array of key names to translate"),
targetLocales: z
.array(z.string())
.optional()
.describe(
"Specific locales to translate to (defaults to all non-default locales)"
),
},
async ({ projectId, keys, targetLocales }) => {
try {
const { ok, data } = await verbaApi(
"POST",
`/api/v1/projects/${projectId}/translate`,
{ keys, targetLocales }
);
if (!ok) {
return {
content: [{ type: "text", text: `Error: ${JSON.stringify(data)}` }],
isError: true,
};
}
return {
content: [{ type: "text", text: JSON.stringify(data, null, 2) }],
};
} catch (error) {
return {
content: [
{ type: "text", text: `Error: ${(error as Error).message}` },
],
isError: true,
};
}
}
);
Connect the transport
Last step - wire it up to stdio:
const transport = new StdioServerTransport();
await server.connect(transport);
That's the entire server. Build it with tsc and you're done.
The 9 tools
Here's what the server exposes:
| Tool | What it does |
|---|---|
list_projects |
List all your Verba projects |
get_project |
Get project details (locales, key count) |
list_keys |
List translation keys with search, filter, pagination |
add_key |
Add a new key with auto AI-translation |
set_translation |
Set a translation for a key + locale |
translate |
AI-translate keys to target locales |
list_untranslated |
Find keys missing translations |
add_locale |
Add a new locale to a project |
delete_key |
Delete a translation key |
Testing with MCP Inspector
Before connecting to Claude, test your tools in isolation:
VERBA_API_KEY=sk_your-key npx @modelcontextprotocol/inspector build/index.js
This opens a browser UI where you can click any tool, fill in parameters, and see the raw response. Invaluable for debugging.
Using it in Claude Desktop
Add this to your claude_desktop_config.json:
{
"mcpServers": {
"verba": {
"command": "node",
"args": ["/path/to/mcp-server/build/index.js"],
"env": {
"VERBA_API_KEY": "sk_your-secret-key"
}
}
}
}
Restart Claude Desktop, and you'll see the hammer icon showing 9 available tools.
Using it in Claude Code
claude mcp add verba -- node /path/to/mcp-server/build/index.js
Then set the environment variable:
export VERBA_API_KEY=sk_your_key_here
Now you can say things like:
- "Show me all untranslated keys for French in my project"
- "Add a key 'checkout.success' with value 'Payment successful!' and translate it to all languages"
- "What languages does my app support? Add Japanese."
The AI figures out which tool to call, extracts the parameters from your natural language, and executes it. You never touch a dashboard.
Three things I learned building this
1. Tool descriptions are prompts. The LLM reads your tool descriptions to decide which tool to use. Write them like you're explaining the tool to a junior developer. Be specific about what each parameter does.
2. Return structured data, not prose. The LLM will format the output for the user. Give it clean JSON or structured text, not sentences. Let the AI do the presentation.
3. Error handling matters more than usual. When a tool fails, the LLM needs to understand why so it can either retry or explain the issue to the user. Return clear error messages, not stack traces.
Why open source it?
The MCP server contains zero business logic. It's just an HTTP client with tool definitions. The value is in the Verba platform - the AI translations, OTA delivery, and dashboard. Open-sourcing the MCP server lowers friction for developers and lets the community contribute tool improvements.
If you're building a developer tool, I'd encourage you to do the same. MCP servers are the new API clients. They're how developers will interact with your product in 2026.
What's next
I'm working on adding the MCP server to the official MCP Registry so it shows up in MCP-compatible clients by default. I'm also exploring adding resources (read-only data the LLM can pull from) and prompts (templates for common translation workflows).
If you're building a SaaS product for developers, consider this: the next generation of developers won't visit your dashboard. They'll talk to their AI assistant, and their AI assistant will talk to your MCP server. Build for that future.
Verba (verba.dev) is a translation management system built for small businesses and indie developers. Three lines of code, AI-powered translations, OTA delivery. $29/month for unlimited everything.
MCP Server: github.com/verbadev/mcp-server (open source, MIT)
If you found this useful, I'm @verbadotdev on X.
Top comments (0)