@i18n-agent/mcp-client 1.8.462 → 1.9.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/install.js +248 -2
  2. package/mcp-client.js +16 -33
  3. package/package.json +1 -1
package/install.js CHANGED
@@ -201,7 +201,7 @@ function detectNodeEnvironment() {
201
201
  const nvmDir = process.env.NVM_DIR || path.join(os.homedir(), '.nvm');
202
202
  const nodeVersion = process.version;
203
203
  const nodePath = process.execPath;
204
-
204
+
205
205
  return {
206
206
  isNvm: nodePath.includes('.nvm') || nodePath.includes('nvm'),
207
207
  nodePath,
@@ -209,6 +209,144 @@ function detectNodeEnvironment() {
209
209
  };
210
210
  }
211
211
 
212
+ // Detect if Codex CLI is available
213
+ function isCodexCLIAvailable() {
214
+ try {
215
+ execSync('codex --version', { stdio: 'pipe' });
216
+ return true;
217
+ } catch {
218
+ return false;
219
+ }
220
+ }
221
+
222
+ // Check if MCP server is already registered in Codex
223
+ function isCodexMCPRegistered(serverName) {
224
+ try {
225
+ const result = execSync(`codex mcp get ${serverName}`, { stdio: 'pipe' });
226
+ return true;
227
+ } catch {
228
+ return false;
229
+ }
230
+ }
231
+
232
+ // Install MCP server via Codex CLI native command
233
+ function installViaCodexCLI(existingApiKey = '') {
234
+ const { mcpClientPath, packageDir } = getMcpClientPaths();
235
+ const nodeEnv = detectNodeEnvironment();
236
+
237
+ // Build the command with env vars
238
+ const envArgs = [
239
+ '--env', `MCP_SERVER_URL=https://mcp.i18nagent.ai`
240
+ ];
241
+
242
+ if (existingApiKey) {
243
+ envArgs.push('--env', `API_KEY=${existingApiKey}`);
244
+ } else {
245
+ envArgs.push('--env', 'API_KEY=');
246
+ }
247
+
248
+ // Determine node command - use absolute path for nvm
249
+ const nodeCmd = nodeEnv.isNvm ? nodeEnv.nodePath : 'node';
250
+
251
+ // Remove existing registration if present
252
+ if (isCodexMCPRegistered('i18n-agent')) {
253
+ try {
254
+ execSync('codex mcp remove i18n-agent', { stdio: 'pipe' });
255
+ console.log(' 🔄 Removed existing i18n-agent registration');
256
+ } catch {
257
+ // Ignore if removal fails
258
+ }
259
+ }
260
+
261
+ // Build the full command
262
+ // codex mcp add [--env KEY=VALUE]... <name> <command> [args...]
263
+ const cmdParts = [
264
+ 'codex', 'mcp', 'add',
265
+ ...envArgs,
266
+ 'i18n-agent',
267
+ nodeCmd,
268
+ mcpClientPath
269
+ ];
270
+
271
+ // Execute the command
272
+ execSync(cmdParts.join(' '), {
273
+ cwd: packageDir,
274
+ stdio: 'pipe'
275
+ });
276
+
277
+ return true;
278
+ }
279
+
280
+ // Detect if Claude Code CLI is available
281
+ function isClaudeCodeCLIAvailable() {
282
+ try {
283
+ execSync('claude mcp list', { stdio: 'pipe' });
284
+ return true;
285
+ } catch {
286
+ return false;
287
+ }
288
+ }
289
+
290
+ // Check if MCP server is already registered in Claude Code CLI
291
+ function isClaudeCodeMCPRegistered(serverName) {
292
+ try {
293
+ execSync(`claude mcp get ${serverName}`, { stdio: 'pipe' });
294
+ return true;
295
+ } catch {
296
+ return false;
297
+ }
298
+ }
299
+
300
+ // Install MCP server via Claude Code CLI native command
301
+ function installViaClaudeCodeCLI(existingApiKey = '', scope = 'user') {
302
+ const { mcpClientPath } = getMcpClientPaths();
303
+ const nodeEnv = detectNodeEnvironment();
304
+
305
+ // Determine node command - use absolute path for nvm
306
+ const nodeCmd = nodeEnv.isNvm ? nodeEnv.nodePath : 'node';
307
+
308
+ // Remove existing registration if present
309
+ if (isClaudeCodeMCPRegistered('i18n-agent')) {
310
+ try {
311
+ execSync(`claude mcp remove --scope ${scope} i18n-agent`, { stdio: 'pipe' });
312
+ console.log(' 🔄 Removed existing i18n-agent registration');
313
+ } catch {
314
+ // Ignore if removal fails
315
+ }
316
+ }
317
+
318
+ // Build env args using -e format (Claude uses -e, not --env)
319
+ // Note: -e args must come AFTER the server name
320
+ const envArgs = [
321
+ '-e', `MCP_SERVER_URL=https://mcp.i18nagent.ai`
322
+ ];
323
+
324
+ if (existingApiKey) {
325
+ envArgs.push('-e', `API_KEY=${existingApiKey}`);
326
+ } else {
327
+ envArgs.push('-e', 'API_KEY=');
328
+ }
329
+
330
+ // Build the full command
331
+ // claude mcp add --transport stdio --scope user <name> -e KEY=VALUE -- <command> [args...]
332
+ // Note: <name> must come BEFORE -e options
333
+ const cmdParts = [
334
+ 'claude', 'mcp', 'add',
335
+ '--transport', 'stdio',
336
+ '--scope', scope,
337
+ 'i18n-agent', // Name must come before -e options
338
+ ...envArgs,
339
+ '--', // Separator for command arguments
340
+ nodeCmd,
341
+ mcpClientPath
342
+ ];
343
+
344
+ // Execute the command
345
+ execSync(cmdParts.join(' '), { stdio: 'pipe' });
346
+
347
+ return true;
348
+ }
349
+
212
350
  function createWrapperScript(targetDir) {
213
351
  const nodeEnv = detectNodeEnvironment();
214
352
  const wrapperPath = path.join(targetDir, 'run-mcp.sh');
@@ -448,11 +586,95 @@ For manual setup instructions, visit: https://docs.i18nagent.ai/setup
448
586
  const idesWithApiKey = [];
449
587
  const idesNeedingApiKey = [];
450
588
 
589
+ // Check if native CLIs are available
590
+ const codexCLIAvailable = isCodexCLIAvailable();
591
+ const claudeCodeCLIAvailable = isClaudeCodeCLIAvailable();
592
+
593
+ if (codexCLIAvailable || claudeCodeCLIAvailable) {
594
+ console.log('🔧 Native CLI support detected:');
595
+ if (claudeCodeCLIAvailable) {
596
+ console.log(' - Claude Code CLI (`claude mcp add`)');
597
+ }
598
+ if (codexCLIAvailable) {
599
+ console.log(' - Codex CLI (`codex mcp add`)');
600
+ }
601
+ console.log('');
602
+ }
603
+
451
604
  for (const ide of availableIDEs) {
452
605
  try {
453
606
  console.log(`⚙️ Configuring ${ide.name}...`);
454
607
 
455
608
  let result;
609
+
610
+ // Special handling for Claude Code CLI - use native CLI if available
611
+ if (ide.key === 'claude-code' && claudeCodeCLIAvailable) {
612
+ // Get existing API key from config file if present
613
+ let existingApiKey = '';
614
+ if (fs.existsSync(ide.configPath)) {
615
+ try {
616
+ const content = fs.readFileSync(ide.configPath, 'utf8');
617
+ const config = JSON.parse(content);
618
+ existingApiKey = config.mcpServers?.["i18n-agent"]?.env?.API_KEY || '';
619
+ } catch {
620
+ // Ignore parse errors
621
+ }
622
+ }
623
+
624
+ try {
625
+ installViaClaudeCodeCLI(existingApiKey);
626
+ console.log(`✅ ${ide.name} configured via native CLI!`);
627
+ console.log(` Run 'claude mcp list' to verify\n`);
628
+ installCount++;
629
+ installedIDEs.push(ide);
630
+
631
+ // Track API key status
632
+ if (existingApiKey) {
633
+ idesWithApiKey.push(ide);
634
+ } else {
635
+ idesNeedingApiKey.push(ide);
636
+ }
637
+ continue;
638
+ } catch (cliError) {
639
+ console.log(` ⚠️ Native CLI failed, falling back to config file...`);
640
+ // Fall through to config file method
641
+ }
642
+ }
643
+
644
+ // Special handling for Codex - use native CLI if available
645
+ if (ide.key === 'codex' && codexCLIAvailable) {
646
+ // Get existing API key from config file if present
647
+ let existingApiKey = '';
648
+ if (fs.existsSync(ide.configPath)) {
649
+ try {
650
+ const content = fs.readFileSync(ide.configPath, 'utf8');
651
+ const config = JSON.parse(content);
652
+ existingApiKey = config.mcpServers?.["i18n-agent"]?.env?.API_KEY || '';
653
+ } catch {
654
+ // Ignore parse errors
655
+ }
656
+ }
657
+
658
+ try {
659
+ installViaCodexCLI(existingApiKey);
660
+ console.log(`✅ ${ide.name} configured via native CLI!`);
661
+ console.log(` Run 'codex mcp list' to verify\n`);
662
+ installCount++;
663
+ installedIDEs.push(ide);
664
+
665
+ // Track API key status
666
+ if (existingApiKey) {
667
+ idesWithApiKey.push(ide);
668
+ } else {
669
+ idesNeedingApiKey.push(ide);
670
+ }
671
+ continue;
672
+ } catch (cliError) {
673
+ console.log(` ⚠️ Native CLI failed, falling back to config file...`);
674
+ // Fall through to config file method
675
+ }
676
+ }
677
+
456
678
  if (ide.key === 'claude' || ide.key === 'claude-code') {
457
679
  result = updateClaudeConfig(ide.configPath, ide.key);
458
680
  } else {
@@ -495,6 +717,12 @@ For manual setup instructions, visit: https://docs.i18nagent.ai/setup
495
717
  });
496
718
  console.log('');
497
719
 
720
+ // Check if CLIs need API key
721
+ const codexNeedsKey = idesNeedingApiKey.some(ide => ide.key === 'codex');
722
+ const claudeCodeNeedsKey = idesNeedingApiKey.some(ide => ide.key === 'claude-code');
723
+ const showCodexInstructions = codexNeedsKey && codexCLIAvailable;
724
+ const showClaudeCodeInstructions = claudeCodeNeedsKey && claudeCodeCLIAvailable;
725
+
498
726
  // Show setup instructions only for IDEs that need them
499
727
  console.log(`🔑 Setup Instructions
500
728
  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
@@ -503,7 +731,25 @@ Step 1: Get your API key
503
731
  👉 Sign up or log in
504
732
  👉 Copy your API key (starts with "i18n_")
505
733
 
506
- Step 2: Add API key to the config file
734
+ Step 2: Add API key to your IDE`);
735
+
736
+ if (showClaudeCodeInstructions) {
737
+ console.log(`
738
+ For Claude Code CLI (recommended):
739
+ claude mcp remove --scope user i18n-agent
740
+ claude mcp add --transport stdio --scope user i18n-agent -e MCP_SERVER_URL=https://mcp.i18nagent.ai -e API_KEY=your_key_here -- node ~/.claude/mcp-servers/i18n-agent/mcp-client.js
741
+ `);
742
+ }
743
+
744
+ if (showCodexInstructions) {
745
+ console.log(`
746
+ For Codex CLI (recommended):
747
+ codex mcp remove i18n-agent
748
+ codex mcp add --env MCP_SERVER_URL=https://mcp.i18nagent.ai --env API_KEY=your_key_here i18n-agent node ~/.claude/mcp-servers/i18n-agent/mcp-client.js
749
+ `);
750
+ }
751
+
752
+ console.log(` For config file method:
507
753
  Open the config file and edit the "API_KEY" field:
508
754
 
509
755
  "mcpServers": {
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.8.461';
8
+ const MCP_CLIENT_VERSION = '1.9.0';
9
9
 
10
10
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
11
11
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
@@ -49,7 +49,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
49
49
  tools: [
50
50
  {
51
51
  name: 'translate_text',
52
- description: '⚠️ CRITICAL: For multi-language translation, use targetLanguages parameter (not targetLanguage). Translate text content with cultural adaptation using AI subagents. Supports both single and multi-language translation. 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. Set pseudoTranslation=true for testing i18n implementations without AI cost.',
52
+ description: 'Translate text content with cultural adaptation using AI subagents. Supports single or multi-language translation via targetLanguages parameter (string for single, array for multiple). 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. Set pseudoTranslation=true for testing i18n implementations without AI cost.',
53
53
  inputSchema: {
54
54
  type: 'object',
55
55
  properties: {
@@ -59,7 +59,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
59
59
  description: 'Array of source texts to translate (any language)',
60
60
  },
61
61
  targetLanguages: {
62
- description: '⚠️ REQUIRED: Target language(s) - can be a single string (e.g., "es") OR an array of strings (e.g., ["es", "fr", "zh-CN"]) for multi-language translation',
62
+ description: 'Target language(s) - provide a single string (e.g., "es") OR an array (e.g., ["es", "fr", "zh-CN"]) for multi-language translation. Use specific locale codes like "en-US" or "en-GB" instead of generic "en".',
63
63
  oneOf: [
64
64
  { type: 'string' },
65
65
  { type: 'array', items: { type: 'string' } }
@@ -144,7 +144,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
144
144
  },
145
145
  {
146
146
  name: 'translate_file',
147
- description: '⚠️ CRITICAL: For multi-language translation, use targetLanguages parameter (not targetLanguage). Translate file content while preserving structure and format. Supports both single and multi-language translation. 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. Set pseudoTranslation=true for testing i18n implementations without AI cost.',
147
+ description: 'Translate file content while preserving structure and format. Supports single or multi-language translation via targetLanguages parameter (string for single, array for multiple). Supports JSON, YAML, XML, CSV, TXT, MD, and other text files. Always returns a jobId for async processing - use check_translation_status to monitor progress and download_translations to get results. Set pseudoTranslation=true for testing i18n implementations without AI cost.',
148
148
  inputSchema: {
149
149
  type: 'object',
150
150
  properties: {
@@ -163,7 +163,7 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
163
163
  default: 'auto',
164
164
  },
165
165
  targetLanguages: {
166
- description: '⚠️ REQUIRED: Target language(s) - can be a single string (e.g., "es") OR an array of strings (e.g., ["es", "fr", "zh-CN"]) for multi-language translation',
166
+ description: 'Target language(s) - provide a single string (e.g., "es") OR an array (e.g., ["es", "fr", "zh-CN"]) for multi-language translation. Use specific locale codes like "en-US" or "en-GB" instead of generic "en".',
167
167
  oneOf: [
168
168
  { type: 'string' },
169
169
  { type: 'array', items: { type: 'string' } }
@@ -555,17 +555,9 @@ async function handleTranslateText(args) {
555
555
  // Namespace is optional for text translation, but recommended for organizational tracking
556
556
 
557
557
  // Normalize targetLanguages - accept both string and array
558
- let targetLanguages = rawTargetLanguages;
559
- let targetLanguage = undefined;
560
-
561
- if (typeof rawTargetLanguages === 'string') {
562
- // Single language provided as string - convert to array for internal processing
563
- targetLanguages = [rawTargetLanguages];
564
- targetLanguage = rawTargetLanguages;
565
- } else if (Array.isArray(rawTargetLanguages) && rawTargetLanguages.length === 1) {
566
- // Single language provided as array - extract for backward compatibility
567
- targetLanguage = rawTargetLanguages[0];
568
- }
558
+ const targetLanguages = typeof rawTargetLanguages === 'string'
559
+ ? [rawTargetLanguages]
560
+ : rawTargetLanguages;
569
561
 
570
562
  if (!targetLanguages?.length) {
571
563
  throw new Error('targetLanguages parameter is required (can be a string for single language or array for multiple languages)');
@@ -585,7 +577,6 @@ async function handleTranslateText(args) {
585
577
  arguments: {
586
578
  apiKey: API_KEY,
587
579
  texts: texts,
588
- targetLanguage: targetLanguage,
589
580
  targetLanguages: targetLanguages,
590
581
  sourceLanguage: sourceLanguage && sourceLanguage !== 'auto' ? sourceLanguage : undefined,
591
582
  targetAudience: targetAudience,
@@ -640,12 +631,12 @@ async function handleTranslateText(args) {
640
631
  // Extract the actual translation result from the job result
641
632
  if (jobResult && jobResult.content && jobResult.content[0]) {
642
633
  const translationData = JSON.parse(jobResult.content[0].text);
643
- return formatTranslationResult(translationData, texts, targetLanguage, sourceLanguage, targetAudience, industry, region);
634
+ return formatTranslationResult(translationData, texts, targetLanguages, sourceLanguage, targetAudience, industry, region);
644
635
  }
645
636
  return jobResult;
646
637
  } else {
647
638
  // Regular synchronous result
648
- return formatTranslationResult(parsed, texts, targetLanguage, sourceLanguage, targetAudience, industry, region);
639
+ return formatTranslationResult(parsed, texts, targetLanguages, sourceLanguage, targetAudience, industry, region);
649
640
  }
650
641
  } catch {
651
642
  // Not JSON or error parsing - return as-is
@@ -894,17 +885,9 @@ async function handleTranslateFile(args) {
894
885
  }
895
886
 
896
887
  // Normalize targetLanguages - accept both string and array
897
- let targetLanguages = rawTargetLanguages;
898
- let targetLanguage = undefined;
899
-
900
- if (typeof rawTargetLanguages === 'string') {
901
- // Single language provided as string - convert to array for internal processing
902
- targetLanguages = [rawTargetLanguages];
903
- targetLanguage = rawTargetLanguages;
904
- } else if (Array.isArray(rawTargetLanguages) && rawTargetLanguages.length === 1) {
905
- // Single language provided as array - extract for backward compatibility
906
- targetLanguage = rawTargetLanguages[0];
907
- }
888
+ const targetLanguages = typeof rawTargetLanguages === 'string'
889
+ ? [rawTargetLanguages]
890
+ : rawTargetLanguages;
908
891
 
909
892
  if (!targetLanguages?.length) {
910
893
  throw new Error('targetLanguages parameter is required (can be a string for single language or array for multiple languages)');
@@ -939,7 +922,6 @@ async function handleTranslateFile(args) {
939
922
  };
940
923
 
941
924
  // Add optional parameters only if defined
942
- if (targetLanguage !== undefined) requestArgs.targetLanguage = targetLanguage;
943
925
  if (targetLanguages !== undefined) requestArgs.targetLanguages = targetLanguages;
944
926
  if (region !== undefined) requestArgs.region = region;
945
927
  if (context !== undefined) requestArgs.context = context;
@@ -1078,14 +1060,15 @@ async function handleTranslateFile(args) {
1078
1060
  }
1079
1061
 
1080
1062
  // Format translation result for consistent output
1081
- function formatTranslationResult(parsedResult, texts, targetLanguage, sourceLanguage, targetAudience, industry, region) {
1063
+ function formatTranslationResult(parsedResult, texts, targetLanguages, sourceLanguage, targetAudience, industry, region) {
1064
+ const targetLangsDisplay = Array.isArray(targetLanguages) ? targetLanguages.join(', ') : targetLanguages;
1082
1065
  return {
1083
1066
  translatedTexts: parsedResult?.translatedTexts || [],
1084
1067
  content: [
1085
1068
  {
1086
1069
  type: 'text',
1087
1070
  text: `Translation Results:\n\n` +
1088
- `🌍 ${parsedResult?.sourceLanguage || sourceLanguage || 'Auto-detected'} → ${parsedResult?.targetLanguage || targetLanguage}\n` +
1071
+ `🌍 ${parsedResult?.sourceLanguage || sourceLanguage || 'Auto-detected'} → ${parsedResult?.targetLanguages?.join(', ') || targetLangsDisplay}\n` +
1089
1072
  `👥 Audience: ${parsedResult?.targetAudience || targetAudience}\n` +
1090
1073
  `🏭 Industry: ${parsedResult?.industry || industry}\n` +
1091
1074
  `${parsedResult?.region || region ? `📍 Region: ${parsedResult?.region || region}\n` : ''}` +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i18n-agent/mcp-client",
3
- "version": "1.8.462",
3
+ "version": "1.9.0",
4
4
  "description": "🌍 i18n-agent MCP Client - 48 languages, AI-powered translation for Claude, Claude Code, Cursor, VS Code, Codex. Get API key at https://app.i18nagent.ai",
5
5
  "main": "mcp-client.js",
6
6
  "bin": {