-
Notifications
You must be signed in to change notification settings - Fork 0
mcp_documentation
VoxCtrl ships a built-in Model Context Protocol (MCP) server that exposes the app's voice I/O pipeline as tools any MCP-capable AI can call. An agent can trigger the microphone, receive the transcript, and queue a spoken response — all through a standardised JSON-RPC 2.0 interface.
When the MCP server is enabled, VoxCtrl listens on a local transport and presents three tools:
| Tool | What it does |
|---|---|
transcribe_voice |
Opens the mic, records the user, returns the transcript |
speak_text |
Queues text for TTS playback through the current voice |
get_status |
Returns whether the mic is open and TTS is playing |
This lets an AI agent have a full voice conversation:
Agent → transcribe_voice() → user speaks → transcript returned
Agent → speak_text("…") → VoxCtrl speaks the response aloud
Agent → transcribe_voice() → next turn …
| Package | Purpose | Install |
|---|---|---|
piper |
Neural TTS engine |
yay -S piper-tts or piper releases
|
aplay |
PCM audio playback (Linux) | sudo pacman -S alsa-utils |
socat |
Stdio ↔ Unix socket bridge (Claude Desktop Linux) | sudo pacman -S socat |
Automatic Fallback Engine:
espeak-ng serves as an automatic fallback when the configured piper engine fails or its local ONNX voice models are missing. To ensure fallback functions correctly, install espeak-ng:
sudo pacman -S espeak-ngVoice models are downloaded from inside the app. Go to Settings → TTS, select a voice from the picker, and click ⬇ Download. Models are stored locally in:
~/.local/share/voxctrl/piper-voices/
- Open Settings → General
- Scroll to the MCP Server section
- Toggle "Enable MCP Server"
- The server will bind to the standard socket/pipe path shown in the settings window.
Add the mcp config block to your active config.json configuration file located at ~/.config/voxctrl/config.json:
{
"mcp": {
"server_enabled": true,
"record_timeout": 15.0,
"visual_feedback": true
}
}The server starts automatically when the app launches if server_enabled is true.
The transport layer is platform-dependent:
-
Linux: A Unix domain socket located at:
/tmp/voxctrl-mcp.sock -
Windows: A Named Pipe located at:
\\.\pipe\voxctrl-mcp
Each connection is spawned as an asynchronous tokio task. The protocol is newline-delimited JSON-RPC 2.0: one JSON object per line, terminated with \n.
Standard MCP / JSON-RPC 2.0. Every request must include "jsonrpc": "2.0".
→ {"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}
← {"jsonrpc":"2.0","id":1,"result":{
"protocolVersion":"2024-11-05",
"capabilities":{"tools":{}},
"serverInfo":{"name":"voxctrl","version":"1.0.0"}
}}After initialize, send notifications/initialized (no response expected):
→ {"jsonrpc":"2.0","method":"notifications/initialized","params":{}}→ {"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}
← {"jsonrpc":"2.0","id":2,"result":{"tools":[…]}}Opens the microphone and returns a transcript when speech ends.
Parameters
| Name | Type | Default | Description |
|---|---|---|---|
timeout_seconds |
number |
15.0 |
Maximum seconds to wait for speech before returning |
Request
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "transcribe_voice",
"arguments": {"timeout_seconds": 10.0}
}
}Response — speech detected
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [{"type": "text", "text": "Schedule a meeting for Thursday at 3 pm"}]
}
}Response — no speech / silence
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"content": [{"type": "text", "text": "(no speech detected)"}]
}
}Behaviour notes
- While recording, the active waveform or recording overlay is shown — the user always has a visual indicator that the mic is live.
- The microphone is released automatically once VAD detects silence or
timeout_secondselapses. - VoxCtrl's full post-processing pipeline (including OpenAI API LLM formatting/cleaning if enabled) is applied before the transcript is returned.
Queues text for playback using the configured TTS voice.
Parameters
| Name | Type | Required | Description |
|---|---|---|---|
text |
string |
yes | The text to speak |
Request
{
"jsonrpc": "2.0",
"id": 4,
"method": "tools/call",
"params": {
"name": "speak_text",
"arguments": {"text": "The meeting has been scheduled."}
}
}Response
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"content": [{"type": "text", "text": "spoken"}]
}
}Behaviour notes
- Returns as soon as the text is queued — does not block until playback finishes.
- If
response_overlayis enabled, a visual speaking overlay is displayed while TTS plays. - The user can interrupt playback at any time with the configured TTS stop key (default:
Escape). - If
piperis not installed, fails to spawn, or its ONNX voice files are missing, the system automatically falls back to speaking viaespeak-ng.
Error — missing text argument
{
"jsonrpc": "2.0",
"id": 4,
"error": {"code": -32603, "message": "speak_text requires 'text' argument"}
}Returns the current state of audio I/O.
Request
{
"jsonrpc": "2.0",
"id": 5,
"method": "tools/call",
"params": {"name": "get_status", "arguments": {}}
}Response
{
"jsonrpc": "2.0",
"id": 5,
"result": {
"content": [{"type": "text", "text": "{\"recording\": false, \"speaking\": true}"}]
}
}The text field is a JSON-encoded object:
| Field | Type | Description |
|---|---|---|
recording |
boolean |
true while the microphone is open |
speaking |
boolean |
true while TTS is playing |
| Code | Meaning |
|---|---|
-32700 |
Parse error (malformed JSON) |
-32601 |
Method not found |
-32603 |
Internal error (unknown tool name, missing required argument, callback exception) |
Add the following to ~/.config/claude/claude_desktop_config.json:
{
"mcpServers": {
"voxctrl": {
"command": "socat",
"args": ["STDIO", "UNIX-CONNECT:/tmp/voxctrl-mcp.sock"]
}
}
}Restart Claude Desktop. The tools transcribe_voice, speak_text, and get_status appear automatically in the tool picker.
Note: VoxCtrl must already be running before Claude Desktop connects.
import socket
import json
import platform
SOCK = "/tmp/voxctrl-mcp.sock"
PIPE = r"\\.\pipe\voxctrl-mcp"
def rpc(sock, method, params=None, rpc_id=1):
req = {"jsonrpc": "2.0", "id": rpc_id, "method": method, "params": params or {}}
sock.sendall((json.dumps(req) + "\n").encode())
data = b""
while True:
chunk = sock.recv(4096)
if not chunk:
break
data += chunk
if b"\n" in data:
break
return json.loads(data.split(b"\n")[0])
# Linux/Unix connection example
if platform.system() != "Windows":
with socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) as s:
s.connect(SOCK)
# Handshake
rpc(s, "initialize")
s.sendall((json.dumps({"jsonrpc":"2.0","method":"notifications/initialized","params":{}}) + "\n").encode())
# Ask the user a question
rpc(s, "tools/call", {"name": "speak_text", "arguments": {"text": "What would you like to do?"}}, rpc_id=2)
# Record the reply
resp = rpc(s, "tools/call", {"name": "transcribe_voice", "arguments": {"timeout_seconds": 15}}, rpc_id=3)
transcript = resp["result"]["content"][0]["text"]
print("User said:", transcript)# One-shot: list tools
echo '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}' \
| socat - UNIX-CONNECT:/tmp/voxctrl-mcp.sock \
| head -1 | python3 -m json.toolFor agents that generate responses to a named FIFO, VoxCtrl can dynamically read those responses and speak them automatically — without needing the agent to call speak_text directly.
- The user configures an output target specifying a
response_pipepath. - The Tauri application spawns an asynchronous
run_fifo_respondertask that watches this FIFO file. - When the agent writes its response text to the FIFO, VoxCtrl reads each line and pushes it to the TTS Engine worker queue.
- Playback proceeds automatically, showing the visual speaking overlay.
- The task automatically handles agent disconnects (EOF) and will reconnect on the next session without restarting VoxCtrl.
[[target]]
id = "my-agent"
label = "My Agent"
delivery = "pipe"
pipe_path = "/tmp/my-agent.in"
response_pipe = "/tmp/my-agent.out" # ← agent writes responses here
append_newline = trueAll TTS settings live in ~/.config/voxctrl/config.json under the tts key (Settings → TTS). MCP settings live under the mcp key (Settings → General).
| Key | Type | Default | Description |
|---|---|---|---|
tts.enabled |
bool |
false |
Master TTS on/off switch |
tts.engine |
string |
"piper" |
"piper" or "espeak"
|
tts.voice |
string |
"en-us-lessac-medium" |
Voice ID from the catalog |
tts.stop_key |
string[] |
["KEY_ESCAPE"] |
Hotkeys to stop TTS playback |
tts.response_overlay |
bool |
true |
Show speaking overlay while TTS plays |
mcp.server_enabled |
bool |
false |
Start the MCP server on launch |
mcp.record_timeout |
number |
15.0 |
Default recording timeout in seconds |
mcp.visual_feedback |
bool |
true |
Show overlay indicator while MCP server is listening to microphone |
| Voice ID | Language | Quality | Sample Rate |
|---|---|---|---|
en-us-libritts-high |
US English | High | 22050 Hz |
en-us-amy-low |
US English | Low | 16000 Hz |
en-us-kathleen-low |
US English | Low | 16000 Hz |
en-gb-southern_english_female-low |
GB English | Low | 16000 Hz |
en-us-ryan-high |
US English | High | 22050 Hz |
en-us-ryan-medium |
US English | Medium | 22050 Hz |
en-us-ryan-low |
US English | Low | 16000 Hz |
en-us-lessac-medium |
US English | Medium | 16000 Hz |
en-us-lessac-low |
US English | Low | 16000 Hz |
en-us-danny-low |
US English | Low | 16000 Hz |
en-gb-alan-low |
GB English | Low | 16000 Hz |
Download voices in Settings → TTS → Voice Picker → ⬇ Download.
-
Linux:
/tmp/voxctrl-mcp.sock -
Windows:
\\.\pipe\voxctrl-mcp
- Spawns an asynchronous transport listener loop via
tokio::spawnupon application startup if enabled in settings. - Each incoming connection spawns its own async tokio task (
tokio::spawn), enabling fully non-blocking handling of concurrent client requests. - The
transcribe_voicehandler operates asynchronously: other connections can continue to list status or write spoken text without being blocked.
transcribe_voice is handled by the shared atomic AppState structure:
- The tool handler locks and clears
last_text(a thread-safe Mutex-protected string representing the latest transcription result). - It sets the
recordingatomic boolean totrue, which immediately triggers the active recording overlays in the Tauri/Svelte frontend. - A background timer task is spawned that sleeps for
timeout_secondsand automatically flipsrecordingback tofalse(and feeds a sentinel empty buffer to the audio channel to flush the VAD processor). - The tool task polls
self.is_recording()at 50ms intervals until the recording session closes (triggered by the timer or manual user stop). - Once recording ends, the task polls the
last_textbuffer for up to 3.0 seconds (60 iterations × 50ms) to allow the local Whisper.cpp or Moonshine inference worker thread to compile the waveform. - The resulting transcription text is packaged into standard MCP JSON-RPC format and returned. If no speech is recorded or the buffer is blank,
(no speech detected)is returned.
Socket does not exist
VoxCtrl is not running, or the MCP server is disabled. Enable it in Settings → General or set "mcp": { "server_enabled": true } in config.json and restart.
socat connection refused
The socket exists but the server is not listening yet. Wait a moment after VoxCtrl starts, or check the app's console output for errors.
TTS plays but no audio
-
Linux: Check that
aplayis installed (which aplay). - Verify the voice model is downloaded: models live in
~/.local/share/voxctrl/piper-voices/. - Try changing the global TTS engine to
"espeak"in settings orconfig.jsonas a fallback choice.
transcribe_voice returns (no speech detected)
- Confirm your microphone is selected in Settings → Audio.
- Raise
timeout_seconds— the default 15 s may be too short if recording takes time to initialize. - Check the VAD threshold in Settings → Audio — a higher sensitivity value (lower raw threshold) may be needed for quiet speech.
Claude Desktop does not see the tools
- Restart Claude Desktop after editing
claude_desktop_config.json. - Confirm
socatis installed andsocat STDIO UNIX-CONNECT:/tmp/voxctrl-mcp.sockconnects successfully from a terminal. - Check that
voxctrl-mcp.sockexists (ls -la /tmp/*.sock). - Ensure VoxCtrl is running and the MCP server is enabled in Settings → General.