vapi-mcp
# Vapi client MCP
A local stdio MCP server for provisioning client-level Vapi inbound answering assistants. It wraps the Vapi REST API rather than the smaller hosted Vapi MCP tool set.
The first template answers immediately, identifies itself as AI, captures the caller's name, callback number, reason, and callback window, then offers an available transfer or a clearly explained callback. End-of-call data can go to a public webhook or remain in Vapi for later CRM handling.
## What is implemented
| MCP tool | Vapi operation |
|---|---|
| `provision_client` | Render the answering template, `POST /assistant`, save local client state |
| `apply_template` | Render and `PATCH /assistant/:id` while retaining attached tool IDs |
| `set_prompt_and_voice` | Targeted assistant prompt, model, first-message, and voice update |
| `attach_phone` | `PATCH /phone-number/:id` or `POST /phone-number`, always binding `assistantId` |
| `attach_tools` | `POST /tool`, then attach IDs through the assistant model |
| `set_structured_outputs` | `POST /structured-output` with the client's `assistantIds` |
| `smoke_test_call` | Confirmed `POST /call`, with optional polling for transcript and structured data |
| `list_client_status` | Read assistant, bound number, and latest call; report partial errors |
Client records live at `clients/<slug>.json`. They contain Vapi resource IDs and non-secret business configuration, never the Vapi key or provider credentials. These JSON files are suitable for config-as-code if the business metadata itself is acceptable to commit.
## Install and run
Requires Node.js 20 or newer.
```bash
cd /home/blaine/projects/vapi-mcp
npm install
npm run check
npm run build
export VAPI_API_KEY="your private server-side Vapi key"
npm start
```
The server speaks MCP over stdio, so its standard output is reserved for protocol messages. Logs go to standard error.
For an MCP host, copy [examples/mcp-client.json](examples/mcp-client.json), replace the absolute paths, and put the real key only in the host's private configuration or secret manager. The checked-in example and [.env.example](.env.example) contain placeholders only. This project does not automatically load `.env` files.
## First client workflow
Call the tools in this order:
1. `provision_client`
2. `set_structured_outputs`
3. `attach_phone`
4. `attach_tools` for transfer, callback, calendar, or CRM actions
5. `smoke_test_call`
6. `list_client_status`
Example `provision_client` arguments:
```json
{
"clientSlug": "acme-hvac",
"businessName": "Acme HVAC",
"trade": "HVAC",
"timezone": "America/New_York",
"businessHours": "Monday-Friday 08:00-17:00; closed federal holidays",
"transferNumber": "+12125550123",
"crm": "GoHighLevel",
"webhookUrl": "https://example.com/webhooks/vapi/end-of-call"
}
```
With no `outputDefinitions`, `set_structured_outputs` installs the schema from [templates/inbound-answering.json](templates/inbound-answering.json). It extracts:
- `callerName`
- `callbackNumber`
- `reason`
- `callbackWindow`
- `transferOutcome`
- `complete`
Bind an existing number:
```json
{
"clientSlug": "acme-hvac",
"phoneNumberId": "00000000-0000-4000-8000-000000000000"
}
```
Or pass a provider-specific `POST /phone-number` body as `phoneConfiguration`. The server overrides any supplied `assistantId` with the client's assistant ID. Prefer Vapi credential IDs over raw Twilio, Telnyx, Vonage, or SIP secrets; transient MCP arguments are not written to the client record.
`attach_tools` accepts complete Vapi `POST /tool` definitions, existing tool UUIDs, or both. This intentionally preserves Vapi's provider-specific tool unions instead of inventing a narrower local schema. A successful Vapi tool response is required before its ID is attached to the assistant.
## Smoke path
`smoke_test_call` creates a real outbound phone call and may incur Vapi and carrier charges. It will not run unless `confirmBillableCall` is exactly `true`.
```json
{
"clientSlug": "acme-hvac",
"customerNumber": "+12125550999",
"waitSeconds": 45,
"confirmBillableCall": true
}
```
At `waitSeconds: 0`, the tool returns the queued call immediately. At 1–55 seconds, it polls `GET /call/:id` and returns current status, ended reason, transcript, and structured data when available. Test only numbers you are authorized to call and follow applicable calling, recording-consent, and privacy rules.
## Template behavior and limits
The template uses an assistant-first message so disclosure is immediate. The prompt uses spoken intent routing and tells the model not to claim a transfer, booking, callback, or CRM write succeeded without tool confirmation. When open/closed status is uncertain, it captures a callback rather than guessing.
The included hours are prompt context, not a deterministic holiday/calendar engine. Actual transfer and callback execution require attached Vapi tools. A webhook must be public and able to accept Vapi `end-of-call-report` payloads. For regulated businesses, add the required recording-consent, retention, and professional-advice controls before production.
## Verification
```bash
npm run check
```
The focused tests enumerate all eight MCP tools, inspect the template contract, exercise the REST workflow against a mock Vapi client, and verify bearer-key redaction. They do not make a real Vapi call because no private key, provisioned number, or consented destination is included in this repository.
## Sources
Implementation contracts follow Vapi's current official documentation for [assistants](https://docs.vapi.ai/api-reference/assistants/create), [phone numbers](https://docs.vapi.ai/api-reference/phone-numbers/create), [custom tools](https://docs.vapi.ai/tools/custom-tools), [structured outputs](https://docs.vapi.ai/assistants/structured-outputs), [calls](https://docs.vapi.ai/api-reference/calls/create), and [server events](https://docs.vapi.ai/server-url/events).
TDQS
Scored across 8 tools
Each tool focuses on a distinct aspect of client provisioning (creation, calling, templates, prompts, phone, tools, outputs, status), but 'apply_template' and 'set_prompt_and_voice' both modify assistant settings, which could cause minor confusion. 'smoke_test_call' is clearly separate.
All names use snake_case with verb_noun pattern (provision_client, attach_phone, list_client_status), but verbs vary in style: 'provision' and 'apply' are less conventional than 'set', 'attach', 'list'. 'smoke_test_call' is a multi-word verb, a minor deviation.
8 tools is within the ideal range for a client lifecycle management server, covering provisioning, configuration, and testing. It feels slightly heavy but each tool serves a clear purpose, so it is well-scoped.
The tools cover create, read/status, update (apply_template, set_prompt_and_voice), and attach resources, but there is no explicit delete/deprovision tool, which is a notable gap for a lifecycle. The smoke_test and structured outputs are nice-to-haves, but the missing teardown functionality is a gap.