#!/usr/bin/env npx tsx /** * MCP Translation Tool — Monetized with SettleGrid * * A complete MCP server that translates text via the DeepL API. * Fork this template, add your DeepL API key, and deploy. * * Setup: * 1. npm install @settlegrid/mcp * 2. Set DEEPL_API_KEY and SETTLEGRID_API_KEY in your env * 3. Register your tool at settlegrid.ai/dashboard/tools * 4. Run: npx tsx mcp-translation.ts * * Pricing: 3 cents per translation, 1 cent per detect, 2 cents per batch item * - DeepL Free API: 500K chars/mo free; Pro API ~$0.00002/char * - Average translation ~500 chars = ~$0.01 on Pro plan * - 3 cents = ~3x margin on Pro, pure profit on Free tier * - Batch discount at 2 cents/item encourages bulk usage * * Revenue: You keep 95-100% (100% on Free tier, 95% on paid tiers) */ import { settlegrid } from '@settlegrid/mcp' // ── SettleGrid Setup ──────────────────────────────────────────────────────── const sg = settlegrid.init({ toolSlug: 'my-translation', // Replace with your tool slug pricing: { defaultCostCents: 3, methods: { translate: { costCents: 3, displayName: 'Translate Text' }, detect_language: { costCents: 1, displayName: 'Detect Language' }, translate_batch: { costCents: 2, displayName: 'Batch Translate (per item)' }, }, }, }) // ── DeepL API Helper ──────────────────────────────────────────────────────── const DEEPL_BASE = 'https://api-free.deepl.com/v2' async function deeplPost(path: string, body: Record): Promise> { const response = await fetch(`${DEEPL_BASE}${path}`, { method: 'POST', headers: { 'Content-Type': 'application/json', Authorization: `DeepL-Auth-Key ${process.env.DEEPL_API_KEY!}` }, body: JSON.stringify(body), }) if (!response.ok) throw new Error(`DeepL API returned ${response.status}: ${response.statusText}`) return response.json() } // ── Validation ────────────────────────────────────────────────────────────── const SUPPORTED_LANGS = ['BG','CS','DA','DE','EL','EN','ES','ET','FI','FR','HU','ID','IT','JA','KO','LT','LV','NB','NL','PL','PT','RO','RU','SK','SL','SV','TR','UK','ZH'] as const function validateText(text: string, maxLen = 10_000): void { if (!text || text.trim().length === 0) throw new Error('Text must be non-empty') if (text.length > maxLen) throw new Error(`Text exceeds ${maxLen.toLocaleString()} character limit`) } function validateLang(lang: string): string { const upper = lang.toUpperCase().trim() if (!SUPPORTED_LANGS.includes(upper as (typeof SUPPORTED_LANGS)[number])) { throw new Error(`Unsupported language "${lang}". Supported: ${SUPPORTED_LANGS.join(', ')}`) } return upper } // ── Translation Methods ───────────────────────────────────────────────────── interface TranslateArgs { text: string; targetLang: string; sourceLang?: string } async function translate(args: TranslateArgs): Promise<{ result: { translatedText: string; detectedSourceLang: string; targetLang: string } }> { validateText(args.text) const targetLang = validateLang(args.targetLang) const body: Record = { text: [args.text], target_lang: targetLang } if (args.sourceLang) body.source_lang = validateLang(args.sourceLang) const data = await deeplPost('/translate', body) const first = (data.translations as Array>)?.[0] if (!first) throw new Error('No translation returned') return { result: { translatedText: first.text, detectedSourceLang: first.detected_source_language ?? 'unknown', targetLang } } } interface DetectArgs { text: string } async function detectLanguage(args: DetectArgs): Promise<{ result: { language: string; confidence: number } }> { validateText(args.text, 1_000) const data = await deeplPost('/translate', { text: [args.text.slice(0, 500)], target_lang: 'EN' }) const detected = (data.translations as Array>)?.[0]?.detected_source_language ?? 'unknown' return { result: { language: detected, confidence: detected !== 'unknown' ? 0.95 : 0 } } } interface BatchArgs { items: Array<{ text: string; targetLang: string }> } async function translateBatch(args: BatchArgs): Promise<{ batch: { translations: Array<{ original: string; translated: string; sourceLang: string; targetLang: string }> } }> { if (!args.items || args.items.length === 0) throw new Error('At least one item is required') if (args.items.length > 20) throw new Error('Batch size is limited to 20 items per call') const translations: Array<{ original: string; translated: string; sourceLang: string; targetLang: string }> = [] for (const item of args.items) { validateText(item.text, 5_000) const targetLang = validateLang(item.targetLang) const data = await deeplPost('/translate', { text: [item.text], target_lang: targetLang }) const result = (data.translations as Array>)?.[0] translations.push({ original: item.text, translated: result?.text ?? '', sourceLang: result?.detected_source_language ?? 'unknown', targetLang }) } return { batch: { translations } } } // ── Wrap with SettleGrid Billing ───────────────────────────────────────────── export const billedTranslate = sg.wrap(translate, { method: 'translate' }) export const billedDetect = sg.wrap(detectLanguage, { method: 'detect_language' }) export const billedBatch = sg.wrap(translateBatch, { method: 'translate_batch' }) // ── REST Alternative ──────────────────────────────────────────────────────── // import { settlegridMiddleware } from '@settlegrid/mcp/rest' // // const withBilling = settlegridMiddleware({ // toolSlug: 'my-translation', // pricing: { // defaultCostCents: 3, // methods: { // translate: { costCents: 3 }, // detect_language: { costCents: 1 }, // translate_batch: { costCents: 2 }, // }, // }, // }) // // export async function POST(request: Request) { // return withBilling(request, async () => { // const body = await request.json() // const result = await translate(body) // return Response.json(result) // }, 'translate') // }