Skip to main content
Glama

MCP-Transcribe

index.ts16 kB
#!/usr/bin/env node // Transcribe.com MCP Server import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; // import { CallToolResult, McpError, ErrorCode, ReadResourceResult, ResourceLink } from "@modelcontextprotocol/sdk/types.js"; // import getPath from 'platform-folders'; // Not working in restricted NodeJS envs (chat client) import FormData from 'form-data'; import { z } from "zod"; import * as fs from 'fs'; import * as path from 'path'; import * as jsutils from './jsutils.js'; let g_mcp_server_version = '1.0.4'; // Auto-replaced (by regex) let g_api_url = jsutils.safeStr(process.env["MCP_INTEGRATION_URL"]); let g_debug_file = jsutils.safeStr(process.env["MCP_DEBUG_FILE"]); let g_max_upload_size = 300*1000*1000; let kMcpMaxLastOps = 100; // let g_files:any = {}; const mcp_query_oplist_wscheme = z.object({ transcriptions: z.array(z.object({ title: z.string().describe("Transcription title"), lang: z.string().describe("Transcription language (BCP-47 language code)"), creation_date: z.string().describe("Transcription creation date in ISO format"), unique_id: z.string().describe("Transcription ID, should be used for deleting and renaming transcriptions"), text_content: z.string().optional().describe("Transcription text"), })) }) // Create an MCP server const mcpServer = new McpServer( { name: 'Transcribe.com-mcp-local-server', title: 'Transcribe.com tools', version: g_mcp_server_version }, { capabilities: { logging: {} }, instructions: "Transcribe.com MCP Server for converting speech to text\n"+ "⚠️ IMPORTANT: This server provides access to Transcribe.com endpoints which may incur costs.\n"+ "Each tool that makes an API call is marked with a cost warning.\n"+ "Tools without cost warnings in their description are free to use as they only read existing data." } ); async function clog(message:any) { try{ // // Testing debug paths // if(g_debug_file.length == 0){ // if(fs.existsSync("/Users/***/Downloads/")){ // g_debug_file = "/Users/***/Downloads/mcp_dbg.log"; // }else if(fs.existsSync("/Users/***/Downloads/")){ // g_debug_file = "/Users/***/Downloads/mcp_dbg.log"; // } // } if(g_debug_file.length == 0){ return; } if(!message || (typeof message !== "string")){ message = JSON.stringify(message); } const timestamp = new Date().toISOString(); const logEntry = `${timestamp} - ${message}\n`; fs.appendFileSync(g_debug_file, logEntry); }catch(e){ console.error("clog failed",e); } } function mcp_sys_error(message:string, so:any=null){ clog(['mcp_error', message]); let result:any = { content: [ { type: 'text', text: "Error: "+message, }, ], isError: true, } if(so){ result["structuredContent"] = so; } return result; } function mcp_sys_text(message:string, so:any=null){ clog(['mcp_text', message]); let result:any = { content: [ { type: 'text', text: message, }, ], } if(so){ result["structuredContent"] = so; } return result; } // mcpServer.registerResource( // "transcription", // new ResourceTemplate("tcom-data://{file_name}",{list: undefined}), // { // title: 'Transcription text', // description: 'Audio file transcription', // mimeType: 'text/plain' // }, // async (uri, args): Promise<ReadResourceResult> => { // clog(['resource-request', uri, args]); // let data_text = jsutils.safeStr(g_files[ jsutils.safeStr(args?.file_name) ]) // return { // contents: [{ // uri: uri?.href, // text: data_text // }] // } // } // ); // Add an addition tool mcpServer.registerTool('get-balance', { title: 'Get Transcribe.com balance', description: "Use this to read user balance, returns amount of time credits in hours." + "\nTool retrieves account balance of the user at Transcribe.com", annotations: { readOnlyHint: true, idempotentHint: true } }, async (args, req_extra): Promise<any> => { let tool_name = 'get-balance'; clog([tool_name+':init', args, req_extra]); try{ if(g_api_url.length == 0 || g_api_url.indexOf("/remote_tools") < 0){ return mcp_sys_error("MCP_INTEGRATION_URL environment variable is not set. Please configure the server correctly."); } let api_url = g_api_url.replace("/remote_tools","/remote_exec"); let output_json:any = null; const formData = new FormData();// Blob madness => any formData.append('remote_tool', tool_name); formData.append('remote_sender', g_mcp_server_version); const { default: nfetch } = await import('node-fetch'); const response = await nfetch(api_url, { method: 'POST', body: formData, headers: formData.getHeaders() }) output_json = await response.json(); clog(["api_result", output_json]); let balance_h = output_json.text return mcp_sys_text(balance_h) }catch(error){ clog(["internal_error", jsutils.dbgJSON(error)]); return mcp_sys_error("Unexpected interruption."); } } ); mcpServer.registerTool('convert-to-text', { title: 'Transcribe file to text', description: "Use this to transcribe speech to text from an audio file located in file system, to convert audio file into text." + "\nTool adds transcription to user transcriptions at Transcribe.com." + "\n⚠️ COST WARNING: This tool makes an API call which may incur costs.", inputSchema: { audio_file_path: z.string().describe('Path of the audio File'), language_code: z.string().describe('BCP-47 language code for transcription (default: "en-US" for US English)').default('en-US'), should_save_text_to_output_directory: z.boolean().describe('Whether to save the text to a file in output directory').default(false), should_return_text_to_client: z.boolean().describe('Whether to return the text to the client directly').default(true), output_directory: z.string().describe('Output directory where text file should be saved.'), }, annotations: { readOnlyHint: false, idempotentHint: false } }, async (args, req_extra): Promise<any> => { let tool_name = 'convert-to-text'; clog([tool_name+':init', args, req_extra]); try{ if(g_api_url.length == 0 || g_api_url.indexOf("/remote_tools") < 0){ return mcp_sys_error("MCP_INTEGRATION_URL environment variable is not set. Please configure the server correctly."); } let api_url = g_api_url.replace("/remote_tools","/remote_exec"); if(!args.should_save_text_to_output_directory && !args.should_return_text_to_client){ return mcp_sys_error("Must save text to file or return it to the client directly."); } if(args.should_save_text_to_output_directory && jsutils.safeLen(args.output_directory) == 0){ return mcp_sys_error("Output directory must be specified."); } let input_lang = jsutils.safeStr(args?.language_code); if(input_lang.length == 0){ input_lang = "en-US"; } let input_path = jsutils.safeStr(args?.audio_file_path); if(input_path.length == 0){ return mcp_sys_error("Input file path is not provided or path not valid."); } let input_filename:string = input_path; let input_filedata:Buffer|null = null; if(input_path.indexOf("http://") < 0 && input_path.indexOf("https://") < 0){ input_filename = path.basename(input_path); try { const stats = fs.statSync(input_path); const fileSizeInBytes = stats.size; if(fileSizeInBytes > g_max_upload_size){ return mcp_sys_error("Input file too big. The maximum size for upload is 300mb."); } input_filedata = await fs.readFileSync(input_path); } catch (error) { clog(["file_error", jsutils.dbgJSON(error)]); return mcp_sys_error("Input file path is not valid or not accessible."); } } let output_json:any = {}; try{ const formData = new FormData();// Blob madness => any formData.append('remote_tool', tool_name); formData.append('remote_sender', g_mcp_server_version); formData.append('file_name', input_filename); formData.append('file_lang', input_lang); if(input_filedata != null){ formData.append('file_data', input_filedata, input_filename); clog(["api_request_file", input_filename, input_lang, input_filedata.length]); }else{ clog(["api_request_url", input_filename, input_lang]); } const { default: nfetch } = await import('node-fetch'); const response = await nfetch(api_url, { method: 'POST', body: formData, headers: formData.getHeaders() }) output_json = await response.json(); clog(["api_result", output_json]); }catch(error){ clog(["upload_error", jsutils.dbgJSON(error)]); return mcp_sys_error("Failed to upload file for further processing."); } if(!output_json || output_json?.status != 'ok'){ return mcp_sys_error("Failed to process file."); } let output_result = jsutils.safeStr(output_json.text); if(args.should_save_text_to_output_directory){ let output_path = "???"; try{ let downloads_path:string = args.output_directory; // downloads_path = jsutils.safeStr(getPath('downloads')); // if(downloads_path.length == 0){ // downloads_path = jsutils.safeStr(getPath('desktop')); // } if(downloads_path.length == 0){ return mcp_sys_error("Failed to get output folder."); } let downloads_name = input_filename+".txt"; output_path = path.join(downloads_path,downloads_name); clog(["return_file v1", output_path]); fs.writeFileSync(output_path, output_result); }catch(error){ clog(["save_error", jsutils.dbgJSON(error)]); return mcp_sys_error(`Failed to save file into ${output_path}.`); } args.should_return_text_to_client = false; output_result = `Transcription saved to ${output_path}`; } if(args.should_return_text_to_client){ return mcp_sys_text(output_result); // ??? https://github.com/tkellogg/pagefind-mcp/blob/4b4b426d895b9830e4d5a0c0787af578f3f3ca61/pagefind-mcp.js#L18 // let downloads_name = input_filename+".txt"; // let data_uri = `tcom-data://${downloads_name}` // g_files[downloads_name] = output_result; // clog(["return_resource_link v5", data_uri); // let result: ResourceLink = { // type: 'resource_link', // uri: data_uri, // name: downloads_name, // mimeType: "text/plain", // description: 'Audio transcription' // }; // return { // content: [ // result // ] // } // clog(["return_resource v4", output_result]); // return { // content: [ // { // type: 'resource', // resource: { // uri: data_uri, // mimeType: "text/plain", // text: output_result // // blob: Buffer.from(output_result).toString('base64') // } // }, // ] // } } return mcp_sys_text(output_result); }catch(error){ clog(["internal_error", jsutils.dbgJSON(error)]); return mcp_sys_error("Unexpected interruption."); } } ); mcpServer.registerTool( "read-transcriptions", { title: "Retrieve text content of ready transcriptions", description: "Use this to get text content and titles of ready transcriptions." + "\nTool can read and retrieve user transcriptions saved at Transcribe.com." + "\nResults may be limited by a keyword-focused search query applied to the transcription title or the transcription content." + "\nSearch query is case-insensitive. Most recently added transcriptions are returned first.", // + "\nResults are numbered continuously, so that a user may be able to refer to a result by a specific number.", inputSchema: z.object({ search_query: z.string().optional().describe('One or more keyword-focused search query for looking in transcription title or content. Leave this blank when requesting all transcriptions.').default(''), limit: z.number().describe('Maximum number of recent transcriptions to return. Maximum: '+kMcpMaxLastOps+' transcriptions').default(10), }).shape, outputSchema: mcp_query_oplist_wscheme.shape, annotations: { readOnlyHint: true, idempotentHint: true } }, async (args, req_extra) => { let tool_name = 'read-transcriptions'; clog([tool_name+':init', args, req_extra]); try{ if(g_api_url.length == 0 || g_api_url.indexOf("/remote_tools") < 0){ return mcp_sys_error("MCP_INTEGRATION_URL environment variable is not set. Please configure the server correctly."); } let api_url = g_api_url.replace("/remote_tools","/remote_exec"); let output_json:any = null; const formData = new FormData(); formData.append('remote_tool', tool_name); formData.append('remote_sender', g_mcp_server_version); for(const [arg_name, arg_value] of Object.entries(args)){ formData.append(arg_name, arg_value); } const { default: nfetch } = await import('node-fetch'); const response = await nfetch(api_url, { method: 'POST', body: formData, headers: formData.getHeaders() }) output_json = await response.json(); clog(["api_result", output_json]); let mcp_reply = output_json.mcp; if(!mcp_reply){ return mcp_sys_error("Remote service unaviable."); } return mcp_reply; }catch(error){ clog(["internal_error", jsutils.dbgJSON(error)]); return mcp_sys_error("Unexpected interruption."); } } ); mcpServer.registerTool( "update-transcription", { title: "Update or delete transcription", description: "Use this to rename transcription or delete transcription." + "\nTool can update user transcriptions saved at Transcribe.com.", inputSchema: z.object({ unique_id: z.string().nonempty().describe('Unique Transcription ID or full title of the transcription. Unique Transcription ID is preferable'), action: z.string().nonempty().describe('The type of Update operation. Possible values are: "rename", "delete"'), action_rename_new_name: z.string().describe('New name for "rename" type of update'), }).shape, annotations: { readOnlyHint: false, idempotentHint: false, destructiveHint: true } }, async (args, req_extra) => { let tool_name = 'update-transcription'; clog([tool_name+':init', args, req_extra]); try{ if(g_api_url.length == 0 || g_api_url.indexOf("/remote_tools") < 0){ return mcp_sys_error("MCP_INTEGRATION_URL environment variable is not set. Please configure the server correctly."); } let api_url = g_api_url.replace("/remote_tools","/remote_exec"); let output_json:any = null; const formData = new FormData(); formData.append('remote_tool', tool_name); formData.append('remote_sender', g_mcp_server_version); for(const [arg_name, arg_value] of Object.entries(args)){ formData.append(arg_name, arg_value); } const { default: nfetch } = await import('node-fetch'); const response = await nfetch(api_url, { method: 'POST', body: formData, headers: formData.getHeaders() }) output_json = await response.json(); clog(["api_result", output_json]); let mcp_reply = output_json.mcp; if(!mcp_reply){ return mcp_sys_error("Remote service unaviable."); } return mcp_reply; }catch(error){ clog(["internal_error", jsutils.dbgJSON(error)]); return mcp_sys_error("Unexpected interruption."); } } ); async function main() { clog(["MCP server: Starting", process.versions]); // Access validation: on tool invocation only // - https://smithery.ai/docs/build/deployments#best-practices // if(api_url.length == 0){ // // throw new Error('MCP_INTEGRATION_URL environment variable is not set. Please configure the server correctly.'); // throw new McpError( // ErrorCode.InvalidParams, // 'MCP_INTEGRATION_URL environment variable is not set. Please configure the server correctly.' // ); // } const transport = new StdioServerTransport(); await mcpServer.connect(transport); } main().catch((error) => { clog(["main_error", jsutils.dbgJSON(error)]) console.error("MCPServer error:", error); process.exit(1); });

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/transcribe-app/mcp-transcribe'

If you have feedback or need assistance with the MCP directory API, please join our Discord server