# Directory Structure
```
├── .gitignore
├── LICENSE
├── package-lock.json
├── package.json
├── README.md
├── src
│ └── index.ts
└── tsconfig.json
```
# Files
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
```
# Dependencies
node_modules/
# Build artifacts
build/
# Logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
jspm_packages/
# TypeScript cache
*.tsbuildinfo
# Optional eslint cache
.eslintcache
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
*.db
*.rdb
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
```
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
```markdown
# DeepL MCP Server
An MCP (Model Context Protocol) server providing DeepL translation capabilities.
## Features
This server exposes the following tools via MCP:
* **`translate_text`**: Translates one or more text strings between supported languages using the DeepL API.
* **`list_languages`**: Retrieves the list of languages supported by the DeepL API (either source or target languages).
## Prerequisites
* **Node.js and npm/yarn:** Required to install dependencies and run the server.
* **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).
## Installation
1. **Clone the repository:**
```bash
git clone https://github.com/watchdealer-pavel/deepl-mcp-server.git
cd deepl-mcp-server
```
2. **Install dependencies:**
```bash
npm install
# or
# yarn install
```
3. **Build the server:**
```bash
npm run build
```
This command compiles the TypeScript source code into JavaScript, placing the output in the `build/` directory (specifically `build/index.js`).
## Configuration
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.
**Example Configuration:**
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.**
### Cline / Roo Code (VS Code Extension)
1. Open your VS Code settings for MCP servers. On macOS, this is typically located at:
`~/Library/Application Support/Code/User/globalStorage/rooveterinaryinc.roo-cline/settings/mcp_settings.json`
*(Note: The exact path might vary based on your operating system and VS Code installation type (e.g., Insiders).)*
2. Add the following configuration block under the `mcpServers` key:
```json
"deepl-translator": {
"command": "node",
"args": ["/path/to/your/deepl-mcp-server/build/index.js"], // <-- IMPORTANT: Replace with the ACTUAL absolute path to build/index.js
"env": {
"DEEPL_API_KEY": "YOUR_DEEPL_API_KEY" // <-- IMPORTANT: Replace with your DeepL API Key
},
"disabled": false,
"alwaysAllow": []
}
```
### Claude Desktop App
1. Open the Claude Desktop App configuration file. On macOS, this is typically located at:
`~/Library/Application Support/Claude/claude_desktop_config.json`
*(Note: The exact path might vary based on your operating system.)*
2. Add the following configuration block under the `mcpServers` key:
```json
"deepl-translator": {
"command": "node",
"args": ["/path/to/your/deepl-mcp-server/build/index.js"], // <-- IMPORTANT: Replace with the ACTUAL absolute path to build/index.js
"env": {
"DEEPL_API_KEY": "YOUR_DEEPL_API_KEY" // <-- IMPORTANT: Replace with your DeepL API Key
},
"disabled": false,
"alwaysAllow": []
}
```
## Usage
Once configured, you can invoke the server's tools from your AI assistant using the `use_mcp_tool` command/tool.
### `list_languages` Example
```xml
<use_mcp_tool>
<server_name>deepl-translator</server_name>
<tool_name>list_languages</tool_name>
<arguments>
{
"type": "target" // Optional: "source" or "target". Defaults to listing all if omitted.
}
</arguments>
</use_mcp_tool>
```
### `translate_text` Example
```xml
<use_mcp_tool>
<server_name>deepl-translator</server_name>
<tool_name>translate_text</tool_name>
<arguments>
{
"text": ["Hello world", "How are you?"], // Required: An array of strings to translate
"target_lang": "DE", // Required: Target language code (e.g., DE, FR, ES)
"source_lang": "EN" // Optional: Source language code. DeepL will auto-detect if omitted.
}
</arguments>
</use_mcp_tool>
```
## License
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
```
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
```json
{
"compilerOptions": {
"target": "ES2022",
"module": "Node16",
"moduleResolution": "Node16",
"outDir": "./build",
"rootDir": "./src",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules"]
}
```
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
```json
{
"name": "deepl-mcp-server",
"version": "0.1.0",
"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.",
"private": true,
"type": "module",
"bin": {
"deepl-mcp-server": "./build/index.js"
},
"files": [
"build"
],
"scripts": {
"build": "tsc && node -e \"require('fs').chmodSync('build/index.js', '755')\"",
"prepare": "npm run build",
"watch": "tsc --watch",
"inspector": "npx @modelcontextprotocol/inspector build/index.js"
},
"dependencies": {
"@modelcontextprotocol/sdk": "^0.6.0",
"axios": "^1.8.4",
"zod": "^3.24.2"
},
"devDependencies": {
"@types/node": "^20.11.24",
"typescript": "^5.3.3"
}
}
```
--------------------------------------------------------------------------------
/src/index.ts:
--------------------------------------------------------------------------------
```typescript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
CallToolRequestSchema,
ListToolsRequestSchema,
McpError,
ErrorCode,
Tool, // Corrected type name
CallToolResultSchema, // Corrected type name based on error suggestion
ListToolsResultSchema, // Corrected type name based on error suggestion
TextContent,
} from '@modelcontextprotocol/sdk/types.js';
import axios, { AxiosInstance, AxiosError } from 'axios';
import { z } from 'zod';
// --- Environment Variable Check ---
const API_KEY = process.env.DEEPL_API_KEY;
if (!API_KEY) {
console.error('DEEPL_API_KEY environment variable is not set.');
process.exit(1); // Exit if API key is missing
}
// --- Input Schemas (using Zod) ---
const TranslateTextInputSchema = z.object({
text: z.array(z.string()).min(1, "The 'text' array cannot be empty."),
target_lang: z.string(),
source_lang: z.string().optional(),
});
const ListLanguagesInputSchema = z.object({
type: z.enum(['source', 'target']).optional(),
});
// --- DeepL API Response Types ---
interface DeepLTranslation {
detected_source_language: string;
text: string;
}
interface DeepLLanguage {
language: string;
name: string;
supports_formality: boolean;
}
// --- DeepL Server Class ---
class DeepLServer {
private server: Server;
private axiosInstance: AxiosInstance;
private readonly baseUrl = 'https://api-free.deepl.com';
constructor() {
this.server = new Server(
{
name: 'deepl-mcp-server',
version: '0.1.0',
},
{
capabilities: {
// Resources not implemented in this version
resources: {},
tools: {}, // Tool handlers are registered below
},
}
);
this.axiosInstance = axios.create({
baseURL: this.baseUrl,
headers: {
Authorization: `DeepL-Auth-Key ${API_KEY}`,
'Content-Type': 'application/json',
},
});
this.setupToolHandlers();
// Basic error logging for the server itself
this.server.onerror = (error) => console.error('[MCP Server Error]', error);
process.on('SIGINT', async () => {
console.error('Received SIGINT, shutting down server...');
await this.server.close();
process.exit(0);
});
}
private setupToolHandlers() {
// --- ListTools Handler ---
this.server.setRequestHandler(
ListToolsRequestSchema,
async (): Promise<z.infer<typeof ListToolsResultSchema>> => { // Corrected response type
const tools: Tool[] = [ // Corrected type name
{
name: 'translate_text',
description: 'Translates one or more text strings using the DeepL API.',
inputSchema: {
type: 'object',
properties: {
text: {
type: 'array',
items: { type: 'string' },
description: 'Text(s) to translate',
},
target_lang: {
type: 'string',
description: 'Target language code (e.g., DE, FR)',
},
source_lang: {
type: 'string',
description: 'Source language code; auto-detected if omitted',
},
},
required: ['text', 'target_lang'],
},
},
{
name: 'list_languages',
description: 'Retrieves the list of languages supported by the DeepL API.',
inputSchema: {
type: 'object',
properties: {
type: {
type: 'string',
enum: ['source', 'target'],
description: "Filter by 'source' or 'target' languages",
},
},
required: [],
},
},
];
return { tools };
}
);
// --- CallTool Handler ---
this.server.setRequestHandler(
CallToolRequestSchema,
async (request): Promise<z.infer<typeof CallToolResultSchema>> => { // Corrected response type
try {
switch (request.params.name) {
case 'translate_text':
return await this.callTranslateText(request.params.arguments);
case 'list_languages':
return await this.callListLanguages(request.params.arguments);
default:
throw new McpError(ErrorCode.MethodNotFound, `Tool '${request.params.name}' not found.`);
}
} catch (error) {
return this.handleToolError(error);
}
}
);
}
// --- Specific Tool Implementations ---
private async callTranslateText(args: any): Promise<z.infer<typeof CallToolResultSchema>> { // Corrected response type
const validatedArgs = TranslateTextInputSchema.parse(args); // Validate input (throws ZodError on failure)
const response = await this.axiosInstance.post<{ translations: DeepLTranslation[] }>(
'/v2/translate',
{
text: validatedArgs.text,
target_lang: validatedArgs.target_lang,
...(validatedArgs.source_lang && { source_lang: validatedArgs.source_lang }),
}
);
const content: TextContent = {
type: 'text',
text: JSON.stringify(response.data.translations, null, 2), // Pretty print JSON
mimeType: 'application/json',
};
return { content: [content] };
}
private async callListLanguages(args: any): Promise<z.infer<typeof CallToolResultSchema>> { // Corrected response type
const validatedArgs = ListLanguagesInputSchema.parse(args); // Validate input
const params: { type?: 'source' | 'target' } = {};
if (validatedArgs.type) {
params.type = validatedArgs.type;
}
const response = await this.axiosInstance.get<DeepLLanguage[]>(
'/v2/languages',
{ params }
);
const content: TextContent = {
type: 'text',
text: JSON.stringify(response.data, null, 2), // Pretty print JSON
mimeType: 'application/json',
};
return { content: [content] };
}
// --- Centralized Error Handling for Tools ---
private handleToolError(error: unknown): z.infer<typeof CallToolResultSchema> { // Corrected response type
let mcpError: McpError;
if (error instanceof McpError) {
mcpError = error; // Use existing McpError
} else if (error instanceof z.ZodError) {
// Handle Zod validation errors
mcpError = new McpError(ErrorCode.InvalidParams, "Input validation failed", error.errors);
} else if (axios.isAxiosError(error)) {
// Handle Axios errors specifically
const axiosError = error as AxiosError<any>;
const status = axiosError.response?.status;
const data = axiosError.response?.data;
const message = data?.message || axiosError.message;
let errorCode = ErrorCode.InternalError; // Default
if (status === 400) errorCode = ErrorCode.InvalidParams; // Use available code
// Map other client errors (like auth) to InvalidRequest for now
if (status && status >= 401 && status < 500 && status !== 400) {
errorCode = ErrorCode.InvalidRequest;
}
// Map server errors (5xx) to InternalError (as UpstreamError isn't available)
if (status && status >= 500) {
errorCode = ErrorCode.InternalError; // Using InternalError as UpstreamError is not available
}
mcpError = new McpError(errorCode, `DeepL API Error (${status}): ${message}`, data);
} else {
// Handle other unexpected errors
console.error('Unexpected error calling tool:', error);
const errorMessage = error instanceof Error ? error.message : String(error);
mcpError = new McpError(ErrorCode.InternalError, `An unexpected error occurred: ${errorMessage}`);
}
// Format error for MCP response
const errorContent: TextContent = {
type: 'text',
text: `Error: ${mcpError.message}${mcpError.data ? `\nDetails: ${JSON.stringify(mcpError.data)}` : ''}`,
};
return { content: [errorContent], isError: true, errorCode: mcpError.code };
}
// --- Server Start Method ---
async run() {
const transport = new StdioServerTransport();
await this.server.connect(transport);
console.error('DeepL MCP Server started and listening via stdio.'); // Log to stderr
}
}
// --- Initialize and Run ---
const server = new DeepLServer();
server.run().catch(error => {
console.error("Failed to start DeepL MCP Server:", error);
process.exit(1);
});
```