@pipeworx/mcp-libretranslate 0.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.
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/package.json +20 -0
- package/server.json +18 -0
- package/src/index.ts +189 -0
- package/tsconfig.json +14 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pipeworx
|
|
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,57 @@
|
|
|
1
|
+
# mcp-libretranslate
|
|
2
|
+
|
|
3
|
+
LibreTranslate MCP — open-source machine translation (BYO endpoint)
|
|
4
|
+
|
|
5
|
+
Part of [Pipeworx](https://pipeworx.io) — an MCP gateway connecting AI agents to 250+ live data sources.
|
|
6
|
+
|
|
7
|
+
## Tools
|
|
8
|
+
|
|
9
|
+
| Tool | Description |
|
|
10
|
+
|------|-------------|
|
|
11
|
+
| `detect_language` | Detect the source language of a piece of text. Returns ranked language candidates with confidence. |
|
|
12
|
+
| `list_languages` | List languages supported by the configured LibreTranslate instance. |
|
|
13
|
+
|
|
14
|
+
## Quick Start
|
|
15
|
+
|
|
16
|
+
Add to your MCP client (Claude Desktop, Cursor, Windsurf, etc.):
|
|
17
|
+
|
|
18
|
+
```json
|
|
19
|
+
{
|
|
20
|
+
"mcpServers": {
|
|
21
|
+
"libretranslate": {
|
|
22
|
+
"url": "https://gateway.pipeworx.io/libretranslate/mcp"
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Or connect to the full Pipeworx gateway for access to all 250+ data sources:
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"mcpServers": {
|
|
33
|
+
"pipeworx": {
|
|
34
|
+
"url": "https://gateway.pipeworx.io/mcp"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Using with ask_pipeworx
|
|
41
|
+
|
|
42
|
+
Instead of calling tools directly, you can ask questions in plain English:
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
ask_pipeworx({ question: "your question about Libretranslate data" })
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The gateway picks the right tool and fills the arguments automatically.
|
|
49
|
+
|
|
50
|
+
## More
|
|
51
|
+
|
|
52
|
+
- [All tools and guides](https://github.com/pipeworx-io/examples)
|
|
53
|
+
- [pipeworx.io](https://pipeworx.io)
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pipeworx/mcp-libretranslate",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "LibreTranslate MCP — open-source machine translation (BYO endpoint)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.ts",
|
|
7
|
+
"types": "src/index.ts",
|
|
8
|
+
"keywords": ["mcp", "mcp-server", "model-context-protocol", "pipeworx", "libretranslate"],
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/pipeworx-io/mcp-libretranslate"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"typecheck": "tsc --noEmit"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"typescript": "^5.7.0"
|
|
19
|
+
}
|
|
20
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.pipeworx-io/libretranslate",
|
|
4
|
+
"title": "Libretranslate",
|
|
5
|
+
"description": "LibreTranslate MCP — open-source machine translation (BYO endpoint)",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"websiteUrl": "https://pipeworx.io/packs/libretranslate",
|
|
8
|
+
"repository": {
|
|
9
|
+
"url": "https://github.com/pipeworx-io/mcp-libretranslate",
|
|
10
|
+
"source": "github"
|
|
11
|
+
},
|
|
12
|
+
"remotes": [
|
|
13
|
+
{
|
|
14
|
+
"type": "streamable-http",
|
|
15
|
+
"url": "https://gateway.pipeworx.io/libretranslate/mcp"
|
|
16
|
+
}
|
|
17
|
+
]
|
|
18
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
interface McpToolDefinition {
|
|
2
|
+
name: string;
|
|
3
|
+
description: string;
|
|
4
|
+
inputSchema: {
|
|
5
|
+
type: 'object';
|
|
6
|
+
properties: Record<string, unknown>;
|
|
7
|
+
required?: string[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface McpToolExport {
|
|
12
|
+
tools: McpToolDefinition[];
|
|
13
|
+
callTool: (name: string, args: Record<string, unknown>) => Promise<unknown>;
|
|
14
|
+
meter?: { credits: number };
|
|
15
|
+
cost?: Record<string, unknown>;
|
|
16
|
+
provider?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* LibreTranslate MCP — open-source machine translation (BYO endpoint)
|
|
21
|
+
*
|
|
22
|
+
* Important: the free public LibreTranslate instances have largely shut down
|
|
23
|
+
* or migrated to paid plans. This pack is a thin abstraction — you point it
|
|
24
|
+
* at any LibreTranslate-compatible instance:
|
|
25
|
+
* - self-hosted (https://github.com/LibreTranslate/LibreTranslate)
|
|
26
|
+
* - paid: https://libretranslate.com (BYO their API key)
|
|
27
|
+
* - a community instance that's still up
|
|
28
|
+
*
|
|
29
|
+
* API (LibreTranslate v1): https://github.com/LibreTranslate/LibreTranslate#api
|
|
30
|
+
*
|
|
31
|
+
* Tools:
|
|
32
|
+
* - translate: translate text between languages
|
|
33
|
+
* - detect_language: detect source language of a text
|
|
34
|
+
* - list_languages: languages supported by the configured instance
|
|
35
|
+
*/
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
const tools: McpToolExport['tools'] = [
|
|
39
|
+
{
|
|
40
|
+
name: 'translate',
|
|
41
|
+
description:
|
|
42
|
+
'Translate text. Source can be "auto" to auto-detect. Returns translated text and the detected source (if applicable).',
|
|
43
|
+
inputSchema: {
|
|
44
|
+
type: 'object',
|
|
45
|
+
properties: {
|
|
46
|
+
text: { type: 'string', description: 'Text to translate' },
|
|
47
|
+
source: { type: 'string', description: 'Source language code or "auto" (default "auto")' },
|
|
48
|
+
target: { type: 'string', description: 'Target language code (e.g., "es", "fr", "ja")' },
|
|
49
|
+
format: { type: 'string', description: 'text (default) | html', enum: ['text', 'html'] },
|
|
50
|
+
},
|
|
51
|
+
required: ['text', 'target'],
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'detect_language',
|
|
56
|
+
description: 'Detect the source language of a piece of text. Returns ranked language candidates with confidence.',
|
|
57
|
+
inputSchema: {
|
|
58
|
+
type: 'object',
|
|
59
|
+
properties: {
|
|
60
|
+
text: { type: 'string', description: 'Text to analyze' },
|
|
61
|
+
},
|
|
62
|
+
required: ['text'],
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
name: 'list_languages',
|
|
67
|
+
description: 'List languages supported by the configured LibreTranslate instance.',
|
|
68
|
+
inputSchema: { type: 'object', properties: {}, required: [] },
|
|
69
|
+
},
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
async function callTool(name: string, args: Record<string, unknown>): Promise<unknown> {
|
|
73
|
+
const endpoint = resolveEndpoint(args);
|
|
74
|
+
const apiKey = (args._apiKey as string | undefined)?.trim();
|
|
75
|
+
|
|
76
|
+
switch (name) {
|
|
77
|
+
case 'translate':
|
|
78
|
+
return translate(
|
|
79
|
+
endpoint,
|
|
80
|
+
apiKey,
|
|
81
|
+
reqStr(args, 'text', '"hello world"'),
|
|
82
|
+
(args.source as string) ?? 'auto',
|
|
83
|
+
reqStr(args, 'target', '"es"'),
|
|
84
|
+
(args.format as string) ?? 'text',
|
|
85
|
+
);
|
|
86
|
+
case 'detect_language':
|
|
87
|
+
return detectLanguage(endpoint, apiKey, reqStr(args, 'text', '"bonjour le monde"'));
|
|
88
|
+
case 'list_languages':
|
|
89
|
+
return listLanguages(endpoint, apiKey);
|
|
90
|
+
default:
|
|
91
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function reqStr(args: Record<string, unknown>, key: string, example: string): string {
|
|
96
|
+
const v = args[key];
|
|
97
|
+
if (typeof v !== 'string' || !v.trim()) {
|
|
98
|
+
throw new Error(`Required argument "${key}" is missing or empty. Pass a string like ${example}.`);
|
|
99
|
+
}
|
|
100
|
+
return v;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function resolveEndpoint(args: Record<string, unknown>): string {
|
|
104
|
+
const ep = (args._endpoint as string | undefined)?.trim();
|
|
105
|
+
if (!ep) {
|
|
106
|
+
throw new Error(
|
|
107
|
+
'LibreTranslate requires a LibreTranslate-compatible endpoint URL. Set it via the gateway PLATFORM_LIBRETRANSLATE_ENDPOINT env var, or pass ?_endpoint=https://your-instance/ on the gateway URL. Public free instances have largely shut down — self-host or use libretranslate.com (paid).',
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
return ep.replace(/\/+$/, '');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function ltPost<T>(endpoint: string, path: string, body: Record<string, string>, apiKey?: string): Promise<T> {
|
|
114
|
+
const params = new URLSearchParams(body);
|
|
115
|
+
if (apiKey) params.set('api_key', apiKey);
|
|
116
|
+
|
|
117
|
+
const res = await fetch(`${endpoint}${path}`, {
|
|
118
|
+
method: 'POST',
|
|
119
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded', Accept: 'application/json' },
|
|
120
|
+
body: params.toString(),
|
|
121
|
+
});
|
|
122
|
+
if (res.status === 401 || res.status === 403) throw new Error('LibreTranslate: unauthorized — check the API key');
|
|
123
|
+
if (res.status === 429) throw new Error('LibreTranslate: rate-limit (HTTP 429)');
|
|
124
|
+
if (!res.ok) {
|
|
125
|
+
const text = await res.text();
|
|
126
|
+
throw new Error(`LibreTranslate error: ${res.status} ${text.slice(0, 200)}`);
|
|
127
|
+
}
|
|
128
|
+
return res.json() as Promise<T>;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function ltGet<T>(endpoint: string, path: string, apiKey?: string): Promise<T> {
|
|
132
|
+
const url = apiKey ? `${endpoint}${path}?api_key=${encodeURIComponent(apiKey)}` : `${endpoint}${path}`;
|
|
133
|
+
const res = await fetch(url, { headers: { Accept: 'application/json' } });
|
|
134
|
+
if (!res.ok) {
|
|
135
|
+
const text = await res.text();
|
|
136
|
+
throw new Error(`LibreTranslate error: ${res.status} ${text.slice(0, 200)}`);
|
|
137
|
+
}
|
|
138
|
+
return res.json() as Promise<T>;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
async function translate(
|
|
142
|
+
endpoint: string,
|
|
143
|
+
apiKey: string | undefined,
|
|
144
|
+
text: string,
|
|
145
|
+
source: string,
|
|
146
|
+
target: string,
|
|
147
|
+
format: string,
|
|
148
|
+
) {
|
|
149
|
+
const data = await ltPost<{ translatedText?: string; detectedLanguage?: { confidence?: number; language?: string } }>(
|
|
150
|
+
endpoint,
|
|
151
|
+
'/translate',
|
|
152
|
+
{ q: text, source, target, format },
|
|
153
|
+
apiKey,
|
|
154
|
+
);
|
|
155
|
+
return {
|
|
156
|
+
text,
|
|
157
|
+
source,
|
|
158
|
+
target,
|
|
159
|
+
format,
|
|
160
|
+
translated_text: data.translatedText ?? null,
|
|
161
|
+
detected_source: data.detectedLanguage?.language ?? null,
|
|
162
|
+
detected_confidence: data.detectedLanguage?.confidence ?? null,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function detectLanguage(endpoint: string, apiKey: string | undefined, text: string) {
|
|
167
|
+
const data = await ltPost<{ language?: string; confidence?: number }[]>(endpoint, '/detect', { q: text }, apiKey);
|
|
168
|
+
return {
|
|
169
|
+
text,
|
|
170
|
+
candidates: (Array.isArray(data) ? data : []).map((d) => ({
|
|
171
|
+
language: d.language ?? null,
|
|
172
|
+
confidence: d.confidence ?? null,
|
|
173
|
+
})),
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async function listLanguages(endpoint: string, apiKey: string | undefined) {
|
|
178
|
+
const data = await ltGet<{ code?: string; name?: string; targets?: string[] }[]>(endpoint, '/languages', apiKey);
|
|
179
|
+
return {
|
|
180
|
+
count: data.length,
|
|
181
|
+
languages: data.map((l) => ({
|
|
182
|
+
code: l.code ?? null,
|
|
183
|
+
name: l.name ?? null,
|
|
184
|
+
target_codes: l.targets ?? [],
|
|
185
|
+
})),
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
export default { tools, callTool, meter: { credits: 1 } } satisfies McpToolExport;
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "bundler",
|
|
6
|
+
"strict": true,
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"outDir": "dist",
|
|
10
|
+
"rootDir": "src",
|
|
11
|
+
"declaration": true
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|