chore: update workspace config and memory
This commit is contained in:
102
projects/stt-mcp/stt_server.mjs
Normal file
102
projects/stt-mcp/stt_server.mjs
Normal file
@@ -0,0 +1,102 @@
|
||||
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");
|
||||
Reference in New Issue
Block a user