Files
hold-slayer/docs/mcp-server.md
Robert Helewka ff7ea8623a Stage 6: learn_call_flow rebuilt on the learner, docs truth sweep
The call-flow learner finally gets fed: exploration mode records its
IVR discoveries on the call (ActiveCall.exploration_steps) instead of
throwing them away, persistence stores them in the call record's
metadata, and the rebuilt learn_call_flow MCP tool turns a completed
exploration call into a stored flow via CallFlowLearner — correct
constructor (llm_client from get_llm, heuristic labels when the LLM
is unavailable), build for a new number, merge/refine when a flow
already exists. save_learned_flow/update_flow_from_model keep the
CallFlow↔row mapping in call_persistence.

Test gaps closed: tests/test_learner.py (discoveries→linked steps,
exploration persistence, learn-then-refine through the in-memory MCP
client, no-data and unknown-call answers) and tests/test_websocket.py
(4401 without token, trunk-status-then-replay on connect, per-call
stream filtering).

Docs aligned to code: README (15 tools incl. learn_call_flow, HTTP
not SSE, Python 3.12+, PostgreSQL+Alembic — no SQLite fallback, media
pipeline marked stub-mode until pjsua2 installed, Alembic and honest
/health checked off); docs/mcp-server.md rewritten against the actual
tool surface (hangup not end_call, real params, 3 real resources,
/mcp/ streamable HTTP + bearer auth); architecture/development/
configuration drift fixed.

pyproject: pruned never-imported deps (websockets, librosa,
soundfile, python-multipart).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:45:35 -04:00

191 lines
6.0 KiB
Markdown

# MCP Server
The MCP (Model Context Protocol) server lets any MCP-compatible AI assistant
control the Hold Slayer gateway. Built with [FastMCP](https://github.com/jlowin/fastmcp),
it is mounted on the FastAPI app at **`/mcp/`** (trailing slash) over
**streamable HTTP** and authenticates with the same static bearer token as the
REST API and WebSocket.
## Overview
An AI assistant connects to the MCP endpoint and gains access to 15 tools and
3 resources for placing calls, checking status, sending DTMF, getting
transcripts, and managing call flows. The assistant can orchestrate an entire
call through natural language.
`make_call` places a **real PSTN call** that may incur charges; emergency
numbers are always refused, and the concurrent-call cap applies.
## Tools
### make_call
Place an outbound call through the SIP trunk.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `number` | string | Yes | Phone number to call (E.164 format) |
| `mode` | string | No | `direct`, `hold_slayer`, or `ai_assisted` (default: `direct`) |
| `intent` | string | No | What you want to accomplish on the call |
| `call_flow_id` | string | No | ID of a stored call flow to follow |
| `device` | string | No | Device to transfer to when a human is detected |
Returns: call ID and initial status.
### get_call_status
Check the current state of a call — status, duration, hold time, current
audio classification, recent transcript.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `call_id` | string | Yes | The call to check |
### transfer_call
Transfer an active call to a registered device.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `call_id` | string | Yes | The call to transfer |
| `device` | string | Yes | Device ID or type to ring |
### hangup
Hang up an active call.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `call_id` | string | Yes | The call to hang up |
### list_active_calls
List all calls currently in progress. No parameters.
### send_dtmf
Send touch-tone digits on an active call (manual IVR navigation).
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `call_id` | string | Yes | The call to send digits on |
| `digits` | string | Yes | DTMF digits (e.g., `"1"`, `"123#"`) |
### get_call_transcript
Get the full transcript of an active call.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `call_id` | string | Yes | The call to get the transcript for |
### get_call_recording
Get recording metadata (path, duration) for a persisted call.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `call_id` | string | Yes | The call to look up |
### get_call_summary
Stored summary, action items, and sentiment for a persisted call.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `call_id` | string | Yes | The call to look up |
### search_call_history
Search past call records.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `phone_number` | string | No | Filter by phone number (partial match) |
| `intent` | string | No | Filter by intent text (partial match) |
| `limit` | int | No | Max results (default: 10) |
### get_call_flow
Look up the stored IVR call flow for a phone number.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `phone_number` | string | Yes | Number to look up (E.164) |
### create_call_flow
Store a new IVR call flow by hand.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `name` | string | Yes | Human-readable name |
| `phone_number` | string | Yes | Phone number (E.164) |
| `steps_json` | string | Yes | JSON array of call flow steps |
| `notes` | string | No | General notes |
### learn_call_flow
Build (or refine) a reusable IVR call flow from a completed hold-slayer
exploration call. Exploration calls record every IVR prompt heard and DTMF
sent; this turns those discoveries into a stored flow so the next call
navigates directly.
| Param | Type | Required | Description |
|-------|------|----------|-------------|
| `call_id` | string | Yes | A completed call that ran in exploration mode |
| `company_name` | string | No | Company name for labeling a new flow |
### list_devices
List registered devices and their online/offline status. No parameters.
### gateway_status
Trunk registration, device count, active calls, engine mode. No parameters.
## Resources
| Resource URI | Description |
|-------------|-------------|
| `gateway://status` | Current gateway status — trunk registration, active calls |
| `gateway://call-flows` | List of all stored call flows |
| `gateway://active-calls` | All active calls with current status |
## Connecting an AI Assistant
Claude Code:
```bash
claude mcp add hold-slayer --transport http http://localhost:8000/mcp/ \
--header "Authorization: Bearer $API_TOKEN"
```
Generic MCP client configuration:
```json
{
"mcpServers": {
"hold-slayer": {
"url": "http://localhost:8000/mcp/",
"headers": {"Authorization": "Bearer <API_TOKEN>"}
}
}
}
```
## Example Conversation
**User:** "Call Chase Bank and dispute the Amazon charge from December 15th"
**Assistant actions:**
1. Calls `make_call(number="+18005551234", mode="hold_slayer", intent="dispute Amazon charge Dec 15th", call_flow_id="chase-bank-main")`
2. Receives `call_id: "call_abc123"`
3. Polls `get_call_status("call_abc123")` periodically
4. Status progression: `initiating``ringing``connected``on_hold`
5. Tells user: "I'm on hold with Chase Bank. Currently 4 minutes in. I'll let you know when someone picks up."
6. Status changes to `transferring` — human detected!
7. Tells user: "A live agent just picked up. I'm transferring the call to your desk phone now. Pick up!"
8. After the call, calls `learn_call_flow("call_abc123", company_name="Chase Bank")` to save the IVR path for next time.