@i18n-agent/mcp-client 1.1.3 → 1.4.1

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 (4) hide show
  1. package/README.md +111 -7
  2. package/install.js +69 -10
  3. package/mcp-client.js +442 -143
  4. package/package.json +2 -2
package/README.md CHANGED
@@ -69,6 +69,11 @@ Check my translation credits
69
69
  List supported languages with quality ratings
70
70
  ```
71
71
 
72
+ ### Content Analysis
73
+ ```
74
+ Analyze content for translation readiness and get improvement suggestions
75
+ ```
76
+
72
77
  ## šŸ›  Supported AI IDEs
73
78
 
74
79
  | IDE | Status | Config Location |
@@ -79,8 +84,6 @@ List supported languages with quality ratings
79
84
  | **Other MCP IDEs** | šŸ”§ Manual setup | Varies |
80
85
 
81
86
  ## 🌐 Language Support
82
-
83
- ### Tier 1 - Excellent Quality
84
87
  - **en**: English
85
88
  - **fr**: French
86
89
  - **de**: German
@@ -91,8 +94,6 @@ List supported languages with quality ratings
91
94
  - **ja**: Japanese
92
95
  - **ko**: Korean
93
96
  - **zh-CN**: Chinese (Simplified)
94
-
95
- ### Tier 2 - High Quality
96
97
  - **nl**: Dutch
97
98
  - **pl**: Polish
98
99
  - **cs**: Czech
@@ -104,8 +105,6 @@ List supported languages with quality ratings
104
105
  - **da**: Danish
105
106
  - **no**: Norwegian
106
107
  - **fi**: Finnish
107
-
108
- ### Tier 3 - Good Quality
109
108
  - **tr**: Turkish
110
109
  - **hu**: Hungarian
111
110
  - **th**: Thai
@@ -216,6 +215,50 @@ ls ~/.cursor/
216
215
  ls ~/.vscode/
217
216
  ```
218
217
 
218
+ ### MCP Connection Issues
219
+
220
+ **"Failed" status in Claude Code:**
221
+
222
+ This usually happens with Node Version Managers (nvm, fnm, n). The installer now automatically detects nvm and creates a wrapper script. If you still have issues:
223
+
224
+ 1. **Check your Node installation:**
225
+ ```bash
226
+ which node
227
+ # If output contains .nvm, you're using nvm
228
+ ```
229
+
230
+ 2. **Manual wrapper script (if auto-detection fails):**
231
+ Create `~/.claude/run-mcp.sh`:
232
+ ```bash
233
+ #!/bin/bash
234
+ export PATH="$(dirname $(which node)):$PATH"
235
+ cd ~/.claude
236
+ exec node node_modules/@i18n-agent/mcp-client/mcp-client.js
237
+ ```
238
+
239
+ Make it executable:
240
+ ```bash
241
+ chmod +x ~/.claude/run-mcp.sh
242
+ ```
243
+
244
+ 3. **Update Claude configuration:**
245
+ Edit `~/.claude.json`:
246
+ ```json
247
+ {
248
+ "mcpServers": {
249
+ "i18n-agent": {
250
+ "command": "/Users/YOUR_USERNAME/.claude/run-mcp.sh",
251
+ "env": {
252
+ "MCP_SERVER_URL": "https://mcp.i18nagent.ai",
253
+ "API_KEY": "your-api-key"
254
+ }
255
+ }
256
+ }
257
+ }
258
+ ```
259
+
260
+ 4. **Restart Claude Code completely** (not just close window, quit the app)
261
+
219
262
  ### Runtime Issues
220
263
 
221
264
  **API Key not found:**
@@ -277,10 +320,71 @@ Copyright (c) 2025 FatCouple OÜ
277
320
 
278
321
  ## šŸ†˜ Support
279
322
 
