wixzel-voice-mcp 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +57 -0
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/client.d.ts +69 -0
- package/dist/client.js +137 -0
- package/dist/client.js.map +1 -0
- package/dist/guide.d.ts +11 -0
- package/dist/guide.js +86 -0
- package/dist/guide.js.map +1 -0
- package/dist/http.d.ts +80 -0
- package/dist/http.js +281 -0
- package/dist/http.js.map +1 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +88 -0
- package/dist/index.js.map +1 -0
- package/dist/prompts.d.ts +7 -0
- package/dist/prompts.js +83 -0
- package/dist/prompts.js.map +1 -0
- package/dist/schemas.d.ts +105 -0
- package/dist/schemas.js +122 -0
- package/dist/schemas.js.map +1 -0
- package/dist/server.d.ts +16 -0
- package/dist/server.js +58 -0
- package/dist/server.js.map +1 -0
- package/dist/site.d.ts +12 -0
- package/dist/site.js +17 -0
- package/dist/site.js.map +1 -0
- package/dist/tooling.d.ts +59 -0
- package/dist/tooling.js +129 -0
- package/dist/tooling.js.map +1 -0
- package/dist/tools/agents.d.ts +97 -0
- package/dist/tools/agents.js +105 -0
- package/dist/tools/agents.js.map +1 -0
- package/dist/tools/api-keys.d.ts +37 -0
- package/dist/tools/api-keys.js +61 -0
- package/dist/tools/api-keys.js.map +1 -0
- package/dist/tools/appointments.d.ts +30 -0
- package/dist/tools/appointments.js +77 -0
- package/dist/tools/appointments.js.map +1 -0
- package/dist/tools/billing.d.ts +29 -0
- package/dist/tools/billing.js +72 -0
- package/dist/tools/billing.js.map +1 -0
- package/dist/tools/calls.d.ts +37 -0
- package/dist/tools/calls.js +98 -0
- package/dist/tools/calls.js.map +1 -0
- package/dist/tools/campaigns.d.ts +13 -0
- package/dist/tools/campaigns.js +76 -0
- package/dist/tools/campaigns.js.map +1 -0
- package/dist/tools/engines.d.ts +4 -0
- package/dist/tools/engines.js +43 -0
- package/dist/tools/engines.js.map +1 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/index.js +37 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/knowledge-bases.d.ts +27 -0
- package/dist/tools/knowledge-bases.js +69 -0
- package/dist/tools/knowledge-bases.js.map +1 -0
- package/dist/tools/leads.d.ts +28 -0
- package/dist/tools/leads.js +88 -0
- package/dist/tools/leads.js.map +1 -0
- package/dist/tools/phone-numbers.d.ts +19 -0
- package/dist/tools/phone-numbers.js +72 -0
- package/dist/tools/phone-numbers.js.map +1 -0
- package/dist/tools/realtime.d.ts +8 -0
- package/dist/tools/realtime.js +28 -0
- package/dist/tools/realtime.js.map +1 -0
- package/dist/tools/sip-trunks.d.ts +45 -0
- package/dist/tools/sip-trunks.js +118 -0
- package/dist/tools/sip-trunks.js.map +1 -0
- package/dist/tools/webhooks.d.ts +52 -0
- package/dist/tools/webhooks.js +90 -0
- package/dist/tools/webhooks.js.map +1 -0
- package/dist/version.d.ts +7 -0
- package/dist/version.js +8 -0
- package/dist/version.js.map +1 -0
- package/package.json +65 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.4.0 (2026-09-29)
|
|
4
|
+
|
|
5
|
+
Renamed from `wixzel-phone-mcp` to `wixzel-voice-mcp`, because the product is
|
|
6
|
+
called Wixzel Voice again. The tools and prompts are unchanged.
|
|
7
|
+
|
|
8
|
+
- Install and run `wixzel-voice-mcp` (`npx -y wixzel-voice-mcp`); the binary is
|
|
9
|
+
`wixzel-voice-mcp` too. The server advertises itself as `wixzel-voice`, and
|
|
10
|
+
the examples add it under that name, so its prompts appear as
|
|
11
|
+
`/mcp__wixzel-voice__quickstart`. A connection already added as
|
|
12
|
+
`wixzel-phone` keeps the name it was given.
|
|
13
|
+
- The default API base URL is now `https://api.voice.wixzel.com`, and the
|
|
14
|
+
hosted server is `https://mcp.voice.wixzel.com/mcp`.
|
|
15
|
+
`https://api.phone.wixzel.com` and `https://mcp.phone.wixzel.com/mcp` keep
|
|
16
|
+
serving, so `wixzel-phone-mcp` 0.3.0 and earlier, and connectors added with
|
|
17
|
+
the old address, keep working without a change.
|
|
18
|
+
- In HTTP mode on the hosted names, the protected-resource metadata and the
|
|
19
|
+
`401` challenge name the host the client connected to. MCP clients refuse
|
|
20
|
+
metadata for another origin, so this is what keeps a connector added as
|
|
21
|
+
`mcp.phone.wixzel.com` able to sign in again.
|
|
22
|
+
- Requests send `User-Agent: wixzel-voice-mcp/0.4.0`. It said `0.2.1` through
|
|
23
|
+
the 0.3.0 release; it now follows the package version.
|
|
24
|
+
- The operating guide the server sends as its instructions describes Wixzel
|
|
25
|
+
Voice as the API and MCP server people build agents with, not as the agents.
|
|
26
|
+
- Carries what the official MCP Registry needs to list it as
|
|
27
|
+
`com.wixzel/voice`: `mcpName` in `package.json`, and `server.json` in the
|
|
28
|
+
source.
|
|
29
|
+
|
|
30
|
+
## 0.3.0
|
|
31
|
+
|
|
32
|
+
`create_realtime_session`: mint a one-minute, single-use client secret so a
|
|
33
|
+
web page or app can talk to an agent over `wss /v1/realtime`, with no SIP
|
|
34
|
+
trunk. Annotated as a write, not a spend — minting charges nothing; the
|
|
35
|
+
session bills when a client connects. 65 tools.
|
|
36
|
+
|
|
37
|
+
## 0.2.2
|
|
38
|
+
|
|
39
|
+
Five webhook tools: `get_webhook`, `update_webhook`, `rotate_webhook_secret`,
|
|
40
|
+
`test_webhook` and `list_webhook_deliveries`. The `webhooks:read` and
|
|
41
|
+
`webhooks:write` scopes were already in the scope list and reached nothing;
|
|
42
|
+
they now reach a real resource. 64 tools.
|
|
43
|
+
|
|
44
|
+
`test_webhook` is annotated as reaching the outside world — it POSTs to a
|
|
45
|
+
third-party endpoint — but it spends no credit.
|
|
46
|
+
|
|
47
|
+
## 0.2.1
|
|
48
|
+
|
|
49
|
+
Point `repository` at the public mirror, github.com/aqeelshamz/wixzel-phone-sdks. The 0.2.0 metadata named the private monorepo, so the link on the npm page did not resolve. No code changes.
|
|
50
|
+
|
|
51
|
+
## 0.2.0
|
|
52
|
+
|
|
53
|
+
Moved into the monorepo, added Streamable HTTP mode with OAuth discovery, protected-resource metadata and a token probe.
|
|
54
|
+
|
|
55
|
+
## 0.1.0
|
|
56
|
+
|
|
57
|
+
First release: 56 tools, three prompts, two resources, over stdio.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Wixzel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
# wixzel-voice-mcp
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/wixzel-voice-mcp)
|
|
4
|
+
[](LICENSE)
|
|
5
|
+
**Wixzel Voice for AI agents.** A [Model Context Protocol](https://modelcontextprotocol.io) server that lets Claude Code, claude.ai, Claude Desktop, Cursor, and any other MCP client create voice agents, connect a carrier, place calls, run campaigns, and read what it all cost, using the [Wixzel Voice](https://voice.wixzel.com) API.
|
|
6
|
+
|
|
7
|
+
> Wixzel Voice: APIs for AI voice agents. One API key, one balance, every voice engine.
|
|
8
|
+
|
|
9
|
+
One tool per API operation, 56 in all, each annotated so a client knows which are read-only, which delete, and which **spend money or make a real phone ring**. Three prompts (`quickstart`, `diagnose_call`, `spend_report`) package the common workflows.
|
|
10
|
+
|
|
11
|
+
Published as `wixzel-phone-mcp` up to 0.3.0, while the product was briefly called Wixzel Phone. Those releases keep working: `api.phone.wixzel.com` and `mcp.phone.wixzel.com` keep serving.
|
|
12
|
+
|
|
13
|
+
Two ways to run it:
|
|
14
|
+
|
|
15
|
+
| | Where | Auth |
|
|
16
|
+
|---|---|---|
|
|
17
|
+
| **Hosted**, `https://mcp.voice.wixzel.com/mcp` | Wixzel's servers | OAuth sign-in from the client, or a bearer key |
|
|
18
|
+
| **Local**, `npx -y wixzel-voice-mcp` | Your machine, stdio | `WIXZEL_API_KEY` in the environment |
|
|
19
|
+
|
|
20
|
+
The console's **AI clients** page at [voice.wixzel.com/connect](https://voice.wixzel.com/connect) generates every command below with a key filled in.
|
|
21
|
+
|
|
22
|
+
## Claude Code
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
# hosted: sign in with your Wixzel account when Claude Code asks (/mcp → Authenticate)
|
|
26
|
+
claude mcp add --transport http wixzel-voice https://mcp.voice.wixzel.com/mcp
|
|
27
|
+
|
|
28
|
+
# local: a scoped key from the console
|
|
29
|
+
claude mcp add wixzel-voice -e WIXZEL_API_KEY=wv_live_... -- npx -y wixzel-voice-mcp
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Or commit a `.mcp.json` so every collaborator gets it:
|
|
33
|
+
|
|
34
|
+
```json
|
|
35
|
+
{
|
|
36
|
+
"mcpServers": {
|
|
37
|
+
"wixzel-voice": {
|
|
38
|
+
"command": "npx",
|
|
39
|
+
"args": ["-y", "wixzel-voice-mcp"],
|
|
40
|
+
"env": { "WIXZEL_API_KEY": "${WIXZEL_API_KEY}" }
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Then:
|
|
47
|
+
|
|
48
|
+
```
|
|
49
|
+
/mcp__wixzel-voice__quickstart
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## claude.ai and Claude Desktop
|
|
53
|
+
|
|
54
|
+
Settings → Connectors → Add custom connector → `https://mcp.voice.wixzel.com/mcp` → Connect. You sign in to the console, tick what the client may do, and it receives a scoped key of its own. Disconnect it from the console's AI clients page at any time.
|
|
55
|
+
|
|
56
|
+
## Cursor, Windsurf, VS Code, others
|
|
57
|
+
|
|
58
|
+
Same shape as `.mcp.json` above: command `npx`, args `["-y", "wixzel-voice-mcp"]`, env `WIXZEL_API_KEY`. Clients that connect over HTTP but cannot do OAuth send the key as `Authorization: Bearer wv_live_...`.
|
|
59
|
+
|
|
60
|
+
## What the agent can do
|
|
61
|
+
|
|
62
|
+
| Family | Tools |
|
|
63
|
+
|---|---|
|
|
64
|
+
| Engines | `list_engines` |
|
|
65
|
+
| Agents | `list_agents` `get_agent` `create_agent` `update_agent` `delete_agent` |
|
|
66
|
+
| Calls | `place_call` `list_calls` `get_call` `get_call_transcript` `hangup_call` `delete_call` |
|
|
67
|
+
| SIP trunks | `list_sip_trunks` `get_sip_trunk` `create_sip_trunk` `update_sip_trunk` `delete_sip_trunk` `check_sip_trunk_status` `test_sip_trunk` `get_sip_trunk_logs` |
|
|
68
|
+
| Phone numbers | `list_phone_numbers` `get_phone_number` `create_phone_number` `update_phone_number` `delete_phone_number` |
|
|
69
|
+
| Leads | `list_leads` `get_lead` `create_lead` `update_lead` `delete_lead` `import_leads` |
|
|
70
|
+
| Campaigns | `list_campaigns` `get_campaign` `create_campaign` `start_campaign` `pause_campaign` `delete_campaign` |
|
|
71
|
+
| Knowledge bases | `list_knowledge_bases` `get_knowledge_base` `create_knowledge_base` `update_knowledge_base` `delete_knowledge_base` |
|
|
72
|
+
| Appointments | `list_appointments` `get_appointment` `create_appointment` `update_appointment` `delete_appointment` |
|
|
73
|
+
| Billing & usage | `get_balance` `list_ledger_entries` `create_topup` `get_usage_summary` `list_usage_events` |
|
|
74
|
+
| API keys | `list_api_keys` `create_api_key` `rotate_api_key` `revoke_api_key` |
|
|
75
|
+
|
|
76
|
+
Prompts: `quickstart` (connect a carrier, register a number, build an agent, place a first call), `diagnose_call` (why did call X fail), `spend_report` (what did this month cost, and where).
|
|
77
|
+
|
|
78
|
+
Resources: `wixzel://guide` (the operating guide the server also sends as its instructions) and `wixzel://connection` (base URL and whether the key is live or test; never the key itself).
|
|
79
|
+
|
|
80
|
+
## How it keeps the agent honest
|
|
81
|
+
|
|
82
|
+
- **Money is explicit.** `place_call`, `start_campaign` and `create_topup` are annotated `openWorldHint: true` and their descriptions tell the model to confirm with the user first. Clients that gate on annotations will ask before running them.
|
|
83
|
+
- **Idempotency is automatic.** The two money paths require an `Idempotency-Key`; the server generates one per call and accepts an explicit `idempotency_key` for deliberate retries, so a network timeout never turns into two calls.
|
|
84
|
+
- **Errors are actionable.** Every failure returns the API's machine-readable `code`, the `request_id`, and a hint that says what to do: which scope is missing, that the balance is short, that a cursor was mangled.
|
|
85
|
+
- **Rate limits are absorbed.** A `429` is retried after `Retry-After` up to twice before the model sees it. Those requests are never charged.
|
|
86
|
+
- **Scopes do the gating.** The key decides what works. There is no admin scope, and a key cannot create one broader than itself.
|
|
87
|
+
- **Secrets stay put.** SIP passwords are write-only; API key secrets appear once, in the tool result, and nowhere else. The key this server runs with is never exposed through any tool or resource.
|
|
88
|
+
|
|
89
|
+
## Running the hosted mode yourself
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
npx wixzel-voice-mcp --http 3939
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
| Variable | Purpose |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `MCP_PORT` | Port. Default 3939; `--http <port>` overrides. |
|
|
98
|
+
| `MCP_BIND` | Interface. Default `127.0.0.1`; terminate TLS in front rather than binding wider. |
|
|
99
|
+
| `MCP_PUBLIC_URL` | The URL clients use, e.g. `https://mcp.voice.wixzel.com/mcp`. Advertised as the OAuth resource. |
|
|
100
|
+
| `WIXZEL_API_BASE_URL` | The API, which is also the OAuth authorization server. Default `https://api.voice.wixzel.com`. |
|
|
101
|
+
| `WIXZEL_API_VERSION` | Optional `Wixzel-Version` date pin. |
|
|
102
|
+
| `WIXZEL_API_KEY` | Stdio mode: the key. HTTP mode: a fallback for requests without a bearer, **accepted only when `MCP_PUBLIC_URL` is a loopback address**. |
|
|
103
|
+
|
|
104
|
+
Each request's bearer becomes the API key for a fresh, throwaway MCP server; nothing is shared between requests. A request without a bearer gets a `401` with `WWW-Authenticate: Bearer resource_metadata="…"`, and `/.well-known/oauth-protected-resource` names the API as the authorization server, which is how connectors discover sign-in on their own. `GET /healthz` reports liveness.
|
|
105
|
+
|
|
106
|
+
## Development
|
|
107
|
+
|
|
108
|
+
The source is mirrored at [wixzel-voice-sdks](https://github.com/aqeelshamz/wixzel-voice-sdks); it is developed in the private monorepo that also holds the API, so a change to an endpoint and the change to its tool land together.
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
npm install # at the repo root
|
|
112
|
+
npm test -w apps/mcp # offline: a fake API behind in-memory and HTTP transports, plus a spec-coverage check
|
|
113
|
+
npm run build -w apps/mcp
|
|
114
|
+
npm run inspect -w apps/mcp # MCP Inspector against the built server
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Tool schemas are hand-written zod. The spec-coverage test compares them against the API's OpenAPI document (`docs/openapi.json`) in both directions, so an endpoint added to the API fails the build here until it gets a tool.
|
|
118
|
+
|
|
119
|
+
## Not for application code
|
|
120
|
+
|
|
121
|
+
This server is for AI agents. Application code should use the SDKs:
|
|
122
|
+
[`wixzel-voice`](https://www.npmjs.com/package/wixzel-voice) on npm and
|
|
123
|
+
[`wixzel_voice`](https://pub.dev/packages/wixzel_voice) on pub.dev.
|
|
124
|
+
|
|
125
|
+
## License
|
|
126
|
+
|
|
127
|
+
MIT. Wixzel Voice itself is a hosted service; see [voice.wixzel.com](https://voice.wixzel.com) for terms.
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small HTTP client for the Wixzel Voice API.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately dependency-free: `fetch` is built into Node 20+, and the API's
|
|
5
|
+
* conventions (bearer key, JSON, structured errors with a request_id, cursor
|
|
6
|
+
* pagination, Idempotency-Key on money paths) are simple enough that a wrapper
|
|
7
|
+
* around fetch is the whole client.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* api.phone.wixzel.com, the default before 0.4.0, keeps serving the same API,
|
|
11
|
+
* so older releases of this package keep working.
|
|
12
|
+
*/
|
|
13
|
+
export declare const DEFAULT_BASE_URL = "https://api.voice.wixzel.com";
|
|
14
|
+
export declare const USER_AGENT = "wixzel-voice-mcp/0.4.0";
|
|
15
|
+
export type HttpMethod = 'GET' | 'POST' | 'PATCH' | 'DELETE';
|
|
16
|
+
export type QueryValue = string | number | boolean | null | undefined;
|
|
17
|
+
export interface RequestOptions {
|
|
18
|
+
query?: Record<string, QueryValue>;
|
|
19
|
+
body?: unknown;
|
|
20
|
+
/** Sent as `Idempotency-Key`. Generated when `idempotent` is true and none is given. */
|
|
21
|
+
idempotencyKey?: string;
|
|
22
|
+
idempotent?: boolean;
|
|
23
|
+
}
|
|
24
|
+
export interface WixzelErrorBody {
|
|
25
|
+
type: string;
|
|
26
|
+
code: string;
|
|
27
|
+
message: string;
|
|
28
|
+
param?: string | null;
|
|
29
|
+
request_id?: string;
|
|
30
|
+
}
|
|
31
|
+
/** Everything a caller needs to act on a failed request, in one object. */
|
|
32
|
+
export declare class WixzelApiError extends Error {
|
|
33
|
+
readonly status: number;
|
|
34
|
+
readonly type: string;
|
|
35
|
+
readonly code: string;
|
|
36
|
+
readonly param: string | null;
|
|
37
|
+
readonly requestId: string | null;
|
|
38
|
+
readonly retryAfterSeconds: number | null;
|
|
39
|
+
readonly balanceHeader: string | null;
|
|
40
|
+
constructor(status: number, body: Partial<WixzelErrorBody>, headers?: {
|
|
41
|
+
retryAfter?: string | null;
|
|
42
|
+
balance?: string | null;
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
export interface WixzelClientOptions {
|
|
46
|
+
apiKey: string;
|
|
47
|
+
baseUrl?: string;
|
|
48
|
+
/** Sent as `Wixzel-Version` to pin dated behaviour. */
|
|
49
|
+
apiVersion?: string;
|
|
50
|
+
/** Per-request timeout in milliseconds. */
|
|
51
|
+
timeoutMs?: number;
|
|
52
|
+
/** Injected in tests. */
|
|
53
|
+
fetch?: typeof fetch;
|
|
54
|
+
}
|
|
55
|
+
export declare class WixzelClient {
|
|
56
|
+
readonly baseUrl: string;
|
|
57
|
+
private readonly apiKey;
|
|
58
|
+
private readonly apiVersion?;
|
|
59
|
+
private readonly timeoutMs;
|
|
60
|
+
private readonly fetchImpl;
|
|
61
|
+
constructor(opts: WixzelClientOptions);
|
|
62
|
+
/** `wv_live_` or `wv_test_` — surfaced so an agent can tell which it is on. */
|
|
63
|
+
get keyMode(): 'live' | 'test' | 'unknown';
|
|
64
|
+
get<T = unknown>(path: string, query?: Record<string, QueryValue>): Promise<T>;
|
|
65
|
+
post<T = unknown>(path: string, body?: unknown, opts?: Omit<RequestOptions, 'body'>): Promise<T>;
|
|
66
|
+
patch<T = unknown>(path: string, body: unknown): Promise<T>;
|
|
67
|
+
delete<T = unknown>(path: string): Promise<T>;
|
|
68
|
+
request<T = unknown>(method: HttpMethod, path: string, opts?: RequestOptions): Promise<T>;
|
|
69
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A small HTTP client for the Wixzel Voice API.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately dependency-free: `fetch` is built into Node 20+, and the API's
|
|
5
|
+
* conventions (bearer key, JSON, structured errors with a request_id, cursor
|
|
6
|
+
* pagination, Idempotency-Key on money paths) are simple enough that a wrapper
|
|
7
|
+
* around fetch is the whole client.
|
|
8
|
+
*/
|
|
9
|
+
import { randomUUID } from 'node:crypto';
|
|
10
|
+
import { VERSION } from './version.js';
|
|
11
|
+
/**
|
|
12
|
+
* api.phone.wixzel.com, the default before 0.4.0, keeps serving the same API,
|
|
13
|
+
* so older releases of this package keep working.
|
|
14
|
+
*/
|
|
15
|
+
export const DEFAULT_BASE_URL = 'https://api.voice.wixzel.com';
|
|
16
|
+
export const USER_AGENT = `wixzel-voice-mcp/${VERSION}`;
|
|
17
|
+
/** Everything a caller needs to act on a failed request, in one object. */
|
|
18
|
+
export class WixzelApiError extends Error {
|
|
19
|
+
status;
|
|
20
|
+
type;
|
|
21
|
+
code;
|
|
22
|
+
param;
|
|
23
|
+
requestId;
|
|
24
|
+
retryAfterSeconds;
|
|
25
|
+
balanceHeader;
|
|
26
|
+
constructor(status, body, headers = {}) {
|
|
27
|
+
super(body.message ?? `HTTP ${status}`);
|
|
28
|
+
this.name = 'WixzelApiError';
|
|
29
|
+
this.status = status;
|
|
30
|
+
this.type = body.type ?? (status >= 500 ? 'api_error' : 'invalid_request_error');
|
|
31
|
+
this.code = body.code ?? `http_${status}`;
|
|
32
|
+
this.param = body.param ?? null;
|
|
33
|
+
this.requestId = body.request_id ?? null;
|
|
34
|
+
this.retryAfterSeconds = headers.retryAfter ? Number(headers.retryAfter) || null : null;
|
|
35
|
+
this.balanceHeader = headers.balance ?? null;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export class WixzelClient {
|
|
39
|
+
baseUrl;
|
|
40
|
+
apiKey;
|
|
41
|
+
apiVersion;
|
|
42
|
+
timeoutMs;
|
|
43
|
+
fetchImpl;
|
|
44
|
+
constructor(opts) {
|
|
45
|
+
if (!opts.apiKey) {
|
|
46
|
+
throw new Error('WixzelClient needs an apiKey');
|
|
47
|
+
}
|
|
48
|
+
this.apiKey = opts.apiKey;
|
|
49
|
+
this.baseUrl = (opts.baseUrl ?? DEFAULT_BASE_URL).replace(/\/+$/, '');
|
|
50
|
+
this.apiVersion = opts.apiVersion;
|
|
51
|
+
this.timeoutMs = opts.timeoutMs ?? 30_000;
|
|
52
|
+
this.fetchImpl = opts.fetch ?? globalThis.fetch;
|
|
53
|
+
}
|
|
54
|
+
/** `wv_live_` or `wv_test_` — surfaced so an agent can tell which it is on. */
|
|
55
|
+
get keyMode() {
|
|
56
|
+
if (this.apiKey.startsWith('wv_live_'))
|
|
57
|
+
return 'live';
|
|
58
|
+
if (this.apiKey.startsWith('wv_test_'))
|
|
59
|
+
return 'test';
|
|
60
|
+
return 'unknown';
|
|
61
|
+
}
|
|
62
|
+
get(path, query) {
|
|
63
|
+
return this.request('GET', path, { query });
|
|
64
|
+
}
|
|
65
|
+
post(path, body, opts = {}) {
|
|
66
|
+
return this.request('POST', path, { ...opts, body });
|
|
67
|
+
}
|
|
68
|
+
patch(path, body) {
|
|
69
|
+
return this.request('PATCH', path, { body });
|
|
70
|
+
}
|
|
71
|
+
delete(path) {
|
|
72
|
+
return this.request('DELETE', path);
|
|
73
|
+
}
|
|
74
|
+
async request(method, path, opts = {}) {
|
|
75
|
+
const url = new URL(this.baseUrl + path);
|
|
76
|
+
for (const [key, value] of Object.entries(opts.query ?? {})) {
|
|
77
|
+
if (value === undefined || value === null || value === '')
|
|
78
|
+
continue;
|
|
79
|
+
url.searchParams.set(key, String(value));
|
|
80
|
+
}
|
|
81
|
+
const headers = {
|
|
82
|
+
Authorization: `Bearer ${this.apiKey}`,
|
|
83
|
+
Accept: 'application/json',
|
|
84
|
+
'User-Agent': USER_AGENT,
|
|
85
|
+
};
|
|
86
|
+
if (this.apiVersion)
|
|
87
|
+
headers['Wixzel-Version'] = this.apiVersion;
|
|
88
|
+
if (opts.body !== undefined)
|
|
89
|
+
headers['Content-Type'] = 'application/json';
|
|
90
|
+
const idempotencyKey = opts.idempotencyKey ?? (opts.idempotent ? randomUUID() : undefined);
|
|
91
|
+
if (idempotencyKey)
|
|
92
|
+
headers['Idempotency-Key'] = idempotencyKey;
|
|
93
|
+
// One retry budget for the whole call. 429s are never charged and carry
|
|
94
|
+
// Retry-After, so honouring it is the correct behaviour rather than
|
|
95
|
+
// surfacing a transient limit to the agent as a failure.
|
|
96
|
+
const maxAttempts = 3;
|
|
97
|
+
for (let attempt = 1;; attempt++) {
|
|
98
|
+
const response = await this.fetchImpl(url, {
|
|
99
|
+
method,
|
|
100
|
+
headers,
|
|
101
|
+
body: opts.body === undefined ? undefined : JSON.stringify(opts.body),
|
|
102
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
103
|
+
});
|
|
104
|
+
if (response.status === 429 && attempt < maxAttempts) {
|
|
105
|
+
const retryAfter = Number(response.headers.get('retry-after')) || 1;
|
|
106
|
+
await sleep(Math.min(retryAfter, 10) * 1000);
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (response.status === 204)
|
|
110
|
+
return null;
|
|
111
|
+
const text = await response.text();
|
|
112
|
+
let parsed = null;
|
|
113
|
+
if (text) {
|
|
114
|
+
try {
|
|
115
|
+
parsed = JSON.parse(text);
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
parsed = null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (!response.ok) {
|
|
122
|
+
const errorBody = parsed && typeof parsed === 'object' && 'error' in parsed
|
|
123
|
+
? (parsed.error ?? {})
|
|
124
|
+
: { message: text || response.statusText };
|
|
125
|
+
throw new WixzelApiError(response.status, errorBody, {
|
|
126
|
+
retryAfter: response.headers.get('retry-after'),
|
|
127
|
+
balance: response.headers.get('x-wixzel-balance'),
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
return (parsed ?? text);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
function sleep(ms) {
|
|
135
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
136
|
+
}
|
|
137
|
+
//# sourceMappingURL=client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/guide.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The operating guide an agent reads before it touches the account. Served
|
|
3
|
+
* as the server's `instructions`, as a resource, and reused by the prompts,
|
|
4
|
+
* so there is one place to keep it true.
|
|
5
|
+
*
|
|
6
|
+
* Built from the environment. A self-hosted install has no prepaid balance and
|
|
7
|
+
* no top-ups, and telling an agent to buy credit there would send it after a
|
|
8
|
+
* checkout that does not exist. The hosted text is unchanged.
|
|
9
|
+
*/
|
|
10
|
+
export declare function buildGuide(env?: NodeJS.ProcessEnv): string;
|
|
11
|
+
export declare const GUIDE: string;
|
package/dist/guide.js
ADDED
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The operating guide an agent reads before it touches the account. Served
|
|
3
|
+
* as the server's `instructions`, as a resource, and reused by the prompts,
|
|
4
|
+
* so there is one place to keep it true.
|
|
5
|
+
*
|
|
6
|
+
* Built from the environment. A self-hosted install has no prepaid balance and
|
|
7
|
+
* no top-ups, and telling an agent to buy credit there would send it after a
|
|
8
|
+
* checkout that does not exist. The hosted text is unchanged.
|
|
9
|
+
*/
|
|
10
|
+
import { consoleUrl, isSelfHosted } from './site.js';
|
|
11
|
+
export function buildGuide(env = process.env) {
|
|
12
|
+
const selfHosted = isSelfHosted(env);
|
|
13
|
+
const site = consoleUrl(env);
|
|
14
|
+
const intro = selfHosted
|
|
15
|
+
? `One API key, every voice engine, on your own server. ${site}`
|
|
16
|
+
: `One API key, one prepaid credit balance, every voice engine. ${site}`;
|
|
17
|
+
const money = selfHosted
|
|
18
|
+
? `- COST accrues per second while a call is live and is itemised in usage events.
|
|
19
|
+
Nothing is charged on a self-hosted server; the figure estimates what the voice
|
|
20
|
+
providers bill. Amounts are micro-USD: 1,000,000 = $1.00.`
|
|
21
|
+
: `- CREDIT is a dollar balance. Cost accrues per second while a call is live and is
|
|
22
|
+
itemised in usage events. Amounts are micro-USD: 1,000,000 = $1.00.`;
|
|
23
|
+
const firstCall = selfHosted ? '5. place_call.' : '5. get_balance. Then place_call.';
|
|
24
|
+
const spending = selfHosted
|
|
25
|
+
? `place_call and start_campaign make real phones ring, and the voice providers bill
|
|
26
|
+
for every minute. Confirm with the user before calling them, say what it will do, and
|
|
27
|
+
never retry one with a different idempotency_key: the same key returns the original
|
|
28
|
+
result, a new key is a second call.`
|
|
29
|
+
: `place_call, start_campaign and create_topup spend money or make real phones ring.
|
|
30
|
+
Confirm with the user before calling them, say what it will do, and never retry one
|
|
31
|
+
with a different idempotency_key: the same key returns the original result, a new
|
|
32
|
+
key is a second call. create_topup returns a checkout URL that a human must open;
|
|
33
|
+
never try to pay.`;
|
|
34
|
+
return `# Wixzel Voice, for AI agents
|
|
35
|
+
|
|
36
|
+
Wixzel Voice is the API and MCP server for building AI agents that place and answer
|
|
37
|
+
real phone calls over your own SIP trunk, or talk to people in web and mobile apps.
|
|
38
|
+
${intro}
|
|
39
|
+
|
|
40
|
+
## The mental model
|
|
41
|
+
|
|
42
|
+
- An AGENT is a prompt plus a voice engine (speech-to-text, LLM, text-to-speech,
|
|
43
|
+
or one realtime model that does all three).
|
|
44
|
+
- A SIP TRUNK is the user's own carrier account (Twilio, Telnyx, Plivo, Vonage,
|
|
45
|
+
Bandwidth, Exotel, Vobiz, any SIP provider). Wixzel does not sell numbers or
|
|
46
|
+
minutes; the carrier's rates stay theirs.
|
|
47
|
+
- A PHONE NUMBER is a number the user owns at that carrier, registered on the trunk.
|
|
48
|
+
Give it an inbound_agent_id and inbound calls are answered by that agent.
|
|
49
|
+
- A CALL connects an agent to a real phone over the trunk. Outbound calls are placed
|
|
50
|
+
with place_call; campaigns place one call per lead.
|
|
51
|
+
${money}
|
|
52
|
+
|
|
53
|
+
## The order things go in
|
|
54
|
+
|
|
55
|
+
1. list_engines: see which models are available right now and what they cost.
|
|
56
|
+
Never guess a model id.
|
|
57
|
+
2. create_sip_trunk with the user's carrier credentials, then tell the user to
|
|
58
|
+
allowlist platform_ip with the carrier (outbound) and to point the carrier at
|
|
59
|
+
origination_uri (inbound). check_sip_trunk_status shows whether it is reachable.
|
|
60
|
+
3. create_phone_number on that trunk.
|
|
61
|
+
4. create_agent with a short spoken-style system_prompt, an opening_message, and a
|
|
62
|
+
voice from step 1. Set outbound_phone_number_id so calls have a caller id.
|
|
63
|
+
${firstCall}
|
|
64
|
+
6. get_call to watch status, read the transcript, and see cost_micros. If it did not
|
|
65
|
+
connect, failure_code and failure_reason say why and what to do.
|
|
66
|
+
|
|
67
|
+
## Money and the real world
|
|
68
|
+
|
|
69
|
+
${spending}
|
|
70
|
+
|
|
71
|
+
Delete and revoke tools are permanent. revoke_api_key on the key this server is
|
|
72
|
+
using will cut this server off.
|
|
73
|
+
|
|
74
|
+
## Reading results
|
|
75
|
+
|
|
76
|
+
- Errors carry a machine-readable code; act on the code and the Hint line.
|
|
77
|
+
- Lists are cursor-paginated: pass next_cursor as starting_after until has_more is false.
|
|
78
|
+
- 404 means "not on this account", including records owned by someone else.
|
|
79
|
+
- The key's scopes decide what works. A permission error names the missing scope;
|
|
80
|
+
the fix is a new key from Dashboard -> API keys, not a retry.
|
|
81
|
+
|
|
82
|
+
Docs: https://docs.voice.wixzel.com
|
|
83
|
+
`;
|
|
84
|
+
}
|
|
85
|
+
export const GUIDE = buildGuide();
|
|
86
|
+
//# sourceMappingURL=guide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guide.js","sourceRoot":"","sources":["../src/guide.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,WAAW,CAAC;AAErD,MAAM,UAAU,UAAU,CAAC,MAAyB,OAAO,CAAC,GAAG;IAC3D,MAAM,UAAU,GAAG,YAAY,CAAC,GAAG,CAAC,CAAC;IACrC,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,KAAK,GAAG,UAAU;QACpB,CAAC,CAAC,wDAAwD,IAAI,EAAE;QAChE,CAAC,CAAC,gEAAgE,IAAI,EAAE,CAAC;IAC7E,MAAM,KAAK,GAAG,UAAU;QACpB,CAAC,CAAC;;4DAEkD;QACpD,CAAC,CAAC;sEAC4D,CAAC;IACnE,MAAM,SAAS,GAAG,UAAU,CAAC,CAAC,CAAC,gBAAgB,CAAC,CAAC,CAAC,kCAAkC,CAAC;IACrF,MAAM,QAAQ,GAAG,UAAU;QACvB,CAAC,CAAC;;;oCAG0B;QAC5B,CAAC,CAAC;;;;kBAIQ,CAAC;IAEf,OAAO;;;;EAIT,KAAK;;;;;;;;;;;;;EAaL,KAAK;;;;;;;;;;;;EAYL,SAAS;;;;;;EAMT,QAAQ;;;;;;;;;;;;;;CAcT,CAAC;AACF,CAAC;AAED,MAAM,CAAC,MAAM,KAAK,GAAG,UAAU,EAAE,CAAC"}
|
package/dist/http.d.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Streamable HTTP mode: the hosted server at mcp.voice.wixzel.com, and the
|
|
3
|
+
* same thing on a laptop with `--http`.
|
|
4
|
+
*
|
|
5
|
+
* Stateless by design. Every request carries its own `Authorization: Bearer`,
|
|
6
|
+
* which is a Wixzel Voice API key — either pasted by a developer into
|
|
7
|
+
* `claude mcp add --header`, or issued by the API's OAuth server after the
|
|
8
|
+
* user consented in the console. Either way this process builds a fresh MCP
|
|
9
|
+
* server bound to that key, answers the request, and forgets it. Nothing here
|
|
10
|
+
* can mix two users up, because nothing here remembers anyone.
|
|
11
|
+
*
|
|
12
|
+
* The OAuth half lives in the API (api/oauth/index.ts there). This side only
|
|
13
|
+
* has to say where it is: RFC 9728 protected-resource metadata, and a 401 that
|
|
14
|
+
* points at it, are what make claude.ai's "add custom connector" flow work.
|
|
15
|
+
*/
|
|
16
|
+
import { type IncomingMessage, type Server, type ServerResponse } from 'node:http';
|
|
17
|
+
export interface HttpOptions {
|
|
18
|
+
/** TCP port. 0 picks a free one (tests). */
|
|
19
|
+
port: number;
|
|
20
|
+
/** Interface to bind. Loopback by default: nginx terminates TLS in front. */
|
|
21
|
+
bind: string;
|
|
22
|
+
/** The resource identifier tokens are issued for, e.g. https://mcp.voice.wixzel.com/mcp */
|
|
23
|
+
publicUrl: string;
|
|
24
|
+
/**
|
|
25
|
+
* Other hostnames this same server answers on. A request that arrives on
|
|
26
|
+
* one is told the resource is publicUrl with that host. Defaults to
|
|
27
|
+
* hostAliases(publicUrl).
|
|
28
|
+
*/
|
|
29
|
+
hostAliases?: readonly string[] | undefined;
|
|
30
|
+
/** The Wixzel Voice API, which is also the OAuth authorization server. */
|
|
31
|
+
apiBaseUrl: string;
|
|
32
|
+
/** The console the landing page links to. Defaults to voice.wixzel.com. */
|
|
33
|
+
consoleUrl?: string | undefined;
|
|
34
|
+
apiVersion?: string | undefined;
|
|
35
|
+
/** Used when a request carries no bearer. Refused on a public host; see resolveHttpOptions. */
|
|
36
|
+
fallbackKey?: string | undefined;
|
|
37
|
+
fetch?: typeof fetch | undefined;
|
|
38
|
+
log?: ((line: string) => void) | undefined;
|
|
39
|
+
}
|
|
40
|
+
/** The other names of the host in publicUrl, if it is the hosted server; none otherwise. */
|
|
41
|
+
export declare function hostAliases(publicUrl: string): string[];
|
|
42
|
+
/**
|
|
43
|
+
* The resource a request addressed: publicUrl, or the same URL on the alias
|
|
44
|
+
* host it arrived on (nginx passes the client's Host through). Any other Host
|
|
45
|
+
* gets publicUrl, so a forged header can only pick between our own names.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resourceFor(publicUrl: string, aliases: readonly string[], hostHeader: string | undefined): string;
|
|
48
|
+
/**
|
|
49
|
+
* Environment → options, with the one guard that matters: a fallback key on a
|
|
50
|
+
* host anyone can reach would hand that key's account to every anonymous
|
|
51
|
+
* caller. Refuse to start rather than trust the operator noticed.
|
|
52
|
+
*/
|
|
53
|
+
export declare function resolveHttpOptions(env: NodeJS.ProcessEnv, cliPort?: number): HttpOptions;
|
|
54
|
+
type Verdict = 'ok' | 'rejected' | 'unknown';
|
|
55
|
+
export declare class TokenProbe {
|
|
56
|
+
private readonly apiBaseUrl;
|
|
57
|
+
private readonly fetchImpl;
|
|
58
|
+
private readonly cache;
|
|
59
|
+
constructor(apiBaseUrl: string, fetchImpl: typeof fetch);
|
|
60
|
+
check(apiKey: string): Promise<Verdict>;
|
|
61
|
+
}
|
|
62
|
+
export declare function protectedResourceMetadata(opts: HttpOptions, resource?: string): {
|
|
63
|
+
resource: string;
|
|
64
|
+
authorization_servers: string[];
|
|
65
|
+
bearer_methods_supported: string[];
|
|
66
|
+
scopes_supported: ("agents:read" | "agents:write" | "calls:read" | "calls:write" | "leads:read" | "leads:write" | "campaigns:read" | "campaigns:write" | "knowledge_bases:read" | "knowledge_bases:write" | "phone_numbers:read" | "phone_numbers:write" | "sip_trunks:read" | "sip_trunks:write" | "appointments:read" | "appointments:write" | "webhooks:read" | "webhooks:write" | "api_keys:read" | "api_keys:write" | "usage:read" | "billing:read" | "billing:write")[];
|
|
67
|
+
resource_name: string;
|
|
68
|
+
resource_documentation: string;
|
|
69
|
+
};
|
|
70
|
+
/** RFC 9728 §3: the well-known path carries the resource's own path as a suffix. */
|
|
71
|
+
export declare function resourceMetadataUrl(publicUrl: string): string;
|
|
72
|
+
export declare function createHttpHandler(opts: HttpOptions): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
73
|
+
export interface RunningHttpServer {
|
|
74
|
+
server: Server;
|
|
75
|
+
/** The bound address, useful when port 0 was requested. */
|
|
76
|
+
port: number;
|
|
77
|
+
close(): Promise<void>;
|
|
78
|
+
}
|
|
79
|
+
export declare function startHttp(opts: HttpOptions): Promise<RunningHttpServer>;
|
|
80
|
+
export {};
|