| name | voicebox-voice-mcp-agent |
| description | Local-first AI voice studio for voice cloning, speech synthesis, dictation, and MCP agent voice integration |
| triggers | ["how do I use voicebox for voice cloning","set up voicebox TTS and STT locally","integrate voicebox with MCP agents","clone a voice with voicebox","configure voicebox dictation hotkey","generate speech with voicebox API","troubleshoot voicebox engine startup","add custom TTS engine to voicebox"] |
Voicebox Voice MCP Agent
Skill by ara.so — MCP Skills collection.
Overview
Voicebox is a local-first AI voice studio that combines voice input (Whisper STT + global dictation) and voice output (7 TTS engines with voice cloning) in a single TypeScript/Rust application. All inference runs on your hardware by default; audio never leaves your machine unless explicitly configured. It provides:
- Seven TTS engines: Qwen3-TTS, Qwen CustomVoice, LuxTTS, Chatterbox Multilingual/Turbo, HumeAI TADA, Kokoro
- Voice cloning: Zero-shot cloning from short reference samples
- Global dictation: System-wide hotkey with paste-into-focused-field
- MCP HTTP server: Agent-driven voice workflows at
/mcp
- 23 languages: Multilingual synthesis
- Audio effects: Pitch, reverb, delay, chorus, compression, filters
- Local LLM refinement: Bundled Qwen3 for transcript cleanup
Stack: Bun monorepo, TypeScript, Tauri 2 (Rust), React 18, Zustand, TanStack Router, Vite
Installation
Prerequisites
curl -fsSL https://bun.sh/install | bash
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
brew install ffmpeg
sudo apt install ffmpeg
Quick Setup
git clone https://github.com/jamiepine/voicebox.git
cd voicebox
just setup
just dev
Development Modes
bun run dev:web
bun run dev
bun run build
just build:desktop
Configuration
Environment Variables
Create .env in project root:
VOICEBOX_HOST=127.0.0.1
VOICEBOX_PORT=17493
VOICEBOX_CORS_ORIGINS=http://localhost:3000,http://localhost:5173
LOG_LEVEL=info
HF_TOKEN=your_huggingface_token_here
VOICEBOX_REDIS_ENABLED=false
VOICEBOX_REDIS_URL=redis://127.0.0.1:6379
VOICEBOX_REDIS_KEY_PREFIX=voicebox:
MCP Client Configuration
Create .mcp.json to configure MCP client access:
{
"mcpServers": {
"voicebox": {
"url": "http://127.0.0.1:17493/mcp",
"description": "Voicebox voice synthesis and dictation server"
}
}
}
API Usage
REST API
The service exposes a REST API at http://127.0.0.1:17493 when running.
Generate Speech
interface GenerationRequest {
text: string;
profileId: string;
engine?: string;
effects?: AudioEffect[];
}
interface AudioEffect {
type: 'pitch' | 'reverb' | 'delay' | 'chorus' | 'compression';
params: Record<string, number>;
}
async function generateSpeech(text: string, profileId: string): Promise<Blob> {
const response = await fetch('http://127.0.0.1:17493/generations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text,
profileId,
effects: [
{ type: 'pitch', params: { semitones: 2 } },
{ : , : { : , : } }
]
})
});
response.();
}
audioBlob = (
,
);
audioUrl = .(audioBlob);
audio = (audioUrl);
audio.();
Stream Generation Progress
async function streamGeneration(text: string, profileId: string) {
const response = await fetch('http://127.0.0.1:17493/generations/stream', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ text, profileId })
});
const reader = response.body!.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
const data = JSON.parse(line.slice(6));
.(, data., );
.(, data.);
}
}
}
}
Voice Cloning
async function cloneVoice(
name: string,
referenceAudio: File,
targetEngine: string = 'qwen-customvoice'
): Promise<string> {
const formData = new FormData();
formData.append('name', name);
formData.append('reference', referenceAudio);
formData.append('engine', targetEngine);
const response = await fetch('http://127.0.0.1:17493/voices/clone', {
method: 'POST',
body: formData
});
const { voiceId } = await response.json();
return voiceId;
}
const fileInput = document.querySelector('input[type="file"]') as HTMLInputElement;
const audioFile = fileInput.files![0];
const voiceId = await cloneVoice('My Custom Voice', audioFile);
console.log(, voiceId);
Service Layer (Internal)
When extending Voicebox or adding features:
export interface TTSEngine {
name: string;
synthesize(text: string, options: SynthesisOptions): Promise<AudioBuffer>;
clone?(reference: AudioBuffer): Promise<VoiceModel>;
supportedLanguages: string[];
}
export interface SynthesisOptions {
voiceId?: string;
language?: string;
speed?: number;
pitch?: number;
}
import type { TTSEngine, SynthesisOptions } from './types';
export class CustomTTSEngine implements TTSEngine {
name = 'custom-tts';
supportedLanguages = ['en', 'es', 'fr'];
async synthesize(text: string, options: SynthesisOptions): <> {
response = (, {
: ,
: { : },
: .({
text,
: options.,
: options. || ,
: options. || ,
: options. ||
})
});
arrayBuffer = response.();
audioContext = ();
audioContext.(arrayBuffer);
}
(: ): <> {
();
}
}
{ } ;
: [] = [
(),
];
Platform Adapters
Voicebox uses platform adapters to abstract native capabilities:
export interface Platform {
readFile(path: string): Promise<Uint8Array>;
writeFile(path: string, data: Uint8Array): Promise<void>;
startAudioCapture(): Promise<MediaStream>;
stopAudioCapture(): void;
registerHotkey?(combo: string, callback: () => void): Promise<void>;
unregisterHotkey?(combo: string): Promise<void>;
checkForUpdates?(): Promise<UpdateInfo | null>;
installUpdate?(): Promise<void>;
}
import { invoke } from '@tauri-apps/api/core';
import { register, unregister } from '@tauri-apps/plugin-global-shortcut';
: = {
(: ): <> {
(, { path });
},
(: , : ): <> {
(, { path, : .(data) });
},
(): <> {
navigator..({ : });
},
(): {
},
(: , : ): <> {
(combo, callback);
},
(: ): <> {
(combo);
}
};
MCP Integration
MCP Server Endpoints
The MCP server runs at http://127.0.0.1:17493/mcp and exposes tools for agent-driven workflows:
{
"tools": [
{
"name": "synthesize_speech",
"description": "Generate speech audio from text using a specific voice profile",
"inputSchema": {
"type": "object",
"properties": {
"text": { "type": "string", "description": "Text to synthesize" },
"profile_id": { "type": "string", "description": "Voice profile ID" },
"engine": { "type": "string", "description": "Optional TTS engine override" }
},
"required": ["text", "profile_id"]
}
},
{
"name": "clone_voice",
"description": "Clone a voice from reference audio",
"inputSchema": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Name for the cloned voice" },
"reference_path": { "type": "string", "description": "Path to reference audio" },
"engine": { "type": , : }
},
: [, ]
}
},
{
: ,
: ,
: {
: ,
: {
: { : , : }
}
}
}
]
}
MCP Client Usage
interface MCPRequest {
tool: string;
arguments: Record<string, unknown>;
}
async function callMCPTool(tool: string, args: Record<string, unknown>) {
const response = await fetch('http://127.0.0.1:17493/mcp/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ tool, arguments: args })
});
return await response.json();
}
const result = await callMCPTool('synthesize_speech', {
text: 'Hello from the MCP agent!',
profile_id: 'default-voice',
engine: 'qwen3-tts'
});
console.log('Audio URL:', result.audioUrl);
State Management
Voicebox uses Zustand for state management:
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
interface VoiceProfile {
id: string;
name: string;
engine: string;
voiceId: string;
settings: Record<string, unknown>;
}
interface VoiceStore {
profiles: VoiceProfile[];
activeProfileId: string | null;
addProfile: (profile: VoiceProfile) => void;
removeProfile: (id: string) => void;
setActiveProfile: (id: string) => void;
updateProfile: (id: string, updates: Partial<VoiceProfile>) => void;
}
export useVoiceStore = create<>()(
(
({
: [],
: ,
:
( ({
: [...state., profile]
})),
:
( ({
: state..( p. !== id),
: state. === id ? : state.
})),
:
({ : id }),
:
( ({
: state..(
p. === id ? { ...p, ...updates } : p
)
}))
}),
{ : }
)
);
{ useVoiceStore } ;
() {
{ profiles, activeProfileId, setActiveProfile } = ();
(
);
}
Common Patterns
Voice Generation with Effects
import { useState } from 'react';
import { useVoiceStore } from '../stores/voice';
export function SpeechGenerator() {
const [text, setText] = useState('');
const [isGenerating, setIsGenerating] = useState(false);
const [audioUrl, setAudioUrl] = useState<string | null>(null);
const activeProfileId = useVoiceStore((s) => s.activeProfileId);
async function handleGenerate() {
if (!activeProfileId || !text) return;
setIsGenerating(true);
try {
const response = await fetch('http://127.0.0.1:17493/generations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text,
profileId: activeProfileId,
effects: [
{ : , : { : } },
{ : , : { : , : } }
]
})
});
blob = response.();
url = .(blob);
(url);
} (error) {
.(, error);
} {
();
}
}
(
);
}
Global Dictation Hotkey (Desktop)
import { register } from '@tauri-apps/plugin-global-shortcut';
import { invoke } from '@tauri-apps/api/core';
export async function setupDictationHotkey(combo: string = 'CommandOrControl+Shift+D') {
await register(combo, async () => {
console.log('Dictation hotkey triggered');
await invoke('start_dictation');
});
}
#[tauri::command]
async fn start_dictation() -> Result<(), String> {
Ok(())
}
Voice Cloning Workflow
import { useState } from 'react';
export function VoiceCloner() {
const [name, setName] = useState('');
const [file, setFile] = useState<File | null>(null);
const [isCloning, setIsCloning] = useState(false);
const [result, setResult] = useState<string | null>(null);
async function handleClone() {
if (!name || !file) return;
setIsCloning(true);
try {
const formData = new FormData();
formData.append('name', name);
formData.append('reference', file);
formData.append('engine', 'qwen-customvoice');
const response = await fetch('http://127.0.0.1:17493/voices/clone', {
method: 'POST',
body: formData
});
const { voiceId } = response.();
();
} (error) {
.(, error);
();
} {
();
}
}
(
);
}
Testing
TypeScript Unit Tests
bun run test
bun run test -- --coverage
bun test src/redis/cache.test.ts
Example test structure:
import { describe, it, expect, beforeEach } from 'vitest';
import { TTSEngineRegistry } from './registry';
import { MockTTSEngine } from './mocks';
describe('TTS Engine Registry', () => {
let registry: TTSEngineRegistry;
beforeEach(() => {
registry = new TTSEngineRegistry();
registry.register(new MockTTSEngine());
});
it('should register and retrieve engines', () => {
const engine = registry.get('mock-tts');
expect(engine).toBeDefined();
expect(engine?.name).toBe('mock-tts');
});
it('should synthesize text', async () => {
const engine = registry.get('mock-tts')!;
const buffer = await engine.synthesize('Hello world', {
language: 'en',
:
});
(buffer).();
});
});
Rust Tests
cd tauri/src-tauri
cargo test
cargo test -- --nocapture
End-to-End Checks
bun run ci
Troubleshooting
Service Won't Start
Problem: API service fails to start or port 17493 is in use.
Solutions:
lsof -i :17493
netstat -ano | findstr :17493
kill -9 <PID>
export VOICEBOX_PORT=8080
bun run dev:web
curl http://127.0.0.1:17493/health
Missing Sidecar Binaries (Tauri)
Problem: bun run dev fails with missing sidecar error.
Solution:
bun run setup:dev
bun run build:sidecar
bun run dev
TTS Engine Failures
Problem: Speech generation fails or returns empty audio.
Solutions:
const engines = await fetch('http://127.0.0.1:17493/engines').then(r => r.json());
console.log('Available engines:', engines);
const profiles = await fetch('http://127.0.0.1:17493/profiles').then(r => r.json());
console.log('Voice profiles:', profiles);
const response = await fetch('http://127.0.0.1:17493/generations', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
text: 'Test',
profileId: 'default-profile-id',
engine: 'kokoro'
})
});
if (!response.ok) {
const error = await response.text();
console.(, error);
}
Model Download Failures
Problem: HuggingFace model downloads fail or timeout.
Solutions:
export HF_TOKEN=your_token_here
df -h
Get-PSDrive
huggingface-cli download model-name --local-dir ./models
export HTTP_PROXY=http://proxy:port
export HTTPS_PROXY=http://proxy:port
Redis Persistence Errors (Web Deployments)
Problem: Connection errors when persistence cache is enabled.
Solutions:
export VOICEBOX_REDIS_ENABLED=false
docker run --rm -p 6379:6379 redis:7-alpine
redis-cli -h 127.0.0.1 -p 6379 ping
export VOICEBOX_REDIS_URL=redis://127.0.0.1:6379
bun run bootstrap:persistence
TypeScript Build Errors
Problem: Type errors across workspaces.
Solutions:
bun run typecheck
cd app && bun run typecheck
cd tauri && bun run typecheck
rm -rf node_modules bun.lockb
bun install
bun run build:types
Audio Capture Issues (Desktop)
Problem: Microphone access denied or no audio captured.
Solutions:
- Grant microphone permissions in system settings
- Check browser/app permissions
- Test with different audio input device:
const devices = await navigator.mediaDevices.enumerateDevices();
const audioInputs = devices.filter(d => d.kind === 'audioinput');
console.log('Available microphones:', audioInputs);
const stream = await navigator.mediaDevices.getUserMedia({
audio: { deviceId: { exact: audioInputs[0].deviceId } }
});
GPU Acceleration Issues
Problem: TTS inference is slow or not using GPU.
Solutions:
nvidia-smi
python3 -c "import mlx; print(mlx.core.metal.is_available())"
export VOICEBOX_FORCE_CPU=true
export VOICEBOX_MAX_BATCH_SIZE=1
Project Commands Reference
just setup
bun install
bun run setup:dev
just dev
bun run dev:web
bun run dev
bun run build
bun run build:web
just build:desktop
bun run test
bun run typecheck
bun run check
bun run ci
just check
bun run preview
docker-compose up
curl http://127.0.0.1:17493/health
just clean
Key Files
app/src/services/tts/ — TTS engine implementations
app/src/services/stt/ — Speech-to-text (Whisper)
app/src/stores/ — Zustand state management
tauri/src/platform/ — Native capability implementations
src/redis/ — Persistence cache layer
.env.redis.example — Redis configuration template
justfile — Primary task orchestration
biome.json — Lint and format configuration