@better-i18n/mcp-server 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +158 -0
  2. package/dist/client.d.ts +17 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +34 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/context.d.ts +23 -0
  7. package/dist/context.d.ts.map +1 -0
  8. package/dist/context.js +142 -0
  9. package/dist/context.js.map +1 -0
  10. package/dist/index.d.ts +10 -0
  11. package/dist/index.d.ts.map +1 -0
  12. package/dist/index.js +149 -0
  13. package/dist/index.js.map +1 -0
  14. package/dist/tools/bulkCreateKeys.d.ts +9 -0
  15. package/dist/tools/bulkCreateKeys.d.ts.map +1 -0
  16. package/dist/tools/bulkCreateKeys.js +113 -0
  17. package/dist/tools/bulkCreateKeys.js.map +1 -0
  18. package/dist/tools/bulkUpdateTranslations.d.ts +9 -0
  19. package/dist/tools/bulkUpdateTranslations.d.ts.map +1 -0
  20. package/dist/tools/bulkUpdateTranslations.js +107 -0
  21. package/dist/tools/bulkUpdateTranslations.js.map +1 -0
  22. package/dist/tools/createTranslationKey.d.ts +9 -0
  23. package/dist/tools/createTranslationKey.d.ts.map +1 -0
  24. package/dist/tools/createTranslationKey.js +95 -0
  25. package/dist/tools/createTranslationKey.js.map +1 -0
  26. package/dist/tools/getProjectInfo.d.ts +9 -0
  27. package/dist/tools/getProjectInfo.d.ts.map +1 -0
  28. package/dist/tools/getProjectInfo.js +67 -0
  29. package/dist/tools/getProjectInfo.js.map +1 -0
  30. package/dist/tools/listKeys.d.ts +9 -0
  31. package/dist/tools/listKeys.d.ts.map +1 -0
  32. package/dist/tools/listKeys.js +113 -0
  33. package/dist/tools/listKeys.js.map +1 -0
  34. package/dist/tools/updateTranslation.d.ts +9 -0
  35. package/dist/tools/updateTranslation.d.ts.map +1 -0
  36. package/dist/tools/updateTranslation.js +101 -0
  37. package/dist/tools/updateTranslation.js.map +1 -0
  38. package/dist/types/index.d.ts +44 -0
  39. package/dist/types/index.d.ts.map +1 -0
  40. package/dist/types/index.js +5 -0
  41. package/dist/types/index.js.map +1 -0
  42. package/package.json +61 -0
