@i18n-agent/mcp-client 1.0.0 ā 1.0.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.
- package/LICENSE +21 -0
- package/README.md +264 -0
- package/install.js +218 -0
- package/mcp-client.js +808 -0
- package/package.json +34 -12
- package/index.js +0 -8
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 FatCouple OĆ
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# š i18n-agent MCP Client
|
|
2
|
+
|
|
3
|
+
Professional translation service client for Claude, Cursor, VS Code, and other AI IDEs using the Model Context Protocol (MCP).
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@i18n-agent/mcp-client)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## ⨠Features
|
|
9
|
+
|
|
10
|
+
- **šÆ Smart Translation**: Context-aware translations with cultural adaptation
|
|
11
|
+
- **š File Translation**: Support for JSON, YAML, CSV, XML, Markdown, and more
|
|
12
|
+
- **š° Credit Tracking**: Real-time credit balance and word count estimates
|
|
13
|
+
- **š 30+ Languages**: Multi-tier language support with quality ratings
|
|
14
|
+
- **š§ Easy Setup**: One-command installation for major AI IDEs
|
|
15
|
+
|
|
16
|
+
## š Quick Installation
|
|
17
|
+
|
|
18
|
+
Install via npx (recommended):
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npx @i18n-agent/mcp-client install
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
Or install globally:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
npm install -g @i18n-agent/mcp-client
|
|
28
|
+
i18n-agent-install
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## š Setup API Key
|
|
32
|
+
|
|
33
|
+
1. **Get your API key** from [i18nagent.ai/dashboard](https://i18nagent.ai/dashboard)
|
|
34
|
+
|
|
35
|
+
2. **Set environment variable**:
|
|
36
|
+
```bash
|
|
37
|
+
export API_KEY=your-api-key-here
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
3. **Make it permanent** (add to ~/.bashrc or ~/.zshrc):
|
|
41
|
+
```bash
|
|
42
|
+
echo 'export API_KEY=your-api-key-here' >> ~/.zshrc
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
4. **Restart your AI IDE** to load the new configuration
|
|
46
|
+
|
|
47
|
+
## š® Usage Examples
|
|
48
|
+
|
|
49
|
+
### Text Translation
|
|
50
|
+
```
|
|
51
|
+
Translate "Hello, how are you?" to Spanish for a casual audience
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### File Translation
|
|
55
|
+
```
|
|
56
|
+
Translate this JSON file to French, preserving the structure
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Credit Check
|
|
60
|
+
```
|
|
61
|
+
Check my translation credits
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Language Support
|
|
65
|
+
```
|
|
66
|
+
List supported languages with quality ratings
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## š Supported AI IDEs
|
|
70
|
+
|
|
71
|
+
| IDE | Status | Config Location |
|
|
72
|
+
|-----|--------|----------------|
|
|
73
|
+
| **Claude Desktop** | ā
Auto-configured | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
74
|
+
| **Cursor** | ā
Auto-configured | `~/.cursor/mcp_settings.json` |
|
|
75
|
+
| **VS Code** | ā
Auto-configured | `~/.vscode/mcp_settings.json` |
|
|
76
|
+
| **Other MCP IDEs** | š§ Manual setup | Varies |
|
|
77
|
+
|
|
78
|
+
## š Language Support
|
|
79
|
+
|
|
80
|
+
### Tier 1 - Production Ready (80-90% Quality)
|
|
81
|
+
- **en**: English
|
|
82
|
+
- **es**: Spanish
|
|
83
|
+
- **fr**: French
|
|
84
|
+
- **de**: German
|
|
85
|
+
- **it**: Italian
|
|
86
|
+
- **pt**: Portuguese
|
|
87
|
+
- **nl**: Dutch
|
|
88
|
+
|
|
89
|
+
### Tier 2 - Production Viable (50-75% Quality)
|
|
90
|
+
- **ru**: Russian
|
|
91
|
+
- **zh-CN**: Chinese (Simplified)
|
|
92
|
+
- **ja**: Japanese
|
|
93
|
+
- **ko**: Korean
|
|
94
|
+
- **ar**: Arabic
|
|
95
|
+
- **he**: Hebrew
|
|
96
|
+
- **hi**: Hindi
|
|
97
|
+
- **pl**: Polish
|
|
98
|
+
- **cs**: Czech
|
|
99
|
+
|
|
100
|
+
### Tier 3 - Basic Support (20-50% Quality)
|
|
101
|
+
- **zh-TW**: Chinese (Traditional)
|
|
102
|
+
- **th**: Thai
|
|
103
|
+
- **vi**: Vietnamese
|
|
104
|
+
- **sv**: Swedish
|
|
105
|
+
- **da**: Danish
|
|
106
|
+
- **no**: Norwegian
|
|
107
|
+
- **fi**: Finnish
|
|
108
|
+
- **tr**: Turkish
|
|
109
|
+
- **hu**: Hungarian
|
|
110
|
+
|
|
111
|
+
## š Supported File Formats
|
|
112
|
+
|
|
113
|
+
| Format | Extension | Features |
|
|
114
|
+
|--------|-----------|----------|
|
|
115
|
+
| JSON | `.json` | Preserves structure, nested objects |
|
|
116
|
+
| YAML | `.yaml`, `.yml` | Maintains formatting, comments |
|
|
117
|
+
| CSV | `.csv` | Handles quoted fields, commas |
|
|
118
|
+
| XML/HTML | `.xml`, `.html` | Extracts text content |
|
|
119
|
+
| Markdown | `.md` | Preserves formatting, skips code |
|
|
120
|
+
| Properties | `.properties` | Key-value pairs |
|
|
121
|
+
| Plain Text | `.txt` | Direct translation |
|
|
122
|
+
|
|
123
|
+
## š§ Manual Setup
|
|
124
|
+
|
|
125
|
+
If auto-installation fails, you can manually configure your IDE:
|
|
126
|
+
|
|
127
|
+
### Claude Desktop
|
|
128
|
+
Edit `~/Library/Application Support/Claude/claude_desktop_config.json`:
|
|
129
|
+
|
|
130
|
+
```json
|
|
131
|
+
{
|
|
132
|
+
"mcpServers": {
|
|
133
|
+
"i18n-agent": {
|
|
134
|
+
"command": "node",
|
|
135
|
+
"args": ["/path/to/mcp-client.js"],
|
|
136
|
+
"env": {
|
|
137
|
+
"MCP_SERVER_URL": "https://mcp.i18nagent.ai",
|
|
138
|
+
"API_KEY": "your-api-key-here"
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Cursor / VS Code
|
|
146
|
+
Create `.cursor/mcp_settings.json` or `.vscode/mcp_settings.json`:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"mcpServers": {
|
|
151
|
+
"i18n-agent": {
|
|
152
|
+
"command": "node",
|
|
153
|
+
"args": ["/path/to/mcp-client.js"],
|
|
154
|
+
"env": {
|
|
155
|
+
"MCP_SERVER_URL": "https://mcp.i18nagent.ai",
|
|
156
|
+
"API_KEY": "your-api-key-here"
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## š” Usage Tips
|
|
164
|
+
|
|
165
|
+
### Translation Context
|
|
166
|
+
- **Target Audience**: Specify "technical", "casual", "formal", or "general"
|
|
167
|
+
- **Industry Context**: Use "technology", "healthcare", "finance", "education"
|
|
168
|
+
- **Regional Variations**: Add regions like "Spain", "Mexico", "Brazil"
|
|
169
|
+
|
|
170
|
+
### File Translation
|
|
171
|
+
- **Preserve Structure**: Keeps original file format and structure
|
|
172
|
+
- **Output Format**: Convert between formats (JSON ā YAML ā CSV)
|
|
173
|
+
- **Large Files**: Automatically chunks large files for processing
|
|
174
|
+
|
|
175
|
+
### Credit Management
|
|
176
|
+
- **Cost**: 0.001 credits per word
|
|
177
|
+
- **Monitoring**: Check balance before large translations
|
|
178
|
+
- **Estimates**: Get word count estimates before translation
|
|
179
|
+
|
|
180
|
+
## šØ Troubleshooting
|
|
181
|
+
|
|
182
|
+
### Installation Issues
|
|
183
|
+
|
|
184
|
+
**Permission denied:**
|
|
185
|
+
```bash
|
|
186
|
+
sudo npm install -g @i18n-agent/mcp-client
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
**IDE not detected:**
|
|
190
|
+
```bash
|
|
191
|
+
# Check if IDE directory exists
|
|
192
|
+
ls ~/Library/Application\ Support/Claude/
|
|
193
|
+
ls ~/.cursor/
|
|
194
|
+
ls ~/.vscode/
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
### Runtime Issues
|
|
198
|
+
|
|
199
|
+
**API Key not found:**
|
|
200
|
+
```bash
|
|
201
|
+
echo $API_KEY # Should show your key
|
|
202
|
+
export API_KEY=your-key-here
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
**Connection errors:**
|
|
206
|
+
- Check your internet connection
|
|
207
|
+
- Verify API key is valid
|
|
208
|
+
- Try again after a few seconds
|
|
209
|
+
|
|
210
|
+
**Translation quality:**
|
|
211
|
+
- Use Tier 1 languages for production
|
|
212
|
+
- Add context with industry/audience parameters
|
|
213
|
+
- Review Tier 2/3 translations manually
|
|
214
|
+
|
|
215
|
+
## š Pricing
|
|
216
|
+
|
|
217
|
+
- **Pay-per-use**: 0.001 credits per word
|
|
218
|
+
- **No subscriptions**: Only pay for what you translate
|
|
219
|
+
- **Bulk discounts**: Available for enterprise usage
|
|
220
|
+
- **Free tier**: New accounts get starter credits
|
|
221
|
+
|
|
222
|
+
## š Privacy & Security
|
|
223
|
+
|
|
224
|
+
- **No data storage**: Translations are processed in real-time
|
|
225
|
+
- **Encrypted transport**: All data sent over HTTPS
|
|
226
|
+
- **API key security**: Keys are stored locally, never transmitted in logs
|
|
227
|
+
- **GDPR compliant**: EU privacy standards
|
|
228
|
+
|
|
229
|
+
## š¤ Contributing
|
|
230
|
+
|
|
231
|
+
We welcome contributions! Please see our [Contributing Guidelines](CONTRIBUTING.md).
|
|
232
|
+
|
|
233
|
+
### Development Setup
|
|
234
|
+
|
|
235
|
+
```bash
|
|
236
|
+
git clone https://github.com/i18n-agent/mcp-client.git
|
|
237
|
+
cd mcp-client
|
|
238
|
+
npm install
|
|
239
|
+
npm test
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
## š License
|
|
243
|
+
|
|
244
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
245
|
+
|
|
246
|
+
Copyright (c) 2024 FatCouple OĆ
|
|
247
|
+
|
|
248
|
+
## š Links
|
|
249
|
+
|
|
250
|
+
- **Website**: [i18nagent.ai](https://i18nagent.ai)
|
|
251
|
+
- **Dashboard**: [i18nagent.ai/dashboard](https://i18nagent.ai/dashboard)
|
|
252
|
+
- **Documentation**: [docs.i18nagent.ai](https://docs.i18nagent.ai)
|
|
253
|
+
- **GitHub**: [github.com/i18n-agent/mcp-client](https://github.com/i18n-agent/mcp-client)
|
|
254
|
+
- **Issues**: [github.com/i18n-agent/mcp-client/issues](https://github.com/i18n-agent/mcp-client/issues)
|
|
255
|
+
|
|
256
|
+
## š Support
|
|
257
|
+
|
|
258
|
+
- **Discord**: [Join our community](https://discord.gg/i18nagent)
|
|
259
|
+
- **Email**: support@i18nagent.ai
|
|
260
|
+
- **Documentation**: [docs.i18nagent.ai](https://docs.i18nagent.ai)
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
Made with ā¤ļø by [FatCouple OĆ](https://fatcouple.com)
|
package/install.js
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* i18n-agent MCP Client Installer
|
|
5
|
+
* Installs the MCP client to work with Claude, Cursor, VS Code and other AI IDEs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import os from 'os';
|
|
11
|
+
import { fileURLToPath } from 'url';
|
|
12
|
+
|
|
13
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
|
|
15
|
+
// Supported IDE configurations
|
|
16
|
+
const IDE_CONFIGS = {
|
|
17
|
+
claude: {
|
|
18
|
+
name: 'Claude Desktop',
|
|
19
|
+
configPath: path.join(os.homedir(), 'Library/Application Support/Claude/claude_desktop_config.json'),
|
|
20
|
+
displayPath: '~/Library/Application Support/Claude/claude_desktop_config.json'
|
|
21
|
+
},
|
|
22
|
+
cursor: {
|
|
23
|
+
name: 'Cursor',
|
|
24
|
+
configPath: path.join(os.homedir(), '.cursor/mcp_settings.json'),
|
|
25
|
+
displayPath: '~/.cursor/mcp_settings.json'
|
|
26
|
+
},
|
|
27
|
+
vscode: {
|
|
28
|
+
name: 'VS Code (with MCP extension)',
|
|
29
|
+
configPath: path.join(os.homedir(), '.vscode/mcp_settings.json'),
|
|
30
|
+
displayPath: '~/.vscode/mcp_settings.json'
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
console.log(`
|
|
35
|
+
š i18n-agent MCP Client Installer
|
|
36
|
+
===================================
|
|
37
|
+
|
|
38
|
+
This installer will set up the i18n-agent MCP client for your AI IDE.
|
|
39
|
+
|
|
40
|
+
Features:
|
|
41
|
+
⨠Text translation with cultural context
|
|
42
|
+
š File translation (JSON, YAML, CSV, MD, etc.)
|
|
43
|
+
š° Credit balance checking
|
|
44
|
+
š 30+ language support with quality tiers
|
|
45
|
+
`);
|
|
46
|
+
|
|
47
|
+
async function detectAvailableIDEs() {
|
|
48
|
+
const available = [];
|
|
49
|
+
|
|
50
|
+
for (const [key, config] of Object.entries(IDE_CONFIGS)) {
|
|
51
|
+
const configDir = path.dirname(config.configPath);
|
|
52
|
+
if (fs.existsSync(configDir)) {
|
|
53
|
+
available.push({ key, ...config });
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return available;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function createMCPConfig() {
|
|
61
|
+
const mcpClientPath = path.resolve(__dirname, 'mcp-client.js');
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
mcpServers: {
|
|
65
|
+
"i18n-agent": {
|
|
66
|
+
command: "node",
|
|
67
|
+
args: [mcpClientPath],
|
|
68
|
+
env: {
|
|
69
|
+
MCP_SERVER_URL: "https://mcp.i18nagent.ai",
|
|
70
|
+
API_KEY: ""
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function updateClaudeConfig(configPath) {
|
|
78
|
+
let config = {};
|
|
79
|
+
|
|
80
|
+
// Read existing config if it exists
|
|
81
|
+
if (fs.existsSync(configPath)) {
|
|
82
|
+
try {
|
|
83
|
+
const content = fs.readFileSync(configPath, 'utf8');
|
|
84
|
+
config = JSON.parse(content);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.warn(`Warning: Could not parse existing config at ${configPath}`);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Ensure mcpServers exists
|
|
91
|
+
if (!config.mcpServers) {
|
|
92
|
+
config.mcpServers = {};
|
|
93
|
+
}
|
|
94
|
+
|
|
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
|
+
};
|
|
105
|
+
|
|
106
|
+
// Write updated config
|
|
107
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
108
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
109
|
+
|
|
110
|
+
return config;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function updateGenericMCPConfig(configPath) {
|
|
114
|
+
const config = createMCPConfig();
|
|
115
|
+
|
|
116
|
+
// Write config
|
|
117
|
+
fs.mkdirSync(path.dirname(configPath), { recursive: true });
|
|
118
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
119
|
+
|
|
120
|
+
return config;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function main() {
|
|
124
|
+
try {
|
|
125
|
+
console.log('š Detecting available AI IDEs...\n');
|
|
126
|
+
|
|
127
|
+
const availableIDEs = await detectAvailableIDEs();
|
|
128
|
+
|
|
129
|
+
if (availableIDEs.length === 0) {
|
|
130
|
+
console.log(`ā No supported AI IDEs detected.
|
|
131
|
+
|
|
132
|
+
Supported IDEs:
|
|
133
|
+
- Claude Desktop (macOS)
|
|
134
|
+
- Cursor
|
|
135
|
+
- VS Code (with MCP extension)
|
|
136
|
+
|
|
137
|
+
Manual setup:
|
|
138
|
+
1. Create the configuration file for your IDE
|
|
139
|
+
2. Add the i18n-agent MCP server configuration
|
|
140
|
+
3. Set your API_KEY environment variable
|
|
141
|
+
|
|
142
|
+
For manual setup instructions, visit: https://docs.i18nagent.ai/setup
|
|
143
|
+
`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
console.log('ā
Available AI IDEs:');
|
|
148
|
+
availableIDEs.forEach((ide, index) => {
|
|
149
|
+
console.log(`${index + 1}. ${ide.name}`);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
console.log('\nš Installing for all available IDEs...\n');
|
|
153
|
+
|
|
154
|
+
let installCount = 0;
|
|
155
|
+
|
|
156
|
+
for (const ide of availableIDEs) {
|
|
157
|
+
try {
|
|
158
|
+
console.log(`āļø Configuring ${ide.name}...`);
|
|
159
|
+
|
|
160
|
+
if (ide.key === 'claude') {
|
|
161
|
+
updateClaudeConfig(ide.configPath);
|
|
162
|
+
} else {
|
|
163
|
+
updateGenericMCPConfig(ide.configPath);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(`ā
${ide.name} configured successfully!`);
|
|
167
|
+
console.log(` Config: ${ide.displayPath}\n`);
|
|
168
|
+
installCount++;
|
|
169
|
+
|
|
170
|
+
} catch (error) {
|
|
171
|
+
console.error(`ā Failed to configure ${ide.name}: ${error.message}\n`);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (installCount > 0) {
|
|
176
|
+
console.log(`š Installation complete! Configured ${installCount} IDE(s).
|
|
177
|
+
|
|
178
|
+
š Important: Set your API key
|
|
179
|
+
āāāāāāāāāāāāāāāāāāāāāāāāāāāāāāāā
|
|
180
|
+
You need to set your API key to use the translation service:
|
|
181
|
+
|
|
182
|
+
1. Get your API key from: https://i18nagent.ai/dashboard
|
|
183
|
+
2. Set it as an environment variable:
|
|
184
|
+
|
|
185
|
+
export API_KEY=your-api-key-here
|
|
186
|
+
|
|
187
|
+
Or add it to your shell profile (~/.bashrc, ~/.zshrc):
|
|
188
|
+
echo 'export API_KEY=your-api-key-here' >> ~/.zshrc
|
|
189
|
+
|
|
190
|
+
š Restart your IDE
|
|
191
|
+
After setting the API key, restart your AI IDE to load the new configuration.
|
|
192
|
+
|
|
193
|
+
š§Ŗ Test the installation
|
|
194
|
+
Try these commands in your AI IDE:
|
|
195
|
+
- "Translate 'Hello world' to Spanish"
|
|
196
|
+
- "Check my translation credits"
|
|
197
|
+
- "List supported languages"
|
|
198
|
+
|
|
199
|
+
š Documentation: https://docs.i18nagent.ai
|
|
200
|
+
š Issues: https://github.com/i18n-agent/mcp-client/issues
|
|
201
|
+
`);
|
|
202
|
+
} else {
|
|
203
|
+
console.error('ā Installation failed for all IDEs. Please check the error messages above.');
|
|
204
|
+
process.exit(1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
} catch (error) {
|
|
208
|
+
console.error(`ā Installation failed: ${error.message}`);
|
|
209
|
+
process.exit(1);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// Handle command line execution
|
|
214
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
215
|
+
main();
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
export { main, IDE_CONFIGS, createMCPConfig };
|
package/mcp-client.js
ADDED
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* MCP Client for i18n-agent Translation Service
|
|
5
|
+
* Integrates with Claude Code CLI to provide translation capabilities
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
|
|
9
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
+
import {
|
|
11
|
+
CallToolRequestSchema,
|
|
12
|
+
ErrorCode,
|
|
13
|
+
ListToolsRequestSchema,
|
|
14
|
+
McpError,
|
|
15
|
+
} from '@modelcontextprotocol/sdk/types.js';
|
|
16
|
+
import axios from 'axios';
|
|
17
|
+
import fs from 'fs';
|
|
18
|
+
import path from 'path';
|
|
19
|
+
|
|
20
|
+
const server = new Server(
|
|
21
|
+
{
|
|
22
|
+
name: 'i18n-agent',
|
|
23
|
+
version: '1.0.0',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
capabilities: {
|
|
27
|
+
tools: {},
|
|
28
|
+
},
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Configuration
|
|
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://i18nagent.ai/dashboard');
|
|
40
|
+
console.error('š” Set it with: export API_KEY=your-api-key-here');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Available tools
|
|
45
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
46
|
+
return {
|
|
47
|
+
tools: [
|
|
48
|
+
{
|
|
49
|
+
name: 'translate_text',
|
|
50
|
+
description: 'Translate text from one language to another with cultural context',
|
|
51
|
+
inputSchema: {
|
|
52
|
+
type: 'object',
|
|
53
|
+
properties: {
|
|
54
|
+
texts: {
|
|
55
|
+
type: 'array',
|
|
56
|
+
items: { type: 'string' },
|
|
57
|
+
description: 'Array of texts to translate',
|
|
58
|
+
},
|
|
59
|
+
targetLanguage: {
|
|
60
|
+
type: 'string',
|
|
61
|
+
description: 'Target language code (e.g., "es", "fr", "ja", "de") or full name (e.g., "Spanish", "French")',
|
|
62
|
+
},
|
|
63
|
+
sourceLanguage: {
|
|
64
|
+
type: 'string',
|
|
65
|
+
description: 'Source language code (optional, auto-detected if not provided)',
|
|
66
|
+
default: 'auto',
|
|
67
|
+
},
|
|
68
|
+
targetAudience: {
|
|
69
|
+
type: 'string',
|
|
70
|
+
description: 'Target audience (e.g., "general", "technical", "casual", "formal")',
|
|
71
|
+
default: 'general',
|
|
72
|
+
},
|
|
73
|
+
industry: {
|
|
74
|
+
type: 'string',
|
|
75
|
+
description: 'Industry context (e.g., "technology", "healthcare", "finance", "education")',
|
|
76
|
+
default: 'technology',
|
|
77
|
+
},
|
|
78
|
+
region: {
|
|
79
|
+
type: 'string',
|
|
80
|
+
description: 'Specific region for localization (e.g., "Spain", "Mexico", "Brazil")',
|
|
81
|
+
},
|
|
82
|
+
notes: {
|
|
83
|
+
type: 'string',
|
|
84
|
+
description: 'Optional additional context or instructions for the translation (e.g., "Keep technical terms in English", "Use formal tone")',
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
required: ['texts', 'targetLanguage'],
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
name: 'list_supported_languages',
|
|
92
|
+
description: 'Get list of supported languages with quality ratings',
|
|
93
|
+
inputSchema: {
|
|
94
|
+
type: 'object',
|
|
95
|
+
properties: {
|
|
96
|
+
includeQuality: {
|
|
97
|
+
type: 'boolean',
|
|
98
|
+
description: 'Include quality ratings for each language',
|
|
99
|
+
default: true,
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
name: 'translate_file',
|
|
106
|
+
description: 'Translate file content while preserving structure and format. Supports JSON, YAML, XML, CSV, TXT, MD, and other text files',
|
|
107
|
+
inputSchema: {
|
|
108
|
+
type: 'object',
|
|
109
|
+
properties: {
|
|
110
|
+
filePath: {
|
|
111
|
+
type: 'string',
|
|
112
|
+
description: 'Path to the file to translate (required if fileContent is not provided)',
|
|
113
|
+
},
|
|
114
|
+
fileContent: {
|
|
115
|
+
type: 'string',
|
|
116
|
+
description: 'File content as string (required if filePath is not provided)',
|
|
117
|
+
},
|
|
118
|
+
fileType: {
|
|
119
|
+
type: 'string',
|
|
120
|
+
description: 'File type: json, yaml, yml, xml, csv, txt, md, html, properties',
|
|
121
|
+
enum: ['json', 'yaml', 'yml', 'xml', 'csv', 'txt', 'md', 'html', 'properties', 'auto'],
|
|
122
|
+
default: 'auto',
|
|
123
|
+
},
|
|
124
|
+
targetLanguage: {
|
|
125
|
+
type: 'string',
|
|
126
|
+
description: 'Target language code or name',
|
|
127
|
+
},
|
|
128
|
+
targetAudience: {
|
|
129
|
+
type: 'string',
|
|
130
|
+
description: 'Target audience',
|
|
131
|
+
default: 'general',
|
|
132
|
+
},
|
|
133
|
+
industry: {
|
|
134
|
+
type: 'string',
|
|
135
|
+
description: 'Industry context',
|
|
136
|
+
default: 'technology',
|
|
137
|
+
},
|
|
138
|
+
preserveKeys: {
|
|
139
|
+
type: 'boolean',
|
|
140
|
+
description: 'Whether to preserve keys/structure (for structured files)',
|
|
141
|
+
default: true,
|
|
142
|
+
},
|
|
143
|
+
outputFormat: {
|
|
144
|
+
type: 'string',
|
|
145
|
+
description: 'Output format: same, json, yaml, txt',
|
|
146
|
+
default: 'same',
|
|
147
|
+
},
|
|
148
|
+
sourceLanguage: {
|
|
149
|
+
type: 'string',
|
|
150
|
+
description: 'Source language code (auto-detected if not provided)',
|
|
151
|
+
},
|
|
152
|
+
region: {
|
|
153
|
+
type: 'string',
|
|
154
|
+
description: 'Specific region for localization (e.g., "Spain", "Mexico", "Brazil")',
|
|
155
|
+
},
|
|
156
|
+
notes: {
|
|
157
|
+
type: 'string',
|
|
158
|
+
description: 'Optional additional context or instructions for the translation (e.g., "Keep technical terms in English", "Use formal tone")',
|
|
159
|
+
},
|
|
160
|
+
},
|
|
161
|
+
required: ['targetLanguage'],
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'get_credits',
|
|
166
|
+
description: 'Get remaining credits for the user and approximate word count available at 0.001 credits per word',
|
|
167
|
+
inputSchema: {
|
|
168
|
+
type: 'object',
|
|
169
|
+
properties: {},
|
|
170
|
+
required: [],
|
|
171
|
+
},
|
|
172
|
+
},
|
|
173
|
+
],
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
// Tool execution handler
|
|
178
|
+
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
179
|
+
const { name, arguments: args } = request.params;
|
|
180
|
+
|
|
181
|
+
try {
|
|
182
|
+
switch (name) {
|
|
183
|
+
case 'translate_text':
|
|
184
|
+
return await handleTranslateText(args);
|
|
185
|
+
|
|
186
|
+
case 'list_supported_languages':
|
|
187
|
+
return await handleListLanguages(args);
|
|
188
|
+
|
|
189
|
+
case 'translate_file':
|
|
190
|
+
return await handleTranslateFile(args);
|
|
191
|
+
|
|
192
|
+
case 'get_credits':
|
|
193
|
+
return await handleGetCredits(args);
|
|
194
|
+
|
|
195
|
+
default:
|
|
196
|
+
throw new McpError(
|
|
197
|
+
ErrorCode.MethodNotFound,
|
|
198
|
+
`Unknown tool: ${name}`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
console.error(`Error executing tool ${name}:`, error);
|
|
203
|
+
throw new McpError(
|
|
204
|
+
ErrorCode.InternalError,
|
|
205
|
+
`Tool execution failed: ${error.message}`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
async function handleTranslateText(args) {
|
|
211
|
+
const { texts, targetLanguage, sourceLanguage, targetAudience = 'general', industry = 'technology', region } = args;
|
|
212
|
+
|
|
213
|
+
if (!texts || !Array.isArray(texts) || texts.length === 0) {
|
|
214
|
+
throw new Error('texts must be a non-empty array');
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!targetLanguage) {
|
|
218
|
+
throw new Error('targetLanguage is required');
|
|
219
|
+
}
|
|
220
|
+
|
|
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,
|
|
229
|
+
};
|
|
230
|
+
|
|
231
|
+
try {
|
|
232
|
+
const response = await axios.post(`${MCP_SERVER_URL}/translate`, requestData, {
|
|
233
|
+
headers: {
|
|
234
|
+
'Content-Type': 'application/json',
|
|
235
|
+
},
|
|
236
|
+
timeout: 60000, // 60 second timeout
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
if (response.data.error) {
|
|
240
|
+
throw new Error(`Translation service error: ${response.data.error}`);
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Direct API response format: { translatedTexts: [...], ... }
|
|
244
|
+
const parsedResult = response.data;
|
|
245
|
+
|
|
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
|
+
};
|
|
265
|
+
} catch (error) {
|
|
266
|
+
if (error.code === 'ECONNABORTED') {
|
|
267
|
+
throw new Error('Translation request timed out. The service may be processing a large request.');
|
|
268
|
+
}
|
|
269
|
+
throw new Error(`Translation service unavailable: ${error.message}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function handleListLanguages(args) {
|
|
274
|
+
const { includeQuality = true } = args;
|
|
275
|
+
|
|
276
|
+
// Language support matrix based on GPT-OSS analysis
|
|
277
|
+
const languages = {
|
|
278
|
+
'Tier 1 - Production Ready (Excellent Quality 80-90%)': {
|
|
279
|
+
'en': 'English',
|
|
280
|
+
'es': 'Spanish',
|
|
281
|
+
'fr': 'French',
|
|
282
|
+
'de': 'German',
|
|
283
|
+
'it': 'Italian',
|
|
284
|
+
'pt': 'Portuguese',
|
|
285
|
+
'nl': 'Dutch',
|
|
286
|
+
},
|
|
287
|
+
'Tier 2 - Production Viable (Good Quality 50-75%)': {
|
|
288
|
+
'ru': 'Russian',
|
|
289
|
+
'zh-CN': 'Chinese (Simplified)',
|
|
290
|
+
'ja': 'Japanese',
|
|
291
|
+
'ko': 'Korean',
|
|
292
|
+
'ar': 'Arabic',
|
|
293
|
+
'he': 'Hebrew',
|
|
294
|
+
'hi': 'Hindi',
|
|
295
|
+
'pl': 'Polish',
|
|
296
|
+
'cs': 'Czech',
|
|
297
|
+
},
|
|
298
|
+
'Tier 3 - Basic Support (Use with Caution 20-50%)': {
|
|
299
|
+
'zh-TW': 'Chinese (Traditional)',
|
|
300
|
+
'th': 'Thai',
|
|
301
|
+
'vi': 'Vietnamese',
|
|
302
|
+
'sv': 'Swedish',
|
|
303
|
+
'da': 'Danish',
|
|
304
|
+
'no': 'Norwegian',
|
|
305
|
+
'fi': 'Finnish',
|
|
306
|
+
'tr': 'Turkish',
|
|
307
|
+
'hu': 'Hungarian',
|
|
308
|
+
},
|
|
309
|
+
};
|
|
310
|
+
|
|
311
|
+
let content = 'š Supported Languages\n';
|
|
312
|
+
content += '===================\n\n';
|
|
313
|
+
|
|
314
|
+
if (includeQuality) {
|
|
315
|
+
for (const [tier, langs] of Object.entries(languages)) {
|
|
316
|
+
content += `## ${tier}\n`;
|
|
317
|
+
for (const [code, name] of Object.entries(langs)) {
|
|
318
|
+
content += `- \`${code}\`: ${name}\n`;
|
|
319
|
+
}
|
|
320
|
+
content += '\n';
|
|
321
|
+
}
|
|
322
|
+
} else {
|
|
323
|
+
const allLanguages = Object.values(languages).reduce((acc, tier) => ({ ...acc, ...tier }), {});
|
|
324
|
+
for (const [code, name] of Object.entries(allLanguages)) {
|
|
325
|
+
content += `- \`${code}\`: ${name}\n`;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
content += '\nš” Usage Tips:\n';
|
|
330
|
+
content += '- Use language codes (e.g., "es") or full names (e.g., "Spanish")\n';
|
|
331
|
+
content += '- Tier 1 languages are recommended for production use\n';
|
|
332
|
+
content += '- Tier 2 languages work well with human review\n';
|
|
333
|
+
content += '- Tier 3 languages provide basic translation quality\n';
|
|
334
|
+
|
|
335
|
+
return {
|
|
336
|
+
content: [
|
|
337
|
+
{
|
|
338
|
+
type: 'text',
|
|
339
|
+
text: content,
|
|
340
|
+
},
|
|
341
|
+
],
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function handleTranslateFile(args) {
|
|
346
|
+
const {
|
|
347
|
+
filePath,
|
|
348
|
+
fileContent,
|
|
349
|
+
fileType = 'auto',
|
|
350
|
+
targetLanguage,
|
|
351
|
+
targetAudience = 'general',
|
|
352
|
+
industry = 'technology',
|
|
353
|
+
preserveKeys = true,
|
|
354
|
+
outputFormat = 'same',
|
|
355
|
+
sourceLanguage,
|
|
356
|
+
region
|
|
357
|
+
} = args;
|
|
358
|
+
|
|
359
|
+
if (!filePath && !fileContent) {
|
|
360
|
+
throw new Error('Either filePath or fileContent must be provided');
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
if (!targetLanguage) {
|
|
364
|
+
throw new Error('targetLanguage is required');
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Read file content if path provided and no content given
|
|
368
|
+
let content = fileContent;
|
|
369
|
+
|
|
370
|
+
if (filePath && !fileContent) {
|
|
371
|
+
try {
|
|
372
|
+
content = fs.readFileSync(filePath, 'utf8');
|
|
373
|
+
} catch (error) {
|
|
374
|
+
throw new Error(`Failed to read file: ${error.message}`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
// Use MCP JSON-RPC protocol for translate_file
|
|
379
|
+
const mcpRequest = {
|
|
380
|
+
jsonrpc: '2.0',
|
|
381
|
+
id: Date.now(),
|
|
382
|
+
method: 'tools/call',
|
|
383
|
+
params: {
|
|
384
|
+
name: 'translate_file',
|
|
385
|
+
arguments: {
|
|
386
|
+
apiKey: API_KEY,
|
|
387
|
+
filePath,
|
|
388
|
+
fileContent: content,
|
|
389
|
+
fileType,
|
|
390
|
+
targetLanguage,
|
|
391
|
+
sourceLanguage,
|
|
392
|
+
targetAudience,
|
|
393
|
+
industry,
|
|
394
|
+
region,
|
|
395
|
+
preserveKeys,
|
|
396
|
+
outputFormat
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
try {
|
|
402
|
+
const response = await axios.post(MCP_SERVER_URL, mcpRequest, {
|
|
403
|
+
headers: {
|
|
404
|
+
'Content-Type': 'application/json',
|
|
405
|
+
},
|
|
406
|
+
timeout: 60000,
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
if (response.data.error) {
|
|
410
|
+
throw new Error(`Translation service error: ${response.data.error.message || response.data.error}`);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
// MCP response format
|
|
414
|
+
const result = response.data.result;
|
|
415
|
+
return result;
|
|
416
|
+
|
|
417
|
+
} catch (error) {
|
|
418
|
+
if (error.code === 'ECONNABORTED') {
|
|
419
|
+
throw new Error('Translation request timed out. The service may be processing a large request.');
|
|
420
|
+
}
|
|
421
|
+
throw new Error(`Translation service unavailable: ${error.message}`);
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
async function handleGetCredits(args) {
|
|
426
|
+
try {
|
|
427
|
+
const response = await axios.post(`${MCP_SERVER_URL}/api/mcp`, {
|
|
428
|
+
name: 'get_credits',
|
|
429
|
+
arguments: {
|
|
430
|
+
apiKey: API_KEY,
|
|
431
|
+
}
|
|
432
|
+
}, {
|
|
433
|
+
headers: {
|
|
434
|
+
'Content-Type': 'application/json'
|
|
435
|
+
},
|
|
436
|
+
timeout: 10000
|
|
437
|
+
});
|
|
438
|
+
|
|
439
|
+
const result = response.data;
|
|
440
|
+
|
|
441
|
+
if (result.isError) {
|
|
442
|
+
throw new Error(result.content[0].text);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const creditsInfo = JSON.parse(result.content[0].text);
|
|
446
|
+
|
|
447
|
+
return {
|
|
448
|
+
content: [
|
|
449
|
+
{
|
|
450
|
+
type: 'text',
|
|
451
|
+
text: `š° **Credits Information**
|
|
452
|
+
|
|
453
|
+
š¢ **Team**: ${creditsInfo.teamName}
|
|
454
|
+
š³ **Credits Remaining**: ${creditsInfo.creditsRemaining}
|
|
455
|
+
š **Approximate Words Available**: ${creditsInfo.approximateWordsAvailable.toLocaleString()}
|
|
456
|
+
šµ **Cost per Word**: ${creditsInfo.costPerWord} credits
|
|
457
|
+
ā° **Last Updated**: ${new Date(creditsInfo.timestamp).toLocaleString()}
|
|
458
|
+
|
|
459
|
+
Note: Word count is approximate and may vary based on actual content complexity and translation requirements.`,
|
|
460
|
+
},
|
|
461
|
+
],
|
|
462
|
+
};
|
|
463
|
+
} catch (error) {
|
|
464
|
+
console.error('Credits check error:', error);
|
|
465
|
+
throw new Error(`Unable to check credits: ${error.message}`);
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
function detectFileType(filePath, content) {
|
|
470
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
471
|
+
|
|
472
|
+
switch (ext) {
|
|
473
|
+
case '.json': return 'json';
|
|
474
|
+
case '.yaml': case '.yml': return 'yaml';
|
|
475
|
+
case '.xml': case '.svg': return 'xml';
|
|
476
|
+
case '.csv': return 'csv';
|
|
477
|
+
case '.md': return 'md';
|
|
478
|
+
case '.html': case '.htm': return 'html';
|
|
479
|
+
case '.properties': return 'properties';
|
|
480
|
+
case '.txt': return 'txt';
|
|
481
|
+
default: return detectFileTypeFromContent(content);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
function detectFileTypeFromContent(content) {
|
|
486
|
+
const trimmed = content.trim();
|
|
487
|
+
|
|
488
|
+
if (trimmed.startsWith('{') || trimmed.startsWith('[')) {
|
|
489
|
+
try { JSON.parse(trimmed); return 'json'; } catch {}
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
if (trimmed.match(/^---\s*$|^\s*\w+:\s*[|\->\s]|^\s*\w+:\s*.+$/m)) {
|
|
493
|
+
return 'yaml';
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (trimmed.startsWith('<') && trimmed.includes('>')) {
|
|
497
|
+
return 'xml';
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
if (trimmed.includes(',') && trimmed.split('\n').length > 1) {
|
|
501
|
+
return 'csv';
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
if (trimmed.includes('#') || trimmed.includes('**') || trimmed.includes('`')) {
|
|
505
|
+
return 'md';
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (trimmed.includes('=') && trimmed.split('\n').some(line => line.includes('='))) {
|
|
509
|
+
return 'properties';
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
return 'txt';
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
async function extractTextsFromFile(content, fileType, preserveKeys) {
|
|
516
|
+
const texts = [];
|
|
517
|
+
let structure = {};
|
|
518
|
+
|
|
519
|
+
switch (fileType) {
|
|
520
|
+
case 'json':
|
|
521
|
+
const jsonData = JSON.parse(content);
|
|
522
|
+
structure = { type: 'json', keys: [] };
|
|
523
|
+
extractFromJson(jsonData, texts, structure.keys);
|
|
524
|
+
break;
|
|
525
|
+
|
|
526
|
+
case 'yaml':
|
|
527
|
+
case 'yml':
|
|
528
|
+
// Simple YAML parsing - extract values after colons
|
|
529
|
+
structure = { type: 'yaml', lines: content.split('\n') };
|
|
530
|
+
const yamlLines = content.split('\n');
|
|
531
|
+
yamlLines.forEach((line, index) => {
|
|
532
|
+
const match = line.match(/^(\s*)([^:]+):\s*(.+)$/);
|
|
533
|
+
if (match && match[3] && !match[3].match(/^[|\->]/)) {
|
|
534
|
+
const value = match[3].replace(/^["']|["']$/g, '');
|
|
535
|
+
if (value && !isNumericOrBoolean(value)) {
|
|
536
|
+
texts.push(value);
|
|
537
|
+
structure.lines[index] = { original: line, textIndex: texts.length - 1 };
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
});
|
|
541
|
+
break;
|
|
542
|
+
|
|
543
|
+
case 'xml':
|
|
544
|
+
case 'html':
|
|
545
|
+
structure = { type: fileType, content: content };
|
|
546
|
+
// Extract text content between tags
|
|
547
|
+
const xmlMatches = content.matchAll(/>([^<]+)</g);
|
|
548
|
+
for (const match of xmlMatches) {
|
|
549
|
+
const text = match[1].trim();
|
|
550
|
+
if (text && !isNumericOrBoolean(text)) {
|
|
551
|
+
texts.push(text);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
break;
|
|
555
|
+
|
|
556
|
+
case 'csv':
|
|
557
|
+
structure = { type: 'csv', rows: [] };
|
|
558
|
+
const csvLines = content.split('\n');
|
|
559
|
+
csvLines.forEach((line, rowIndex) => {
|
|
560
|
+
if (line.trim()) {
|
|
561
|
+
const cells = parseCsvLine(line);
|
|
562
|
+
structure.rows[rowIndex] = [];
|
|
563
|
+
cells.forEach((cell, colIndex) => {
|
|
564
|
+
if (cell && !isNumericOrBoolean(cell)) {
|
|
565
|
+
texts.push(cell);
|
|
566
|
+
structure.rows[rowIndex][colIndex] = texts.length - 1;
|
|
567
|
+
} else {
|
|
568
|
+
structure.rows[rowIndex][colIndex] = null;
|
|
569
|
+
}
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
});
|
|
573
|
+
break;
|
|
574
|
+
|
|
575
|
+
case 'properties':
|
|
576
|
+
structure = { type: 'properties', lines: content.split('\n') };
|
|
577
|
+
const propLines = content.split('\n');
|
|
578
|
+
propLines.forEach((line, index) => {
|
|
579
|
+
const match = line.match(/^([^=]+)=(.*)$/);
|
|
580
|
+
if (match && match[2]) {
|
|
581
|
+
texts.push(match[2]);
|
|
582
|
+
structure.lines[index] = { key: match[1], textIndex: texts.length - 1 };
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
break;
|
|
586
|
+
|
|
587
|
+
case 'md':
|
|
588
|
+
structure = { type: 'md', content: content };
|
|
589
|
+
// Extract text content, avoiding code blocks
|
|
590
|
+
const mdText = content
|
|
591
|
+
.replace(/```[\s\S]*?```/g, '') // Remove code blocks
|
|
592
|
+
.replace(/`[^`]+`/g, '') // Remove inline code
|
|
593
|
+
.replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // Extract link text
|
|
594
|
+
.replace(/[#*_`]/g, '') // Remove markdown formatting
|
|
595
|
+
.split('\n')
|
|
596
|
+
.filter(line => line.trim())
|
|
597
|
+
.join(' ');
|
|
598
|
+
|
|
599
|
+
if (mdText.trim()) {
|
|
600
|
+
texts.push(mdText.trim());
|
|
601
|
+
}
|
|
602
|
+
break;
|
|
603
|
+
|
|
604
|
+
default: // txt and others
|
|
605
|
+
structure = { type: 'txt', content: content };
|
|
606
|
+
const cleanText = content.trim();
|
|
607
|
+
if (cleanText) {
|
|
608
|
+
texts.push(cleanText);
|
|
609
|
+
}
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
return { texts, structure };
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
function extractFromJson(obj, texts, keys, prefix = '') {
|
|
617
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
618
|
+
const fullKey = prefix ? `${prefix}.${key}` : key;
|
|
619
|
+
if (typeof value === 'string' && value.trim()) {
|
|
620
|
+
texts.push(value);
|
|
621
|
+
keys.push(fullKey);
|
|
622
|
+
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
623
|
+
extractFromJson(value, texts, keys, fullKey);
|
|
624
|
+
} else if (Array.isArray(value)) {
|
|
625
|
+
value.forEach((item, index) => {
|
|
626
|
+
if (typeof item === 'string' && item.trim()) {
|
|
627
|
+
texts.push(item);
|
|
628
|
+
keys.push(`${fullKey}[${index}]`);
|
|
629
|
+
} else if (typeof item === 'object' && item !== null) {
|
|
630
|
+
extractFromJson(item, texts, keys, `${fullKey}[${index}]`);
|
|
631
|
+
}
|
|
632
|
+
});
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
function extractTranslatedTexts(translationResult) {
|
|
638
|
+
const translatedTexts = [];
|
|
639
|
+
const lines = translationResult.split('\n');
|
|
640
|
+
const translationSection = lines.findIndex(line => line.includes('š Translations:'));
|
|
641
|
+
|
|
642
|
+
if (translationSection !== -1) {
|
|
643
|
+
for (let i = translationSection + 1; i < lines.length; i++) {
|
|
644
|
+
const match = lines[i].match(/\d+\. ".*?" ā "(.*)"/);
|
|
645
|
+
if (match) {
|
|
646
|
+
translatedTexts.push(match[1]);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
return translatedTexts;
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
async function reconstructFile(originalContent, translatedTexts, structure, fileType, outputFormat) {
|
|
655
|
+
const format = outputFormat === 'same' ? fileType : outputFormat;
|
|
656
|
+
let textIndex = 0;
|
|
657
|
+
|
|
658
|
+
switch (format) {
|
|
659
|
+
case 'json':
|
|
660
|
+
if (structure.type === 'json') {
|
|
661
|
+
const jsonData = JSON.parse(originalContent);
|
|
662
|
+
replaceJsonStrings(jsonData, translatedTexts, textIndex);
|
|
663
|
+
return JSON.stringify(jsonData, null, 2);
|
|
664
|
+
} else {
|
|
665
|
+
// Convert other formats to JSON
|
|
666
|
+
const jsonObj = {};
|
|
667
|
+
translatedTexts.forEach((text, i) => {
|
|
668
|
+
jsonObj[`text_${i + 1}`] = text;
|
|
669
|
+
});
|
|
670
|
+
return JSON.stringify(jsonObj, null, 2);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
case 'yaml':
|
|
674
|
+
if (structure.type === 'yaml') {
|
|
675
|
+
const lines = [...structure.lines];
|
|
676
|
+
lines.forEach((lineInfo, index) => {
|
|
677
|
+
if (typeof lineInfo === 'object' && lineInfo.textIndex !== undefined) {
|
|
678
|
+
const match = lineInfo.original.match(/^(\s*)([^:]+):\s*(.+)$/);
|
|
679
|
+
if (match) {
|
|
680
|
+
lines[index] = `${match[1]}${match[2]}: "${translatedTexts[lineInfo.textIndex]}"`;
|
|
681
|
+
}
|
|
682
|
+
} else if (typeof lineInfo === 'string') {
|
|
683
|
+
lines[index] = lineInfo;
|
|
684
|
+
}
|
|
685
|
+
});
|
|
686
|
+
return lines.join('\n');
|
|
687
|
+
}
|
|
688
|
+
break;
|
|
689
|
+
|
|
690
|
+
case 'csv':
|
|
691
|
+
if (structure.type === 'csv') {
|
|
692
|
+
return structure.rows.map(row => {
|
|
693
|
+
return row.map(cellIndex => {
|
|
694
|
+
return cellIndex !== null ? `"${translatedTexts[cellIndex]}"` : '';
|
|
695
|
+
}).join(',');
|
|
696
|
+
}).join('\n');
|
|
697
|
+
}
|
|
698
|
+
break;
|
|
699
|
+
|
|
700
|
+
case 'properties':
|
|
701
|
+
if (structure.type === 'properties') {
|
|
702
|
+
const lines = structure.lines.map(lineInfo => {
|
|
703
|
+
if (typeof lineInfo === 'object' && lineInfo.textIndex !== undefined) {
|
|
704
|
+
return `${lineInfo.key}=${translatedTexts[lineInfo.textIndex]}`;
|
|
705
|
+
}
|
|
706
|
+
return lineInfo;
|
|
707
|
+
});
|
|
708
|
+
return lines.join('\n');
|
|
709
|
+
}
|
|
710
|
+
break;
|
|
711
|
+
|
|
712
|
+
case 'txt':
|
|
713
|
+
return translatedTexts.join('\n\n');
|
|
714
|
+
|
|
715
|
+
default:
|
|
716
|
+
return translatedTexts.join('\n');
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Fallback for unsupported combinations
|
|
720
|
+
return translatedTexts.join('\n');
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
function replaceJsonStrings(obj, translatedTexts, startIndex = 0) {
|
|
724
|
+
let currentIndex = startIndex;
|
|
725
|
+
|
|
726
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
727
|
+
if (typeof value === 'string' && value.trim()) {
|
|
728
|
+
if (currentIndex < translatedTexts.length) {
|
|
729
|
+
obj[key] = translatedTexts[currentIndex];
|
|
730
|
+
currentIndex++;
|
|
731
|
+
}
|
|
732
|
+
} else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
733
|
+
currentIndex = replaceJsonStrings(value, translatedTexts, currentIndex);
|
|
734
|
+
} else if (Array.isArray(value)) {
|
|
735
|
+
value.forEach((item, index) => {
|
|
736
|
+
if (typeof item === 'string' && item.trim()) {
|
|
737
|
+
if (currentIndex < translatedTexts.length) {
|
|
738
|
+
value[index] = translatedTexts[currentIndex];
|
|
739
|
+
currentIndex++;
|
|
740
|
+
}
|
|
741
|
+
} else if (typeof item === 'object' && item !== null) {
|
|
742
|
+
currentIndex = replaceJsonStrings(item, translatedTexts, currentIndex);
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
|
|
748
|
+
return currentIndex;
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
function parseCsvLine(line) {
|
|
752
|
+
const result = [];
|
|
753
|
+
let current = '';
|
|
754
|
+
let inQuotes = false;
|
|
755
|
+
|
|
756
|
+
for (let i = 0; i < line.length; i++) {
|
|
757
|
+
const char = line[i];
|
|
758
|
+
if (char === '"') {
|
|
759
|
+
inQuotes = !inQuotes;
|
|
760
|
+
} else if (char === ',' && !inQuotes) {
|
|
761
|
+
result.push(current.trim().replace(/^"|"$/g, ''));
|
|
762
|
+
current = '';
|
|
763
|
+
} else {
|
|
764
|
+
current += char;
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
|
|
768
|
+
result.push(current.trim().replace(/^"|"$/g, ''));
|
|
769
|
+
return result;
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
function isNumericOrBoolean(value) {
|
|
773
|
+
return /^\d+$/.test(value) ||
|
|
774
|
+
/^\d+\.\d+$/.test(value) ||
|
|
775
|
+
value === 'true' ||
|
|
776
|
+
value === 'false' ||
|
|
777
|
+
value === 'null' ||
|
|
778
|
+
value === 'undefined';
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
function getCodeBlockLanguage(fileType) {
|
|
782
|
+
const languageMap = {
|
|
783
|
+
'json': 'json',
|
|
784
|
+
'yaml': 'yaml',
|
|
785
|
+
'yml': 'yaml',
|
|
786
|
+
'xml': 'xml',
|
|
787
|
+
'html': 'html',
|
|
788
|
+
'csv': 'csv',
|
|
789
|
+
'md': 'markdown',
|
|
790
|
+
'properties': 'properties',
|
|
791
|
+
'txt': 'text'
|
|
792
|
+
};
|
|
793
|
+
return languageMap[fileType] || 'text';
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// Start the server
|
|
797
|
+
async function main() {
|
|
798
|
+
const transport = new StdioServerTransport();
|
|
799
|
+
await server.connect(transport);
|
|
800
|
+
console.error('i18n-agent MCP server running...');
|
|
801
|
+
console.error('MCP_SERVER_URL:', MCP_SERVER_URL);
|
|
802
|
+
console.error('API_KEY:', API_KEY ? 'Set ā' : 'Not set ā');
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
main().catch((error) => {
|
|
806
|
+
console.error('Failed to start server:', error);
|
|
807
|
+
process.exit(1);
|
|
808
|
+
});
|
package/package.json
CHANGED
|
@@ -1,29 +1,51 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@i18n-agent/mcp-client",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "
|
|
5
|
-
"main": "
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "MCP client for i18n-agent translation service - supports Claude, Cursor, VS Code, and other AI IDEs",
|
|
5
|
+
"main": "mcp-client.js",
|
|
6
6
|
"bin": {
|
|
7
|
-
"i18n-agent-
|
|
7
|
+
"i18n-agent-install": "./install.js"
|
|
8
8
|
},
|
|
9
|
+
"type": "module",
|
|
9
10
|
"scripts": {
|
|
10
|
-
"
|
|
11
|
+
"install": "node install.js",
|
|
12
|
+
"test": "node test.js",
|
|
13
|
+
"postinstall": "echo '\nš i18n-agent MCP Client installed successfully!\nš” Run: npx @i18n-agent/mcp-client install\n'"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git+https://github.com/i18n-agent/mcp-client.git"
|
|
11
18
|
},
|
|
12
19
|
"keywords": [
|
|
13
20
|
"i18n",
|
|
14
21
|
"translation",
|
|
15
22
|
"mcp",
|
|
16
|
-
"claude
|
|
17
|
-
"gemini",
|
|
23
|
+
"claude",
|
|
18
24
|
"cursor",
|
|
19
|
-
"
|
|
25
|
+
"vscode",
|
|
26
|
+
"ai",
|
|
27
|
+
"internationalization",
|
|
28
|
+
"localization"
|
|
20
29
|
],
|
|
21
|
-
"author": "
|
|
30
|
+
"author": "FatCouple OĆ",
|
|
22
31
|
"license": "MIT",
|
|
23
|
-
"
|
|
24
|
-
"
|
|
25
|
-
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/i18n-agent/mcp-client/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://i18nagent.ai",
|
|
36
|
+
"dependencies": {
|
|
37
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
38
|
+
"axios": "^1.6.0"
|
|
26
39
|
},
|
|
40
|
+
"engines": {
|
|
41
|
+
"node": ">=16.0.0"
|
|
42
|
+
},
|
|
43
|
+
"files": [
|
|
44
|
+
"mcp-client.js",
|
|
45
|
+
"install.js",
|
|
46
|
+
"README.md",
|
|
47
|
+
"LICENSE"
|
|
48
|
+
],
|
|
27
49
|
"publishConfig": {
|
|
28
50
|
"access": "public"
|
|
29
51
|
}
|
package/index.js
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
console.log('ā ļø Please use: npx i18n-agent-mcp-client install');
|
|
4
|
-
console.log('š Documentation: https://github.com/i18n-agent/mcp-client');
|
|
5
|
-
console.log('');
|
|
6
|
-
console.log('This package redirects to the main installer.');
|
|
7
|
-
console.log('The scoped package @i18n-agent/mcp-client is a placeholder.');
|
|
8
|
-
process.exit(1);
|