@i18n-agent/mcp-client 1.8.401 → 1.8.403

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 (2) hide show
  1. package/mcp-client.js +421 -1
  2. package/package.json +1 -1
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.260';
8
+ const MCP_CLIENT_VERSION = '1.8.403';
9
9
 
10
10
  import { Server } from '@modelcontextprotocol/sdk/server/index.js';
11
11
  import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
@@ -373,6 +373,82 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
373
373
  required: ['jobId'],
374
374
  },
375
375
  },
376
+ {
377
+ name: 'upload_translations',
378
+ description: 'Upload existing translations for reuse to reduce translation costs. Supports two modes: (1) Single file mode - upload a translated i18n file (JSON, YAML, PO, XLIFF, etc.) using filePath/fileContent params. (2) Parallel document mode - upload source + target document pairs (MD, TXT, HTML) using sourceFilePath/targetFilePath params for sentence-aligned extraction. Namespace is auto-detected from file path or can be explicitly provided.',
379
+ inputSchema: {
380
+ type: 'object',
381
+ properties: {
382
+ // Single file mode parameters
383
+ filePath: {
384
+ type: 'string',
385
+ description: 'Path to translated i18n file (for single file mode). Use this OR sourceFilePath/targetFilePath.',
386
+ },
387
+ fileContent: {
388
+ type: 'string',
389
+ description: 'File content as string (for single file mode, required if filePath is not provided)',
390
+ },
391
+ // Parallel document mode parameters
392
+ sourceFilePath: {
393
+ type: 'string',
394
+ description: 'Path to source language document (for parallel mode). Use with targetFilePath.',
395
+ },
396
+ sourceFileContent: {
397
+ type: 'string',
398
+ description: 'Source document content as string (for parallel mode)',
399
+ },
400
+ targetFilePath: {
401
+ type: 'string',
402
+ description: 'Path to target language document (for parallel mode). Use with sourceFilePath.',
403
+ },
404
+ targetFileContent: {
405
+ type: 'string',
406
+ description: 'Target document content as string (for parallel mode)',
407
+ },
408
+ // Common parameters
409
+ fileType: {
410
+ type: 'string',
411
+ description: 'File format. Single file mode: json, yaml, yml, po, pot, mo, xml, arb, strings, stringsdict, plist, properties, xliff, xlf, resx. Parallel mode: md, markdown, txt, html, htm, json, yaml, yml. Default: auto',
412
+ default: 'auto',
413
+ },
414
+ sourceLocale: {
415
+ type: 'string',
416
+ description: 'Source language code (e.g., "en", "en-US")',
417
+ },
418
+ targetLocale: {
419
+ type: 'string',
420
+ description: 'Target language code (e.g., "es", "fr", "ja")',
421
+ },
422
+ namespace: {
423
+ type: 'string',
424
+ description: 'Namespace identifier for translation tracking (auto-detected from file path if not provided)',
425
+ },
426
+ },
427
+ required: ['sourceLocale', 'targetLocale'],
428
+ },
429
+ },
430
+ {
431
+ name: 'list_uploaded_translations',
432
+ description: 'List all uploaded translation files in a namespace, showing file names, language pairs, translation counts, and upload dates. Helps track what translations are available for reuse.',
433
+ inputSchema: {
434
+ type: 'object',
435
+ properties: {
436
+ namespace: {
437
+ type: 'string',
438
+ description: 'Namespace identifier',
439
+ },
440
+ sourceLocale: {
441
+ type: 'string',
442
+ description: 'Filter by source language (optional)',
443
+ },
444
+ targetLocale: {
445
+ type: 'string',
446
+ description: 'Filter by target language (optional)',
447
+ },
448
+ },
449
+ required: ['namespace'],
450
+ },
451
+ },
376
452
  ],
377
453
  };
378
454
  });
@@ -413,6 +489,12 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
413
489
  case 'download_translations':
414
490
  return await handleDownloadTranslations(args);
415
491
 
492
+ case 'upload_translations':
493
+ return await handleUploadTranslations(args);
494
+
495
+ case 'list_uploaded_translations':
496
+ return await handleListUploadedTranslations(args);
497
+
416
498
  default:
417
499
  throw new McpError(
418
500
  ErrorCode.MethodNotFound,
@@ -1873,6 +1955,344 @@ async function handleDownloadTranslations(args) {
1873
1955
  }
1874
1956
  }
1875
1957
 