280
- - **Discord**: [Join our community](https://discord.gg/i18nagent)
281
323
  - **Email**: support@i18nagent.ai
282
324
  - **Documentation**: [docs.i18nagent.ai](https://docs.i18nagent.ai)
283
325
 
326
+ ## šŸ”§ Available MCP Tools
327
+
328
+ ### translate_text
329
+ Translate text content with cultural adaptation and context awareness.
330
+
331
+ **Parameters:**
332
+ - `texts` (array): Array of strings to translate
333
+ - `targetLanguage` (string): Target language code
334
+ - `targetAudience` (string): Target audience context
335
+ - `industry` (string): Industry context
336
+ - `sourceLanguage` (string, optional): Source language (auto-detected if not provided)
337
+ - `region` (string, optional): Specific region for localization
338
+
339
+ ### translate_file
340
+ Translate files while preserving structure and format.
341
+
342
+ **Parameters:**
343
+ - `filePath` or `fileContent` (string): File path or content to translate
344
+ - `fileType` (string): File format (json, yaml, xml, csv, txt, md, etc.)
345
+ - `targetLanguage` (string): Target language code
346
+ - `preserveKeys` (boolean): Whether to preserve object keys/structure
347
+ - `outputFormat` (string): Output format (same, json, yaml, txt)
348
+
349
+ ### analyze_content
350
+ Analyze content for translation readiness and get improvement suggestions before translation.
351
+
352
+ **Parameters:**
353
+ - `content` (string/array/object): Content to analyze
354
+ - `targetLanguage` (string): Target language for translation
355
+ - `fileType` (string, optional): File type if content is from a file
356
+ - `sourceLanguage` (string, optional): Source language (auto-detected)
357
+ - `industry` (string): Industry context
358
+ - `targetAudience` (string): Target audience
359
+ - `region` (string, optional): Specific region for localization
360
+
361
+ **Returns:**
362
+ - Source language detection with confidence score
363
+ - Content type and tone analysis
364
+ - Translation readiness score (0-100)
365
+ - Specific improvement suggestions
366
+ - Quality metrics and issues
367
+ - Warnings for potential problems
368
+ - Estimated credits required
369
+
370
+ ### list_supported_languages
371
+ Get list of all supported languages with quality ratings.
372
+
373
+ **Parameters:**
374
+ - `includeQuality` (boolean): Include quality ratings (default: true)
375
+
376
+ ### get_credits
377
+ Check remaining translation credits and word count estimates.
378
+
379
+ **Parameters:**
380
+ - `apiKey` (string): Your API key
381
+
382
+ ### check_translation_status
383
+ Check status of async translation jobs (for large files).
384
+
385
+ **Parameters:**
386
+ - `jobId` (string): Job ID from async translation
387
+
284
388
  ---
285
389
 
286
390
  Made with ā¤ļø by [FatCouple OÜ](https://fireinbelly.com)
package/install.js CHANGED
@@ -74,6 +74,45 @@ function createMCPConfig() {
74
74
  };
75
75
  }
76
76
 
77
+ function detectNodeEnvironment() {
78
+ // Check if using nvm or other version managers
79
+ const nvmDir = process.env.NVM_DIR || path.join(os.homedir(), '.nvm');
80
+ const nodeVersion = process.version;
81
+ const nodePath = process.execPath;
82
+
83
+ return {
84
+ isNvm: nodePath.includes('.nvm') || nodePath.includes('nvm'),
85
+ nodePath,
86
+ nodeVersion
87
+ };
88
+ }
89
+
90
+ function createWrapperScript(targetDir) {
91
+ const nodeEnv = detectNodeEnvironment();
92
+ const wrapperPath = path.join(targetDir, 'run-mcp.sh');
93
+ const mcpClientPath = path.join(targetDir, 'node_modules', '@i18n-agent', 'mcp-client', 'mcp-client.js');
94
+
95
+ let wrapperContent;
96
+
97
+ if (nodeEnv.isNvm) {
98
+ // For nvm users, we need to set up the PATH properly
99
+ wrapperContent = `#!/bin/bash
100
+ # Wrapper script for i18n-agent MCP client (handles nvm environments)
101
+ export PATH="${path.dirname(nodeEnv.nodePath)}:$PATH"
102
+ cd "${targetDir}"
103
+ exec node "${mcpClientPath}"`;
104
+ } else {
105
+ // For system node installations
106
+ wrapperContent = `#!/bin/bash
107
+ # Wrapper script for i18n-agent MCP client
108
+ cd "${targetDir}"
109
+ exec node "${mcpClientPath}"`;
110
+ }
111
+
112
+ fs.writeFileSync(wrapperPath, wrapperContent, { mode: 0o755 });
113
+ return wrapperPath;
114
+ }
115
+
77
116
  function updateClaudeConfig(configPath) {
78
117
  let config = {};
79
118
 
@@ -92,16 +131,36 @@ function updateClaudeConfig(configPath) {
92
131
  config.mcpServers = {};
93
132
  }
94
133
 
95
- // Add i18n-agent configuration
96
- const mcpClientPath = path.resolve(__dirname, 'mcp-client.js');
97
- config.mcpServers["i18n-agent"] = {
98
- command: "node",
99
- args: [mcpClientPath],
100
- env: {
101
- MCP_SERVER_URL: "https://mcp.i18nagent.ai",
102
- API_KEY: ""
103
- }
104
- };
134
+ // Detect if we need a wrapper script (for nvm users)
135
+ const nodeEnv = detectNodeEnvironment();
136
+ const claudeDir = path.join(os.homedir(), '.claude');
137
+
138
+ if (nodeEnv.isNvm) {
139
+ // Create wrapper script for nvm users
140
+ console.log(' šŸ”§ Detected nvm environment, creating wrapper script...');
141
+ const wrapperPath = createWrapperScript(claudeDir);
142
+
143
+ config.mcpServers["i18n-agent"] = {
144
+ command: wrapperPath,
145
+ env: {
146
+ MCP_SERVER_URL: "https://mcp.i18nagent.ai",
147
+ API_KEY: ""
148
+ }
149
+ };
150
+ } else {
151
+ // Standard configuration for system node
152
+ const mcpClientPath = path.join(claudeDir, 'node_modules', '@i18n-agent', 'mcp-client', 'mcp-client.js');
153
+
154
+ config.mcpServers["i18n-agent"] = {
155
+ command: "node",
156
+ args: [mcpClientPath],
157
+ cwd: claudeDir,
158
+ env: {
159
+ MCP_SERVER_URL: "https://mcp.i18nagent.ai",
160
+ API_KEY: ""
161
+ }
162
+ };
163
+ }
105
164
 
106
165
  // Write updated config
107
166
  fs.mkdirSync(path.dirname(configPath), { recursive: true });
package/mcp-client.js CHANGED
@@ -5,7 +5,7 @@
5
5
  * Integrates with Claude Code CLI to provide translation capabilities
6
6
  */
7
7
 
8
- const MCP_CLIENT_VERSION = '1.1.2';
8
+ const MCP_CLIENT_VERSION = '1.4.1';
9
9
 
10
10
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
11
11
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
@@ -47,44 +47,41 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
47
47
  tools: [
48
48
  {
49
49
  name: 'translate_text',
50
- description: 'Translate text from one language to another with cultural context',
50
+ description: 'Translate text content with cultural adaptation using AI subagents. For large requests (>100 texts or >50,000 characters), returns a jobId for async processing. Use check_translation_status to monitor progress and download results.',
51
51
  inputSchema: {
52
52
  type: 'object',
53
53
  properties: {
54
54
  texts: {
55
55
  type: 'array',
56
56
  items: { type: 'string' },
57
- description: 'Array of texts to translate',
57
+ description: 'Array of source texts to translate (any language)',
58
58
  },
59
59
  targetLanguage: {
60
60
  type: 'string',
61
- description: 'Target language code (e.g., "es", "fr", "ja", "de") or full name (e.g., "Spanish", "French")',
61
+ description: 'Target language code (e.g., "es", "fr", "zh-CN")',
62
62
  },
63
63
  sourceLanguage: {
64
64
  type: 'string',
65
- description: 'Source language code (optional, auto-detected if not provided)',
66
- default: 'auto',
65
+ description: 'Source language code (auto-detected if not provided)',
67
66
  },
68
67
  targetAudience: {
69
68
  type: 'string',
70
- description: 'Target audience (e.g., "general", "technical", "casual", "formal")',
71
- default: 'general',
69
+ description: 'Target audience for the content (e.g., "software developers", "marketing professionals")',
72
70
  },
73
71
  industry: {
74
72
  type: 'string',
75
- description: 'Industry context (e.g., "technology", "healthcare", "finance", "education")',
76
- default: 'technology',
73
+ description: 'Industry context (e.g., "technology", "healthcare", "finance")',
77
74
  },
78
75
  region: {
79
76
  type: 'string',
80
77
  description: 'Specific region for localization (e.g., "Spain", "Mexico", "Brazil")',
81
78
  },
82
- notes: {
79
+ context: {
83
80
  type: 'string',
84
81
  description: 'Optional additional context or instructions for the translation (e.g., "Keep technical terms in English", "Use formal tone")',
85
82
  },
86
83
  },
87
- required: ['texts', 'targetLanguage'],
84
+ required: ['texts', 'targetLanguage', 'targetAudience', 'industry'],
88
85
  },
89
86
  },
90
87
  {
@@ -103,7 +100,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
103
100
  },
104
101
  {
105
102
  name: 'translate_file',
106
- description: 'Translate file content while preserving structure and format. Supports JSON, YAML, XML, CSV, TXT, MD, and other text files',
103
+ description: 'Translate file content while preserving structure and format. Supports JSON, YAML, XML, CSV, TXT, MD, and other text files. For large files (>100KB), returns a jobId for async processing. Use check_translation_status to monitor progress and download results.',
107
104
  inputSchema: {
108
105
  type: 'object',
109
106
  properties: {
@@ -153,7 +150,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
153
150
  type: 'string',
154
151
  description: 'Specific region for localization (e.g., "Spain", "Mexico", "Brazil")',
155
152
  },
156
- notes: {
153
+ context: {
157
154
  type: 'string',
158
155
  description: 'Optional additional context or instructions for the translation (e.g., "Keep technical terms in English", "Use formal tone")',
159
156
  },
@@ -166,10 +163,55 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
166
163
  description: 'Get remaining credits for the user and approximate word count available at 0.001 credits per word',
167
164
  inputSchema: {
168
165
  type: 'object',
169
- properties: {},
166
+ properties: {
167
+ apiKey: {
168
+ type: 'string',
169
+ description: 'API key to get credits for (optional, will use environment variable if not provided)',
170
+ },
171
+ },
170
172
  required: [],
171
173
  },
172
174
  },
175
+ /*
176
+ * ====================================================================
177
+ * TOKEN USAGE TOOLS - RESTRICTED FROM MCP CLIENT ACCESS
178
+ * ====================================================================
179
+ *
180
+ * HARD LIMIT POLICY: Token usage analytics tools are NOT available
181
+ * through MCP client interfaces for security and privacy reasons.
182
+ *
183
+ * Restricted Tools:
184
+ * - get_token_usage_stats
185
+ * - get_token_usage_by_translation
186
+ * - get_token_usage_by_api_key
187
+ *
188
+ * These tools contain sensitive usage data and billing information
189
+ * that should only be accessible through authenticated web interfaces,
190
+ * not through programmatic MCP access.
191
+ *
192
+ * If you need token usage data, please use:
193
+ * - Web dashboard at https://app.i18nagent.ai
194
+ * - Direct API calls with proper authentication
195
+ * - Admin interfaces (for internal use only)
196
+ *
197
+ * This restriction is enforced at the service level and cannot be
198
+ * bypassed through client modifications.
199
+ * ====================================================================
200
+ */
201
+ {
202
+ name: 'check_translation_status',
203
+ description: 'Check the status and progress of an async translation job. Returns progress percentage, elapsed time, and downloads completed translation results when finished.',
204
+ inputSchema: {
205
+ type: 'object',
206
+ properties: {
207
+ jobId: {
208
+ type: 'string',
209
+ description: 'The job ID returned from translate_text (>100 texts or >50,000 chars) or translate_file (>100KB) for async processing',
210
+ },
211
+ },
212
+ required: ['jobId'],
213
+ },
214
+ },
173
215
  ],
174
216
  };
175
217
  });
@@ -192,6 +234,15 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
192
234
  case 'get_credits':
193
235
  return await handleGetCredits(args);
194
236
 
237
+ /*
238
+ * TOKEN USAGE TOOLS - BLOCKED FOR SECURITY
239
+ * These cases are intentionally removed to prevent access to sensitive analytics data
240
+ * through MCP interfaces. See tool definition comments above for details.
241
+ */
242
+
243
+ case 'check_translation_status':
244
+ return await handleCheckTranslationStatus(args);
245
+
195
246
  default:
196
247
  throw new McpError(
197
248
  ErrorCode.MethodNotFound,
@@ -200,15 +251,34 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
200
251
  }
201
252
  } catch (error) {
202
253
  console.error(`Error executing tool ${name}:`, error);
254
+
255
+ // Check if error is about API key or credit issues
256
+ const errorMsg = error.message || '';
257
+ const isAuthError = errorMsg.toLowerCase().includes('api key') ||
258
+ errorMsg.toLowerCase().includes('api_key') ||
259
+ errorMsg.toLowerCase().includes('unauthorized') ||
260
+ errorMsg.includes('(401)');
261
+ const isCreditError = errorMsg.toLowerCase().includes('credit') ||
262
+ errorMsg.toLowerCase().includes('quota') ||
263
+ errorMsg.toLowerCase().includes('limit exceeded') ||
264
+ errorMsg.includes('(402)');
265
+
266
+ let finalErrorMsg = error.message;
267
+
268
+ // Add retry guidance for non-auth/credit errors (but not for status checking)
269
+ if (!isAuthError && !isCreditError && name !== 'check_translation_status') {
270
+ finalErrorMsg = `${error.message}. Please retry with smaller chunks or split the content into multiple requests.`;
271
+ }
272
+
203
273
  throw new McpError(
204
274
  ErrorCode.InternalError,
205
- `Tool execution failed: ${error.message}`
275
+ `Tool execution failed: ${finalErrorMsg}`
206
276
  );
207
277
  }
208
278
  });
