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>
191 lines
6.0 KiB
Markdown
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.
|