1958
+ async function handleUploadTranslations(args) {
1959
+ const {
1960
+ // Single file mode
1961
+ filePath,
1962
+ fileContent,
1963
+ // Parallel document mode
1964
+ sourceFilePath,
1965
+ sourceFileContent,
1966
+ targetFilePath,
1967
+ targetFileContent,
1968
+ // Common
1969
+ fileType = 'auto',
1970
+ sourceLocale,
1971
+ targetLocale,
1972
+ namespace
1973
+ } = args;
1974
+
1975
+ if (!sourceLocale || !targetLocale) {
1976
+ throw new Error('sourceLocale and targetLocale are required');
1977
+ }
1978
+
1979
+ // Detect mode based on parameters provided
1980
+ const hasParallelParams = sourceFilePath || sourceFileContent || targetFilePath || targetFileContent;
1981
+ const hasSingleFileParams = filePath || fileContent;
1982
+
1983
+ if (hasParallelParams && hasSingleFileParams) {
1984
+ throw new Error('Cannot mix single file mode (filePath/fileContent) with parallel mode (sourceFilePath/targetFilePath). Use one mode at a time.');
1985
+ }
1986
+
1987
+ if (!hasParallelParams && !hasSingleFileParams) {
1988
+ throw new Error('Either provide filePath/fileContent (single file mode) OR sourceFilePath/targetFilePath (parallel document mode)');
1989
+ }
1990
+
1991
+ // Route to appropriate handler
1992
+ if (hasParallelParams) {
1993
+ return await handleParallelDocumentUpload(args);
1994
+ } else {
1995
+ return await handleSingleFileUpload(args);
1996
+ }
1997
+ }
1998
+
1999
+ // Single file upload handler (for translated i18n files)
2000
+ async function handleSingleFileUpload(args) {
2001
+ const {
2002
+ filePath,
2003
+ fileContent,
2004
+ fileType = 'auto',
2005
+ sourceLocale,
2006
+ targetLocale,
2007
+ namespace
2008
+ } = args;
2009
+
2010
+ if (!filePath && !fileContent) {
2011
+ throw new Error('Either filePath or fileContent must be provided');
2012
+ }
2013
+
2014
+ // Auto-detect namespace if not provided
2015
+ let finalNamespace = namespace;
2016
+ let detectionInfo = null;
2017
+
2018
+ if (!namespace && filePath) {
2019
+ const detection = detectNamespaceFromPath(filePath);
2020
+ if (detection.suggestion && detection.confidence > 0.5) {
2021
+ finalNamespace = detection.suggestion;
2022
+ detectionInfo = detection;
2023
+ console.error(`🎯 [MCP CLIENT] Auto-detected namespace: "${finalNamespace}" (confidence: ${Math.round(detection.confidence * 100)}%, source: ${detection.source})`);
2024
+ }
2025
+ }
2026
+
2027
+ if (!finalNamespace) {
2028
+ const suggestionText = filePath
2029
+ ? getNamespaceSuggestionText(filePath, path.basename(filePath))
2030
+ : getNamespaceSuggestionText(null, null);
2031
+
2032
+ throw new Error(`namespace is required for translation upload.\n\n${suggestionText}`);
2033
+ }
2034
+
2035
+ // Read file content if path provided
2036
+ let content = fileContent;
2037
+ if (filePath && !fileContent) {
2038
+ try {
2039
+ content = fs.readFileSync(filePath, 'utf8');
2040
+ } catch (error) {
2041
+ throw new Error(`Failed to read file: ${error.message}`);
2042
+ }
2043
+ }
2044
+
2045
+ // Detect file type if auto
2046
+ const detectedFileType = fileType === 'auto' ? path.extname(filePath || 'file').slice(1) || 'json' : fileType;
2047
+
2048
+ // Build request to backend endpoint
2049
+ const requestData = {
2050
+ apiKey: API_KEY,
2051
+ namespace: finalNamespace,
2052
+ fileName: filePath ? path.basename(filePath) : 'uploaded-file',
2053
+ fileContent: content,
2054
+ fileType: detectedFileType,
2055
+ sourceLocale,
2056
+ targetLocale
2057
+ };
2058
+
2059
+ try {
2060
+ const response = await axios.post(`${MCP_SERVER_URL.replace('/mcp', '')}/namespaces/${finalNamespace}/translations/upload`,
2061
+ requestData.fileContent,
2062
+ {
2063
+ headers: {
2064
+ 'Content-Type': 'text/plain',
2065
+ 'X-API-Key': API_KEY,
2066
+ 'X-Source-Locale': sourceLocale,
2067
+ 'X-Target-Locale': targetLocale,
2068
+ 'X-File-Name': requestData.fileName,
2069
+ 'X-File-Type': detectedFileType
2070
+ },
2071
+ timeout: 60000
2072
+ }
2073
+ );
2074
+
2075
+ if (response.data.error) {
2076
+ throw new Error(`Upload error: ${response.data.error.message || response.data.error}`);
2077
+ }
2078
+
2079
+ const result = response.data;
2080
+
2081
+ return {
2082
+ content: [{
2083
+ type: 'text',
2084
+ text: `✅ Translation Upload Successful\n\n` +
2085
+ `📂 Namespace: ${finalNamespace}${detectionInfo ? ` (auto-detected)` : ''}\n` +
2086
+ `📄 File: ${requestData.fileName}\n` +
2087
+ `🌍 Languages: ${sourceLocale} → ${targetLocale}\n` +
2088
+ `✨ Translation Pairs Stored: ${result.pairsStored || 0}\n` +
2089
+ `🔄 Translation Pairs Updated: ${result.pairsUpdated || 0}\n\n` +
2090
+ `💡 These translations will be automatically reused when translating files in the "${finalNamespace}" namespace, reducing costs and ensuring consistency.`
2091
+ }]
2092
+ };
2093
+ } catch (error) {
2094
+ console.error('Upload translations error:', error);
2095
+
2096
+ // Handle 401 unauthorized
2097
+ if (error.response?.status === 401) {
2098
+ throw new Error(`❌ Invalid API key (401)\nPlease check your API key at https://app.i18nagent.ai`);
2099
+ }
2100
+
2101
+ // Handle 404 namespace not found
2102
+ if (error.response?.status === 404) {
2103
+ throw new Error(`❌ Namespace "${finalNamespace}" not found. Create it first by translating a file with this namespace.`);
2104
+ }
2105
+
2106
+ // Handle 409 duplicate
2107
+ if (error.response?.status === 409) {
2108
+ const errorData = error.response.data;
2109
+ throw new Error(`⚠️ Duplicate file detected\n${errorData.message || 'This file was already uploaded'}`);
2110
+ }
2111
+
2112
+ throw new Error(`Unable to upload translations: ${error.message}`);
2113
+ }
2114
+ }
2115
+
2116
+ // Parallel document upload handler (for source + target document pairs)
2117
+ async function handleParallelDocumentUpload(args) {
2118
+ const {
2119
+ sourceFilePath,
2120
+ sourceFileContent,
2121
+ targetFilePath,
2122
+ targetFileContent,
2123
+ fileType = 'auto',
2124
+ sourceLocale,
2125
+ targetLocale,
2126
+ namespace
2127
+ } = args;
2128
+
2129
+ // Validation
2130
+ if (!sourceFilePath && !sourceFileContent) {
2131
+ throw new Error('Either sourceFilePath or sourceFileContent must be provided');
2132
+ }
2133
+ if (!targetFilePath && !targetFileContent) {
2134
+ throw new Error('Either targetFilePath or targetFileContent must be provided');
2135
+ }
2136
+
2137
+ // Auto-detect namespace (prefer source file path)
2138
+ let finalNamespace = namespace;
2139
+ if (!namespace && sourceFilePath) {
2140
+ const detection = detectNamespaceFromPath(sourceFilePath);
2141
+ if (detection.suggestion && detection.confidence > 0.5) {
2142
+ finalNamespace = detection.suggestion;
2143
+ console.error(`🎯 [MCP CLIENT] Auto-detected namespace: "${finalNamespace}"`);
2144
+ }
2145
+ }
2146
+
2147
+ if (!finalNamespace) {
2148
+ const suggestionText = getNamespaceSuggestionText(sourceFilePath || targetFilePath, null);
2149
+ throw new Error(`namespace is required for parallel document upload.\n\n${suggestionText}`);
2150
+ }
2151
+
2152
+ // Read files
2153
+ let sourceContent = sourceFileContent;
2154
+ let targetContent = targetFileContent;
2155
+
2156
+ if (sourceFilePath && !sourceFileContent) {
2157
+ sourceContent = fs.readFileSync(sourceFilePath, 'utf8');
2158
+ }
2159
+ if (targetFilePath && !targetFileContent) {
2160
+ targetContent = fs.readFileSync(targetFilePath, 'utf8');
2161
+ }
2162
+
2163
+ // Detect file type
2164
+ const detectedFileType = fileType === 'auto'
2165
+ ? path.extname(sourceFilePath || 'file').slice(1) || 'md'
2166
+ : fileType;
2167
+
2168
+ // Build request
2169
+ const formData = new FormData();
2170
+ formData.append('sourceFile', new Blob([sourceContent]), sourceFilePath ? path.basename(sourceFilePath) : 'source.md');
2171
+ formData.append('targetFile', new Blob([targetContent]), targetFilePath ? path.basename(targetFilePath) : 'target.md');
2172
+ formData.append('sourceLanguage', sourceLocale);
2173
+ formData.append('targetLanguage', targetLocale);
2174
+ formData.append('namespace', finalNamespace);
2175
+
2176
+ try {
2177
+ const response = await axios.post(`${MCP_SERVER_URL.replace('/mcp', '')}/translations/upload-parallel`,
2178
+ formData,
2179
+ {
2180
+ headers: {
2181
+ 'X-API-Key': API_KEY,
2182
+ ...formData.getHeaders()
2183
+ },
2184
+ timeout: 120000
2185
+ }
2186
+ );
2187
+
2188
+ if (response.data.error) {
2189
+ throw new Error(`Upload error: ${response.data.error.message || response.data.error}`);
2190
+ }
2191
+
2192
+ const result = response.data;
2193
+
2194
+ return {
2195
+ content: [{
2196
+ type: 'text',
2197
+ text: `✅ Parallel Document Upload Successful\n\n` +
2198
+ `📂 Namespace: ${finalNamespace}\n` +
2199
+ `📄 Source: ${sourceFilePath ? path.basename(sourceFilePath) : 'source content'}\n` +
2200
+ `📄 Target: ${targetFilePath ? path.basename(targetFilePath) : 'target content'}\n` +
2201
+ `🌍 Languages: ${sourceLocale} → ${targetLocale}\n` +
2202
+ `✨ Translation Pairs Extracted: ${result.pairsStored || 0}\n` +
2203
+ `🔄 Translation Pairs Updated: ${result.pairsUpdated || 0}\n\n` +
2204
+ `💡 These aligned translations will be automatically reused when translating similar documents.`
2205
+ }]
2206
+ };
2207
+ } catch (error) {
2208
+ console.error('Upload parallel documents error:', error);
2209
+
2210
+ if (error.response?.status === 401) {
2211
+ throw new Error(`❌ Invalid API key (401)`);
2212
+ }
2213
+
2214
+ if (error.response?.status === 404) {
2215
+ throw new Error(`❌ Namespace "${finalNamespace}" not found`);
2216
+ }
2217
+
2218
+ throw new Error(`Unable to upload parallel documents: ${error.message}`);
2219
+ }
2220
+ }
2221
+
2222
+ async function handleListUploadedTranslations(args) {
2223
+ const {
2224
+ namespace,
2225
+ sourceLocale,
2226
+ targetLocale
2227
+ } = args;
2228
+
2229
+ if (!namespace) {
2230
+ throw new Error('namespace is required');
2231
+ }
2232
+
2233
+ // Build query parameters
2234
+ const params = new URLSearchParams();
2235
+ if (sourceLocale) params.append('sourceLocale', sourceLocale);
2236
+ if (targetLocale) params.append('targetLocale', targetLocale);
2237
+
2238
+ try {
2239
+ const response = await axios.get(
2240
+ `${MCP_SERVER_URL.replace('/mcp', '')}/namespaces/${namespace}/translations/files?${params.toString()}`,
2241
+ {
2242
+ headers: {
2243
+ 'X-API-Key': API_KEY
2244
+ },
2245
+ timeout: 30000
2246
+ }
2247
+ );
2248
+
2249
+ if (response.data.error) {
2250
+ throw new Error(`List error: ${response.data.error.message || response.data.error}`);
2251
+ }
2252
+
2253
+ const result = response.data;
2254
+
2255
+ if (!result.files || result.files.length === 0) {
2256
+ return {
2257
+ content: [{
2258
+ type: 'text',
2259
+ text: `📂 Namespace: ${namespace}\n\n` +
2260
+ `No uploaded translation files found.\n\n` +
2261
+ `💡 Use upload_translations to upload translation files for reuse.`
2262
+ }]
2263
+ };
2264
+ }
2265
+
2266
+ const filesList = result.files.map((file, idx) =>
2267
+ `${idx + 1}. ${file.originalFileName}\n` +
2268
+ ` 🌍 ${file.sourceLocale} → ${file.targetLocale}\n` +
2269
+ ` ✨ ${file.pairsExtracted} pairs\n` +
2270
+ ` 📅 Uploaded: ${new Date(file.createdAt).toLocaleDateString()}`
2271
+ ).join('\n\n');
2272
+
2273
+ return {
2274
+ content: [{
2275
+ type: 'text',
2276
+ text: `📂 Namespace: ${namespace}\n` +
2277
+ `📊 Total Files: ${result.totalFiles}\n\n` +
2278
+ `Uploaded Translation Files:\n\n${filesList}`
2279
+ }]
2280
+ };
2281
+ } catch (error) {
2282
+ console.error('List uploaded translations error:', error);
2283
+
2284
+ if (error.response?.status === 401) {
2285
+ throw new Error(`❌ Invalid API key (401)`);
2286
+ }
2287
+
2288
+ if (error.response?.status === 404) {
2289
+ throw new Error(`❌ Namespace "${namespace}" not found`);
2290
+ }
2291
+
2292
+ throw new Error(`Unable to list uploaded translations: ${error.message}`);
2293
+ }
2294
+ }
2295
+
1876
2296
  // Start the server
1877
2297
  async function main() {
1878
2298
  const transport = new StdioServerTransport();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@i18n-agent/mcp-client",
3
- "version": "1.8.401",
3
+ "version": "1.8.403",
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": {