@jambonz/mcp-schema-server 0.1.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 (41) hide show
  1. package/AGENTS.md +305 -0
  2. package/dist/index.d.ts +2 -0
  3. package/dist/index.js +135 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +47 -0
  6. package/schema/components/actionHook.schema.json +36 -0
  7. package/schema/components/actionHookDelayAction.schema.json +37 -0
  8. package/schema/components/auth.schema.json +18 -0
  9. package/schema/components/bidirectionalAudio.schema.json +22 -0
  10. package/schema/components/fillerNoise.schema.json +25 -0
  11. package/schema/components/recognizer.schema.json +280 -0
  12. package/schema/components/synthesizer.schema.json +82 -0
  13. package/schema/components/target.schema.json +105 -0
  14. package/schema/components/vad.schema.json +48 -0
  15. package/schema/jambonz-app.schema.json +106 -0
  16. package/schema/verbs/alert.schema.json +20 -0
  17. package/schema/verbs/answer.schema.json +12 -0
  18. package/schema/verbs/conference.schema.json +43 -0
  19. package/schema/verbs/config.schema.json +174 -0
  20. package/schema/verbs/dequeue.schema.json +36 -0
  21. package/schema/verbs/dial.schema.json +157 -0
  22. package/schema/verbs/dtmf.schema.json +27 -0
  23. package/schema/verbs/dub.schema.json +52 -0
  24. package/schema/verbs/enqueue.schema.json +38 -0
  25. package/schema/verbs/gather.schema.json +145 -0
  26. package/schema/verbs/hangup.schema.json +29 -0
  27. package/schema/verbs/leave.schema.json +12 -0
  28. package/schema/verbs/listen.schema.json +110 -0
  29. package/schema/verbs/llm.schema.json +131 -0
  30. package/schema/verbs/message.schema.json +30 -0
  31. package/schema/verbs/pause.schema.json +26 -0
  32. package/schema/verbs/pipeline.schema.json +61 -0
  33. package/schema/verbs/play.schema.json +69 -0
  34. package/schema/verbs/redirect.schema.json +23 -0
  35. package/schema/verbs/say.schema.json +84 -0
  36. package/schema/verbs/sip-decline.schema.json +31 -0
  37. package/schema/verbs/sip-refer.schema.json +41 -0
  38. package/schema/verbs/sip-request.schema.json +33 -0
  39. package/schema/verbs/stream.schema.json +30 -0
  40. package/schema/verbs/tag.schema.json +21 -0
  41. package/schema/verbs/transcribe.schema.json +44 -0
