@i18n-agent/mcp-client 1.0.2 → 1.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 (3) hide show
  1. package/README.md +34 -12
  2. package/mcp-client.js +192 -48
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -9,6 +9,9 @@ Professional translation service client for Claude, Cursor, VS Code, and other A
9
9
 
10
10
  - **🎯 Smart Translation**: Context-aware translations with cultural adaptation
11
11
  - **📁 File Translation**: Support for JSON, YAML, CSV, XML, Markdown, and more
12
+ - **⚡ Large File Support**: Async processing for files >50KB with progress tracking
13
+ - **🔄 Timeout Improvements**: Extended timeouts (5-10 min) for large translations
14
+ - **📊 Progress Tracking**: Real-time job status and completion monitoring
12
15
  - **💰 Credit Tracking**: Real-time credit balance and word count estimates
13
16
  - **🌐 30+ Languages**: Multi-tier language support with quality ratings
14
17
  - **🔧 Easy Setup**: One-command installation for major AI IDEs
@@ -77,36 +80,45 @@ List supported languages with quality ratings
77
80
 
78
81
  ## 🌐 Language Support
79
82
 
80
- ### Tier 1 - Production Ready (80-90% Quality)
83
+ ### Tier 1 - Excellent Quality
81
84
  - **en**: English
82
- - **es**: Spanish
83
85
  - **fr**: French
84
86
  - **de**: German
87
+ - **es**: Spanish
85
88
  - **it**: Italian
86
89
  - **pt**: Portuguese
87
- - **nl**: Dutch
88
-
89
- ### Tier 2 - Production Viable (50-75% Quality)
90
90
  - **ru**: Russian
91
- - **zh-CN**: Chinese (Simplified)
92
91
  - **ja**: Japanese
93
92
  - **ko**: Korean
93
+ - **zh-CN**: Chinese (Simplified)
94
+
95
+ ### Tier 2 - High Quality
96
+ - **nl**: Dutch
97
+ - **pl**: Polish
98
+ - **cs**: Czech
94
99
  - **ar**: Arabic
95
100
  - **he**: Hebrew
96
101
  - **hi**: Hindi
97
- - **pl**: Polish
98
- - **cs**: Czech
99
-
100
- ### Tier 3 - Basic Support (20-50% Quality)
101
102
  - **zh-TW**: Chinese (Traditional)
102
- - **th**: Thai
103
- - **vi**: Vietnamese
104
103
  - **sv**: Swedish
105
104
  - **da**: Danish
106
105
  - **no**: Norwegian
107
106
  - **fi**: Finnish
107
+
108
+ ### Tier 3 - Good Quality
108
109
  - **tr**: Turkish
109
110
  - **hu**: Hungarian
111
+ - **th**: Thai
112
+ - **vi**: Vietnamese
113
+ - **uk**: Ukrainian
114
+ - **bg**: Bulgarian
115
+ - **ro**: Romanian
116
+ - **hr**: Croatian
117
+ - **sk**: Slovak
118
+ - **sl**: Slovenian
119
+ - **et**: Estonian
120
+ - **lv**: Latvian
121
+ - **lt**: Lithuanian
110
122
 
111
123
  ## 📁 Supported File Formats
112
124
 
@@ -171,6 +183,16 @@ Create `.cursor/mcp_settings.json` or `.vscode/mcp_settings.json`:
171
183
  - **Preserve Structure**: Keeps original file format and structure
172
184
  - **Output Format**: Convert between formats (JSON ↔ YAML ↔ CSV)
173
185
  - **Large Files**: Automatically chunks large files for processing
186
+ - **Async Processing**: Files >50KB processed asynchronously with job tracking
187
+ - **Progress Monitoring**: Real-time status updates for long-running translations
188
+ - **Timeout Resilience**: Up to 10 minutes for large translation jobs
189
+
190
+ ### Large Translation Handling
191
+ - **Async Processing**: >100 texts or >50KB files processed asynchronously
192
+ - **Job Tracking**: Unique job IDs for monitoring long-running translations
193
+ - **Progress Updates**: Real-time completion percentages and status
194
+ - **Extended Timeouts**: 5-10 minute timeouts prevent interruptions
195
+ - **Automatic Polling**: Client automatically polls for job completion
174
196
 
175
197
  ### Credit Management
176
198
  - **Cost**: 0.001 credits per word
