# Directory Structure
```
├── .gitignore
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
1 | # Dependencies
2 | node_modules/
3 |
4 | # Build artifacts
5 | build/
6 |
7 | # Logs
8 | *.log
9 | npm-debug.log*
10 | yarn-debug.log*
11 | yarn-error.log*
12 | lerna-debug.log*
13 |
14 | # Diagnostic reports (https://nodejs.org/api/report.html)
15 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
16 |
17 | # Runtime data
18 | pids
19 | *.pid
20 | *.seed
21 | *.pid.lock
22 |
23 | # Directory for instrumented libs generated by jscoverage/JSCover
24 | lib-cov
25 |
26 | # Coverage directory used by tools like istanbul
27 | coverage
28 | *.lcov
29 |
30 | # nyc test coverage
31 | .nyc_output
32 |
33 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
34 | .grunt
35 |
36 | # Bower dependency directory (https://bower.io/)
37 | bower_components
38 |
39 | # node-waf configuration
40 | .lock-wscript
41 |
42 | # Compiled binary addons (https://nodejs.org/api/addons.html)
43 | build/Release
44 |
45 | # Dependency directories
46 | jspm_packages/
47 |
48 | # TypeScript cache
49 | *.tsbuildinfo
50 |
51 | # Optional eslint cache
52 | .eslintcache
53 |
54 | # Output of 'npm pack'
55 | *.tgz
56 |
57 | # Yarn Integrity file
58 | .yarn-integrity
59 |
60 | # dotenv environment variables file
61 | .env
62 | .env.test
63 |
64 | # parcel-bundler cache (https://parceljs.org/)
65 | .cache
66 |
67 | # Next.js build output
68 | .next
69 | out
70 |
71 | # Nuxt.js build / generate output
72 | .nuxt
73 | dist
74 |
75 | # Gatsby files
76 | .cache/
77 | # Comment in the public line in if your project uses Gatsby and not Next.js
78 | # https://nextjs.org/blog/next-9-1#public-directory-support
79 | # public
80 |
81 | # vuepress build output
82 | .vuepress/dist
83 |
84 | # Serverless directories
85 | .serverless/
86 |
87 | # FuseBox cache
88 | .fusebox/
89 |
90 | # DynamoDB Local files
91 | *.db
92 | *.rdb
93 |
94 | # TernJS port file
95 | .tern-port
96 |
97 | # Stores VSCode versions used for testing VSCode extensions
98 | .vscode-test
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
1 | # DeepL MCP Server
2 |
3 | An MCP (Model Context Protocol) server providing DeepL translation capabilities.
4 |
5 | ## Features
6 |
7 | This server exposes the following tools via MCP:
8 |
9 | * **`translate_text`**: Translates one or more text strings between supported languages using the DeepL API.
10 | * **`list_languages`**: Retrieves the list of languages supported by the DeepL API (either source or target languages).
11 |
12 | ## Prerequisites
13 |
14 | * **Node.js and npm/yarn:** Required to install dependencies and run the server.
15 | * **DeepL API Key:** You need an API key from DeepL. Both Free and Pro plans provide API access. Sign up or learn more at [https://www.deepl.com/pro-api](https://www.deepl.com/pro-api).
16 |
17 | ## Installation
18 |
19 | 1. **Clone the repository:**
20 | ```bash
21 | git clone https://github.com/watchdealer-pavel/deepl-mcp-server.git
22 | cd deepl-mcp-server
23 | ```
24 |
25 | 2. **Install dependencies:**
26 | ```bash
27 | npm install
28 | # or
29 | # yarn install
30 | ```
31 |
32 | 3. **Build the server:**
33 | ```bash
34 | npm run build
35 | ```
36 | This command compiles the TypeScript source code into JavaScript, placing the output in the `build/` directory (specifically `build/index.js`).
37 |
38 | ## Configuration
39 |
40 | The server requires your DeepL API key to be provided via the `DEEPL_API_KEY` environment variable. You need to configure your MCP client (like Cline/Roo Code or the Claude Desktop App) to run this server and pass the environment variable.
41 |
42 | **Example Configuration:**
43 |
44 | Below are examples for common MCP clients. **Remember to replace `/path/to/your/deepl-mcp-server/build/index.js` with the actual absolute path to the compiled server file on your system, and `YOUR_DEEPL_API_KEY` with your real DeepL API key.**
45 |
46 | ### Cline / Roo Code (VS Code Extension)
47 |
48 | 1. Open your VS Code settings for MCP servers. On macOS, this is typically located at:
49 | `~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json`
50 | *(Note: The exact path might vary based on your operating system and VS Code installation type (e.g., Insiders).)*
51 |
52 | 2. Add the following configuration block under the `mcpServers` key:
53 |
54 | ```json
55 | "deepl-translator": {
56 | "command": "node",
57 | "args": ["/path/to/your/deepl-mcp-server/build/index.js"], // <-- IMPORTANT: Replace with the ACTUAL absolute path to build/index.js
58 | "env": {
59 | "DEEPL_API_KEY": "YOUR_DEEPL_API_KEY" // <-- IMPORTANT: Replace with your DeepL API Key
60 | },
61 | "disabled": false,
62 | "alwaysAllow": []
63 | }
64 | ```
65 |
66 | ### Claude Desktop App
67 |
68 | 1. Open the Claude Desktop App configuration file. On macOS, this is typically located at:
69 | `~/Library/Application Support/Claude/claude_desktop_config.json`
70 | *(Note: The exact path might vary based on your operating system.)*
71 |
72 | 2. Add the following configuration block under the `mcpServers` key:
73 |
74 | ```json
75 | "deepl-translator": {
76 | "command": "node",
77 | "args": ["/path/to/your/deepl-mcp-server/build/index.js"], // <-- IMPORTANT: Replace with the ACTUAL absolute path to build/index.js
78 | "env": {
79 | "DEEPL_API_KEY": "YOUR_DEEPL_API_KEY" // <-- IMPORTANT: Replace with your DeepL API Key
80 | },
81 | "disabled": false,
82 | "alwaysAllow": []
83 | }
84 | ```
85 |
86 | ## Usage
87 |
88 | Once configured, you can invoke the server's tools from your AI assistant using the `use_mcp_tool` command/tool.
89 |
90 | ### `list_languages` Example
91 |
92 | ```xml
93 | <use_mcp_tool>
94 | <server_name>deepl-translator</server_name>
95 | <tool_name>list_languages</tool_name>
96 | <arguments>
97 | {
98 | "type": "target" // Optional: "source" or "target". Defaults to listing all if omitted.
99 | }
100 | </arguments>
101 | </use_mcp_tool>
102 | ```
103 |
104 | ### `translate_text` Example
105 |
106 | ```xml
107 | <use_mcp_tool>
108 | <server_name>deepl-translator</server_name>
109 | <tool_name>translate_text</tool_name>
110 | <arguments>
111 | {
112 | "text": ["Hello world", "How are you?"], // Required: An array of strings to translate
113 | "target_lang": "DE", // Required: Target language code (e.g., DE, FR, ES)
114 | "source_lang": "EN" // Optional: Source language code. DeepL will auto-detect if omitted.
115 | }
116 | </arguments>
117 | </use_mcp_tool>
118 | ```
119 |
120 | ## License
121 |
122 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
123 |
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "compilerOptions": {
3 | "target": "ES2022",
4 | "module": "Node16",
5 | "moduleResolution": "Node16",
6 | "outDir": "./build",
7 | "rootDir": "./src",
8 | "strict": true,
9 | "esModuleInterop": true,
10 | "skipLibCheck": true,
11 | "forceConsistentCasingInFileNames": true
12 | },
13 | "include": ["src/**/*"],
14 | "exclude": ["node_modules"]
15 | }
16 |
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
1 | {
2 | "name": "deepl-mcp-server",
3 | "version": "0.1.0",
4 | "description": "This server implements the Model Context Protocol (MCP) to provide high-quality text translation services by acting as an interface to the DeepL API.",
5 | "private": true,
6 | "type": "module",
7 | "bin": {
8 | "deepl-mcp-server": "./build/index.js"
9 | },
10 | "files": [
11 | "build"
12 | ],
13 | "scripts": {
14 | "build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
15 | "prepare": "npm run build",
16 | "watch": "tsc --watch",
17 | "inspector": "npx @modelcontextprotocol/inspector build/index.js"
18 | },
19 | "dependencies": {
20 | "@modelcontextprotocol/sdk": "^0.6.0",
21 | "axios": "^1.8.4",
22 | "zod": "^3.24.2"
23 | },
24 | "devDependencies": {
25 | "@types/node": "^20.11.24",
26 | "typescript": "^5.3.3"
27 | }
28 | }
29 |
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
1 | #!/usr/bin/env node
2 | import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3 | import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4 | import {
5 | CallToolRequestSchema,
6 | ListToolsRequestSchema,
7 | McpError,
8 | ErrorCode,
9 | Tool, // Corrected type name
10 | CallToolResultSchema, // Corrected type name based on error suggestion
11 | ListToolsResultSchema, // Corrected type name based on error suggestion
12 | TextContent,
13 | } from '@modelcontextprotocol/sdk/types.js';
14 | import axios, { AxiosInstance, AxiosError } from 'axios';
15 | import { z } from 'zod';
16 |
17 | // --- Environment Variable Check ---
18 | const API_KEY = process.env.DEEPL_API_KEY;
19 | if (!API_KEY) {
20 | console.error('DEEPL_API_KEY environment variable is not set.');
21 | process.exit(1); // Exit if API key is missing
22 | }
23 |
24 | // --- Input Schemas (using Zod) ---
25 | const TranslateTextInputSchema = z.object({
26 | text: z.array(z.string()).min(1, "The 'text' array cannot be empty."),
27 | target_lang: z.string(),
28 | source_lang: z.string().optional(),
29 | });
30 |
31 | const ListLanguagesInputSchema = z.object({
32 | type: z.enum(['source', 'target']).optional(),
33 | });
34 |
35 | // --- DeepL API Response Types ---
36 | interface DeepLTranslation {
37 | detected_source_language: string;
38 | text: string;
39 | }
40 |
41 | interface DeepLLanguage {
42 | language: string;
43 | name: string;
44 | supports_formality: boolean;
45 | }
46 |
47 | // --- DeepL Server Class ---
48 | class DeepLServer {
49 | private server: Server;
50 | private axiosInstance: AxiosInstance;
51 | private readonly baseUrl = 'https://api-free.deepl.com';
52 |
53 | constructor() {
54 | this.server = new Server(
55 | {
56 | name: 'deepl-mcp-server',
57 | version: '0.1.0',
58 | },
59 | {
60 | capabilities: {
61 | // Resources not implemented in this version
62 | resources: {},
63 | tools: {}, // Tool handlers are registered below
64 | },
65 | }
66 | );
67 |
68 | this.axiosInstance = axios.create({
69 | baseURL: this.baseUrl,
70 | headers: {
71 | Authorization: `DeepL-Auth-Key ${API_KEY}`,
72 | 'Content-Type': 'application/json',
73 | },
74 | });
75 |
76 | this.setupToolHandlers();
77 |
78 | // Basic error logging for the server itself
79 | this.server.onerror = (error) => console.error('[MCP Server Error]', error);
80 | process.on('SIGINT', async () => {
81 | console.error('Received SIGINT, shutting down server...');
82 | await this.server.close();
83 | process.exit(0);
84 | });
85 | }
86 |
87 | private setupToolHandlers() {
88 | // --- ListTools Handler ---
89 | this.server.setRequestHandler(
90 | ListToolsRequestSchema,
91 | async (): Promise<z.infer<typeof ListToolsResultSchema>> => { // Corrected response type
92 | const tools: Tool[] = [ // Corrected type name
93 | {
94 | name: 'translate_text',
95 | description: 'Translates one or more text strings using the DeepL API.',
96 | inputSchema: {
97 | type: 'object',
98 | properties: {
99 | text: {
100 | type: 'array',
101 | items: { type: 'string' },
102 | description: 'Text(s) to translate',
103 | },
104 | target_lang: {
105 | type: 'string',
106 | description: 'Target language code (e.g., DE, FR)',
107 | },
108 | source_lang: {
109 | type: 'string',
110 | description: 'Source language code; auto-detected if omitted',
111 | },
112 | },
113 | required: ['text', 'target_lang'],
114 | },
115 | },
116 | {
117 | name: 'list_languages',
118 | description: 'Retrieves the list of languages supported by the DeepL API.',
119 | inputSchema: {
120 | type: 'object',
121 | properties: {
122 | type: {
123 | type: 'string',
124 | enum: ['source', 'target'],
125 | description: "Filter by 'source' or 'target' languages",
126 | },
127 | },
128 | required: [],
129 | },
130 | },
131 | ];
132 | return { tools };
133 | }
134 | );
135 |
136 | // --- CallTool Handler ---
137 | this.server.setRequestHandler(
138 | CallToolRequestSchema,
139 | async (request): Promise<z.infer<typeof CallToolResultSchema>> => { // Corrected response type
140 | try {
141 | switch (request.params.name) {
142 | case 'translate_text':
143 | return await this.callTranslateText(request.params.arguments);
144 | case 'list_languages':
145 | return await this.callListLanguages(request.params.arguments);
146 | default:
147 | throw new McpError(ErrorCode.MethodNotFound, `Tool '${request.params.name}' not found.`);
148 | }
149 | } catch (error) {
150 | return this.handleToolError(error);
151 | }
152 | }
153 | );
154 | }
155 |
156 | // --- Specific Tool Implementations ---
157 | private async callTranslateText(args: any): Promise<z.infer<typeof CallToolResultSchema>> { // Corrected response type
158 | const validatedArgs = TranslateTextInputSchema.parse(args); // Validate input (throws ZodError on failure)
159 |
160 | const response = await this.axiosInstance.post<{ translations: DeepLTranslation[] }>(
161 | '/v2/translate',
162 | {
163 | text: validatedArgs.text,
164 | target_lang: validatedArgs.target_lang,
165 | ...(validatedArgs.source_lang && { source_lang: validatedArgs.source_lang }),
166 | }
167 | );
168 |
169 | const content: TextContent = {
170 | type: 'text',
171 | text: JSON.stringify(response.data.translations, null, 2), // Pretty print JSON
172 | mimeType: 'application/json',
173 | };
174 |
175 | return { content: [content] };
176 | }
177 |
178 | private async callListLanguages(args: any): Promise<z.infer<typeof CallToolResultSchema>> { // Corrected response type
179 | const validatedArgs = ListLanguagesInputSchema.parse(args); // Validate input
180 |
181 | const params: { type?: 'source' | 'target' } = {};
182 | if (validatedArgs.type) {
183 | params.type = validatedArgs.type;
184 | }
185 |
186 | const response = await this.axiosInstance.get<DeepLLanguage[]>(
187 | '/v2/languages',
188 | { params }
189 | );
190 |
191 | const content: TextContent = {
192 | type: 'text',
193 | text: JSON.stringify(response.data, null, 2), // Pretty print JSON
194 | mimeType: 'application/json',
195 | };
196 |
197 | return { content: [content] };
198 | }
199 |
200 | // --- Centralized Error Handling for Tools ---
201 | private handleToolError(error: unknown): z.infer<typeof CallToolResultSchema> { // Corrected response type
202 | let mcpError: McpError;
203 |
204 | if (error instanceof McpError) {
205 | mcpError = error; // Use existing McpError
206 | } else if (error instanceof z.ZodError) {
207 | // Handle Zod validation errors
208 | mcpError = new McpError(ErrorCode.InvalidParams, "Input validation failed", error.errors);
209 | } else if (axios.isAxiosError(error)) {
210 | // Handle Axios errors specifically
211 | const axiosError = error as AxiosError<any>;
212 | const status = axiosError.response?.status;
213 | const data = axiosError.response?.data;
214 | const message = data?.message || axiosError.message;
215 |
216 | let errorCode = ErrorCode.InternalError; // Default
217 | if (status === 400) errorCode = ErrorCode.InvalidParams; // Use available code
218 | // Map other client errors (like auth) to InvalidRequest for now
219 | if (status && status >= 401 && status < 500 && status !== 400) {
220 | errorCode = ErrorCode.InvalidRequest;
221 | }
222 | // Map server errors (5xx) to InternalError (as UpstreamError isn't available)
223 | if (status && status >= 500) {
224 | errorCode = ErrorCode.InternalError; // Using InternalError as UpstreamError is not available
225 | }
226 |
227 | mcpError = new McpError(errorCode, `DeepL API Error (${status}): ${message}`, data);
228 | } else {
229 | // Handle other unexpected errors
230 | console.error('Unexpected error calling tool:', error);
231 | const errorMessage = error instanceof Error ? error.message : String(error);
232 | mcpError = new McpError(ErrorCode.InternalError, `An unexpected error occurred: ${errorMessage}`);
233 | }
234 |
235 | // Format error for MCP response
236 | const errorContent: TextContent = {
237 | type: 'text',
238 | text: `Error: ${mcpError.message}${mcpError.data ? `\nDetails: ${JSON.stringify(mcpError.data)}` : ''}`,
239 | };
240 | return { content: [errorContent], isError: true, errorCode: mcpError.code };
241 | }
242 |
243 |
244 | // --- Server Start Method ---
245 | async run() {
246 | const transport = new StdioServerTransport();
247 | await this.server.connect(transport);
248 | console.error('DeepL MCP Server started and listening via stdio.'); // Log to stderr
249 | }
250 | }
251 |
252 | // --- Initialize and Run ---
253 | const server = new DeepLServer();
254 | server.run().catch(error => {
255 | console.error("Failed to start DeepL MCP Server:", error);
256 | process.exit(1);
257 | });
258 |
```