Skip to main content
Glama
shesanth
by shesanth
README.md
# avatar-mcp

Desktop avatar companion for [Claude Code](https://docs.anthropic.com/en/docs/claude-code). Gives Claude a visible on-screen presence with text-to-speech, speech-to-text, and an interactive avatar overlay.

Runs as an [MCP server](https://modelcontextprotocol.io/) — Claude Code connects to it automatically and gets access to voice/avatar tools.

## What it does

- **Avatar overlay** — A draggable, always-on-top transparent window that displays poses and emotions (PyQt6)
- **Text-to-speech** — Claude can speak aloud with emotional prosody. Three engines:
  - **Kokoro** (default) — Free, local, runs via ONNX runtime. Auto-downloads ~350MB model on first use
  - **Edge TTS** — Free, uses Microsoft's neural voices, requires internet
  - **ElevenLabs** — Premium quality, requires API key
- **Speech-to-text** — Voice input with two engines:
  - **RealtimeSTT** (recommended) — Local Whisper model via faster-whisper, GPU-accelerated, real-time streaming, no API keys
  - **Google Speech API** — Cloud-based fallback, no GPU required, higher latency
- **Emotion system** — 7 emotions (neutral, happy, sad, excited, angry, shy, smug) that affect avatar pose and voice prosody
- **Automatic pose changes** — Avatar reacts to what Claude is doing (coding, thinking, planning, listening) via Claude Code hooks. No manual tool calls needed

## Why?

Claude Code runs in a terminal — if you alt-tab away, switch monitors, or just glance at another screen, you lose all visual feedback. The avatar sits on top of everything so you always know what Claude is doing: coding, thinking, speaking, or waiting for input. Useful for multi-monitor setups, long-running tasks, and voice-driven workflows where the terminal isn't in focus.

## Setup

### Requirements

- Python 3.11+
- A microphone (for STT)
- Speakers/headphones (for TTS)

### Install

```bash
git clone <this-repo>
cd avatar-mcp
pip install -e .
```

For RealtimeSTT (local Whisper, recommended if you have a GPU):
```bash
pip install -e ".[realtime-stt]"
# For CUDA acceleration (replace cu128 with your CUDA version):
pip install --force-reinstall torch torchaudio --index-url https://download.pytorch.org/whl/cu128
```

For ElevenLabs support:
```bash
pip install -e ".[premium]"
```

For development:
```bash
pip install -e ".[dev]"
```

### Configure Claude Code

Add to your project's `.mcp.json` (or global MCP config):

```json
{
  "mcpServers": {
    "avatar-mcp": {
      "command": "python",
      "args": ["-m", "avatar_mcp.server"],
      "cwd": "/path/to/avatar-mcp"
    }
  }
}
```

Restart Claude Code. The avatar window should appear and tools will be available.

### Automatic Poses via Hooks (recommended)

The avatar can change poses automatically based on what Claude is doing — no manual `set_pose()` calls needed. Add hooks to your Claude Code settings (`~/.claude/settings.json`):

```json
{
  "hooks": {
    "UserPromptSubmit": [
      {
        "hooks": [{ "type": "command", "command": "echo thinking > \"$HOME/.claude/avatar-pose\"" }]
      }
    ],
    "PreToolUse": [
      {
        "matcher": "Edit|Write|NotebookEdit|Bash",
        "hooks": [{ "type": "command", "command": "echo coding > \"$HOME/.claude/avatar-pose\"" }]
      },
      {
        "matcher": "Read|Grep|Glob",
        "hooks": [{ "type": "command", "command": "echo thinking > \"$HOME/.claude/avatar-pose\"" }]
      },
      {
        "matcher": "Task|EnterPlanMode",
        "hooks": [{ "type": "command", "command": "echo planning > \"$HOME/.claude/avatar-pose\"" }]
      }
    ],
    "PermissionRequest": [
      {
        "hooks": [{ "type": "command", "command": "echo listening > \"$HOME/.claude/avatar-pose\"" }]
      }
    ],
    "Stop": [
      {
        "hooks": [{ "type": "command", "command": "echo listening > \"$HOME/.claude/avatar-pose\"" }]
      }
    ]
  }
}
```

This maps avatar poses to Claude's activity:
- **User sends message** → thinking pose (UserPromptSubmit)
- **Edit/Write/Bash** → coding pose (PreToolUse)
- **Read/Grep/Glob** → thinking pose (PreToolUse)
- **Task/Plan mode** → planning pose (PreToolUse)
- **Waiting for approval** → listening pose (PermissionRequest)
- **Turn complete** → listening pose (Stop)
- **speak(text, emotion)** → emotion-matched pose while speaking (built-in, no hook needed)

The MCP server watches the `~/.claude/avatar-pose` file for changes and updates the avatar automatically.

### Auto-allow tools (optional)

To skip approval prompts, add to `.claude/settings.local.json`:

```json
{
  "permissions": {
    "allow": [
      "mcp__avatar-mcp__speak",
      "mcp__avatar-mcp__show_avatar",
      "mcp__avatar-mcp__hide_avatar"
    ]
  }
}
```

## Configuration

Edit `config.toml` in the project root:

```toml
[avatar]
start_visible = true
start_x = 100          # initial window position
start_y = 100
sprite_scale = 1.0
sprite_directory = ""   # empty = use built-in placeholders; set a path to use custom PNGs
poll_interval_ms = 50

[tts]
engine = "kokoro"       # "edge", "kokoro", or "elevenlabs"
voice = "jf_alpha"      # voice ID (run list_voices tool to see options)
kokoro_lang = "en-us"   # language override for Kokoro (auto-detected from voice prefix if empty)
elevenlabs_api_key = ""
elevenlabs_voice_id = ""
elevenlabs_model = "eleven_flash_v2_5"

[stt]
enabled = true
engine = "realtime"            # "google" or "realtime" (local whisper)
language = "en-US"
cooldown_seconds = 3.0
pause_threshold = 1.2
wake_words = ["claude", "hey claude"]
# realtime engine (faster-whisper via RealtimeSTT)
realtime_model = "base"        # tiny / base / small / medium / large-v3
realtime_device = "cuda"       # "cuda" or "cpu"
realtime_silero_sensitivity = 0.4
# google engine (fallback)
energy_threshold = 150
phrase_threshold = 0.1
non_speaking_duration = 0.5

[behavior]
auto_speak = true
```

## MCP Tools

Once connected, Claude Code has access to these tools:

| Tool | Description |
|------|-------------|
| `speak(text, emotion)` | Speak text aloud with emotional prosody. Shows emotion-matched pose while speaking |
| `show_avatar()` | Show the avatar window |
| `hide_avatar()` | Hide the avatar window |
| `start_listening()` | Start speech recognition |
| `stop_listening()` | Stop speech recognition |
| `set_voice(voice_id, engine)` | Change TTS voice or engine |
| `list_voices(engine)` | List available voices |

### Emotions
`neutral`, `happy`, `sad`, `excited`, `angry`, `shy`, `smug`

### Poses
`idle`, `thinking`, `coding`, `angry`, `smug`, `shy`, `planning`, `speaking`, `listening`, `drag`

## Custom Sprites

To use your own avatar sprites, create a directory with PNG files named after poses:

```
my-sprites/
  idle.png
  thinking.png
  coding.png
  angry.png
  smug.png
  shy.png
  planning.png
  speaking.png
  listening.png
  drag.png
```

Then set `sprite_directory = "path/to/my-sprites"` in `config.toml`.

## Voice Input

Speech-to-text uses wake word activation by default. Say **"Claude"** or **"Hey Claude"** followed by your message. Text is injected into Claude Code's input as `[VOICE]` messages.

### STT Engines

**RealtimeSTT** (recommended) — Uses faster-whisper for local, GPU-accelerated transcription. Streams results in real-time with built-in Silero VAD. No network calls, no API keys, no truncation. Requires `pip install -e ".[realtime-stt]"` and CUDA-enabled PyTorch.

**Google Speech API** (fallback) — Cloud-based, works without a GPU but has higher latency and may drop long utterances. Set `engine = "google"` in config to use.

Configure wake words in `config.toml` under `[stt]`. Set `wake_words = []` to disable filtering (all speech passes through).

## Running Tests

```bash
pip install -e ".[dev]"
pytest tests/ -v
```

## Process Cleanup

The MCP server spawns several child processes (avatar display, multiprocessing Manager, STT workers). Multiple mechanisms ensure these are cleaned up when Claude Code exits:

1. **Parent watchdog** — A daemon thread polls every 2s to check if the parent process (Claude Code) is alive. If the parent dies, all children are force-killed immediately. This is the most reliable mechanism since it doesn't depend on signals or atexit.
2. **Job Objects** (Windows) — All child processes are assigned to a Win32 Job Object with `JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE`, so the OS kills them when the MCP server exits.
3. **Display self-monitoring** — The avatar window checks its parent PID every ~2s and self-terminates if the parent is gone.
4. **atexit + signal handlers** — Standard cleanup on normal interpreter shutdown and SIGINT/SIGTERM.
5. **PID file** — `~/.claude/avatar-mcp-children.pid` tracks child PIDs for stale process cleanup on next startup.

## Architecture

```
src/avatar_mcp/
  server.py          # MCP server, pose file watcher, parent watchdog
  lifecycle.py       # Process lifecycle, TTS/STT init, hook pose logic
  config.py          # TOML config parsing
  state.py           # Shared state (multiprocessing.Manager)
  avatar/
    display.py       # PyQt6 overlay window (child process)
    sprites.py       # Sprite loading and placeholder generation
  voice/
    tts_base.py      # Abstract TTS engine interface
    tts_edge.py      # Edge TTS (free, cloud)
    tts_kokoro.py    # Kokoro TTS (free, local ONNX)
    tts_eleven.py    # ElevenLabs TTS (premium)
    audio.py         # Playback queue
    emotions.py      # Emotion → prosody mapping
    stt_base.py      # Abstract STT engine interface
    stt_google.py    # Google Speech API (cloud fallback)
    stt_realtime.py  # RealtimeSTT / faster-whisper (local, GPU)
  input/
    sender.py        # Injects voice text into Claude Code via clipboard
```

## License

MIT