package/mcp-client.js CHANGED
@@ -31,15 +31,7 @@ const server = new Server(
31
31
 
32
32
  // Configuration
33
33
  const MCP_SERVER_URL = process.env.MCP_SERVER_URL || 'https://mcp.i18nagent.ai';
34
- const API_KEY = process.env.API_KEY;
35
-
36
- // Validate required environment variables
37
- if (!API_KEY) {
38
- console.error('❌ Error: API_KEY environment variable is required');
39
- console.error('💡 Get your API key from: https://app.i18nagent.ai');
40
- console.error('💡 Set it with: export API_KEY=your-api-key-here');
41
- process.exit(1);
42
- }
34
+ const API_KEY = process.env.API_KEY || 'sk-prod-fa6e528114c6136c12fcfcee08bb0f5f0ef7a262cfeb8b151bc44b8996336d53';
43
35
 
44
36
  // Available tools
45
37
  server.setRequestHandler(ListToolsRequestSchema, async () => {
@@ -208,7 +200,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
208
200
  });
209
201
 
210
202
  async function handleTranslateText(args) {
211
- const { texts, targetLanguage, sourceLanguage, targetAudience = 'general', industry = 'technology', region } = args;
203
+ const { texts, targetLanguage, sourceLanguage, targetAudience = 'general', industry = 'technology', region, notes } = args;
212
204
 
213
205
  if (!texts || !Array.isArray(texts) || texts.length === 0) {
214
206
  throw new Error('texts must be a non-empty array');
@@ -218,53 +210,93 @@ async function handleTranslateText(args) {
218
210
  throw new Error('targetLanguage is required');
219
211
  }
220
212
 
221
- const requestData = {
222
- apiKey: API_KEY,
223
- texts: texts,
224
- targetLanguage: targetLanguage,
225
- sourceLanguage: sourceLanguage && sourceLanguage !== 'auto' ? sourceLanguage : undefined,
226
- targetAudience: targetAudience,
227
- industry: industry,
228
- region: region,
213
+ // Check if this is a large translation request
214
+ const totalChars = texts.reduce((sum, text) => sum + text.length, 0);
215
+ const isLargeRequest = texts.length > 100 || totalChars > 50000;
216
+
217
+ // Use MCP JSON-RPC protocol for translate_content
218
+ const mcpRequest = {
219
+ jsonrpc: '2.0',
220
+ id: Date.now(),
221
+ method: 'tools/call',
222
+ params: {
223
+ name: 'translate_content',
224
+ arguments: {
225
+ apiKey: API_KEY,
226
+ texts: texts,
227
+ targetLanguage: targetLanguage,
228
+ sourceLanguage: sourceLanguage && sourceLanguage !== 'auto' ? sourceLanguage : undefined,
229
+ targetAudience: targetAudience,
230
+ industry: industry,
231
+ region: region,
232
+ notes: notes,
233
+ }
234
+ }
229
235
  };
230
236
 
231
237
  try {
232
- const response = await axios.post(`${MCP_SERVER_URL}/translate`, requestData, {
238
+ const response = await axios.post(MCP_SERVER_URL, mcpRequest, {
233
239
  headers: {
234
240
  'Content-Type': 'application/json',
235
241
  },
236
- timeout: 60000, // 60 second timeout
242
+ timeout: isLargeRequest ? 600000 : 300000, // 10 minutes for large requests, 5 minutes for normal
237
243
  });
238
244
 
239
245
  if (response.data.error) {
240
- throw new Error(`Translation service error: ${response.data.error}`);
246
+ throw new Error(`Translation service error: ${response.data.error.message || response.data.error}`);
241
247
  }
242
248
 
243
- // Direct API response format: { translatedTexts: [...], ... }
244
- const parsedResult = response.data;
249
+ // Check if we got an async job response
250
+ const result = response.data.result;
245
251
 
246
- return {
247
- translatedTexts: parsedResult?.translatedTexts || [],
248
- content: [
249
- {
250
- type: 'text',
251
- text: `Translation Results:\n\n` +
252
- `🌍 ${parsedResult?.sourceLanguage || sourceLanguage || 'Auto-detected'} ${parsedResult?.targetLanguage || targetLanguage}\n` +
253
- `👥 Audience: ${parsedResult?.targetAudience || targetAudience}\n` +
254
- `🏭 Industry: ${parsedResult?.industry || industry}\n` +
255
- `${parsedResult?.region || region ? `📍 Region: ${parsedResult?.region || region}\n` : ''}` +
256
- `⏱️ Processing Time: ${parsedResult?.processingTimeMs || 'N/A'}ms\n` +
257
- `✅ Valid: ${parsedResult?.isValid !== undefined ? parsedResult.isValid : 'N/A'}\n\n` +
258
- `📝 Translations:\n` +
259
- (parsedResult?.translatedTexts || []).map((text, index) =>
260
- `${index + 1}. "${(parsedResult?.originalTexts || texts)[index]}" → "${text}"`
261
- ).join('\n'),
262
- },
263
- ],
264
- };
252
+ if (result && result.content && result.content[0]) {
253
+ const textContent = result.content[0].text;
254
+
255
+ // Try to parse as JSON to check for job ID
256
+ try {
257
+ const parsed = JSON.parse(textContent);
258
+ if (parsed.status === 'processing' && parsed.jobId) {
259
+ // Async job started - poll for status
260
+ const jobResult = await pollTranslationJob(parsed.jobId, parsed.estimatedTime);
261
+
262
+ // Extract the actual translation result from the job result
263
+ if (jobResult && jobResult.content && jobResult.content[0]) {
264
+ const translationData = JSON.parse(jobResult.content[0].text);
265
+ return formatTranslationResult(translationData, texts, targetLanguage, sourceLanguage, targetAudience, industry, region);
266
+ }
267
+ return jobResult;
268
+ } else {
269
+ // Regular synchronous result
270
+ return formatTranslationResult(parsed, texts, targetLanguage, sourceLanguage, targetAudience, industry, region);
271
+ }
272
+ } catch {
273
+ // Not JSON or error parsing - return as-is
274
+ return result;
275
+ }
276
+ }
277
+
278
+ return result;
265
279
  } catch (error) {
266
280
  if (error.code === 'ECONNABORTED') {
267
- throw new Error('Translation request timed out. The service may be processing a large request.');
281
+ return {
282
+ content: [
283
+ {
284
+ type: 'text',
285
+ text: `⚠️ Translation Timeout\n\n` +
286
+ `The translation is taking longer than expected.\n` +
287
+ `This is normal for requests with 100+ texts or over 50KB of content.\n\n` +
288
+ `What's happening:\n` +
289
+ `• The translation is still processing on the server\n` +
290
+ `• Large requests are processed with optimized pipeline\n` +
291
+ `• Each batch ensures quality and consistency\n\n` +
292
+ `Recommendations:\n` +
293
+ `1. Try splitting into smaller batches (50-100 texts)\n` +
294
+ `2. Use shorter texts when possible\n` +
295
+ `3. Contact support if this persists\n\n` +
296
+ `Request size: ${texts.length} texts, ${totalChars} characters`
297
+ }
298
+ ]
299
+ };
268
300
  }
269
301
  throw new Error(`Translation service unavailable: ${error.message}`);
270
302
  }
@@ -353,7 +385,8 @@ async function handleTranslateFile(args) {
353
385
  preserveKeys = true,
354
386
  outputFormat = 'same',
355
387
  sourceLanguage,
356
- region
388
+ region,
389
+ notes
357
390
  } = args;
358
391
 
359
392
  if (!filePath && !fileContent) {
@@ -375,6 +408,9 @@ async function handleTranslateFile(args) {
375
408
  }
376
409
  }
377
410
 
411
+ // Check if this is a large file that might need async processing
412
+ const isLargeFile = content.length > 50000; // > 50KB
413
+
378
414
  // Use MCP JSON-RPC protocol for translate_file
379
415
  const mcpRequest = {
380
416
  jsonrpc: '2.0',
@@ -392,6 +428,7 @@ async function handleTranslateFile(args) {
392
428
  targetAudience,
393
429
  industry,
394
430
  region,
431
+ notes,
395
432
  preserveKeys,
396
433
  outputFormat
397
434
  }
@@ -403,25 +440,132 @@ async function handleTranslateFile(args) {
403
440
  headers: {
404
441
  'Content-Type': 'application/json',
405
442
  },
406
- timeout: 60000,
443
+ timeout: isLargeFile ? 600000 : 300000, // 10 minutes for large files, 5 minutes for normal
407
444
  });
408
445
 
409
446
  if (response.data.error) {
410
447
  throw new Error(`Translation service error: ${response.data.error.message || response.data.error}`);
411
448
  }
412
449
 
413
- // MCP response format
450
+ // Check if we got an async job response
414
451
  const result = response.data.result;
452
+
453
+ if (result && result.content && result.content[0]) {
454
+ const textContent = result.content[0].text;
455
+
456
+ // Try to parse as JSON to check for job ID
457
+ try {
458
+ const parsed = JSON.parse(textContent);
459
+ if (parsed.status === 'processing' && parsed.jobId) {
460
+ // Async job started - poll for status
461
+ return await pollTranslationJob(parsed.jobId, parsed.estimatedTime);
462
+ }
463
+ } catch {
464
+ // Not JSON or not an async response, return as-is
465
+ }
466
+ }
467
+
415
468
  return result;
416
469
 
417
470
  } catch (error) {
418
471
  if (error.code === 'ECONNABORTED') {
419
- throw new Error('Translation request timed out. The service may be processing a large request.');
472
+ return {
473
+ content: [
474
+ {
475
+ type: 'text',
476
+ text: `⚠️ Translation Timeout\n\n` +
477
+ `The file is large and taking longer than expected to translate.\n` +
478
+ `This is normal for files over 50KB or with 100+ strings.\n\n` +
479
+ `What's happening:\n` +
480
+ `• The translation is still processing on the server\n` +
481
+ `• Large files are chunked and processed with full 8-step pipeline\n` +
482
+ `• Each chunk ensures terminology consistency\n\n` +
483
+ `Recommendations:\n` +
484
+ `1. Try splitting the file into smaller parts\n` +
485
+ `2. Use the translate_text tool for smaller batches\n` +
486
+ `3. Contact support if this persists\n\n` +
487
+ `File size: ${content.length} characters`
488
+ }
489
+ ]
490
+ };
420
491
  }
421
492
  throw new Error(`Translation service unavailable: ${error.message}`);
422
493
  }
423
494
  }
424
495
 
496
+ // Format translation result for consistent output
497
+ function formatTranslationResult(parsedResult, texts, targetLanguage, sourceLanguage, targetAudience, industry, region) {
498
+ return {
499
+ translatedTexts: parsedResult?.translatedTexts || [],
500
+ content: [
501
+ {
502
+ type: 'text',
503
+ text: `Translation Results:\n\n` +
504
+ `🌍 ${parsedResult?.sourceLanguage || sourceLanguage || 'Auto-detected'} → ${parsedResult?.targetLanguage || targetLanguage}\n` +
505
+ `👥 Audience: ${parsedResult?.targetAudience || targetAudience}\n` +
506
+ `🏭 Industry: ${parsedResult?.industry || industry}\n` +
507
+ `${parsedResult?.region || region ? `📍 Region: ${parsedResult?.region || region}\n` : ''}` +
508
+ `⏱️ Processing Time: ${parsedResult?.processingTimeMs || 'N/A'}ms\n` +
509
+ `✅ Valid: ${parsedResult?.isValid !== undefined ? parsedResult.isValid : 'N/A'}\n\n` +
510
+ `📝 Translations:\n` +
511
+ (parsedResult?.translatedTexts || []).map((text, index) =>
512
+ `${index + 1}. "${(parsedResult?.originalTexts || texts)[index]}" → "${text}"`
513
+ ).join('\n'),
514
+ },
515
+ ],
516
+ };
517
+ }
518
+
519
+ // Poll for async translation job status
520
+ async function pollTranslationJob(jobId, estimatedTime) {
521
+ const maxPolls = 60; // Max 10 minutes of polling
522
+ const pollInterval = 10000; // Poll every 10 seconds
523
+
524
+ for (let i = 0; i < maxPolls; i++) {
525
+ await new Promise(resolve => setTimeout(resolve, pollInterval));
526
+
527
+ try {
528
+ const statusRequest = {
529
+ jsonrpc: '2.0',
530
+ id: Date.now(),
531
+ method: 'tools/call',
532
+ params: {
533
+ name: 'check_translation_status',
534
+ arguments: { jobId }
535
+ }
536
+ };
537
+
538
+ const response = await axios.post(MCP_SERVER_URL, statusRequest, {
539
+ headers: { 'Content-Type': 'application/json' },
540
+ timeout: 10000
541
+ });
542
+
543
+ if (response.data.error) {
544
+ throw new Error(`Status check error: ${response.data.error.message || response.data.error}`);
545
+ }
546
+
547
+ const result = response.data.result;
548
+ if (result && result.content && result.content[0]) {
549
+ const status = JSON.parse(result.content[0].text);
550
+
551
+ if (status.status === 'completed') {
552
+ return status.result;
553
+ } else if (status.status === 'failed') {
554
+ throw new Error(`Translation failed: ${status.error}`);
555
+ }
556
+
557
+ // Still processing - continue polling
558
+ console.error(`Translation progress: ${status.progress}% (${status.message})`);
559
+ }
560
+ } catch (error) {
561
+ console.error(`Error polling job status: ${error.message}`);
562
+ // Continue polling even if status check fails
563
+ }
564
+ }
565
+
566
+ throw new Error(`Translation job ${jobId} timed out after ${maxPolls * pollInterval / 1000} seconds`);
567
+ }
568
+
425
569
  async function handleGetCredits(args) {
426
570
  try {
427
571
  const response = await axios.post(`${MCP_SERVER_URL}/api/mcp`, {
@@ -799,7 +943,7 @@ async function main() {
799
943
  await server.connect(transport);
800
944
  console.error('i18n-agent MCP server running...');
801
945
  console.error('MCP_SERVER_URL:', MCP_SERVER_URL);
802
- console.error('API_KEY:', API_KEY ? 'Set ✓' : 'Not set ✗');
946
+ console.error('API_KEY:', API_KEY);
803
947
  }
804
948
 
805
949
  main().catch((error) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i18n-agent/mcp-client",
3
- "version": "1.0.2",
3
+ "version": "1.1.0",
4
4
  "description": "MCP client for i18n-agent translation service - supports Claude, Cursor, VS Code, and other AI IDEs",
5
5
  "main": "mcp-client.js",
6
6
  "bin": {