#
tokens: 3849/50000 5/5 files
lines: off (toggle) GitHub
raw markdown copy
# 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);
});

```