103 lines
3.1 KiB
JavaScript
103 lines
3.1 KiB
JavaScript
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");
|