209
279
 
210
280
  async function handleTranslateText(args) {
211
- const { texts, targetLanguage, sourceLanguage, targetAudience = 'general', industry = 'technology', region, notes } = args;
281
+ const { texts, targetLanguage, sourceLanguage, targetAudience = 'general', industry = 'technology', region, context } = args;
212
282
 
213
283
  if (!texts || !Array.isArray(texts) || texts.length === 0) {
214
284
  throw new Error('texts must be a non-empty array');
@@ -217,18 +287,26 @@ async function handleTranslateText(args) {
217
287
  if (!targetLanguage) {
218
288
  throw new Error('targetLanguage is required');
219
289
  }
290
+
291
+ if (!targetAudience) {
292
+ throw new Error('targetAudience is required');
293
+ }
294
+
295
+ if (!industry) {
296
+ throw new Error('industry is required');
297
+ }
220
298
 
221
299
  // Check if this is a large translation request
222
300
  const totalChars = texts.reduce((sum, text) => sum + text.length, 0);
223
301
  const isLargeRequest = texts.length > 100 || totalChars > 50000;
224
302
 
225
- // Use MCP JSON-RPC protocol for translate_content
303
+ // Use MCP JSON-RPC protocol for translate_text
226
304
  const mcpRequest = {
227
305
  jsonrpc: '2.0',
228
306
  id: Date.now(),
229
307
  method: 'tools/call',
230
308
  params: {
231
- name: 'translate_content',
309
+ name: 'translate_text',
232
310
  arguments: {
233
311
  apiKey: API_KEY,
234
312
  texts: texts,
@@ -237,7 +315,7 @@ async function handleTranslateText(args) {
237
315
  targetAudience: targetAudience,
238
316
  industry: industry,
239
317
  region: region,
240
- notes: notes,
318
+ context: context,
241
319
  }
242
320
  }
243
321
  };
@@ -251,7 +329,19 @@ async function handleTranslateText(args) {
251
329
  });
252
330
 
253
331
  if (response.data.error) {
254
- throw new Error(`Translation service error: ${response.data.error.message || response.data.error}`);
332
+ const errorMsg = response.data.error.message || response.data.error;
333
+ const isAuthError = errorMsg.toString().toLowerCase().includes('api key') ||
334
+ errorMsg.toString().toLowerCase().includes('api_key') ||
335
+ errorMsg.toString().toLowerCase().includes('unauthorized');
336
+ const isCreditError = errorMsg.toString().toLowerCase().includes('credit') ||
337
+ errorMsg.toString().toLowerCase().includes('quota') ||
338
+ errorMsg.toString().toLowerCase().includes('limit exceeded');
339
+
340
+ let finalErrorMsg = `Translation service error: ${errorMsg}`;
341
+ if (!isAuthError && !isCreditError) {
342
+ finalErrorMsg += `. Please retry with a smaller text chunk or split the content into multiple smaller requests.`;
343
+ }
344
+ throw new Error(finalErrorMsg);
255
345
  }
256
346
 
257
347
  // Check if we got an async job response
@@ -309,22 +399,22 @@ async function handleTranslateText(args) {
309
399
 
310
400
  // Handle 401 unauthorized - invalid API key
311
401
  if (error.response?.status === 401) {
312
- const errorDetails = error.response.data?.message || error.response.data?.result?.content?.[0]?.text || error.message;
313
- throw new Error(`āŒ Invalid API key (401)\nDetails: ${errorDetails}\nPlease check your API key at https://app.i18nagent.ai\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_text]`);
402
+ const authErrorDetails = error.response.data?.message || error.response.data?.result?.content?.[0]?.text || error.message;
403
+ throw new Error(`āŒ Invalid API key (401)\nDetails: ${authErrorDetails}\nPlease check your API key at https://app.i18nagent.ai\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_text]`);
314
404
  }
315
405
 
316
406
  // Handle 402 payment required with user-friendly message
317
407
  if (error.response?.status === 402) {
318
- const errorDetails = error.response.data?.message || error.response.data?.result?.content?.[0]?.text || error.message;
319
- throw new Error(`āš ļø Insufficient credits (402)\nDetails: ${errorDetails}\nPlease top up at https://app.i18nagent.ai\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_text]`);
408
+ const creditErrorDetails = error.response.data?.message || error.response.data?.result?.content?.[0]?.text || error.message;
409
+ throw new Error(`āš ļø Insufficient credits (402)\nDetails: ${creditErrorDetails}\nPlease top up at https://app.i18nagent.ai\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_text]`);
320
410
  }
321
411
 
322
412
  // Check if it's a large content issue
323
413
  const totalChars = texts.reduce((sum, text) => sum + text.length, 0);
324
414
  if (error.response?.status === 413 ||
325
415
  (error.response?.status === 503 && totalChars > 50000)) {
326
- const errorDetails = error.response?.data?.message || error.response?.data?.result?.content?.[0]?.text || error.message;
327
- const errorMsg = `Content too large (${totalChars} characters, ${texts.length} texts)\nStatus: ${error.response?.status}\nDetails: ${errorDetails}\n\nPlease break into smaller batches:\n• Split into batches of 50-100 texts\n• Keep total size under 50KB per request\n• Process sequentially to avoid overload\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_text]`;
416
+ const sizeErrorDetails = error.response?.data?.message || error.response?.data?.result?.content?.[0]?.text || error.message;
417
+ const errorMsg = `Content too large (${totalChars} characters, ${texts.length} texts)\nStatus: ${error.response?.status}\nDetails: ${sizeErrorDetails}\n\nPlease break into smaller batches:\n• Split into batches of 50-100 texts\n• Keep total size under 50KB per request\n• Process sequentially to avoid overload\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_text]`;
328
418
  throw new Error(errorMsg);
329
419
  }
330
420
 
@@ -335,18 +425,18 @@ async function handleTranslateText(args) {
335
425
  (error.response?.status === 503 && totalChars <= 50000) ||
336
426
  error.response?.status === 502 ||
337
427
  error.response?.status === 504) {
338
- const errorDetails = error.response?.data?.result?.content?.[0]?.text ||
339
- error.response?.data?.error?.message ||
340
- error.message;
341
- const debugInfo = `Code: ${error.code || 'N/A'}\nStatus: ${error.response?.status || 'N/A'}\nStatusText: ${error.response?.statusText || 'N/A'}\nDetails: ${errorDetails}\nURL: ${error.config?.url || 'N/A'}\nTimestamp: ${new Date().toISOString()}`;
428
+ const serviceErrorDetails = error.response?.data?.result?.content?.[0]?.text ||
429
+ error.response?.data?.error?.message ||
430
+ error.message;
431
+ const debugInfo = `Code: ${error.code || 'N/A'}\nStatus: ${error.response?.status || 'N/A'}\nStatusText: ${error.response?.statusText || 'N/A'}\nDetails: ${serviceErrorDetails}\nURL: ${error.config?.url || 'N/A'}\nTimestamp: ${new Date().toISOString()}`;
342
432
  throw new Error(`Translation service error\n${debugInfo}\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_text]`);
343
433
  }
344
434
 
345
435
  // For other errors, include all debug info in the error message
346
- const errorDetails = error.response?.data?.result?.content?.[0]?.text ||
347
- error.response?.data?.error?.message ||
348
- error.message;
349
- const debugInfo = `Status: ${error.response?.status || 'N/A'}\nStatusText: ${error.response?.statusText || 'N/A'}\nDetails: ${errorDetails}\nTimestamp: ${new Date().toISOString()}`;
436
+ const generalErrorDetails = error.response?.data?.result?.content?.[0]?.text ||
437
+ error.response?.data?.error?.message ||
438
+ error.message;
439
+ const debugInfo = `Status: ${error.response?.status || 'N/A'}\nStatusText: ${error.response?.statusText || 'N/A'}\nDetails: ${generalErrorDetails}\nTimestamp: ${new Date().toISOString()}`;
350
440
  throw new Error(`Error\n${debugInfo}\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_text]`);
351
441
  }
352
442
  }
@@ -354,73 +444,120 @@ async function handleTranslateText(args) {
354
444
  async function handleListLanguages(args) {
355
445
  const { includeQuality = true } = args;
356
446
 
357
- // Language support matrix based on GPT-OSS analysis
358
- const languages = {
359
- 'Tier 1 - Production Ready (Excellent Quality 80-90%)': {
360
- 'en': 'English',
361
- 'es': 'Spanish',
362
- 'fr': 'French',
363
- 'de': 'German',
364
- 'it': 'Italian',
365
- 'pt': 'Portuguese',
366
- 'nl': 'Dutch',
367
- },
368
- 'Tier 2 - Production Viable (Good Quality 50-75%)': {
369
- 'ru': 'Russian',
370
- 'zh-CN': 'Chinese (Simplified)',
371
- 'ja': 'Japanese',
372
- 'ko': 'Korean',
373
- 'ar': 'Arabic',
374
- 'he': 'Hebrew',
375
- 'hi': 'Hindi',
376
- 'pl': 'Polish',
377
- 'cs': 'Czech',
378
- },
379
- 'Tier 3 - Basic Support (Use with Caution 20-50%)': {
380
- 'zh-TW': 'Chinese (Traditional)',
381
- 'th': 'Thai',
382
- 'vi': 'Vietnamese',
383
- 'sv': 'Swedish',
384
- 'da': 'Danish',
385
- 'no': 'Norwegian',
386
- 'fi': 'Finnish',
387
- 'tr': 'Turkish',
388
- 'hu': 'Hungarian',
389
- },
447
+ // Use MCP JSON-RPC protocol for list_supported_languages
448
+ const mcpRequest = {
449
+ jsonrpc: '2.0',
450
+ id: Date.now(),
451
+ method: 'tools/call',
452
+ params: {
453
+ name: 'list_supported_languages',
454
+ arguments: { includeQuality }
455
+ }
390
456
  };
391
-
392
- let content = 'šŸŒ Supported Languages\n';
393
- content += '===================\n\n';
394
457
 
395
- if (includeQuality) {
396
- for (const [tier, langs] of Object.entries(languages)) {
397
- content += `## ${tier}\n`;
398
- for (const [code, name] of Object.entries(langs)) {
399
- content += `- \`${code}\`: ${name}\n`;
400
- }
401
- content += '\n';
458
+ try {
459
+ const response = await axios.post(MCP_SERVER_URL, mcpRequest, {
460
+ headers: {
461
+ 'Content-Type': 'application/json',
462
+ },
463
+ timeout: 30000,
464
+ });
465
+
466
+ if (response.data.error) {
467
+ const errorMsg = response.data.error.message || response.data.error;
468
+ throw new Error(`Languages service error: ${errorMsg}`);
402
469
  }
403
- } else {
404
- const allLanguages = Object.values(languages).reduce((acc, tier) => ({ ...acc, ...tier }), {});
405
- for (const [code, name] of Object.entries(allLanguages)) {
406
- content += `- \`${code}\`: ${name}\n`;
470
+
471
+ const result = response.data.result;
472
+ if (result && result.content && result.content[0]) {
473
+ const textContent = result.content[0].text;
474
+
475
+ // Try to parse as JSON for structured data
476
+ try {
477
+ const parsed = JSON.parse(textContent);
478
+
479
+ // Format the language data nicely
480
+ let content = 'šŸŒ Supported Languages\n';
481
+ content += '===================\n\n';
482
+
483
+ if (parsed.languages && Array.isArray(parsed.languages)) {
484
+ if (includeQuality) {
485
+ // Group by quality levels
486
+ const highQuality = parsed.languages.filter(lang => lang.quality === 'high');
487
+ const mediumQuality = parsed.languages.filter(lang => lang.quality === 'medium');
488
+
489
+ if (highQuality.length > 0) {
490
+ content += '## High Quality (Recommended for Production)\n';
491
+ highQuality.forEach(lang => {
492
+ content += `- \`${lang.code}\`: ${lang.name}\n`;
493
+ });
494
+ content += '\n';
495
+ }
496
+
497
+ if (mediumQuality.length > 0) {
498
+ content += '## Medium Quality (Good with Review)\n';
499
+ mediumQuality.forEach(lang => {
500
+ content += `- \`${lang.code}\`: ${lang.name}\n`;
501
+ });
502
+ content += '\n';
503
+ }
504
+ } else {
505
+ parsed.languages.forEach(lang => {
506
+ content += `- \`${lang.code}\`: ${lang.name}\n`;
507
+ });
508
+ content += '\n';
509
+ }
510
+
511
+ content += `šŸ“Š **Total Languages**: ${parsed.total || parsed.languages.length}\n\n`;
512
+
513
+ if (parsed.qualityLevels) {
514
+ content += `Quality Breakdown:\n`;
515
+ content += `• High Quality: ${parsed.qualityLevels.high} languages\n`;
516
+ content += `• Medium Quality: ${parsed.qualityLevels.medium} languages\n\n`;
517
+ }
518
+ }
519
+
520
+ content += 'šŸ’” Usage Tips:\n';
521
+ content += '- Use language codes (e.g., "es") or full names (e.g., "Spanish")\n';
522
+ content += '- High quality languages are recommended for production use\n';
523
+ content += '- Medium quality languages work well with human review\n';
524
+
525
+ return {
526
+ content: [
527
+ {
528
+ type: 'text',
529
+ text: content,
530
+ },
531
+ ],
532
+ };
533
+ } catch {
534
+ // Return raw text if not JSON
535
+ return result;
536
+ }
407
537
  }
538
+
539
+ return result;
540
+ } catch (error) {
541
+ console.error('List languages error:', error);
542
+
543
+ // Fallback to basic language list if service is unavailable
544
+ const fallbackContent = `šŸŒ Supported Languages (Fallback)\n` +
545
+ `==================================\n\n` +
546
+ `Service temporarily unavailable. Here are the main supported languages:\n\n` +
547
+ `• \`es\`: Spanish\n• \`fr\`: French\n• \`de\`: German\n• \`it\`: Italian\n` +
548
+ `• \`pt\`: Portuguese\n• \`ja\`: Japanese\n• \`ko\`: Korean\n• \`zh\`: Chinese\n` +
549
+ `• \`ru\`: Russian\n• \`ar\`: Arabic\n• \`hi\`: Hindi\n• \`nl\`: Dutch\n\n` +
550
+ `Error: ${error.message}`;
551
+
552
+ return {
553
+ content: [
554
+ {
555
+ type: 'text',
556
+ text: fallbackContent,
557
+ },
558
+ ],
559
+ };
408
560
  }
409
-
410
- content += '\nšŸ’” Usage Tips:\n';
411
- content += '- Use language codes (e.g., "es") or full names (e.g., "Spanish")\n';
412
- content += '- Tier 1 languages are recommended for production use\n';
413
- content += '- Tier 2 languages work well with human review\n';
414
- content += '- Tier 3 languages provide basic translation quality\n';
415
-
416
- return {
417
- content: [
418
- {
419
- type: 'text',
420
- text: content,
421
- },
422
- ],
423
- };
424
561
  }
425
562
 
426
563
  async function handleTranslateFile(args) {
@@ -435,7 +572,7 @@ async function handleTranslateFile(args) {
435
572
  outputFormat = 'same',
436
573
  sourceLanguage,
437
574
  region,
438
- notes
575
+ context
439
576
  } = args;
440
577
 
441
578
  if (!filePath && !fileContent) {
@@ -477,7 +614,7 @@ async function handleTranslateFile(args) {
477
614
  targetAudience,
478
615
  industry,
479
616
  region,
480
- notes,
617
+ context,
481
618
  preserveKeys,
482
619
  outputFormat
483
620
  }
@@ -493,7 +630,19 @@ async function handleTranslateFile(args) {
493
630
  });
494
631
 
495
632
  if (response.data.error) {
496
- throw new Error(`Translation service error: ${response.data.error.message || response.data.error}`);
633
+ const errorMsg = response.data.error.message || response.data.error;
634
+ const isAuthError = errorMsg.toString().toLowerCase().includes('api key') ||
635
+ errorMsg.toString().toLowerCase().includes('api_key') ||
636
+ errorMsg.toString().toLowerCase().includes('unauthorized');
637
+ const isCreditError = errorMsg.toString().toLowerCase().includes('credit') ||
638
+ errorMsg.toString().toLowerCase().includes('quota') ||
639
+ errorMsg.toString().toLowerCase().includes('limit exceeded');
640
+
641
+ let finalErrorMsg = `Translation service error: ${errorMsg}`;
642
+ if (!isAuthError && !isCreditError) {
643
+ finalErrorMsg += `. Please retry with a smaller text chunk or split the content into multiple smaller requests.`;
644
+ }
645
+ throw new Error(finalErrorMsg);
497
646
  }
498
647
 
499
648
  // Check if we got an async job response
@@ -543,24 +692,24 @@ async function handleTranslateFile(args) {
543
692
 
544
693
  // Handle 401 unauthorized - invalid API key
545
694
  if (error.response?.status === 401) {
546
- const errorDetails = error.response.data?.message || error.response.data?.result?.content?.[0]?.text || error.message;
547
- throw new Error(`āŒ Invalid API key (401)\nDetails: ${errorDetails}\nPlease check your API key at https://app.i18nagent.ai\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_file]`);
695
+ const fileAuthErrorDetails = error.response.data?.message || error.response.data?.result?.content?.[0]?.text || error.message;
696
+ throw new Error(`āŒ Invalid API key (401)\nDetails: ${fileAuthErrorDetails}\nPlease check your API key at https://app.i18nagent.ai\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_file]`);
548
697
  }
549
698
 
550
699
  // Handle 402 payment required with user-friendly message
551
700
  if (error.response?.status === 402) {
552
- const errorDetails = error.response.data?.message || error.response.data?.result?.content?.[0]?.text || error.message;
553
- throw new Error(`āš ļø Insufficient credits (402)\nDetails: ${errorDetails}\nPlease top up at https://app.i18nagent.ai\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_file]`);
701
+ const fileCreditErrorDetails = error.response.data?.message || error.response.data?.result?.content?.[0]?.text || error.message;
702
+ throw new Error(`āš ļø Insufficient credits (402)\nDetails: ${fileCreditErrorDetails}\nPlease top up at https://app.i18nagent.ai\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_file]`);
554
703
  }
555
704
 
556
705
  // Check if it's a timeout issue (45-second server timeout) or large file issue
557
- const errorDetails = error.response?.data?.result?.content?.[0]?.text ||
558
- error.response?.data?.error?.message ||
559
- error.message;
706
+ const timeoutErrorDetails = error.response?.data?.result?.content?.[0]?.text ||
707
+ error.response?.data?.error?.message ||
708
+ error.message;
560
709
 
561
710
  if (error.response?.status === 413 ||
562
711
  (error.response?.status === 503 && content.length > 50000) ||
563
- (error.response?.status === 503 && errorDetails.includes('timeout after 45 seconds'))) {
712
+ (error.response?.status === 503 && timeoutErrorDetails.includes('timeout after 45 seconds'))) {
564
713
  const errorMsg = `File too large or complex (${content.length} characters)\n\nThe server has a 45-second timeout. Your file requires more processing time.\n\nPlease break into smaller chunks:\n• Split files over 50KB into multiple parts\n• Translate sections separately (e.g., split by top-level keys for JSON)\n• Use translate_text for batches of 50-100 strings\n• Each chunk should process in under 45 seconds\n\nAlternatively, wait for async job support (coming soon).\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_file]`;
565
714
  throw new Error(errorMsg);
566
715
  }
@@ -572,18 +721,18 @@ async function handleTranslateFile(args) {
572
721
  (error.response?.status === 503 && content.length <= 50000) ||
573
722
  error.response?.status === 502 ||
574
723
  error.response?.status === 504) {
575
- const errorDetails = error.response?.data?.result?.content?.[0]?.text ||
576
- error.response?.data?.error?.message ||
577
- error.message;
578
- const debugInfo = `Code: ${error.code || 'N/A'}\nStatus: ${error.response?.status || 'N/A'}\nStatusText: ${error.response?.statusText || 'N/A'}\nDetails: ${errorDetails}\nURL: ${error.config?.url || 'N/A'}\nTimestamp: ${new Date().toISOString()}`;
724
+ const serviceErrorDetails = error.response?.data?.result?.content?.[0]?.text ||
725
+ error.response?.data?.error?.message ||
726
+ error.message;
727
+ const debugInfo = `Code: ${error.code || 'N/A'}\nStatus: ${error.response?.status || 'N/A'}\nStatusText: ${error.response?.statusText || 'N/A'}\nDetails: ${serviceErrorDetails}\nURL: ${error.config?.url || 'N/A'}\nTimestamp: ${new Date().toISOString()}`;
579
728
  throw new Error(`Translation service error\n${debugInfo}\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_file]`);
580
729
  }
581
730
 
582
731
  // For other errors, include all debug info in the error message
583
- const errorDetails = error.response?.data?.result?.content?.[0]?.text ||
584
- error.response?.data?.error?.message ||
585
- error.message;
586
- const debugInfo = `Status: ${error.response?.status || 'N/A'}\nStatusText: ${error.response?.statusText || 'N/A'}\nDetails: ${errorDetails}\nTimestamp: ${new Date().toISOString()}`;
732
+ const finalErrorDetails = error.response?.data?.result?.content?.[0]?.text ||
733
+ error.response?.data?.error?.message ||
734
+ error.message;
735
+ const debugInfo = `Status: ${error.response?.status || 'N/A'}\nStatusText: ${error.response?.statusText || 'N/A'}\nDetails: ${finalErrorDetails}\nTimestamp: ${new Date().toISOString()}`;
587
736
  throw new Error(`Error\n${debugInfo}\n[MCP v${MCP_CLIENT_VERSION}/STDIO/translate_file]`);
588
737
  }
589
738
  }
@@ -636,7 +785,19 @@ async function pollTranslationJob(jobId, estimatedTime) {
636
785
  });
637
786
 
638
787
  if (response.data.error) {
639
- throw new Error(`Status check error: ${response.data.error.message || response.data.error}`);
788
+ const errorMsg = response.data.error.message || response.data.error;
789
+ const isAuthError = errorMsg.toString().toLowerCase().includes('api key') ||
790
+ errorMsg.toString().toLowerCase().includes('api_key') ||
791
+ errorMsg.toString().toLowerCase().includes('unauthorized');
792
+ const isCreditError = errorMsg.toString().toLowerCase().includes('credit') ||
793
+ errorMsg.toString().toLowerCase().includes('quota') ||
794
+ errorMsg.toString().toLowerCase().includes('limit exceeded');
795
+
796
+ let finalErrorMsg = `Status check error: ${errorMsg}`;
797
+ if (!isAuthError && !isCreditError) {
798
+ finalErrorMsg += `. Please retry with a smaller chunk or split the content into multiple requests.`;
799
+ }
800
+ throw new Error(finalErrorMsg);
640
801
  }
641
802
 
642
803
  const result = response.data.result;
@@ -646,7 +807,19 @@ async function pollTranslationJob(jobId, estimatedTime) {
646
807
  if (status.status === 'completed') {
647
808
  return status.result;
648
809
  } else if (status.status === 'failed') {
649
- throw new Error(`Translation failed: ${status.error}`);
810
+ const errorMsg = status.error;
811
+ const isAuthError = errorMsg.toString().toLowerCase().includes('api key') ||
812
+ errorMsg.toString().toLowerCase().includes('api_key') ||
813
+ errorMsg.toString().toLowerCase().includes('unauthorized');
814
+ const isCreditError = errorMsg.toString().toLowerCase().includes('credit') ||
815
+ errorMsg.toString().toLowerCase().includes('quota') ||
816
+ errorMsg.toString().toLowerCase().includes('limit exceeded');
817
+
818
+ let finalErrorMsg = `Translation failed: ${errorMsg}`;
819
+ if (!isAuthError && !isCreditError) {
820
+ finalErrorMsg += `. Please retry with a smaller chunk or split the content into multiple requests.`;
821
+ }
822
+ throw new Error(finalErrorMsg);
650
823
  }
651
824
 
652
825
  // Still processing - continue polling
@@ -658,43 +831,86 @@ async function pollTranslationJob(jobId, estimatedTime) {
658
831
  }
659
832
  }
660
833
 
661
- throw new Error(`Translation job ${jobId} timed out after ${maxPolls * pollInterval / 1000} seconds`);
834
+ throw new Error(`Translation job ${jobId} timed out after ${maxPolls * pollInterval / 1000} seconds. Please retry with a smaller chunk or split the content into multiple requests.`);
662
835
  }
663
836
 
664
837
  async function handleGetCredits(args) {
838
+ const { apiKey } = args;
839
+ const creditsApiKey = apiKey || API_KEY;
840
+
841
+ // Use MCP JSON-RPC protocol for get_credits
842
+ const mcpRequest = {
843
+ jsonrpc: '2.0',
844
+ id: Date.now(),
845
+ method: 'tools/call',
846
+ params: {
847
+ name: 'get_credits',
848
+ arguments: {
849
+ apiKey: creditsApiKey
850
+ }
851
+ }
852
+ };
853
+
665
854
  try {
666
- // Get team info first using the API key
667
- const teamResponse = await axios.get(`https://app.i18nagent.ai/api/teams/by-api-key/${API_KEY}`, {
855
+ const response = await axios.post(MCP_SERVER_URL, mcpRequest, {
668
856
  headers: {
669
- 'Content-Type': 'application/json'
857
+ 'Content-Type': 'application/json',
670
858
  },
671
- timeout: 10000
859
+ timeout: 30000,
672
860
  });
673
861
 
674
- if (!teamResponse.data.success) {
675
- throw new Error(teamResponse.data.error || 'Failed to get team information');
862
+ if (response.data.error) {
863
+ const errorMsg = response.data.error.message || response.data.error;
864
+ const isAuthError = errorMsg.toString().toLowerCase().includes('api key') ||
865
+ errorMsg.toString().toLowerCase().includes('api_key') ||
866
+ errorMsg.toString().toLowerCase().includes('unauthorized');
867
+ const isCreditError = errorMsg.toString().toLowerCase().includes('credit') ||
868
+ errorMsg.toString().toLowerCase().includes('quota') ||
869
+ errorMsg.toString().toLowerCase().includes('limit exceeded');
870
+
871
+ let finalErrorMsg = `Credits service error: ${errorMsg}`;
872
+ if (!isAuthError && !isCreditError) {
873
+ finalErrorMsg += `. Please check the service status or contact support.`;
874
+ }
875
+ throw new Error(finalErrorMsg);
676
876
  }
677
877
 
678
- const teamInfo = teamResponse.data.data;
679
- const approximateWordsAvailable = Math.floor(teamInfo.credits * 1000); // 0.001 credits per word
878
+ const result = response.data.result;
879
+ if (result && result.content && result.content[0]) {
880
+ const textContent = result.content[0].text;
881
+
882
+ // Try to parse as JSON for structured data
883
+ try {
884
+ const parsed = JSON.parse(textContent);
885
+ const approximateWordsAvailable = parsed.credits ? Math.floor(parsed.credits * 1000) : 0;
886
+
887
+ return {
888
+ content: [
889
+ {
890
+ type: 'text',
891
+ text: `šŸ’° **Credits Information**\n\n` +
892
+ `šŸ’³ **Credits Remaining**: ${parsed.credits || 'N/A'}\n` +
893
+ `šŸ“ **Approximate Words Available**: ${approximateWordsAvailable.toLocaleString()}\n` +
894
+ `šŸ’µ **Cost per Word**: 0.001 credits\n` +
895
+ `ā° **Last Updated**: ${new Date().toLocaleString()}\n\n` +
896
+ `Note: Word count is approximate and may vary based on actual content complexity and translation requirements.`,
897
+ },
898
+ ],
899
+ };
900
+ } catch {
901
+ // Return raw text if not JSON
902
+ return result;
903
+ }
904
+ }
680
905
 
681
- return {
682
- content: [
683
- {
684
- type: 'text',
685
- text: `šŸ’° **Credits Information**
686
-
687
- šŸ¢ **Team**: ${teamInfo.name}
688
- šŸ’³ **Credits Remaining**: ${teamInfo.credits}
689
- šŸ“ **Approximate Words Available**: ${approximateWordsAvailable.toLocaleString()}
690
- šŸ’µ **Cost per Word**: 0.001 credits
691
- ā° **Last Updated**: ${new Date().toLocaleString()}
692
-
693
- Note: Word count is approximate and may vary based on actual content complexity and translation requirements.`,
694
- },
695
- ],
696
- };
906
+ return result;
697
907
  } catch (error) {
908
+ // Handle 401 unauthorized
909
+ if (error.response?.status === 401) {
910
+ const creditsAuthErrorDetails = error.response.data?.message || error.response.data?.result?.content?.[0]?.text || error.message;
911
+ throw new Error(`āŒ Invalid API key (401)\nDetails: ${creditsAuthErrorDetails}\nPlease check your API key at https://app.i18nagent.ai\n[MCP v${MCP_CLIENT_VERSION}/STDIO/get_credits]`);
912
+ }
913
+
698
914
  console.error('Credits check error:', error);
699
915
  throw new Error(`Unable to check credits: ${error.message}`);
700
916
  }
@@ -1027,6 +1243,89 @@ function getCodeBlockLanguage(fileType) {
1027
1243
  return languageMap[fileType] || 'text';
1028
1244
  }
1029
1245
 
1246
+ /*
1247
+ * =====================================================================
1248
+ * TOKEN USAGE HANDLERS - INTENTIONALLY REMOVED FOR SECURITY
1249
+ * =====================================================================
1250
+ *
1251
+ * The following handler functions have been permanently removed from
1252
+ * the MCP client to prevent unauthorized access to sensitive analytics:
1253
+ *
1254
+ * - handleGetTokenUsageStats()
1255
+ * - handleGetTokenUsageByTranslation()
1256
+ * - handleGetTokenUsageByApiKey()
1257
+ *
1258
+ * SECURITY RATIONALE:
1259
+ * Token usage data contains sensitive billing and usage information
1260
+ * that should not be accessible through programmatic MCP clients.
1261
+ * This data includes:
1262
+ * - Detailed usage patterns and costs
1263
+ * - API key performance metrics
1264
+ * - Translation volume analytics
1265
+ * - Billing-related information
1266
+ *
1267
+ * ACCESS ALTERNATIVES:
1268
+ * - Use the web dashboard at https://app.i18nagent.ai
1269
+ * - Contact support for usage reports
1270
+ * - Use admin interfaces (internal only)
1271
+ *
1272
+ * This restriction is enforced as a hard security boundary and
1273
+ * cannot be bypassed through client modifications.
1274
+ * =====================================================================
1275
+ */
1276
+
1277
+ // Handler for checking translation status
1278
+ async function handleCheckTranslationStatus(args) {
1279
+ const { jobId } = args;
1280
+
1281
+ if (!jobId) {
1282
+ throw new Error('jobId is required');
1283
+ }
1284
+
1285
+ const mcpRequest = {
1286
+ jsonrpc: '2.0',
1287
+ id: Date.now(),
1288
+ method: 'tools/call',
1289
+ params: {
1290
+ name: 'check_translation_status',
1291
+ arguments: { jobId }
1292
+ }
1293
+ };
1294
+
1295
+ try {
1296
+ const response = await axios.post(MCP_SERVER_URL, mcpRequest, {
1297
+ headers: { 'Content-Type': 'application/json' },
1298
+ timeout: 30000
1299
+ });
1300
+
1301
+ if (response.data.error) {
1302
+ throw new Error(`Translation status error: ${response.data.error.message || response.data.error}`);
1303
+ }
1304
+
1305
+ return response.data.result;
1306
+ } catch (error) {
1307
+ console.error('Check translation status error:', error);
1308
+
1309
+ // Handle 503 service unavailable
1310
+ if (error.response?.status === 503) {
1311
+ throw new Error(`Translation service is temporarily unavailable (503). Please try again in a few moments.`);
1312
+ }
1313
+
1314
+ // Handle 404 not found
1315
+ if (error.response?.status === 404) {
1316
+ throw new Error(`Translation job ${jobId} not found. The job may have expired or the ID is incorrect.`);
1317
+ }
1318
+
1319
+ // Handle timeout
1320
+ if (error.code === 'ECONNABORTED') {
1321
+ throw new Error(`Status check timed out. The service may be experiencing high load. Please try again.`);
1322
+ }
1323
+
1324
+ // Generic error
1325
+ throw new Error(`Unable to check translation status: ${error.message}`);
1326
+ }
1327
+ }
1328
+
1030
1329
  // Start the server
1031
1330
  async function main() {
1032
1331
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@i18n-agent/mcp-client",
3
- "version": "1.1.3",
4
- "description": "MCP client for i18n-agent translation service - supports Claude, Cursor, VS Code, and other AI IDEs",
3
+ "version": "1.4.1",
4
+ "description": "MCP client for i18n-agent translation service with async job support and enhanced progress tracking - supports Claude, Cursor, VS Code, and other AI IDEs",
5
5
  "main": "mcp-client.js",
6
6
  "bin": {
7
7
  "i18n-agent-install": "install.js"