import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { z } from "zod"; import fs from "fs"; import path from "path"; import http from "http"; const STT_URL = process.env.STT_URL || "http://llama:9090"; const server = new McpServer({ name: "stt-mcp", version: "1.0.0" }); server.tool( "stt_transcribe", "Transcribe an audio file using Whisper STT", { file_path: z.string().describe("Absolute path to the audio file"), }, async ({ file_path }) => { if (!fs.existsSync(file_path)) { return { content: [{ type: "text", text: `Error: file not found: ${file_path}` }] }; } const boundary = "----STTBOUNDARY" + Date.now(); const filename = path.basename(file_path); const fileBuf = fs.readFileSync(file_path); // Determine content type from extension const ext = path.extname(filename).toLowerCase(); const mimeMap = { ".ogg": "audio/ogg", ".mp3": "audio/mpeg", ".wav": "audio/wav", ".m4a": "audio/mp4", ".webm": "audio/webm", ".flac": "audio/flac", }; const mime = mimeMap[ext] || "application/octet-stream"; // Build multipart body manually (no external dependency) const parts = []; parts.push(`--${boundary}\r\nContent-Disposition: form-data; name="file"; filename="${filename}"\r\nContent-Type: ${mime}\r\n\r\n`); parts.push(fileBuf); parts.push(`\r\n--${boundary}--\r\n`); const body = Buffer.concat( parts.map((p) => (typeof p === "string" ? Buffer.from(p) : p)) ); const url = new URL(`${STT_URL}/api/stt`); return new Promise((resolve, reject) => { const req = http.request( { hostname: url.hostname, port: url.port, path: url.pathname, method: "POST", headers: { "Content-Type": `multipart/form-data; boundary=${boundary}`, "Content-Length": body.length, }, timeout: 60000, }, (res) => { let data = ""; res.on("data", (chunk) => (data += chunk)); res.on("end", () => { try { const result = JSON.parse(data); if (result.ok) { resolve({ content: [ { type: "text", text: `**STT Result** (${result.transcribe_sec}s, ${result.duration_sec}s audio, ${result.language})\n\n${result.text}`, }, ], }); } else { resolve({ content: [{ type: "text", text: `STT error: ${data}` }], }); } } catch (e) { resolve({ content: [{ type: "text", text: `Parse error: ${data}` }] }); } }); } ); req.on("error", (e) => resolve({ content: [{ type: "text", text: `Connection error: ${e.message}` }] }) ); req.write(body); req.end(); }); } ); const transport = new StdioServerTransport(); await server.connect(transport); console.error("[stt-mcp] server started");