Skip to main content
Glama

voice_clone

Clone voices from audio files to generate synthetic speech. Save output to specified directories. Uses MiniMax API for voice cloning, with costs applied for new voice models.

Instructions

Clone a voice using the provided audio file. New voices will incur costs when first used.

Note: This tool calls MiniMax API and may incur costs. Use only when explicitly requested by the user.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
audioFileYesPath to the audio file
isUrlNoWhether the audio file is a URL
outputDirectoryNoThe directory to save the output file. `outputDirectory` is relative to `MINIMAX_MCP_BASE_PATH` (or `basePath` in config). The final save path is `${basePath}/${outputDirectory}`. For example, if `MINIMAX_MCP_BASE_PATH=~/Desktop` and `outputDirectory=workspace`, the output will be saved to `~/Desktop/workspace/`
textNoText for the demo audio
voiceIdYesVoice ID to use

Implementation Reference

  • Core implementation of voice_clone tool: validates inputs, uploads audio file to MiniMax, calls voice cloning API, optionally generates and downloads demo audio using provided text.
    async cloneVoice(request: VoiceCloneRequest): Promise<string> { // Validate required parameters if (!request.audioFile) { throw new MinimaxRequestError(ERROR_AUDIO_FILE_REQUIRED); } if (!request.voiceId) { throw new MinimaxRequestError('Voice ID is required'); } try { // Step 1: Upload file let files: any; if (request.isUrl) { // Handle URL file try { const response = await requests.default.get(request.audioFile, { responseType: 'stream' }); const tempFilePath = path.join(process.cwd(), 'temp', path.basename(request.audioFile)); // Ensure temp directory exists const tempDir = path.dirname(tempFilePath); if (!fs.existsSync(tempDir)) { fs.mkdirSync(tempDir, { recursive: true }); } // Save stream to temp file const writer = fs.createWriteStream(tempFilePath); response.data.pipe(writer); await new Promise<void>((resolve, reject) => { writer.on('finish', () => resolve()); writer.on('error', reject); }); // Prepare upload parameters with temp file files = { file: { path: tempFilePath, }, }; } catch (error) { throw new MinimaxRequestError(`Failed to download audio from URL: ${String(error)}`); } } else { // Handle local file try { const filePath = processInputFile(request.audioFile); // Prepare upload parameters files = { file: { path: filePath, }, }; } catch (error) { throw new MinimaxRequestError(`Failed to read local file: ${String(error)}`); } } const data = { files, purpose: 'voice_clone', }; // Upload file const uploadResponse = await this.api.post<any>('/v1/files/upload', data); // Get file ID const fileId = uploadResponse?.file?.file_id; if (!fileId) { throw new MinimaxRequestError('Failed to get file ID from upload response'); } // Step 2: Clone voice const payload: Record<string, any> = { file_id: fileId, voice_id: request.voiceId, }; // If demo text is provided, add it to the request if (request.text) { payload.text = request.text; payload.model = DEFAULT_SPEECH_MODEL; } // Send clone request const cloneResponse = await this.api.post<any>('/v1/voice_clone', payload); // Check if there's a demo audio const demoAudio = cloneResponse?.demo_audio; if (!demoAudio) { // If no demo audio, return voice ID directly return request.voiceId; } // If URL mode, return URL directly const resourceMode = this.api.getResourceMode(); if (resourceMode === RESOURCE_MODE_URL) { return demoAudio; } // Step 3: Download demo audio const outputPath = buildOutputFile('voice_clone', request.outputDirectory, 'wav', true); try { // Download audio const audioResponse = await requests.default.get(demoAudio, { responseType: 'arraybuffer' }); // Ensure directory exists const dirPath = path.dirname(outputPath); if (!fs.existsSync(dirPath)) { fs.mkdirSync(dirPath, { recursive: true }); } // Save file fs.writeFileSync(outputPath, Buffer.from(audioResponse.data)); // Return voice ID with path information return `${request.voiceId} (Demo audio: ${outputPath})`; } catch (error) { // If download fails, still return voice ID return request.voiceId; } } catch (error) { if (error instanceof MinimaxRequestError) { throw error; } throw new MinimaxRequestError(`Error occurred while cloning voice: ${String(error)}`); } }
  • TypeScript interface defining the input parameters for the voice_clone tool.
    export interface VoiceCloneRequest extends BaseToolRequest { audioFile: string; voiceId: string; text?: string; name?: string; description?: string; isUrl?: boolean; }
  • Tool registration/definition in the listTools handler for REST server, specifying name, description, arguments, and inputSchema.
    { name: 'voice_clone', description: 'Clone voice using provided audio file', arguments: [ { name: 'voiceId', description: 'Voice ID to use', required: true }, { name: 'audioFile', description: 'Audio file path', required: true }, { name: 'text', description: 'Text for demo audio', required: false }, { name: 'outputDirectory', description: OUTPUT_DIRECTORY_DESCRIPTION, required: false }, { name: 'isUrl', description: 'Whether audio file is a URL', required: false } ], inputSchema: { type: 'object', properties: { voiceId: { type: 'string' }, audioFile: { type: 'string' }, text: { type: 'string' }, outputDirectory: { type: 'string' }, isUrl: { type: 'boolean' } }, required: ['voiceId', 'audioFile'] } },
  • REST server handler for voice_clone tool: delegates to MediaService.cloneVoice, handles specific real-name verification errors, implements retry logic.
    private async handleVoiceClone(args: any, api: MiniMaxAPI, mediaService: MediaService, attempt = 1): Promise<any> { try { // Call media service to handle request const result = await mediaService.cloneVoice(args); return result; } catch (error) { // Check if this is a real-name verification error const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('voice clone user forbidden') || errorMessage.includes('should complete real-name verification')) { // Domestic platform verification URL const verificationUrl = 'https://platform.minimaxi.com/user-center/basic-information'; return { content: [ { type: 'text', text: `Voice cloning failed: Real-name verification required. To use voice cloning feature, please:\n\n1. Visit MiniMax platform (${verificationUrl})\n2. Complete the real-name verification process\n3. Try again after verification is complete\n\nThis requirement is for security and compliance purposes.`, }, ], }; } // Regular retry mechanism if (attempt < MAX_RETRY_ATTEMPTS) { // console.warn(`[${new Date().toISOString()}] Failed to clone voice, attempting retry (${attempt}/${MAX_RETRY_ATTEMPTS})`, error); // Delay retry await new Promise(resolve => setTimeout(resolve, RETRY_DELAY * Math.pow(2, attempt - 1))); return this.handleVoiceClone(args, api, mediaService, attempt + 1); } throw this.wrapError('Failed to clone voice', error); } }
  • MediaService wrapper method that calls VoiceCloneAPI.cloneVoice and handles errors.
    public async cloneVoice(params: any): Promise<string> { this.checkInitialized(); try { return await this.voiceCloneApi.cloneVoice(params); } catch (error) { // console.error(`[${new Date().toISOString()}] Failed to clone voice:`, error); throw this.wrapError('Failed to clone voice', error); } }

Other Tools

Related Tools

Latest Blog Posts

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/MiniMax-AI/MiniMax-MCP-JS'

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