package/AGENTS.md ADDED
@@ -0,0 +1,305 @@
1
+ # jambonz Agent Toolkit
2
+
3
+ jambonz is an open-source CPaaS (Communications Platform as a Service) for building voice and messaging applications. It handles telephony infrastructure — SIP, carriers, phone numbers, media processing — so you can focus on application logic.
4
+
5
+ ## How jambonz Applications Work
6
+
7
+ A jambonz application controls phone calls by returning **arrays of verbs** — JSON instructions that execute sequentially. The runtime processes each verb in order: speak text, play audio, collect input, dial a number, connect to an AI model, etc.
8
+
9
+ ### The Webhook Lifecycle
10
+
11
+ 1. An incoming call arrives. jambonz invokes your application's URL with call details (caller, called number, SIP headers, etc.).
12
+ 2. Your application returns a JSON array of verbs.
13
+ 3. jambonz executes the verbs in order.
14
+ 4. When a verb with an `actionHook` completes (e.g. `gather` collects speech input), jambonz invokes the actionHook URL with the result.
15
+ 5. The actionHook response (a new verb array) replaces the remaining verb stack.
16
+ 6. This continues until the call ends or a `hangup` verb is executed.
17
+
18
+ ### Transport Modes
19
+
20
+ jambonz supports two transport modes for delivering verb arrays:
21
+
22
+ - **Webhook (HTTP)**: Your server receives HTTP POST requests with call data and returns JSON verb arrays in the response body. Stateless and simple. Good for IVR menus, call routing, and straightforward flows.
23
+ - **WebSocket**: Your server maintains a persistent websocket connection with jambonz. Verb arrays are sent as JSON messages in both directions. Required for real-time features like LLM conversations, audio streaming, and event-driven flows.
24
+
25
+ The verb schemas and JSON structure are identical in both modes. The difference is the transport.
26
+
27
+ ### When to Use Which
28
+
29
+ - **Webhook**: Simple IVR, call routing, voicemail, basic gather-and-respond patterns.
30
+ - **WebSocket**: LLM-powered voice agents, real-time audio streaming, complex conversational flows, anything requiring bidirectional communication, or asynchronous logic, or streaming tts.
31
+
32
+ ## Schema
33
+
34
+ The complete verb schema is at `schema/jambonz-app.schema.json`. This is a JSON Schema (draft 2020-12) that defines the structure of a jambonz application.
35
+
36
+ Individual verb schemas are in `schema/verbs/`. Shared component types (synthesizer, recognizer, target, etc.) are in `schema/components/`.
37
+
38
+ ## Core Verbs
39
+
40
+ ### Audio & Speech
41
+ - **say** — Speak text using TTS. Supports SSML, streaming, multiple voices.
42
+ - **play** — Play an audio file from a URL.
43
+ - **gather** — Collect speech (STT) and/or DTMF input. The workhorse for interactive menus and voice input.
44
+
45
+ ### AI & Real-time
46
+ - **llm** — Connect the caller to an LLM for real-time voice conversation. Handles the full STT→LLM→TTS pipeline.
47
+ - **pipeline** — Higher-level voice AI pipeline with integrated turn detection.
48
+ - **listen** / **stream** — Stream raw audio to a websocket endpoint for custom processing.
49
+ - **transcribe** — Real-time call transcription sent to a webhook.
50
+
51
+ ### Call Control
52
+ - **dial** — Place an outbound call and bridge it to the current caller.
53
+ - **conference** — Multi-party conference room.
54
+ - **enqueue** / **dequeue** — Call queuing.
55
+ - **hangup** — End the call.
56
+ - **redirect** — Transfer control to a different webhook.
57
+ - **pause** — Wait for a specified duration.
58
+
59
+ ### SIP
60
+ - **sip:decline** — Reject an incoming call with a SIP error.
61
+ - **sip:request** — Send a SIP request within the dialog.
62
+ - **sip:refer** — Transfer the call via SIP REFER.
63
+
64
+ ### Utility
65
+ - **config** — Set session-level defaults (TTS vendor/voice, STT vendor, VAD, etc.).
66
+ - **tag** — Attach metadata to the call.
67
+ - **dtmf** — Send DTMF tones.
68
+ - **dub** — Mix auxiliary audio tracks into the call.
69
+ - **message** — Send SMS/MMS.
70
+ - **alert** — Send a SIP 180 with Alert-Info.
71
+ - **answer** — Explicitly answer the call.
72
+ - **leave** — Leave a conference or queue.
73
+
74
+ ## Common Patterns
75
+
76
+ ### Simple Greeting and Gather
77
+ ```json
78
+ [
79
+ { "verb": "say", "text": "Welcome. Press 1 for sales, 2 for support." },
80
+ { "verb": "gather", "input": ["digits"], "numDigits": 1, "actionHook": "/menu" }
81
+ ]
82
+ ```
83
+
84
+ ### LLM Voice Agent
85
+ ```json
86
+ [
87
+ {
88
+ "verb": "config",
89
+ "synthesizer": { "vendor": "elevenlabs", "voice": "Rachel" },
90
+ "recognizer": { "vendor": "deepgram", "language": "en-US" }
91
+ },
92
+ {
93
+ "verb": "llm",
94
+ "vendor": "openai",
95
+ "model": "gpt-4o",
96
+ "llmOptions": {
97
+ "messages": [{ "role": "system", "content": "You are a helpful assistant." }]
98
+ },
99
+ "actionHook": "/llm-done",
100
+ "toolHook": "/tool-call"
101
+ }
102
+ ]
103
+ ```
104
+
105
+ ### Dial with Fallback
106
+ ```json
107
+ [
108
+ { "verb": "say", "text": "Connecting you now." },
109
+ {
110
+ "verb": "dial",
111
+ "target": [{ "type": "phone", "number": "+15085551212" }],
112
+ "answerOnBridge": true,
113
+ "timeout": 30,
114
+ "actionHook": "/dial-result"
115
+ },
116
+ { "verb": "say", "text": "The agent is unavailable. Goodbye." },
117
+ { "verb": "hangup" }
118
+ ]
119
+ ```
120
+
121
+ ### Call Queue
122
+ ```json
123
+ [
124
+ { "verb": "say", "text": "All agents are busy. You are in the queue." },
125
+ {
126
+ "verb": "enqueue",
127
+ "name": "support",
128
+ "waitHook": "/hold-music",
129
+ "actionHook": "/queue-exit"
130
+ }
131
+ ]
132
+ ```
133
+
134
+ ## ActionHook Payloads
135
+
136
+ When a verb completes, jambonz invokes the `actionHook` URL (webhook) or sends an event (WebSocket) with result data. Every actionHook payload includes these base fields:
137
+
138
+ | Field | Description |
139
+ |-------|-------------|
140
+ | `call_sid` | Unique identifier for this call |
141
+ | `account_sid` | Your account identifier |
142
+ | `application_sid` | The application handling this call |
143
+ | `direction` | `inbound` or `outbound` |
144
+ | `from` | Caller phone number or SIP URI |
145
+ | `to` | Called phone number or SIP URI |
146
+ | `call_id` | SIP Call-ID |
147
+ | `call_status` | Current call state (`trying`, `ringing`, `early-media`, `in-progress`, `completed`, `failed`, `busy`, `no-answer`) |
148
+ | `sip_status` | SIP response code (e.g. `200`, `486`) |
149
+
150
+ ### Verb-Specific Payload Fields
151
+
152
+ **gather**: `speech` (object with `alternatives[].transcript`), `digits` (string), `reason` (`speechDetected`, `dtmfDetected`, `timeout`)
153
+
154
+ **dial**: `dial_call_sid`, `dial_call_status`, `dial_sip_status`, `duration`
155
+
156
+ **llm**: `completion_reason` (`normal`, `timeout`, `error`), `llm_usage` (token counts)
157
+
158
+ **enqueue**: `queue_result` (`dequeued`, `hangup`, `error`)
159
+
160
+ **transcribe**: `transcription` (object with transcript text)
161
+
162
+ ## Mid-Call Control
163
+
164
+ Active calls can be modified asynchronously — inject verbs, mute, redirect, or start recording while the call is in progress.
165
+
166
+ ### REST API (Webhook Apps)
167
+
168
+ Use `PUT /v1/Accounts/{accountSid}/Calls/{callSid}` to modify an active call:
169
+
170
+ ```json
171
+ { "whisper": { "verb": "say", "text": "Supervisor is listening." } }
172
+ { "mute_status": "mute" }
173
+ { "call_hook": "https://example.com/new-flow" }
174
+ { "call_status": "completed" }
175
+ { "listen_status": "pause" }
176
+ ```
177
+
178
+ The SDK provides typed methods:
179
+ ```typescript
180
+ import { JambonzClient } from '@jambonz/sdk/client';
181
+ const client = new JambonzClient({ baseUrl, accountSid, apiKey });
182
+
183
+ await client.calls.whisper(callSid, { verb: 'say', text: 'Hello' });
184
+ await client.calls.mute(callSid, 'mute');
185
+ await client.calls.redirect(callSid, 'https://example.com/new-flow');
186
+ await client.calls.update(callSid, { call_status: 'completed' });
187
+ ```
188
+
189
+ ### Inject Commands (WebSocket Apps)
190
+
191
+ WebSocket sessions can inject commands for immediate execution:
192
+
193
+ ```typescript
194
+ // Recording
195
+ session.injectRecord('startCallRecording', { siprecServerURL: 'sip:recorder@example.com' });
196
+ session.injectRecord('stopCallRecording');
197
+
198
+ // Whisper a verb to one party
199
+ session.injectWhisper({ verb: 'say', text: 'You have 5 minutes remaining.' });
200
+
201
+ // Mute/unmute
202
+ session.injectMute('mute');
203
+ session.injectMute('unmute');
204
+
205
+ // Pause/resume audio streaming
206
+ session.injectListenStatus('pause');
207
+
208
+ // Send DTMF
209
+ session.injectDtmf('1');
210
+
211
+ // Attach metadata
212
+ session.injectTag({ supervisor: 'jane', priority: 'high' });
213
+
214
+ // Generic inject (for any command)
215
+ session.injectCommand('redirect', { call_hook: '/new-flow' });
216
+ ```
217
+
218
+ ## WebSocket Protocol
219
+
220
+ ### Message Types (jambonz → app)
221
+
222
+ | Type | Description |
223
+ |------|-------------|
224
+ | `session:new` | New call session established. Contains call details. |
225
+ | `session:redirect` | Call was redirected to this app. |
226
+ | `verb:status` | A verb completed or changed status. Contains actionHook data. |
227
+ | `call:status` | Call state changed (e.g. `completed`). |
228
+ | `llm:tool-call` | LLM requested a tool/function call. |
229
+ | `llm:event` | LLM lifecycle event (connected, tokens, etc.). |
230
+ | `tts:tokens-result` | Ack for a TTS token streaming message. |
231
+ | `tts:streaming-event` | TTS streaming lifecycle event (e.g. user interruption). |
232
+
233
+ ### Message Types (app → jambonz)
234
+
235
+ | Type | Description |
236
+ |------|-------------|
237
+ | `ack` | Acknowledge a received message. Include verbs in the `data` array to replace the current verb stack. |
238
+ | `command` | Send a command (e.g. inject a verb, control recording). |
239
+ | `llm:tool-output` | Return the result of a tool call to the LLM. |
240
+ | `tts:tokens` | Stream TTS text tokens for incremental speech synthesis. |
241
+ | `tts:flush` | Signal end of a TTS token stream. |
242
+
243
+ ### Session Events (SDK)
244
+
245
+ The SDK `Session` object emits events for common message types:
246
+
247
+ ```typescript
248
+ session.on('session:new', (session) => { /* new call */ });
249
+ session.on('verb:status', (data) => { /* verb completed */ });
250
+ session.on('call:status', (data) => { /* call state change */ });
251
+ session.on('llm:tool-call', (data) => { /* tool call from LLM */ });
252
+ session.on('llm:event', (data) => { /* LLM event */ });
253
+ session.on('tts:user_interrupt', () => { /* user interrupted TTS */ });
254
+ session.on('close', (code, reason) => { /* connection closed */ });
255
+ session.on('error', (err) => { /* error */ });
256
+ ```
257
+
258
+ ## Recording
259
+
260
+ jambonz supports SIPREC-based call recording. Recording is controlled mid-call via inject commands (WebSocket) or future REST API extensions.
261
+
262
+ ### WebSocket Recording
263
+ ```typescript
264
+ // Start recording — sends audio via SIPREC to a recording server
265
+ session.injectRecord('startCallRecording', {
266
+ siprecServerURL: 'sip:recorder@example.com',
267
+ recordingID: 'my-recording-123', // optional
268
+ });
269
+
270
+ // Pause/resume recording
271
+ session.injectRecord('pauseCallRecording');
272
+ session.injectRecord('resumeCallRecording');
273
+
274
+ // Stop recording
275
+ session.injectRecord('stopCallRecording');
276
+ ```
277
+
278
+ **Important**: The `dial` verb must use `anchorMedia: true` for recording to work during bridged calls. Without media anchoring, audio doesn't flow through the jambonz media server.
279
+
280
+ ## REST API
281
+
282
+ jambonz provides a REST API for platform management and active call control. The API client is available in the SDK at `@jambonz/sdk/client`.
283
+
284
+ Key resources:
285
+ - **Calls** — Create outbound calls, query active calls, modify in-progress calls (redirect, whisper, mute, hangup)
286
+ - **Messages** — Send SMS/MMS messages
287
+
288
+ ## Examples
289
+
290
+ Complete working examples are in the `examples/` directory:
291
+ - **hello-world** — Minimal greeting (webhook + WebSocket)
292
+ - **ivr-menu** — Interactive menu with speech and DTMF input (webhook)
293
+ - **voice-agent** — LLM-powered conversational AI (webhook + WebSocket)
294
+ - **queue-with-hold** — Call queue with hold music and agent dequeue (webhook + WebSocket)
295
+ - **call-recording** — Mid-call recording control via REST API and inject commands (webhook + WebSocket)
296
+
297
+ ## Key Concepts
298
+
299
+ - **Verb**: A JSON object with a `verb` property that tells jambonz what to do. Verbs execute sequentially.
300
+ - **ActionHook**: A webhook URL that jambonz calls when a verb completes. Returns the next verb array. Payload includes call details and verb-specific results.
301
+ - **Synthesizer**: TTS configuration (vendor, voice, language).
302
+ - **Recognizer**: STT configuration (vendor, language, model).
303
+ - **Target**: A call destination (phone number, SIP URI, registered user, Teams user).
304
+ - **Session**: A single phone call. Session-level settings (set via `config`) persist across verbs.
305
+ - **Inject Command**: Asynchronous mid-call modification (WebSocket). Executes immediately without replacing the verb stack.
@@ -0,0 +1,2 @@
1
+
2
+ export { }
package/dist/index.js ADDED
@@ -0,0 +1,135 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/index.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+
7
+ // src/resources.ts
8
+ import { readFileSync, readdirSync } from "fs";
9
+ import { resolve, basename, dirname } from "path";
10
+ import { fileURLToPath } from "url";
11
+ function findSchemaDir() {
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const candidates = [
14
+ resolve(__dirname, "../schema"),
15
+ // npm: mcp-server/schema/
16
+ resolve(__dirname, "../../schema"),
17
+ // dev: from dist/ or src/
18
+ resolve(__dirname, "../../../schema")
19
+ // dev: nested
20
+ ];
21
+ for (const dir of candidates) {
22
+ try {
23
+ readdirSync(dir);
24
+ return dir;
25
+ } catch {
26
+ }
27
+ }
28
+ throw new Error("Could not locate schema directory");
29
+ }
30
+ function findAgentsMd() {
31
+ const __dirname = dirname(fileURLToPath(import.meta.url));
32
+ const candidates = [
33
+ resolve(__dirname, "../AGENTS.md"),
34
+ // npm: mcp-server/AGENTS.md
35
+ resolve(__dirname, "../../AGENTS.md"),
36
+ // dev: from dist/ or src/
37
+ resolve(__dirname, "../../../AGENTS.md")
38
+ // dev: nested
39
+ ];
40
+ for (const path of candidates) {
41
+ try {
42
+ readFileSync(path, "utf-8");
43
+ return path;
44
+ } catch {
45
+ }
46
+ }
47
+ throw new Error("Could not locate AGENTS.md");
48
+ }
49
+ function registerResources(server2) {
50
+ const schemaDir = findSchemaDir();
51
+ const agentsMdPath = findAgentsMd();
52
+ server2.resource(
53
+ "agents-guide",
54
+ "jambonz://docs/agents-guide",
55
+ {
56
+ description: "jambonz Agent Toolkit guide \u2014 explains the verb model, transport modes, core verbs, and common patterns",
57
+ mimeType: "text/markdown"
58
+ },
59
+ async (uri) => ({
60
+ contents: [{
61
+ uri: uri.href,
62
+ text: readFileSync(agentsMdPath, "utf-8"),
63
+ mimeType: "text/markdown"
64
+ }]
65
+ })
66
+ );
67
+ const appSchemaPath = resolve(schemaDir, "jambonz-app.schema.json");
68
+ server2.resource(
69
+ "app-schema",
70
+ "jambonz://schema/jambonz-app",
71
+ {
72
+ description: "Root JSON Schema for a jambonz application \u2014 an array of verbs",
73
+ mimeType: "application/schema+json"
74
+ },
75
+ async (uri) => ({
76
+ contents: [{
77
+ uri: uri.href,
78
+ text: readFileSync(appSchemaPath, "utf-8"),
79
+ mimeType: "application/schema+json"
80
+ }]
81
+ })
82
+ );
83
+ const verbsDir = resolve(schemaDir, "verbs");
84
+ const verbFiles = readdirSync(verbsDir).filter((f) => f.endsWith(".schema.json"));
85
+ for (const file of verbFiles) {
86
+ const verbName = basename(file, ".schema.json");
87
+ const filePath = resolve(verbsDir, file);
88
+ server2.resource(
89
+ `verb-${verbName}`,
90
+ `jambonz://schema/verbs/${verbName}`,
91
+ {
92
+ description: `JSON Schema for the "${verbName}" verb`,
93
+ mimeType: "application/schema+json"
94
+ },
95
+ async (uri) => ({
96
+ contents: [{
97
+ uri: uri.href,
98
+ text: readFileSync(filePath, "utf-8"),
99
+ mimeType: "application/schema+json"
100
+ }]
101
+ })
102
+ );
103
+ }
104
+ const componentsDir = resolve(schemaDir, "components");
105
+ const componentFiles = readdirSync(componentsDir).filter((f) => f.endsWith(".schema.json"));
106
+ for (const file of componentFiles) {
107
+ const componentName = basename(file, ".schema.json");
108
+ const filePath = resolve(componentsDir, file);
109
+ server2.resource(
110
+ `component-${componentName}`,
111
+ `jambonz://schema/components/${componentName}`,
112
+ {
113
+ description: `JSON Schema for the "${componentName}" component`,
114
+ mimeType: "application/schema+json"
115
+ },
116
+ async (uri) => ({
117
+ contents: [{
118
+ uri: uri.href,
119
+ text: readFileSync(filePath, "utf-8"),
120
+ mimeType: "application/schema+json"
121
+ }]
122
+ })
123
+ );
124
+ }
125
+ }
126
+
127
+ // src/index.ts
128
+ var server = new McpServer({
129
+ name: "jambonz-schema-server",
130
+ version: "0.1.0"
131
+ });
132
+ registerResources(server);
133
+ var transport = new StdioServerTransport();
134
+ await server.connect(transport);
135
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/resources.ts"],"sourcesContent":["/**\n * jambonz MCP Schema Server\n *\n * Serves jambonz verb schemas and documentation as MCP resources.\n * Designed for AI agents that build jambonz voice applications.\n *\n * Usage:\n * npx @jambonz/mcp-schema-server\n *\n * Or in Claude Desktop config:\n * { \"command\": \"npx\", \"args\": [\"@jambonz/mcp-schema-server\"] }\n */\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { registerResources } from './resources.js';\n\nconst server = new McpServer({\n name: 'jambonz-schema-server',\n version: '0.1.0',\n});\n\nregisterResources(server);\n\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n","/**\n * Registers jambonz schema files and documentation as MCP resources.\n */\n\nimport { readFileSync, readdirSync } from 'fs';\nimport { resolve, basename, dirname } from 'path';\nimport { fileURLToPath } from 'url';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\n/** Locate the schema directory. Checks:\n * 1. Bundled in npm package: mcp-server/schema/ (copied by prepack)\n * 2. Development: sibling to mcp-server/ at agent-toolkit/schema/\n */\nfunction findSchemaDir(): string {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n resolve(__dirname, '../schema'), // npm: mcp-server/schema/\n resolve(__dirname, '../../schema'), // dev: from dist/ or src/\n resolve(__dirname, '../../../schema'), // dev: nested\n ];\n for (const dir of candidates) {\n try {\n readdirSync(dir);\n return dir;\n } catch {\n // continue\n }\n }\n throw new Error('Could not locate schema directory');\n}\n\n/** Locate AGENTS.md. Checks:\n * 1. Bundled in npm package: mcp-server/AGENTS.md (copied by prepack)\n * 2. Development: sibling to mcp-server/ at agent-toolkit/AGENTS.md\n */\nfunction findAgentsMd(): string {\n const __dirname = dirname(fileURLToPath(import.meta.url));\n const candidates = [\n resolve(__dirname, '../AGENTS.md'), // npm: mcp-server/AGENTS.md\n resolve(__dirname, '../../AGENTS.md'), // dev: from dist/ or src/\n resolve(__dirname, '../../../AGENTS.md'), // dev: nested\n ];\n for (const path of candidates) {\n try {\n readFileSync(path, 'utf-8');\n return path;\n } catch {\n // continue\n }\n }\n throw new Error('Could not locate AGENTS.md');\n}\n\nexport function registerResources(server: McpServer): void {\n const schemaDir = findSchemaDir();\n const agentsMdPath = findAgentsMd();\n\n // 1. AGENTS.md — the main documentation resource\n server.resource(\n 'agents-guide',\n 'jambonz://docs/agents-guide',\n {\n description: 'jambonz Agent Toolkit guide — explains the verb model, transport modes, core verbs, and common patterns',\n mimeType: 'text/markdown',\n },\n async (uri) => ({\n contents: [{\n uri: uri.href,\n text: readFileSync(agentsMdPath, 'utf-8'),\n mimeType: 'text/markdown',\n }],\n }),\n );\n\n // 2. Root application schema\n const appSchemaPath = resolve(schemaDir, 'jambonz-app.schema.json');\n server.resource(\n 'app-schema',\n 'jambonz://schema/jambonz-app',\n {\n description: 'Root JSON Schema for a jambonz application — an array of verbs',\n mimeType: 'application/schema+json',\n },\n async (uri) => ({\n contents: [{\n uri: uri.href,\n text: readFileSync(appSchemaPath, 'utf-8'),\n mimeType: 'application/schema+json',\n }],\n }),\n );\n\n // 3. Verb schemas\n const verbsDir = resolve(schemaDir, 'verbs');\n const verbFiles = readdirSync(verbsDir).filter((f) => f.endsWith('.schema.json'));\n\n for (const file of verbFiles) {\n const verbName = basename(file, '.schema.json');\n const filePath = resolve(verbsDir, file);\n\n server.resource(\n `verb-${verbName}`,\n `jambonz://schema/verbs/${verbName}`,\n {\n description: `JSON Schema for the \"${verbName}\" verb`,\n mimeType: 'application/schema+json',\n },\n async (uri) => ({\n contents: [{\n uri: uri.href,\n text: readFileSync(filePath, 'utf-8'),\n mimeType: 'application/schema+json',\n }],\n }),\n );\n }\n\n // 4. Component schemas\n const componentsDir = resolve(schemaDir, 'components');\n const componentFiles = readdirSync(componentsDir).filter((f) => f.endsWith('.schema.json'));\n\n for (const file of componentFiles) {\n const componentName = basename(file, '.schema.json');\n const filePath = resolve(componentsDir, file);\n\n server.resource(\n `component-${componentName}`,\n `jambonz://schema/components/${componentName}`,\n {\n description: `JSON Schema for the \"${componentName}\" component`,\n mimeType: 'application/schema+json',\n },\n async (uri) => ({\n contents: [{\n uri: uri.href,\n text: readFileSync(filePath, 'utf-8'),\n mimeType: 'application/schema+json',\n }],\n }),\n );\n }\n}\n"],"mappings":"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","names":["server"]}
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "@jambonz/mcp-schema-server",
3
+ "version": "0.1.0",
4
+ "description": "MCP server that provides jambonz verb schemas and documentation as resources",
5
+ "author": "Dave Horton",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/jambonz/agent-toolkit.git",
10
+ "directory": "mcp-server"
11
+ },
12
+ "homepage": "https://github.com/jambonz/agent-toolkit#readme",
13
+ "keywords": ["jambonz", "mcp", "model-context-protocol", "schema", "voip", "ai-agent"],
14
+ "type": "module",
15
+ "bin": {
16
+ "jambonz-mcp": "./dist/index.js"
17
+ },
18
+ "main": "./dist/index.js",
19
+ "types": "./dist/index.d.ts",
20
+ "files": [
21
+ "dist",
22
+ "schema",
23
+ "AGENTS.md"
24
+ ],
25
+ "scripts": {
26
+ "build": "tsup",
27
+ "dev": "tsup --watch",
28
+ "typecheck": "tsc --noEmit",
29
+ "prepack": "cp -r ../schema . && cp ../AGENTS.md .",
30
+ "prepublishOnly": "npm run build"
31
+ },
32
+ "dependencies": {
33
+ "@modelcontextprotocol/sdk": "^1.12.0"
34
+ },
35
+ "peerDependencies": {
36
+ "zod": "^3.25.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^22.0.0",
40
+ "tsup": "^8.3.0",
41
+ "typescript": "^5.6.0",
42
+ "zod": "^3.25.0"
43
+ },
44
+ "engines": {
45
+ "node": ">=18.0.0"
46
+ }
47
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://jambonz.org/schema/components/actionHook",
4
+ "title": "ActionHook",
5
+ "description": "A webhook or websocket callback that jambonz invokes during call processing. Most jambonz verbs use actionHooks to report results (e.g. speech recognition results from 'gather') and to receive the next set of verbs to execute. Can be specified as a simple URL string or as an object with additional options.",
6
+ "oneOf": [
7
+ {
8
+ "type": "string",
9
+ "format": "uri",
10
+ "description": "A URL to invoke. For webhook applications this is an HTTP(S) URL. For websocket applications this is typically a relative path or event name.",
11
+ "examples": ["https://myapp.example.com/gather-result", "/gather-result"]
12
+ },
13
+ {
14
+ "type": "object",
15
+ "description": "A hook specification with URL and additional options.",
16
+ "properties": {
17
+ "url": {
18
+ "type": "string",
19
+ "format": "uri",
20
+ "description": "The URL to invoke."
21
+ },
22
+ "method": {
23
+ "type": "string",
24
+ "description": "The HTTP method to use. Only applies to webhook applications.",
25
+ "enum": ["GET", "POST"],
26
+ "default": "POST"
27
+ },
28
+ "basicAuth": {
29
+ "$ref": "auth",
30
+ "description": "Basic authentication credentials to include in the request."
31
+ }
32
+ },
33
+ "required": ["url"]
34
+ }
35
+ ]
36
+ }
@@ -0,0 +1,37 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://jambonz.org/schema/components/actionHookDelayAction",
4
+ "title": "ActionHookDelayAction",
5
+ "description": "Configuration for what to do when an actionHook (webhook) takes a long time to respond. Allows playing interim content (e.g. 'please wait' messages, hold music) while waiting for the webhook response, with configurable retry and give-up behavior.",
6
+ "type": "object",
7
+ "properties": {
8
+ "enabled": {
9
+ "type": "boolean",
10
+ "description": "Whether to enable delay handling for actionHooks."
11
+ },
12
+ "noResponseTimeout": {
13
+ "type": "number",
14
+ "description": "Time in seconds to wait before executing the delay actions. If the webhook responds before this timeout, the delay actions are skipped.",
15
+ "examples": [3, 5]
16
+ },
17
+ "noResponseGiveUpTimeout": {
18
+ "type": "number",
19
+ "description": "Total time in seconds to wait for a webhook response before giving up and executing the giveUpActions.",
20
+ "examples": [30, 60]
21
+ },
22
+ "retries": {
23
+ "type": "number",
24
+ "description": "Number of times to retry the delay actions while still waiting for the webhook response."
25
+ },
26
+ "actions": {
27
+ "type": "array",
28
+ "description": "An array of jambonz verbs to execute while waiting for the webhook response. Typically 'say' or 'play' verbs with messages like 'please hold'.",
29
+ "items": { "type": "object" }
30
+ },
31
+ "giveUpActions": {
32
+ "type": "array",
33
+ "description": "An array of jambonz verbs to execute if the webhook never responds within the giveUpTimeout. Typically an error message and/or hangup.",
34
+ "items": { "type": "object" }
35
+ }
36
+ }
37
+ }
@@ -0,0 +1,18 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://jambonz.org/schema/components/auth",
4
+ "title": "Auth",
5
+ "description": "Basic authentication credentials, used for authenticating with external services such as websocket endpoints or SIP registrars.",
6
+ "type": "object",
7
+ "properties": {
8
+ "username": {
9
+ "type": "string",
10
+ "description": "The username for authentication."
11
+ },
12
+ "password": {
13
+ "type": "string",
14
+ "description": "The password for authentication."
15
+ }
16
+ },
17
+ "required": ["username", "password"]
18
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://jambonz.org/schema/components/bidirectionalAudio",
4
+ "title": "BidirectionalAudio",
5
+ "description": "Configuration for bidirectional audio streaming over a websocket connection. When enabled, the remote websocket endpoint can send audio back to jambonz to be played to the caller.",
6
+ "type": "object",
7
+ "properties": {
8
+ "enabled": {
9
+ "type": "boolean",
10
+ "description": "Whether to enable bidirectional audio on the websocket connection."
11
+ },
12
+ "streaming": {
13
+ "type": "boolean",
14
+ "description": "If true, audio is streamed continuously rather than sent as complete messages."
15
+ },
16
+ "sampleRate": {
17
+ "type": "number",
18
+ "description": "The sample rate in Hz for bidirectional audio.",
19
+ "examples": [8000, 16000, 24000]
20
+ }
21
+ }
22
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
3
+ "$id": "https://jambonz.org/schema/components/fillerNoise",
4
+ "title": "FillerNoise",
5
+ "description": "Configuration for playing background filler noise (e.g. keyboard typing, hold music) while the application is processing and the caller would otherwise hear silence. Commonly used during LLM response generation to indicate the system is working.",
6
+ "type": "object",
7
+ "properties": {
8
+ "enable": {
9
+ "type": "boolean",
10
+ "description": "Whether to enable filler noise."
11
+ },
12
+ "url": {
13
+ "type": "string",
14
+ "format": "uri",
15
+ "description": "URL of the audio file to play as filler noise. Should be a short, loopable audio clip.",
16
+ "examples": ["https://example.com/sounds/typing.wav"]
17
+ },
18
+ "startDelaySecs": {
19
+ "type": "number",
20
+ "description": "Number of seconds to wait before starting filler noise. Prevents filler noise from playing during brief processing pauses.",
21
+ "examples": [1, 2]
22
+ }
23
+ },
24
+ "required": ["enable"]
25
+ }