iTerm MCP
README.md
# iTerm MCP
[](https://github.com/research-developer/iterm-mcp/actions/workflows/ci.yml)
[](https://codecov.io/gh/research-developer/iterm-mcp)
A Python implementation for controlling iTerm2 terminal sessions with support for multiple panes and layouts. This implementation uses the iTerm2 Python API for improved reliability and functionality.
**Note:** This project provides multi-agent orchestration infrastructure, complementary to tools like [@steipete/claude-code-mcp](https://github.com/steipete/claude-code-mcp). See [docs/claude-code-mcp-analysis.md](docs/claude-code-mcp-analysis.md) for a detailed comparison.
## Status
✅ **Multi-Pane Orchestration** - Parallel session operations with agent/team targeting
✅ **Agent Registry** - Complete agent and team management with cascading messages
✅ **Test Coverage** - per-module suite via `scripts/test_baseline.py` (36 modules passing; 7 live-iTerm2 integration modules need an interactive session)
✅ **CI/CD** - Automated testing with coverage reporting
See [EPIC_STATUS.md](docs/archive/EPIC_STATUS.md) for the historical implementation status of the multi-agent orchestration epic.
## Features
- Named terminal sessions with persistent identity across restarts
- Persistent session IDs for reconnection after interruptions
- Multiple pane layouts (single, horizontal split, vertical split, quad, etc.)
- Command execution and output capture with configurable line limits
- Real-time session monitoring with callback support
- Log management with filterable output using regex patterns
- Live output snapshots for LLM access with overflow handling
- Multiple session creation and parallel command execution
- Background process execution and status tracking
- Control character support (Ctrl+C, etc.)
- Lightweight telemetry with MCP resource + optional dashboard for pane and team health (see [docs/telemetry.md](docs/telemetry.md))
- **Role-based session specialization** with tool filtering and access control (see [docs/ROLES.md](docs/ROLES.md))
## Requirements
- Python 3.8+
- iTerm2 3.3+ with Python API enabled
- MCP Python SDK (1.8.0+, for streamable-HTTP daemon mode)
## Installation
1. Clone this repository
2. Install dependencies:
```bash
pip install -e .
```
This will install the package with all required dependencies, including the MCP Python SDK.
## Project Structure
```
iterm-mcp/
├── pyproject.toml # Python packaging configuration
├── core/ # Core functionality
│ ├── __init__.py
│ ├── session.py # iTerm session management
│ ├── terminal.py # Terminal window/tab management
│ ├── layouts.py # Predefined layouts
│ ├── agents.py # Agent/team registry
│ ├── roles.py # Role-based session specialization
│ └── models.py # Pydantic request/response models
├── iterm_mcpy/ # Server implementations
│ ├── __init__.py
│ ├── __main__.py # python -m iterm_mcpy entry
│ ├── main.py # iterm-mcp CLI (shim default; daemon/stdio/status/stop/install)
│ ├── app_context.py # Process-level state singleton (AppContext)
│ ├── daemon.py # Singleton streamable-HTTP daemon
│ ├── shim.py # stdio<->HTTP shim (what clients spawn)
│ ├── fastmcp_server.py # FastMCP server wiring (mcp instance, resources, prompts)
│ └── tools/ # MCP tool modules
├── utils/ # Utility functions
│ ├── __init__.py
│ └── logging.py # Logging utilities
└── tests/ # Test suite
```
## Development Setup
1. Clone the repository
2. Install dependencies:
```bash
pip install -e ".[dev]"
```
3. Run tests:
```bash
./scripts/watch_tests.sh
```
## Usage
### MCP Integration with the Official Python SDK
### Architecture: singleton daemon
Both Claude Code and Claude Desktop spawn the stdio shim (`iterm-mcp` / `python -m iterm_mcpy`). The shim discovers or auto-starts a shared singleton daemon over streamable HTTP on `127.0.0.1:12340-12349`. All clients share one iTerm2 connection and one agent/team registry — cross-client agent visibility is the point of this architecture. The daemon auto-starts on first client connect; its state is advertised in `~/.iterm-mcp/daemon.json`; `iterm-mcp status` / `iterm-mcp stop` manage it. Note: the registry's implicit *active session* is shared across all connected clients (last writer wins) — prefer explicit session/agent/team targets when multiple clients are attached.
```bash
iterm-mcp # stdio shim (what Claude Code/Desktop spawn); auto-starts the shared daemon
iterm-mcp daemon # run the singleton HTTP daemon in the foreground
iterm-mcp stdio # single-process stdio server (debugging; no daemon)
iterm-mcp status|stop # inspect / stop the shared daemon
iterm-mcp install # write Claude Desktop config + print Claude Code add command
```
### Running the MCP Server
```bash
# Run the shim (auto-starts the shared daemon)
iterm-mcp
# Or equivalently
python -m iterm_mcpy
```
### Installing the MCP Server for Claude Desktop
Use the built-in install command:
```bash
iterm-mcp install --desktop
```
### Debugging with MCP Inspector
For development and debugging, you can use the MCP Inspector:
```bash
mcp dev iterm_mcpy/fastmcp_server.py
```
### Important Implementation Notes
1. **Process Termination**:
The daemon translates SIGTERM into a clean `SystemExit` so `iterm-mcp stop` (and plain `kill`) clears the `~/.iterm-mcp/daemon.json` state file on the way out. The state-file cleanup is pid-guarded so an old daemon's exit never deletes a successor's registration.
2. **New FastMCP API**:
The FastMCP implementation uses the decorator-based API from the official MCP Python SDK. Tools are defined with `@mcp.tool()`, resources with `@mcp.resource()`, and prompts with `@mcp.prompt()`.
3. **Lifespan Management**:
The FastMCP implementation uses the lifespan API to properly initialize and clean up iTerm2 connections. The lifespan context provides access to the terminal, layout manager, and logger.
4. **WebSocket Handling**:
The FastMCP implementation uses the official SDK which properly handles WebSocket frames, fixing the "no close frame received or sent" error that previously occurred.
5. **Port Selection**:
The server uses port range 12340-12349 to avoid conflicts with common services. It automatically tries the next port in the range if one is busy.
### Using in Your Own Scripts
#### Basic Usage
```python
import asyncio
import iterm2
from core.terminal import ItermTerminal
from core.layouts import LayoutManager, LayoutType
async def my_script():
# Connect to iTerm2
connection = await iterm2.Connection.async_create()
# Initialize terminal and layout manager
terminal = ItermTerminal(connection)
await terminal.initialize()
layout_manager = LayoutManager(terminal)
# Create a layout with named panes
session_map = await layout_manager.create_layout(
layout_type=LayoutType.HORIZONTAL_SPLIT,
pane_names=["Code", "Terminal"]
)
# Get sessions by name
code_session = await terminal.get_session_by_name("Code")
terminal_session = await terminal.get_session_by_name("Terminal")
# Send commands to sessions
await code_session.send_text("vim myfile.py", execute=True)
await terminal_session.send_text("python -m http.server", execute=True)
# Type text without executing (for CLIs with prompts)
await code_session.send_text("i", execute=False) # Enter insert mode in vim
await code_session.send_text("print('Hello, world!')", execute=False)
await code_session.send_special_key("escape") # Switch to command mode
# Run the script
asyncio.run(my_script())
```
#### Advanced Features
```python
import asyncio
import iterm2
from core.terminal import ItermTerminal
async def my_advanced_script():
# Connect to iTerm2
connection = await iterm2.Connection.async_create()
# Initialize terminal with custom line limits
terminal = ItermTerminal(
connection=connection,
default_max_lines=100, # Default lines to retrieve per session
max_snapshot_lines=1000 # Maximum lines to keep in snapshot
)
await terminal.initialize()
# Create multiple sessions with different commands and line limits
session_configs = [
{
"name": "Server",
"command": "python -m http.server",
"monitor": True,
"max_lines": 200 # Custom line limit for this session
},
{
"name": "Logs",
"command": "tail -f server.log",
"layout": True,
"vertical": True
},
{
"name": "Client",
"command": "curl localhost:8000",
"layout": True,
"vertical": False
}
]
session_map = await terminal.create_multiple_sessions(session_configs)
# Get the Server session for monitoring
server_session = await terminal.get_session_by_id(session_map["Server"])
# Store the persistent ID for future reconnection
server_persistent_id = server_session.persistent_id
print(f"Server session persistent ID: {server_persistent_id}")
# Add real-time output handling
async def handle_server_output(content):
if "GET /" in content:
# React to server events
client_session = await terminal.get_session_by_id(session_map["Client"])
await client_session.send_text("echo 'Detected a GET request!'\n")
# Register the callback
server_session.add_monitor_callback(handle_server_output)
# Add output filtering to Logs session
logs_session = await terminal.get_session_by_id(session_map["Logs"])
logs_session.logger.add_output_filter(r"ERROR|WARN") # Only capture errors and warnings
# Wait for events
while True:
await asyncio.sleep(1)
# Get snapshot with limited lines
snapshot = terminal.log_manager.get_snapshot(
server_session.id,
max_lines=50 # Only get last 50 lines
)
if snapshot and "Keyboard interrupt received" in snapshot:
break
# Example of reconnecting by persistent ID in a new session
async def reconnect_later():
# Create a new terminal instance (simulating a new connection)
new_connection = await iterm2.Connection.async_create()
new_terminal = ItermTerminal(new_connection)
await new_terminal.initialize()
# Reconnect to server session using persistent ID
reconnected_session = await new_terminal.get_session_by_persistent_id(server_persistent_id)
if reconnected_session:
print(f"Successfully reconnected to session: {reconnected_session.name}")
# Continue working with the reconnected session
await reconnected_session.send_text("echo 'Reconnected!'\n")
# Run the script
asyncio.run(my_advanced_script())
```
#### Hierarchical team orchestration (CEO -> Team Leads -> ICs)
The layout manager and MCP tools now understand hierarchical pane specs that include
team/agent metadata. Pane titles are derived automatically (e.g., `Team Leads :: TL-Backend`),
and the MCP servers will register the agents and teams for you.
```python
# Create a 2x2 grid with explicit team/agent hierarchy
session_map = await layout_manager.create_layout(
layout_type=LayoutType.QUAD,
pane_hierarchy=[
{"team": "Executive", "agent": "CEO"},
{"team": "Team Leads", "agent": "TL-Frontend"},
{"team": "Team Leads", "agent": "TL-Backend"},
{"team": "ICs", "agent": "IC-Oncall"},
],
)
# When using the sessions create API the same hierarchy is
# auto-registered in AgentRegistry:
# create_sessions(layout="quad", session_configs=[...])
# Target panes by hierarchy and send cascading messages
await select_panes_by_hierarchy([
{"team": "Team Leads", "agent": "TL-Frontend"}
])
await send_hierarchical_message(
targets=[
{"team": "Team Leads", "message": "Share updates for the CEO."},
{"team": "Team Leads", "agent": "TL-Backend", "message": "Ship the API fixes."},
{"team": "ICs", "agent": "IC-Oncall", "message": "Monitor logs for regressions."},
],
broadcast="CEO broadcast: align on launch goals.",
)
```
## MCP Tools and Resources
The FastMCP implementation provides the following:
### Session Management Tools
- `list_sessions` - List all available terminal sessions (with optional `agents_only` filter)
- `set_active_session` - Set the active session for subsequent operations (with optional `focus` to bring to foreground)
- `create_sessions` - Create multiple sessions with layout and agent registration
- `check_session_status` - Check if a session is currently processing a command
### Command Execution Tools
- `write_to_sessions` - Write to multiple sessions in parallel with targeting
- `read_sessions` - Read from multiple sessions in parallel with filtering
- `send_control_character` - Send a control character (Ctrl+C, Ctrl+D, etc.)
- `send_special_key` - Send a special key (Enter, Tab, Escape, Arrow keys, etc.)
### Agent & Team Management
- `register_agent` - Register a named agent bound to a session
- `list_agents` - List all registered agents (optionally filtered by team)
- `remove_agent` - Remove an agent registration
- `create_team` - Create a new team for grouping agents
- `list_teams` - List all teams
- `remove_team` - Remove a team
- `assign_agent_to_team` - Add an agent to a team
- `remove_agent_from_team` - Remove an agent from a team
### Orchestration Tools
- `orchestrate_playbook` - Execute a high-level playbook (layout + commands + cascade + reads)
- `send_cascade_message` - Send priority-based cascading messages to agents/teams
- `select_panes_by_hierarchy` - Resolve panes by team/agent hierarchy
- `send_hierarchical_message` - Send cascading messages using hierarchical specs
### Session Modification Tools
- `modify_sessions` - Modify multiple sessions at once (appearance, focus, active state)
The modification tool allows:
- **set_active** - Set session as the active session for subsequent operations
- **focus** - Bring session to the foreground in iTerm
- **background_color** - RGB values for the session background
- **tab_color** - RGB values for the iTerm tab indicator
- **cursor_color** - RGB values for the cursor
- **badge** - Text badge displayed in the session (supports emoji)
- **reset** - Reset all colors to defaults
Example:
```json
{
"modifications": [
{
"agent": "claude-1",
"focus": true,
"set_active": true,
"tab_color": {"red": 100, "green": 200, "blue": 255},
"badge": "🤖 Claude-1"
},
{
"agent": "claude-2",
"background_color": {"red": 40, "green": 30, "blue": 30},
"tab_color": {"red": 255, "green": 150, "blue": 100}
}
]
}
```
### Monitoring Tools
- `start_monitoring_session` - Start real-time monitoring for a session
- `stop_monitoring_session` - Stop real-time monitoring for a session
### Session Lock & Tag Tools
- `lock_session` - Lock a session for exclusive agent access
- `unlock_session` - Release a session lock
- `request_session_access` - Request permission to write to a locked session
- `set_session_tags` - Set or append tags on a session
### Notification Tools
- `get_notifications` - Get recent agent notifications (errors, completions, blocks)
- `get_agent_status_summary` - Compact one-line-per-agent status summary
- `notify` - Manually add a notification for an agent
- `wait_for_agent` - Wait for an agent to complete or reach idle state
### Resources
- `terminal://{session_id}/output` - Get the output from a terminal session
- `terminal://{session_id}/info` - Get information about a terminal session
- `terminal://sessions` - Get a list of all terminal sessions
- `agents://all` - Get a list of all registered agents
- `teams://all` - Get a list of all teams
### Prompts
- `orchestrate_agents` - Prompt for orchestrating multiple agents
- `monitor_team` - Prompt for monitoring a team of agents
## Multi-Agent Orchestration
The iTerm MCP server supports coordinating multiple Claude Code instances through named agents, teams, and parallel session operations.
### Team Profiles with Auto-Assigned Colors
Teams can be assigned unique iTerm profiles with automatically distributed colors. The `ColorDistributor` uses a maximum-gap algorithm to ensure colors are visually distinct.
```python
from core.profiles import ProfileManager, ColorDistributor
# Create profile manager
profile_manager = ProfileManager()
# Create profiles for teams - colors are auto-assigned
teams = ["backend", "frontend", "devops", "ml", "security"]
for team_name in teams:
profile = profile_manager.get_or_create_team_profile(team_name)
print(f"{team_name}: hue={profile.color.hue:.1f}°, GUID={profile.guid}")
# Save profiles to iTerm Dynamic Profiles
profile_manager.save_profiles()
# Verify: profiles saved to ~/Library/Application Support/iTerm2/DynamicProfiles/
```
**Pass Criteria**:
- 5 team profiles created with unique GUIDs
- Hues are evenly distributed (minimum gap ≥ 40°)
- Profile file created at `~/Library/Application Support/iTerm2/DynamicProfiles/iterm-mcp-profiles.json`
The color distribution algorithm starts at 180° (teal) and bisects the largest gap for each new color:
```python
from core.profiles import ColorDistributor
distributor = ColorDistributor(saturation=70, lightness=38)
# Get 6 colors - each fills the largest gap
colors = [distributor.get_next_color() for _ in range(6)]
hues = [c.hue for c in colors] # [180.0, 0.0, 90.0, 270.0, 45.0, 225.0]
# Verify: min gap ≥ 45°, max gap ≤ 90° for 6 colors
```
### Agent & Team Concepts
Agents bind a name to an iTerm session, enabling targeted communication. Teams group agents for broadcast operations.
```python
# Register agents
register_agent(name="alice", session_id="session-123", teams=["frontend"])
register_agent(name="bob", session_id="session-456", teams=["frontend", "backend"])
# Create teams
create_team(name="frontend", description="Frontend developers")
create_team(name="backend", description="Backend developers")
# List agents by team
list_agents(team="frontend") # Returns alice and bob
```
### Playbook Orchestration
The `orchestrate` tool lets you define multi-team workflows once and execute them with a single request:
1. **Create a layout** with `CreateSessionsRequest` (pane names, optional agent/team assignment, initial commands).
2. **Run command blocks** defined as `PlaybookCommand` entries (parallel flags + `SessionMessage` targets).
3. **Fan out cascades** via `CascadeMessageRequest` to broadcast, team, or agent recipients with deduplication.
4. **Monitor results** using a `ReadSessionsRequest` to collect outputs across teams.
Example payload:
```json
{
"playbook": {
"layout": {"sessions": [{"name": "Ops"}, {"name": "QA"}], "layout": "VERTICAL_SPLIT"},
"commands": [
{"name": "bootstrap", "messages": [{"content": "echo ready", "targets": [{"name": "Ops"}]}]}
],
"cascade": {"broadcast": "Deploying new build"},
"reads": {"targets": [{"team": "qa"}], "parallel": true}
}
}
```
**Python API (verified)**:
```python
from core.models import (
CreateSessionsRequest, SessionConfig,
OrchestrateRequest, Playbook, PlaybookCommand,
SessionMessage, SessionTarget
)
# Create a multi-stage playbook
playbook = Playbook(
commands=[
PlaybookCommand(
name='initial-setup',
messages=[
SessionMessage(
content='echo "Hello from playbook"',
targets=[SessionTarget(team='docs-testing')],
execute=True
)
],
parallel=True
),
PlaybookCommand(
name='verification',
messages=[
SessionMessage(
content='echo "Setup complete"',
targets=[SessionTarget(agent='test-profiles')],
execute=True
)
],
parallel=False
)
]
)
# Wrap in OrchestrateRequest for MCP tool
request = OrchestrateRequest(playbook=playbook)
# Pass Criteria:
# - Commands: 2 stages created
# - Each PlaybookCommand has messages with targets
# - OrchestrateRequest validates successfully
```
### Parallel Session Operations
Write to or read from multiple sessions simultaneously:
```python
# Write to multiple sessions by different targets
write_to_sessions(
messages=[
{"content": "npm test", "targets": [{"team": "frontend"}]},
{"content": "cargo test", "targets": [{"agent": "rust-agent"}]},
{"content": "echo hello", "targets": [{"name": "Session1"}, {"name": "Session2"}]}
],
parallel=True,
skip_duplicates=True
)
# Read from multiple sessions
read_sessions(
targets=[
{"agent": "alice", "max_lines": 50},
{"team": "backend", "max_lines": 100}
],
parallel=True,
filter_pattern="ERROR|WARN"
)
```
### Cascading Messages
Send priority-based messages where the most specific wins:
```python
# Cascading priority: agent > team > broadcast
send_cascade_message(
broadcast="All agents: sync your status",
teams={
"frontend": "Frontend team: run lint check",
"backend": "Backend team: run database migrations"
},
agents={
"alice": "Alice, please handle the API review specifically"
},
skip_duplicates=True
)
```
Resolution order:
1. If agent has a specific message → use it
2. Else if agent's team has a message → use team message
3. Else if broadcast exists → use broadcast
4. Messages are deduplicated to prevent sending the same content twice
**Python API (verified)**:
```python
from core.agents import AgentRegistry, CascadingMessage
registry = AgentRegistry()
# Create teams and register agents
registry.create_team("docs-testing", "Documentation testing team")
registry.register_agent("test-profiles", "session-1", teams=["docs-testing"])
registry.register_agent("test-agents", "session-2", teams=["docs-testing"])
# Create a cascading message targeting a team
team_cascade = CascadingMessage(teams={"docs-testing": "Hello team!"})
result = registry.resolve_cascade_targets(team_cascade)
# Returns: {'Hello team!': ['test-profiles', 'test-agents']}
# Target a specific agent
agent_cascade = CascadingMessage(agents={"test-profiles": "Hello agent!"})
result = registry.resolve_cascade_targets(agent_cascade)
# Returns: {'Hello agent!': ['test-profiles']}
# Broadcast to all registered agents
broadcast_cascade = CascadingMessage(broadcast="Hello everyone!")
result = registry.resolve_cascade_targets(broadcast_cascade)
# Returns: {'Hello everyone!': ['test-profiles', 'test-agents', ...]}
```
**Pass Criteria**:
- Team messages resolve to all agents in that team
- Agent-specific messages resolve to only that agent
- Broadcasts reach all registered agents
### Session Locking
Agents can lock sessions for exclusive access, preventing other agents from writing:
```python
# Via MCP tools:
lock_session(session_id="session-123", agent="alice") # Alice locks
unlock_session(session_id="session-123", agent="alice") # Alice releases
# Other agent trying to lock is rejected
lock_session(session_id="session-123", agent="bob") # Returns: locked=False
```
**Lock behavior**:
- Only the lock owner can unlock the session
- `request_session_access` allows requesting write permission from the owner
- Locks are enforced by the MCP server's `write_to_sessions` tool
**Python API (verified)**:
```python
# Session locking via MCP tools (lock_session, unlock_session)
# Example test flow:
# 1. lock_session(session_id, agent="test-agents") → locked=True
# 2. lock_session(session_id, agent="other-agent") → locked=False (rejected)
# 3. unlock_session(session_id, agent="test-agents") → unlocked=True
#
# Pass Criteria:
# - Lock acquired by owner
# - Lock denied to non-owner
# - Owner can release lock
```
### Data Persistence
Agents and teams are persisted to JSONL files in `~/.iterm_mcp_logs/`:
- `agents.jsonl` - Registered agents with session bindings
- `teams.jsonl` - Team definitions and hierarchies
## Testing
The project includes a comprehensive test suite covering:
- Core session and terminal management
- Agent and team orchestration
- Command output tracking
- Model validation
- Logging infrastructure
### Running Tests
Run all tests (Linux/macOS):
```bash
python -m pytest tests/ -v
```
Run tests with coverage:
```bash
python -m pytest tests/ --cov=core --cov=iterm_mcpy --cov=utils --cov-report=term-missing
```
Run specific test files:
```bash
python -m pytest tests/test_models.py -v
python -m pytest tests/test_agent_registry.py -v
```
### Test Categories
**Unit Tests** (run on all platforms):
- `test_models.py` - Pydantic model validation
- `test_agent_registry.py` - Agent/team management
- `test_command_output_tracking.py` - Command tracking logic
**Integration Tests** (require macOS + iTerm2):
- `test_basic_functionality.py` - Core session operations
- `test_advanced_features.py` - Monitoring, layouts, etc.
- `test_line_limits.py` - Output truncation
- `test_logging.py` - Logging infrastructure
- `test_persistent_session.py` - Session persistence
### Development
Install development dependencies:
```bash
pip install -e ".[dev]"
```
This includes pytest, pytest-cov, black, mypy, and isort for testing and code quality.
## Logging and Monitoring
All session activity is logged to `~/.iterm_mcp_logs` by default. This includes:
- Commands sent to sessions
- Output received from sessions
- Control characters sent
- Session lifecycle events (creation, renaming, closure)
### Real-time Monitoring
Sessions can be monitored in real-time using the `start_monitoring()` method. This allows:
- Capturing output as it happens without polling
- Setting up custom callbacks for output processing
- Reacting to terminal events dynamically
### Output Filtering
Log output can be filtered using regex patterns:
- Only capture specific patterns like errors or warnings
- Reduce log noise for better analysis
- Multiple filters can be combined
### Snapshots and Line Management
Real-time snapshots of terminal output are maintained in snapshot files:
- Separate from main log files
- Always contain the latest output
- Available for LLM or other systems to access
- Useful for state monitoring without interfering with user interaction
Output line management:
- Configure global default line limits for all sessions
- Set per-session line limits via `set_max_lines()`
- Request specific line counts for individual operations
- Overflow files for tracking historic output beyond the line limit
### Persistent Session Management
Sessions maintain persistent identities across restarts and reconnection:
- Each session has a unique UUID-based persistent ID
- IDs are stored in `~/.iterm_mcp_logs/persistent_sessions.json`
- `get_session_by_persistent_id()` allows reconnection to existing sessions
- State is preserved even after chat or connection interruptions
- Session output history is available across reconnections
## OpenTelemetry Integration
The iTerm MCP server includes optional OpenTelemetry integration for production-grade observability, enabling tracing of agent operations, message delivery, and command execution.
### Installation
Install with OpenTelemetry support:
```bash
pip install -e ".[otel]"
```
This installs:
- `opentelemetry-api` - Core tracing API
- `opentelemetry-sdk` - Tracing SDK
- `opentelemetry-exporter-otlp` - OTLP exporter for backends like Jaeger
- `opentelemetry-semantic-conventions` - Standard attribute names
### Configuration
Configure via environment variables:
| Variable | Default | Description |
|----------|---------|-------------|
| `OTEL_ENABLED` | `true` | Enable/disable tracing |
| `OTEL_SERVICE_NAME` | `iterm-mcp` | Service name in traces |
| `OTEL_EXPORTER_OTLP_ENDPOINT` | `http://localhost:4317` | OTLP collector endpoint |
| `OTEL_EXPORTER_OTLP_INSECURE` | `true` | Use insecure (HTTP) connection to collector |
| `OTEL_TRACES_EXPORTER` | `otlp` | Exporter type (`otlp`, `console`, `none`) |
| `OTEL_CONSOLE_EXPORTER` | `false` | Enable console exporter for debugging |
| `OTEL_ENVIRONMENT` | `development` | Deployment environment tag |
### Traced Operations
The following operations are automatically traced:
**Session Operations:**
- `session.send_text` - Text sent to sessions
- `session.execute_command` - Command execution
- `session.get_screen_contents` - Screen content retrieval
- `session.send_control_character` - Control character sends
- `session.send_special_key` - Special key sends
**Agent/Team Operations:**
- `agent_registry.register_agent` - Agent registration
- `agent_registry.remove_agent` - Agent removal
- `agent_registry.create_team` - Team creation
- `agent_registry.remove_team` - Team removal
- `agent_registry.resolve_cascade_targets` - Cascade message resolution
**RPC/Service Operations:**
- `execute_create_sessions` - Create and initialize new sessions
- `execute_write_request` - Write data or commands to sessions
- `execute_read_request` - Read data or screen contents from sessions
- `execute_cascade_request` - Execute cascade operations across agents/teams
Each span includes relevant attributes like `agent.name`, `session.id`, `team.name`, and operation-specific metadata.
### Connecting to Observability Backends
#### Jaeger
Run Jaeger with OTLP support:
```bash
docker run -d --name jaeger \
-p 16686:16686 \
-p 4317:4317 \
jaegertracing/all-in-one:latest
```
Then start the server:
```bash
OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317 iterm-mcp daemon
```
View traces at http://localhost:16686
#### Grafana Tempo
Configure the OTLP endpoint to your Tempo instance:
```bash
OTEL_EXPORTER_OTLP_ENDPOINT=http://tempo:4317 iterm-mcp daemon
```
#### Console Debugging
For local debugging without a backend:
```bash
OTEL_CONSOLE_EXPORTER=true iterm-mcp daemon
```
This prints spans to stdout.
### Programmatic Usage
Use the tracing utilities in your own code:
```python
from utils.otel import (
init_tracing,
trace_operation,
add_span_attributes,
add_span_event,
create_span,
)
# Initialize tracing at startup
init_tracing()
# Use decorator for automatic tracing
@trace_operation("my_operation")
async def my_function(agent: str, session_id: str):
add_span_attributes(custom_attr="value")
# ... do work ...
add_span_event("checkpoint_reached", {"step": 1})
# Or use context manager for manual spans
with create_span("custom_span", attributes={"key": "value"}):
# ... traced code ...
```
### Graceful Fallback
If OpenTelemetry is not installed, the server uses no-op implementations that have zero overhead. This allows the same code to run with or without observability enabled.
## Relationship to Claude Code MCP
This project provides **multi-agent orchestration infrastructure** that complements tools like [@steipete/claude-code-mcp](https://github.com/steipete/claude-code-mcp):
### Use Case Comparison
**@steipete/claude-code-mcp** - Direct code automation
- Wraps a single Claude Code CLI instance
- One-shot code execution with permission bypass
- Stateless operation
- Best for: Direct file/code manipulation by a single agent
**iterm-mcp** - Multi-agent coordination
- Orchestrates multiple Claude Code instances in iTerm sessions
- Agent registry with teams and hierarchies
- Persistent state management
- Best for: Coordinating parallel agents, complex workflows, team-based operations
### Integration Example
You can combine both tools:
1. Use iterm-mcp to create and manage multiple iTerm sessions
2. Run `@steipete/claude-code-mcp` in each session for code automation
3. Use iterm-mcp's agent/team tools to coordinate across sessions
```python
# Create sessions for different agents
create_sessions(
layout_type="horizontal",
session_configs=[
{"name": "Frontend", "agent": "frontend-dev", "team": "dev"},
{"name": "Backend", "agent": "backend-dev", "team": "dev"}
]
)
# Each session can run claude-code-mcp
write_to_terminal(session_id="...", content="npx -y @steipete/claude-code-mcp@latest")
# Coordinate across sessions
send_cascade_message(
teams={"dev": "Run tests before deployment"}
)
```
For a detailed architectural comparison, see [docs/claude-code-mcp-analysis.md](docs/claude-code-mcp-analysis.md).
## Voice control (experimental)
A local voice layer (`core/voice/`, `voice` CLI) lets the agent speak prompts
and capture spoken multiple-choice answers. All on-device: `supertonic`/`say`
(TTS), `sox`/`ffmpeg` (capture), `whisper-cli` + `ggml-base.en` (STT).
Setup: `brew install sox whisper-cpp` and download the model to
`~/.cache/whisper/ggml-base.en.bin`. Grant the terminal Microphone permission.
Usage: `voice arm` to permit capture (idle auto-disarms after 10 min),
`voice disarm` to stop. The agent drives `voice menu --options '<json>'`
and reads the JSON action. Nothing leaves the machine. Tracking: #138.
**Headset / non-default device ("in your ear"):** `pip install iterm-mcp[voice]`
(sounddevice + numpy), then `voice devices` to list devices and set
`VOICE_OUTPUT_DEVICE="<name>"` (e.g. `AirPods`) to route TTS **and** the cue to
that output — others don't hear it. The mic side uses `VOICE_VAD_DEVICE` /
`VOICE_PTT_DEVICE`. Selection is by name (Bluetooth index drift is fine), and it
falls back to the default output if the device is missing. Note the Bluetooth
HFP/A2DP trade-off: using the headset mic drops output to mono ~16 kHz — fine
for speech, but no hi-fi while the mic is live.
## License
[MIT](LICENSE)
This server cannot be deployed
Maintenance
ActivityStale
ResponsivenessUnresponsive