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.
Files changed (76) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/LICENSE +21 -0
  3. package/README.md +127 -0
  4. package/dist/client.d.ts +69 -0
  5. package/dist/client.js +137 -0
  6. package/dist/client.js.map +1 -0
  7. package/dist/guide.d.ts +11 -0
  8. package/dist/guide.js +86 -0
  9. package/dist/guide.js.map +1 -0
  10. package/dist/http.d.ts +80 -0
  11. package/dist/http.js +281 -0
  12. package/dist/http.js.map +1 -0
  13. package/dist/index.d.ts +17 -0
  14. package/dist/index.js +88 -0
  15. package/dist/index.js.map +1 -0
  16. package/dist/prompts.d.ts +7 -0
  17. package/dist/prompts.js +83 -0
  18. package/dist/prompts.js.map +1 -0
  19. package/dist/schemas.d.ts +105 -0
  20. package/dist/schemas.js +122 -0
  21. package/dist/schemas.js.map +1 -0
  22. package/dist/server.d.ts +16 -0
  23. package/dist/server.js +58 -0
  24. package/dist/server.js.map +1 -0
  25. package/dist/site.d.ts +12 -0
  26. package/dist/site.js +17 -0
  27. package/dist/site.js.map +1 -0
  28. package/dist/tooling.d.ts +59 -0
  29. package/dist/tooling.js +129 -0
  30. package/dist/tooling.js.map +1 -0
  31. package/dist/tools/agents.d.ts +97 -0
  32. package/dist/tools/agents.js +105 -0
  33. package/dist/tools/agents.js.map +1 -0
  34. package/dist/tools/api-keys.d.ts +37 -0
  35. package/dist/tools/api-keys.js +61 -0
  36. package/dist/tools/api-keys.js.map +1 -0
  37. package/dist/tools/appointments.d.ts +30 -0
  38. package/dist/tools/appointments.js +77 -0
  39. package/dist/tools/appointments.js.map +1 -0
  40. package/dist/tools/billing.d.ts +29 -0
  41. package/dist/tools/billing.js +72 -0
  42. package/dist/tools/billing.js.map +1 -0
  43. package/dist/tools/calls.d.ts +37 -0
  44. package/dist/tools/calls.js +98 -0
  45. package/dist/tools/calls.js.map +1 -0
  46. package/dist/tools/campaigns.d.ts +13 -0
  47. package/dist/tools/campaigns.js +76 -0
  48. package/dist/tools/campaigns.js.map +1 -0
  49. package/dist/tools/engines.d.ts +4 -0
  50. package/dist/tools/engines.js +43 -0
  51. package/dist/tools/engines.js.map +1 -0
  52. package/dist/tools/index.d.ts +5 -0
  53. package/dist/tools/index.js +37 -0
  54. package/dist/tools/index.js.map +1 -0
  55. package/dist/tools/knowledge-bases.d.ts +27 -0
  56. package/dist/tools/knowledge-bases.js +69 -0
  57. package/dist/tools/knowledge-bases.js.map +1 -0
  58. package/dist/tools/leads.d.ts +28 -0
  59. package/dist/tools/leads.js +88 -0
  60. package/dist/tools/leads.js.map +1 -0
  61. package/dist/tools/phone-numbers.d.ts +19 -0
  62. package/dist/tools/phone-numbers.js +72 -0
  63. package/dist/tools/phone-numbers.js.map +1 -0
  64. package/dist/tools/realtime.d.ts +8 -0
  65. package/dist/tools/realtime.js +28 -0
  66. package/dist/tools/realtime.js.map +1 -0
  67. package/dist/tools/sip-trunks.d.ts +45 -0
  68. package/dist/tools/sip-trunks.js +118 -0
  69. package/dist/tools/sip-trunks.js.map +1 -0
  70. package/dist/tools/webhooks.d.ts +52 -0
  71. package/dist/tools/webhooks.js +90 -0
  72. package/dist/tools/webhooks.js.map +1 -0
  73. package/dist/version.d.ts +7 -0
  74. package/dist/version.js +8 -0
  75. package/dist/version.js.map +1 -0
  76. 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
+ [![npm](https://img.shields.io/npm/v/wixzel-voice-mcp?style=flat-square&logo=npm&logoColor=white&label=npm&color=A0D425&labelColor=0D0D0D)](https://www.npmjs.com/package/wixzel-voice-mcp)
4
+ [![MIT](https://img.shields.io/badge/licence-MIT-A0D425?style=flat-square&labelColor=0D0D0D)](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.
@@ -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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"}
@@ -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 {};