package/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # @better-i18n/mcp-server
2
+
3
+ MCP (Model Context Protocol) server for [Better i18n](https://better-i18n.com) translation management. Enables AI assistants like Claude (via Cursor) to manage your translations directly from your IDE.
4
+
5
+ ## Features
6
+
7
+ - 🤖 **AI-Powered Translation Management** - Let AI handle your translations
8
+ - 📝 **Create Translation Keys** - Add new keys with source text and translations
9
+ - 🔄 **Bulk Operations** - Create/update multiple keys at once
10
+ - 🔍 **Smart Search** - Find keys by name, namespace, or missing translations
11
+ - 🌍 **Multi-language Support** - Manage all your target languages
12
+
13
+ ## Installation
14
+
15
+ ### Global Installation (Recommended)
16
+
17
+ ```bash
18
+ npm install -g @better-i18n/mcp-server
19
+ ```
20
+
21
+ ### With npx
22
+
23
+ You can also run it directly without installing:
24
+
25
+ ```bash
26
+ npx @better-i18n/mcp-server
27
+ ```
28
+
29
+ ## Setup
30
+
31
+ ### 1. Get Your API Key
32
+
33
+ 1. Go to [better-i18n.com/settings/api-keys](https://better-i18n.com/settings/api-keys)
34
+ 2. Create a new API key
35
+ 3. Copy the key (you won't see it again!)
36
+
37
+ ### 2. Add i18n.config.ts to Your Project
38
+
39
+ Create an `i18n.config.ts` in your project root:
40
+
41
+ ```typescript
42
+ import { createI18n } from "@better-i18n/next"; // or your framework
43
+
44
+ export const i18n = createI18n({
45
+ workspaceId: "your-org-slug", // Your organization slug
46
+ projectSlug: "your-project", // Your project slug
47
+ defaultLocale: "en",
48
+ });
49
+ ```
50
+
51
+ ### 3. Configure Your IDE
52
+
53
+ #### Cursor
54
+
55
+ Add to your MCP settings (`~/.cursor/mcp.json` or project's `.cursor/mcp.json`):
56
+
57
+ ```json
58
+ {
59
+ "mcpServers": {
60
+ "better-i18n": {
61
+ "command": "npx",
62
+ "args": ["@better-i18n/mcp-server"],
63
+ "env": {
64
+ "BETTER_I18N_API_KEY": "your-api-key-here"
65
+ }
66
+ }
67
+ }
68
+ }
69
+ ```
70
+
71
+ Or with global install:
72
+
73
+ ```json
74
+ {
75
+ "mcpServers": {
76
+ "better-i18n": {
77
+ "command": "better-i18n-mcp",
78
+ "env": {
79
+ "BETTER_I18N_API_KEY": "your-api-key-here"
80
+ }
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ ## Available Tools
87
+
88
+ ### `getProjectInfo`
89
+ Get project information including available namespaces, languages, and key count.
90
+
91
+ ### `listKeys`
92
+ List translation keys with optional filtering:
93
+ - `search` - Search by key name
94
+ - `namespaces` - Filter by namespaces
95
+ - `missingLanguage` - Find keys missing a specific language translation
96
+
97
+ ### `createTranslationKey`
98
+ Create a single translation key with source text and optional translations.
99
+
100
+ ### `bulkCreateKeys`
101
+ Create multiple translation keys with all translations in one request.
102
+
103
+ ```json
104
+ {
105
+ "keys": [
106
+ {
107
+ "key": "nav.home",
108
+ "namespace": "nav",
109
+ "sourceText": "Home",
110
+ "translations": { "tr": "Ana Sayfa", "de": "Startseite" }
111
+ }
112
+ ]
113
+ }
114
+ ```
115
+
116
+ ### `updateTranslation`
117
+ Update a single translation for a specific language.
118
+
119
+ ### `bulkUpdateTranslations`
120
+ Update translations for multiple keys at once.
121
+
122
+ ## Example Workflow
123
+
124
+ Ask your AI assistant:
125
+
126
+ > "Add Turkish translations for all keys in the nav namespace that are missing Turkish"
127
+
128
+ The AI will:
129
+ 1. Use `listKeys` with `missingLanguage: "tr"` and `namespaces: ["nav"]`
130
+ 2. Use `bulkUpdateTranslations` to add the Turkish translations
131
+
132
+ ## Environment Variables
133
+
134
+ | Variable | Required | Description |
135
+ |----------|----------|-------------|
136
+ | `BETTER_I18N_API_KEY` | Yes | Your API key from better-i18n.com |
137
+ | `BETTER_I18N_API_URL` | No | API URL (default: https://better-i18n.com) |
138
+
139
+ ## Development
140
+
141
+ ```bash
142
+ # Clone the repo
143
+ git clone https://github.com/better-i18n/better-i18n.git
144
+ cd packages/mcp-server
145
+
146
+ # Install dependencies
147
+ npm install
148
+
149
+ # Run in development mode
150
+ npm run dev
151
+
152
+ # Build
153
+ npm run build
154
+ ```
155
+
156
+ ## License
157
+
158
+ MIT © [Better i18n](https://better-i18n.com)
@@ -0,0 +1,17 @@
1
+ /**
2
+ * tRPC client for Paris API with API key authentication
3
+ */
4
+ export interface ClientConfig {
5
+ apiUrl: string;
6
+ apiKey: string;
7
+ }
8
+ /**
9
+ * Create a tRPC client authenticated with API key
10
+ *
11
+ * @param config - API URL, API key, and optional organization ID
12
+ * @returns Configured tRPC client
13
+ */
14
+ export declare function createParisClient(config: ClientConfig & {
15
+ organizationId?: string;
16
+ }): import("@trpc/client").TRPCClient<any>;
17
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAQH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,0CAuBnF"}
package/dist/client.js ADDED
@@ -0,0 +1,34 @@
1
+ /**
2
+ * tRPC client for Paris API with API key authentication
3
+ */
4
+ import { createTRPCClient, httpBatchLink } from "@trpc/client";
5
+ /**
6
+ * Create a tRPC client authenticated with API key
7
+ *
8
+ * @param config - API URL, API key, and optional organization ID
9
+ * @returns Configured tRPC client
10
+ */
11
+ export function createParisClient(config) {
12
+ // Ensure URL ends with /api/trpc for tRPC endpoint
13
+ const url = config.apiUrl.endsWith("/api/trpc")
14
+ ? config.apiUrl
15
+ : `${config.apiUrl.replace(/\/$/, "")}/api/trpc`;
16
+ return createTRPCClient({
17
+ links: [
18
+ httpBatchLink({
19
+ url,
20
+ headers: () => {
21
+ const headers = {
22
+ // Better Auth expects API key in x-api-key header
23
+ "x-api-key": config.apiKey,
24
+ };
25
+ if (config.organizationId) {
26
+ headers["x-organization-id"] = config.organizationId;
27
+ }
28
+ return headers;
29
+ },
30
+ }),
31
+ ],
32
+ });
33
+ }
34
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAW/D;;;;;GAKG;AACH,MAAM,UAAU,iBAAiB,CAAC,MAAkD;IAClF,mDAAmD;IACnD,MAAM,GAAG,GAAG,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC;QAC7C,CAAC,CAAC,MAAM,CAAC,MAAM;QACf,CAAC,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC;IAEnD,OAAO,gBAAgB,CAAY;QACjC,KAAK,EAAE;YACL,aAAa,CAAC;gBACZ,GAAG;gBACH,OAAO,EAAE,GAAG,EAAE;oBACZ,MAAM,OAAO,GAA2B;wBACtC,kDAAkD;wBAClD,WAAW,EAAE,MAAM,CAAC,MAAM;qBAC3B,CAAC;oBACF,IAAI,MAAM,CAAC,cAAc,EAAE,CAAC;wBAC1B,OAAO,CAAC,mBAAmB,CAAC,GAAG,MAAM,CAAC,cAAc,CAAC;oBACvD,CAAC;oBACD,OAAO,OAAO,CAAC;gBACjB,CAAC;aACF,CAAC;SACH;KACF,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Project context detection
3
+ *
4
+ * Automatically detects Better i18n project configuration by searching for
5
+ * createI18n usage from @better-i18n/next or @better-i18n/core in the workspace.
6
+ */
7
+ export interface ProjectContext {
8
+ workspaceId: string;
9
+ projectSlug: string;
10
+ defaultLocale: string;
11
+ cdnBaseUrl?: string;
12
+ }
13
+ /**
14
+ * Find and parse createI18n configuration in workspace
15
+ *
16
+ * Searches for files importing createI18n from @better-i18n packages
17
+ * and extracts the configuration.
18
+ *
19
+ * @param startDir - Starting directory (defaults to process.cwd())
20
+ * @returns Project context or null if not found
21
+ */
22
+ export declare function detectProjectContext(startDir?: string): Promise<ProjectContext | null>;
23
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,WAAW,cAAc;IAC7B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,GAAE,MAAsB,GAC/B,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CA0BhC"}
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Project context detection
3
+ *
4
+ * Automatically detects Better i18n project configuration by searching for
5
+ * createI18n usage from @better-i18n/next or @better-i18n/core in the workspace.
6
+ */
7
+ import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
8
+ import { dirname, join } from "node:path";
9
+ /**
10
+ * Find and parse createI18n configuration in workspace
11
+ *
12
+ * Searches for files importing createI18n from @better-i18n packages
13
+ * and extracts the configuration.
14
+ *
15
+ * @param startDir - Starting directory (defaults to process.cwd())
16
+ * @returns Project context or null if not found
17
+ */
18
+ export async function detectProjectContext(startDir = process.cwd()) {
19
+ try {
20
+ // Find workspace root
21
+ const workspaceRoot = findWorkspaceRoot(startDir);
22
+ console.error(`[better-i18n] Searching for createI18n in ${workspaceRoot}...`);
23
+ // Search for files with createI18n import
24
+ const configFile = findCreateI18nFile(workspaceRoot);
25
+ if (configFile) {
26
+ console.error(`[better-i18n] Found createI18n in ${configFile}`);
27
+ const config = parseI18nConfig(configFile);
28
+ if (config.workspaceId && config.projectSlug) {
29
+ console.error(`[better-i18n] Detected project: ${config.workspaceId}/${config.projectSlug}`);
30
+ return config;
31
+ }
32
+ }
33
+ }
34
+ catch (error) {
35
+ console.error("[better-i18n] Error detecting project context:", error);
36
+ }
37
+ return null;
38
+ }
39
+ /**
40
+ * Find workspace root by looking for .git or package.json with workspaces
41
+ */
42
+ function findWorkspaceRoot(startDir) {
43
+ let currentDir = startDir;
44
+ const maxDepth = 10;
45
+ let depth = 0;
46
+ while (depth < maxDepth) {
47
+ // Check for .git directory
48
+ if (existsSync(join(currentDir, ".git"))) {
49
+ return currentDir;
50
+ }
51
+ // Check for workspace root (package.json with workspaces)
52
+ const pkgPath = join(currentDir, "package.json");
53
+ if (existsSync(pkgPath)) {
54
+ try {
55
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
56
+ if (pkg.workspaces) {
57
+ return currentDir;
58
+ }
59
+ }
60
+ catch {
61
+ // Ignore parse errors
62
+ }
63
+ }
64
+ const parentDir = dirname(currentDir);
65
+ if (parentDir === currentDir)
66
+ break;
67
+ currentDir = parentDir;
68
+ depth++;
69
+ }
70
+ return startDir;
71
+ }
72
+ /**
73
+ * Search for files containing createI18n import from @better-i18n packages
74
+ */
75
+ function findCreateI18nFile(workspaceRoot) {
76
+ const files = [];
77
+ // Recursively search for TypeScript/JavaScript files
78
+ function searchDir(dir, depth = 0) {
79
+ if (depth > 5)
80
+ return; // Limit recursion depth
81
+ try {
82
+ const entries = readdirSync(dir);
83
+ for (const entry of entries) {
84
+ // Skip common directories
85
+ if (["node_modules", ".git", "dist", "build", ".next"].includes(entry)) {
86
+ continue;
87
+ }
88
+ const fullPath = join(dir, entry);
89
+ const stat = statSync(fullPath);
90
+ if (stat.isDirectory()) {
91
+ searchDir(fullPath, depth + 1);
92
+ }
93
+ else if (/\.(ts|tsx|js|jsx)$/.test(entry)) {
94
+ files.push(fullPath);
95
+ }
96
+ }
97
+ }
98
+ catch (error) {
99
+ // Ignore permission errors
100
+ }
101
+ }
102
+ searchDir(workspaceRoot);
103
+ // Search through found files for createI18n import
104
+ for (const file of files) {
105
+ try {
106
+ const content = readFileSync(file, "utf-8");
107
+ // Check for createI18n import from @better-i18n packages
108
+ if (/import\s+.*createI18n.*from\s+['"]@better-i18n\/(next|core)['"]/.test(content) &&
109
+ /createI18n\s*\(/.test(content)) {
110
+ return file;
111
+ }
112
+ }
113
+ catch {
114
+ // Ignore read errors
115
+ }
116
+ }
117
+ return null;
118
+ }
119
+ /**
120
+ * Parse file to extract createI18n configuration
121
+ *
122
+ * Uses regex to extract workspaceId, projectSlug, defaultLocale, and cdnBaseUrl
123
+ * from the createI18n call.
124
+ */
125
+ function parseI18nConfig(filePath) {
126
+ const content = readFileSync(filePath, "utf-8");
127
+ // Extract values using regex
128
+ const workspaceMatch = content.match(/workspaceId:\s*['"]([^'"]+)['"]/);
129
+ const projectMatch = content.match(/projectSlug:\s*['"]([^'"]+)['"]/);
130
+ const localeMatch = content.match(/defaultLocale:\s*['"]([^'"]+)['"]/);
131
+ const cdnMatch = content.match(/cdnBaseUrl:\s*['"]([^'"]+)['"]/);
132
+ if (!workspaceMatch || !projectMatch) {
133
+ throw new Error(`Invalid createI18n config in ${filePath}: missing workspaceId or projectSlug`);
134
+ }
135
+ return {
136
+ workspaceId: workspaceMatch[1],
137
+ projectSlug: projectMatch[1],
138
+ defaultLocale: localeMatch?.[1] || "en",
139
+ cdnBaseUrl: cdnMatch?.[1],
140
+ };
141
+ }
142
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.js","sourceRoot":"","sources":["../src/context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Better i18n MCP Server
4
+ *
5
+ * Model Context Protocol server for Better i18n translation management.
6
+ * Enables AI assistants (Claude, ChatGPT, etc.) to manage translations
7
+ * directly from IDEs like Cursor.
8
+ */
9
+ export {};
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;;GAMG"}
package/dist/index.js ADDED
@@ -0,0 +1,149 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Better i18n MCP Server
4
+ *
5
+ * Model Context Protocol server for Better i18n translation management.
6
+ * Enables AI assistants (Claude, ChatGPT, etc.) to manage translations
7
+ * directly from IDEs like Cursor.
8
+ */
9
+ import { Server } from "@modelcontextprotocol/sdk/server/index.js";
10
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
11
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
12
+ import { createParisClient } from "./client.js";
13
+ import { detectProjectContext } from "./context.js";
14
+ import { bulkCreateKeys } from "./tools/bulkCreateKeys.js";
15
+ import { bulkUpdateTranslations } from "./tools/bulkUpdateTranslations.js";
16
+ import { createTranslationKey } from "./tools/createTranslationKey.js";
17
+ import { getProjectInfo } from "./tools/getProjectInfo.js";
18
+ import { listKeys } from "./tools/listKeys.js";
19
+ import { updateTranslation } from "./tools/updateTranslation.js";
20
+ class ParisI18nServer {
21
+ server;
22
+ parisClient;
23
+ context;
24
+ constructor() {
25
+ this.server = new Server({
26
+ name: "better-i18n",
27
+ version: "0.1.0",
28
+ }, {
29
+ capabilities: {
30
+ tools: {},
31
+ },
32
+ });
33
+ // Will be initialized in init()
34
+ this.parisClient = null;
35
+ this.context = {
36
+ projectContext: null,
37
+ };
38
+ }
39
+ async init() {
40
+ // Read configuration from environment
41
+ const apiUrl = process.env.BETTER_I18N_API_URL || "http://localhost:3000";
42
+ const apiKey = process.env.BETTER_I18N_API_KEY;
43
+ const envOrgId = process.env.BETTER_I18N_ORG_ID;
44
+ if (!apiKey) {
45
+ console.error("[better-i18n] ERROR: BETTER_I18N_API_KEY environment variable is required");
46
+ console.error("[better-i18n] Get your API key from: https://better-i18n.com/settings/api-keys");
47
+ process.exit(1);
48
+ }
49
+ // Detect project context from workspace
50
+ this.context.projectContext = await detectProjectContext();
51
+ // Resolve organization ID
52
+ let organizationId = envOrgId;
53
+ if (!organizationId && this.context.projectContext?.workspaceId) {
54
+ // Try to lookup org ID from slug using the API
55
+ try {
56
+ const authUrl = apiUrl.replace(/\/$/, "") + "/api/auth/organization/list";
57
+ const response = await fetch(authUrl, {
58
+ headers: { "x-api-key": apiKey },
59
+ });
60
+ if (response.ok) {
61
+ const orgs = await response.json();
62
+ const matchingOrg = orgs.find((org) => org.slug === this.context.projectContext?.workspaceId);
63
+ if (matchingOrg) {
64
+ organizationId = matchingOrg.id;
65
+ console.error(`[better-i18n] Resolved org "${matchingOrg.slug}" -> ${organizationId}`);
66
+ }
67
+ }
68
+ }
69
+ catch (err) {
70
+ console.error("[better-i18n] Warning: Could not lookup organization ID:", err);
71
+ }
72
+ }
73
+ // Create Paris API client with organization context
74
+ this.parisClient = createParisClient({ apiUrl, apiKey, organizationId });
75
+ if (this.context.projectContext) {
76
+ console.error(`[better-i18n] Detected project: ${this.context.projectContext.workspaceId}/${this.context.projectContext.projectSlug}`);
77
+ }
78
+ else {
79
+ console.error("[better-i18n] Warning: No i18n.config.ts found. Tools will require explicit projectSlug parameter.");
80
+ }
81
+ this.setupHandlers();
82
+ }
83
+ setupHandlers() {
84
+ // List available tools
85
+ this.server.setRequestHandler(ListToolsRequestSchema, async () => ({
86
+ tools: [
87
+ getProjectInfo.definition,
88
+ listKeys.definition,
89
+ createTranslationKey.definition,
90
+ bulkCreateKeys.definition,
91
+ updateTranslation.definition,
92
+ bulkUpdateTranslations.definition,
93
+ ],
94
+ }));
95
+ // Execute tools
96
+ this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
97
+ const { name, arguments: args } = request.params;
98
+ try {
99
+ let result;
100
+ switch (name) {
101
+ case "getProjectInfo":
102
+ result = await getProjectInfo.execute(this.parisClient, args, this.context);
103
+ break;
104
+ case "createTranslationKey":
105
+ result = await createTranslationKey.execute(this.parisClient, args, this.context);
106
+ break;
107
+ case "updateTranslation":
108
+ result = await updateTranslation.execute(this.parisClient, args, this.context);
109
+ break;
110
+ case "listKeys":
111
+ result = await listKeys.execute(this.parisClient, args, this.context);
112
+ break;
113
+ case "bulkCreateKeys":
114
+ result = await bulkCreateKeys.execute(this.parisClient, args, this.context);
115
+ break;
116
+ case "bulkUpdateTranslations":
117
+ result = await bulkUpdateTranslations.execute(this.parisClient, args, this.context);
118
+ break;
119
+ default:
120
+ throw new Error(`Unknown tool: ${name}`);
121
+ }
122
+ return result;
123
+ }
124
+ catch (error) {
125
+ return {
126
+ content: [
127
+ {
128
+ type: "text",
129
+ text: `Error executing ${name}: ${error instanceof Error ? error.message : String(error)}`,
130
+ },
131
+ ],
132
+ isError: true,
133
+ };
134
+ }
135
+ });
136
+ }
137
+ async run() {
138
+ const transport = new StdioServerTransport();
139
+ await this.server.connect(transport);
140
+ console.error("[better-i18n] MCP Server running on stdio");
141
+ }
142
+ }
143
+ // Start the server
144
+ const server = new ParisI18nServer();
145
+ server.init().then(() => server.run()).catch((error) => {
146
+ console.error("[better-i18n] Fatal error:", error);
147
+ process.exit(1);
148
+ });
149
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * bulkCreateKeys MCP Tool
3
+ *
4
+ * Creates multiple translation keys with all translations in a single request.
5
+ * Perfect for adding entire components or pages worth of translations at once.
6
+ */
7
+ import type { Tool } from "../types/index.js";
8
+ export declare const bulkCreateKeys: Tool;
9
+ //# sourceMappingURL=bulkCreateKeys.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bulkCreateKeys.d.ts","sourceRoot":"","sources":["../../src/tools/bulkCreateKeys.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAY9C,eAAO,MAAM,cAAc,EAAE,IA0G5B,CAAC"}