Transcribe Audio URL (sync)
transcribe_audio_url_syncTranscribe audio or video from a URL by submitting the link and waiting for results (up to 5 minutes). Supports multiple formats and optional language selection.
Instructions
Submit a URL for transcription and poll until completion (up to 5 minutes). Prefer transcribe_audio_url for faster handoff when clients can poll.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| url | Yes | Public URL of audio/video file (mp3, m4a, wav, mp4, webm, etc.) Max 200 MB. | |
| language | No | ISO 639-1 code, e.g. "en", "es". Auto-detected if omitted. |
Implementation Reference
- src/tools/transcribe-url-sync.ts:34-90 (registration)The tool 'transcribe_audio_url_sync' is registered via server.registerTool(). Also contains the handler function inline (the async callback at line 58).
export function register(server: McpServer, client: GhostMinutesClient): void { server.registerTool( 'transcribe_audio_url_sync', { title: 'Transcribe Audio URL (sync)', description: 'Submit a URL for transcription and poll until completion (up to 5 minutes). Prefer transcribe_audio_url for faster handoff when clients can poll.', inputSchema: z.object({ url: z .string() .url() .describe( 'Public URL of audio/video file (mp3, m4a, wav, mp4, webm, etc.) Max 200 MB.', ), language: z .string() .length(2) .optional() .describe( 'ISO 639-1 code, e.g. "en", "es". Auto-detected if omitted.', ), }), annotations: { readOnlyHint: false, openWorldHint: true }, }, async ({ url, language }) => { const submitted = await client.submitTranscription(url, language); const jobId = submitted.id; const deadline = Date.now() + 5 * 60 * 1000; while (Date.now() < deadline) { const statusBody = await client.getJobStatus(jobId); const t = terminalState(statusBody); if (t === 'done') { return { content: [ { type: 'text', text: `Transcription finished for job_id ${jobId}.`, }, ], structuredContent: jsonStructured(statusBody), }; } if (t === 'failed') { throw new Error( `Transcription failed for job_id ${jobId}: ${JSON.stringify(statusBody)}`, ); } await sleep(2000); } throw new Error( `Timed out after 5 minutes waiting for job_id ${jobId}. Use get_transcription_status to poll.`, ); }, ); } - src/tools/transcribe-url-sync.ts:58-88 (handler)The async handler that submits a URL for transcription, polls up to 5 minutes for completion, returns result on success or throws on failure/timeout.
async ({ url, language }) => { const submitted = await client.submitTranscription(url, language); const jobId = submitted.id; const deadline = Date.now() + 5 * 60 * 1000; while (Date.now() < deadline) { const statusBody = await client.getJobStatus(jobId); const t = terminalState(statusBody); if (t === 'done') { return { content: [ { type: 'text', text: `Transcription finished for job_id ${jobId}.`, }, ], structuredContent: jsonStructured(statusBody), }; } if (t === 'failed') { throw new Error( `Transcription failed for job_id ${jobId}: ${JSON.stringify(statusBody)}`, ); } await sleep(2000); } throw new Error( `Timed out after 5 minutes waiting for job_id ${jobId}. Use get_transcription_status to poll.`, ); }, - Input schema defined with Zod: requires 'url' (string.url) and optional 'language' (2-letter ISO code). Also sets title, description, and annotations.
'transcribe_audio_url_sync', { title: 'Transcribe Audio URL (sync)', description: 'Submit a URL for transcription and poll until completion (up to 5 minutes). Prefer transcribe_audio_url for faster handoff when clients can poll.', inputSchema: z.object({ url: z .string() .url() .describe( 'Public URL of audio/video file (mp3, m4a, wav, mp4, webm, etc.) Max 200 MB.', ), language: z .string() .length(2) .optional() .describe( 'ISO 639-1 code, e.g. "en", "es". Auto-detected if omitted.', ), }), annotations: { readOnlyHint: false, openWorldHint: true }, }, - src/server.ts:10-10 (registration)The register function from src/tools/transcribe-url-sync.ts is imported and called at line 33 to register the tool on the McpServer.
import { register as registerTranscribeSync } from './tools/transcribe-url-sync.js'; - src/client.ts:68-105 (helper)GhostMinutesClient methods used by the handler: submitTranscription() posts the URL to /mcp/transcribe, and getJobStatus() polls /mcp/status/{jobId}.
async submitTranscription( audioUrl: string, language?: string, ): Promise<{ id: string; [key: string]: unknown }> { try { const res = await this.http.post< unknown, AxiosResponse<{ id: string; [key: string]: unknown }> >( '/mcp/transcribe', { audio_file_url: audioUrl, ...(language ? { language } : {}), }, { headers: this.hasApiKey() ? { Authorization: `Bearer ${this.apiKey}` } : {}, }, ); return this.ensureOk(res); } catch (e) { this.handleThrown(e); } } async getJobStatus(jobId: string): Promise<unknown> { try { const res = await this.http.get(`/mcp/status/${encodeURIComponent(jobId)}`, { headers: this.hasApiKey() ? { Authorization: `Bearer ${this.apiKey}` } : {}, }); return this.ensureOk(res); } catch (e) { this.handleThrown(e); } }