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
| Name | Required | Description | Default |
|---|---|---|---|
| audioFile | Yes | Path to the audio file | |
| isUrl | No | Whether the audio file is a URL | |
| outputDirectory | No | The 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/` | |
| text | No | Text for the demo audio | |
| voiceId | Yes | Voice ID to use |
Implementation Reference
- src/api/voice-clone.ts:17-147 (handler)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)}`); } }
- src/types/index.ts:51-58 (schema)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; }
- src/mcp-rest-server.ts:330-351 (registration)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'] } },
- src/mcp-rest-server.ts:584-617 (handler)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); } }