@rymi/mcp 0.6.2 → 2.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 (3) hide show
  1. package/README.md +106 -35
  2. package/dist/index.js +1522 -876
  3. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -6,6 +6,16 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
12
+ var __commonJS = (cb, mod) => function __require() {
13
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
14
+ };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
9
19
  var __copyProps = (to, from, except, desc) => {
10
20
  if (from && typeof from === "object" || typeof from === "function") {
11
21
  for (let key of __getOwnPropNames(from))
@@ -22,266 +32,1499 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
22
32
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
23
33
  mod
24
34
  ));
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
25
36
 
26
- // src/server.ts
27
- var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
28
- var import_node = __toESM(require("@rymi/node"));
37
+ // ../shared-types/dist/agentTools.js
38
+ var agentTools_exports = {};
39
+ __export(agentTools_exports, {
40
+ API_SECRETS_PROVIDER: () => API_SECRETS_PROVIDER,
41
+ API_TOOL_AUTH_HEADER_PATTERN: () => API_TOOL_AUTH_HEADER_PATTERN,
42
+ API_TOOL_NAME_PATTERN: () => API_TOOL_NAME_PATTERN,
43
+ API_TOOL_RESERVED_FIELD_NAMES: () => API_TOOL_RESERVED_FIELD_NAMES,
44
+ API_TOOL_SECRET_REF_PATTERN: () => API_TOOL_SECRET_REF_PATTERN,
45
+ BUILTIN_TOOL_CATALOG: () => BUILTIN_TOOL_CATALOG,
46
+ SECRET_NAME_PATTERN: () => SECRET_NAME_PATTERN,
47
+ toToolCapabilitySafeView: () => toToolCapabilitySafeView
48
+ });
49
+ function toToolCapabilitySafeView(state) {
50
+ return {
51
+ toolId: state.toolId,
52
+ available: state.authorized && state.runtimeRegistered,
53
+ unavailableReason: state.unavailableReason,
54
+ sideEffect: state.sideEffect
55
+ };
56
+ }
57
+ var API_TOOL_NAME_PATTERN, SECRET_NAME_PATTERN, API_SECRETS_PROVIDER, API_TOOL_RESERVED_FIELD_NAMES, API_TOOL_AUTH_HEADER_PATTERN, API_TOOL_SECRET_REF_PATTERN, BUILTIN_TOOL_CATALOG;
58
+ var init_agentTools = __esm({
59
+ "../shared-types/dist/agentTools.js"() {
60
+ "use strict";
61
+ API_TOOL_NAME_PATTERN = /^[a-z][a-z0-9_]{2,40}$/;
62
+ SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]{1,63}$/;
63
+ API_SECRETS_PROVIDER = "api_secrets";
64
+ API_TOOL_RESERVED_FIELD_NAMES = /* @__PURE__ */ new Set([
65
+ "False",
66
+ "None",
67
+ "True",
68
+ "and",
69
+ "as",
70
+ "assert",
71
+ "async",
72
+ "await",
73
+ "break",
74
+ "class",
75
+ "continue",
76
+ "def",
77
+ "del",
78
+ "elif",
79
+ "else",
80
+ "except",
81
+ "finally",
82
+ "for",
83
+ "from",
84
+ "global",
85
+ "if",
86
+ "import",
87
+ "in",
88
+ "is",
89
+ "lambda",
90
+ "nonlocal",
91
+ "not",
92
+ "or",
93
+ "pass",
94
+ "raise",
95
+ "return",
96
+ "try",
97
+ "while",
98
+ "with",
99
+ "yield",
100
+ "params"
101
+ ]);
102
+ API_TOOL_AUTH_HEADER_PATTERN = /^(authorization|x-api-key|api[-_]?key|.*token.*|.*secret.*)$/i;
103
+ API_TOOL_SECRET_REF_PATTERN = /\{\{\s*secrets\.[A-Z][A-Z0-9_]{1,63}\s*\}\}/;
104
+ BUILTIN_TOOL_CATALOG = [
105
+ {
106
+ tool_id: "handoff_to_human",
107
+ label: "Human handoff",
108
+ description: "Transfer the live call to the configured human destination when escalation is required.",
109
+ category: "call_control",
110
+ side_effect: "write",
111
+ default_timeout_ms: 1e3,
112
+ requires_credential: false
113
+ },
114
+ {
115
+ tool_id: "check_calendar_availability",
116
+ label: "Check calendar availability",
117
+ description: "Look up open slots in the configured calendar provider.",
118
+ category: "calendar",
119
+ side_effect: "read",
120
+ default_timeout_ms: 4e3,
121
+ requires_credential: true
122
+ },
123
+ {
124
+ tool_id: "list_calendar_events",
125
+ label: "List calendar events",
126
+ description: "Read scheduled events from the configured calendar provider.",
127
+ category: "calendar",
128
+ side_effect: "read",
129
+ default_timeout_ms: 4e3,
130
+ requires_credential: true
131
+ },
132
+ {
133
+ tool_id: "create_calendar_event",
134
+ label: "Create calendar event",
135
+ description: "Create a calendar event after the caller explicitly confirms the details.",
136
+ category: "calendar",
137
+ side_effect: "write",
138
+ default_timeout_ms: 6e3,
139
+ requires_credential: true
140
+ },
141
+ {
142
+ tool_id: "update_calendar_event",
143
+ label: "Update calendar event",
144
+ description: "Move or edit an existing calendar event after explicit caller confirmation.",
145
+ category: "calendar",
146
+ side_effect: "write",
147
+ default_timeout_ms: 6e3,
148
+ requires_credential: true
149
+ },
150
+ {
151
+ tool_id: "delete_calendar_event",
152
+ label: "Delete calendar event",
153
+ description: "Cancel an existing calendar event after explicit caller confirmation.",
154
+ category: "calendar",
155
+ side_effect: "write",
156
+ default_timeout_ms: 6e3,
157
+ requires_credential: true
158
+ },
159
+ {
160
+ tool_id: "lookup_customer",
161
+ label: "Look up customer",
162
+ description: "Find a customer record by phone, email, or external id.",
163
+ category: "crm",
164
+ side_effect: "read",
165
+ default_timeout_ms: 4e3,
166
+ requires_credential: true
167
+ },
168
+ {
169
+ tool_id: "send_whatsapp_message",
170
+ label: "Send WhatsApp message",
171
+ description: "Send a WhatsApp message to the caller mid-call (e.g. confirmation link, address, brochure URL). Outside the 24h customer-care window an approved template must be configured.",
172
+ category: "messaging",
173
+ side_effect: "write",
174
+ default_timeout_ms: 6e3,
175
+ requires_credential: true
176
+ },
177
+ {
178
+ tool_id: "send_telegram_message",
179
+ label: "Send Telegram message",
180
+ description: "Send a Telegram bot message to the caller mid-call. Requires the caller to have an established chat_id with the tenant's bot (no cold-send by phone).",
181
+ category: "messaging",
182
+ side_effect: "write",
183
+ default_timeout_ms: 6e3,
184
+ requires_credential: true
185
+ },
186
+ {
187
+ tool_id: "send_asset",
188
+ label: "Send asset",
189
+ description: "Send a configured shareable asset to the caller when they explicitly request a document, brochure, or guide.",
190
+ category: "messaging",
191
+ side_effect: "write",
192
+ default_timeout_ms: 6e3,
193
+ requires_credential: true
194
+ },
195
+ {
196
+ tool_id: "lookup_ticket",
197
+ label: "Look up ticket",
198
+ description: "Find a support ticket by its number, or the caller's most recent ticket, and read back its status.",
199
+ category: "ticketing",
200
+ side_effect: "read",
201
+ default_timeout_ms: 4e3,
202
+ requires_credential: true
203
+ },
204
+ {
205
+ tool_id: "create_ticket",
206
+ label: "Create ticket",
207
+ description: "Raise a support ticket from the call after the caller confirms the details, and read the ticket number back to them.",
208
+ category: "ticketing",
209
+ side_effect: "write",
210
+ default_timeout_ms: 6e3,
211
+ requires_credential: true
212
+ },
213
+ {
214
+ tool_id: "call_webhook",
215
+ label: "API request",
216
+ description: "Call your own HTTPS API mid-call, to look something up or to send details. Each API tool has its own name, purpose, typed parameters and headers; header secrets stay encrypted.",
217
+ category: "webhook",
218
+ side_effect: "read",
219
+ default_timeout_ms: 6e3,
220
+ // The endpoint lives in the binding's provider_settings and its secrets in
221
+ // the tenant's api_secrets row — nothing to connect in Settings → Connectors.
222
+ requires_credential: false
223
+ }
224
+ ];
225
+ }
226
+ });
29
227
 
30
- // src/tools/agents.ts
31
- var import_zod = require("zod");
32
- var agentConfigFields = {
33
- system_prompt: import_zod.z.string().optional().describe("Full system prompt for the agent. If given without persona/playbook, the server auto-structures it."),
34
- voice: import_zod.z.string().optional().describe('Voice ID (e.g. "Aoede", "Charon"). Call list_llm_options for valid values. Leave empty for realtime LLMs and Deepgram TTS, which carry their own voice.'),
35
- language: import_zod.z.string().optional().describe('Primary BCP-47 language tag (e.g. "hi-IN", "en-US").'),
36
- supported_languages: import_zod.z.array(import_zod.z.string()).optional().describe('All BCP-47 languages the agent should handle, e.g. ["hi-IN","en-US"] for a bilingual agent. The primary `language` is added automatically if omitted here.'),
37
- llm_provider: import_zod.z.enum(["gemini", "openai", "anthropic", "sarvam"]).optional(),
38
- llm_model: import_zod.z.string().optional().describe('LLM model id from list_llm_options (e.g. "gemini-2.5-flash", "sarvam-m"). Must fit the agent_role cost tier.'),
39
- llm_fallback_provider: import_zod.z.string().nullable().optional().describe("Fallback LLM provider; null clears it."),
40
- llm_fallback_model: import_zod.z.string().nullable().optional().describe("Fallback LLM model; null clears it."),
41
- stt_provider: import_zod.z.string().optional().describe('Speech-to-text provider (e.g. "deepgram", "sarvam").'),
42
- stt_model: import_zod.z.string().optional(),
43
- stt_fallback_provider: import_zod.z.string().nullable().optional(),
44
- stt_fallback_model: import_zod.z.string().nullable().optional(),
45
- tts_provider: import_zod.z.string().optional().describe('Text-to-speech provider (e.g. "elevenlabs", "sarvam", "cartesia").'),
46
- tts_model: import_zod.z.string().optional(),
47
- tts_fallback_provider: import_zod.z.string().nullable().optional(),
48
- tts_fallback_model: import_zod.z.string().nullable().optional(),
49
- custom_llm_url: import_zod.z.string().nullable().optional().describe("Self-hosted LLM endpoint (https:// or wss://). Enterprise only; null clears."),
50
- custom_voice_url: import_zod.z.string().nullable().optional().describe("Self-hosted TTS endpoint (https:// or wss://). Enterprise only; null clears."),
51
- custom_voice_mode: import_zod.z.enum(["rymi", "openai-compat"]).optional().describe("Wire format for custom_voice_url."),
52
- custom_transcriber_url: import_zod.z.string().nullable().optional().describe("Self-hosted STT endpoint (https:// or wss://). Enterprise only; null clears."),
53
- agent_role: import_zod.z.enum(["operator", "specialist", "executive", "concierge"]).optional().describe('Cost/capability tier. "concierge" unlocks realtime models; higher tiers cost more.'),
54
- prompt_mode: import_zod.z.enum(["builder", "raw"]).optional().describe('How the system prompt is interpreted: "builder" (server structures persona/playbook from system_prompt) or "raw" (use system_prompt verbatim \u2014 requires system_prompt). Defaults to "builder".'),
55
- persona: import_zod.z.record(import_zod.z.any()).optional().describe("Persona object. Note: callerPersonas must be objects of shape {type, approach, detectedWhen}, NOT plain strings."),
56
- playbook: import_zod.z.record(import_zod.z.any()).optional().describe("Playbook configuration object"),
57
- advanced: import_zod.z.record(import_zod.z.any()).optional(),
58
- features: import_zod.z.record(import_zod.z.any()).optional(),
59
- post_call: import_zod.z.record(import_zod.z.any()).optional()
60
- };
61
- function ok(result) {
62
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
228
+ // ../shared-types/dist/safeUrl.js
229
+ var safeUrl_exports = {};
230
+ __export(safeUrl_exports, {
231
+ isSafeUrl: () => isSafeUrl
232
+ });
233
+ function unmapIpv4(v6) {
234
+ const dotted = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(v6);
235
+ if (dotted)
236
+ return dotted[1];
237
+ const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(v6);
238
+ if (!hex)
239
+ return null;
240
+ const high = parseInt(hex[1], 16);
241
+ const low = parseInt(hex[2], 16);
242
+ return [high >> 8, high & 255, low >> 8, low & 255].join(".");
63
243
  }
64
- function fail(err) {
65
- const e = err;
66
- const body = { error: e?.message || String(err) };
67
- if (e?.code) body.code = e.code;
68
- if (e?.status) body.status = e.status;
69
- return { isError: true, content: [{ type: "text", text: JSON.stringify(body, null, 2) }] };
244
+ function isBlockedHost(host) {
245
+ const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
246
+ if (bare === "localhost" || bare === "0.0.0.0" || bare === "::")
247
+ return true;
248
+ if (bare.endsWith(".local") || bare.endsWith(".internal") || bare.endsWith(".localhost"))
249
+ return true;
250
+ if (/^127\./.test(bare))
251
+ return true;
252
+ if (/^10\./.test(bare))
253
+ return true;
254
+ if (/^192\.168\./.test(bare))
255
+ return true;
256
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(bare))
257
+ return true;
258
+ if (/^169\.254\./.test(bare))
259
+ return true;
260
+ const v6 = bare.toLowerCase();
261
+ if (v6 === "::1")
262
+ return true;
263
+ if (/^f[cd][0-9a-f]{2}:/.test(v6))
264
+ return true;
265
+ if (/^fe[89ab][0-9a-f]:/.test(v6))
266
+ return true;
267
+ const mapped = unmapIpv4(v6);
268
+ if (mapped)
269
+ return isBlockedHost(mapped);
270
+ return false;
70
271
  }
71
- function foldPromptMode(params) {
72
- const { prompt_mode, ...rest } = params;
73
- if (!prompt_mode) return rest;
74
- return { ...rest, advanced: { ...rest.advanced || {}, prompt_mode } };
272
+ function isSafeUrl(raw, options = {}) {
273
+ const protocols = options.protocols ?? ["http:", "https:"];
274
+ let url;
275
+ try {
276
+ url = new URL(raw);
277
+ } catch {
278
+ return false;
279
+ }
280
+ if (!protocols.includes(url.protocol))
281
+ return false;
282
+ return !isBlockedHost(url.hostname.toLowerCase());
75
283
  }
76
- function registerAgentTools(server, rymi, isReadOnly = false) {
77
- server.tool(
78
- "list_agents",
79
- "List all your Rymi AI voice agents.",
80
- {
81
- limit: import_zod.z.number().int().min(1).max(500).optional().describe("Max agents to return (default 50)"),
82
- offset: import_zod.z.number().int().min(0).optional().describe("Pagination offset")
83
- },
84
- async ({ limit, offset }) => {
85
- const result = await rymi.agents.list({ limit, offset });
86
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
87
- }
88
- );
89
- server.tool(
90
- "get_agent",
91
- "Retrieve a single Rymi voice agent by ID.",
92
- { agent_id: import_zod.z.string().uuid().describe("The agent UUID") },
93
- async ({ agent_id }) => {
94
- const result = await rymi.agents.retrieve(agent_id);
95
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
284
+ var init_safeUrl = __esm({
285
+ "../shared-types/dist/safeUrl.js"() {
286
+ "use strict";
287
+ }
288
+ });
289
+
290
+ // ../ops-tools/dist/index.js
291
+ var require_dist = __commonJS({
292
+ "../ops-tools/dist/index.js"(exports2, module2) {
293
+ "use strict";
294
+ var __defProp2 = Object.defineProperty;
295
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
296
+ var __getOwnPropNames2 = Object.getOwnPropertyNames;
297
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
298
+ var __export2 = (target, all) => {
299
+ for (var name in all)
300
+ __defProp2(target, name, { get: all[name], enumerable: true });
301
+ };
302
+ var __copyProps2 = (to, from, except, desc) => {
303
+ if (from && typeof from === "object" || typeof from === "function") {
304
+ for (let key of __getOwnPropNames2(from))
305
+ if (!__hasOwnProp2.call(to, key) && key !== except)
306
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
307
+ }
308
+ return to;
309
+ };
310
+ var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
311
+ var index_exports = {};
312
+ __export2(index_exports, {
313
+ defineTool: () => defineTool,
314
+ opsToolCatalog: () => opsToolCatalog2,
315
+ resolveToolDisposition: () => resolveToolDisposition
316
+ });
317
+ module2.exports = __toCommonJS2(index_exports);
318
+ function defineTool(def) {
319
+ return def;
96
320
  }
97
- );
98
- server.tool(
99
- "list_llm_options",
100
- "Fetch all available LLM models and voices you can use when creating or updating an agent. Always call this before create_agent to pick valid values.",
101
- {},
102
- async () => {
103
- const result = await rymi.agents.llmOptions();
104
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
321
+ var PRIVILEGED = /* @__PURE__ */ new Set(["owner", "admin"]);
322
+ function resolveToolDisposition(p) {
323
+ if (p.risk === "read") return "execute";
324
+ if (p.scope === "account" && !PRIVILEGED.has(p.role)) return "deny";
325
+ return p.risk === "write" ? "propose" : "confirm";
105
326
  }
106
- );
107
- server.tool(
108
- "list_voices",
109
- "List available agent/TTS voices, optionally filtered by provider or model. Much smaller payload than list_llm_options \u2014 use this to pick a `voice` value for create_agent/update_agent.",
110
- {
111
- provider: import_zod.z.string().optional().describe('Filter to one voice provider, e.g. "elevenlabs", "sarvam", "gemini", "cartesia", "deepgram".'),
112
- model_id: import_zod.z.string().optional().describe("Filter to voices compatible with a given model id (matched against the voice's supported_model_ids).")
113
- },
114
- async ({ provider, model_id }) => {
115
- const { voices } = await rymi.agents.llmOptions();
116
- let filtered = Array.isArray(voices) ? voices : [];
117
- if (provider) {
118
- const p = provider.toLowerCase();
119
- filtered = filtered.filter((v) => String(v?.provider || "").toLowerCase() === p);
327
+ var import_zod = require("zod");
328
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
329
+ var norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
330
+ async function resolveAgentId(client, agentId) {
331
+ const raw = String(agentId ?? "").trim();
332
+ if (UUID_RE.test(raw)) return raw;
333
+ const { agents } = await client.agents.list({ limit: 500 });
334
+ const target = norm(raw);
335
+ const matches = agents.filter((a) => norm(a.name ?? "") === target);
336
+ if (matches.length === 1) return matches[0].id;
337
+ if (matches.length > 1) {
338
+ throw new Error(`"${raw}" matches ${matches.length} agents \u2014 call list_agents and pass the exact agent UUID.`);
120
339
  }
121
- if (model_id) {
122
- filtered = filtered.filter((v) => Array.isArray(v?.supported_model_ids) && v.supported_model_ids.includes(model_id));
123
- }
124
- const trimmed = filtered.map((v) => ({
125
- id: v?.id,
126
- provider: v?.provider,
127
- name: v?.name,
128
- label: v?.label,
129
- gender: v?.gender,
130
- supported_model_ids: v?.supported_model_ids,
131
- byok_required: v?.byok_required,
132
- preview_url: v?.preview_url
133
- }));
134
- return { content: [{ type: "text", text: JSON.stringify({ voices: trimmed, total: trimmed.length }, null, 2) }] };
340
+ throw new Error(`No agent named "${raw}" found \u2014 call list_agents to get a valid agent UUID.`);
135
341
  }
136
- );
137
- server.tool(
138
- "preview_stack",
139
- "Preview the resolved per-language model stack (STT/LLM/TTS), blockers, warnings, model diffs, and any required role upgrades for a set of supported languages \u2014 without saving. Use before create/update to confirm a multi-language setup is valid. Note: the concierge (realtime) role is not supported here.",
140
- {
141
- supported_languages: import_zod.z.array(import_zod.z.string()).min(1).describe('BCP-47 languages to resolve a stack for, e.g. ["hi-IN","en-US"].'),
142
- agent_role: import_zod.z.enum(["operator", "specialist", "executive"]).optional().describe("Role to resolve the stack for (default operator)."),
143
- language: import_zod.z.string().optional().describe("Primary BCP-47 language (defaults to the first supported language)."),
144
- current_provider_config: import_zod.z.record(import_zod.z.any()).optional().describe("Existing provider_config to diff against, if any.")
145
- },
146
- async (params) => {
147
- const result = await rymi.agents.previewStack(params);
148
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
342
+ var agentConfigFields = {
343
+ system_prompt: import_zod.z.string().optional().describe("Full system prompt for the agent. If given without persona/playbook, the server auto-structures it."),
344
+ voice: import_zod.z.string().optional().describe('Voice ID (e.g. "Aoede", "Charon"). Call list_llm_options for valid values. Leave empty for realtime LLMs and Deepgram TTS, which carry their own voice.'),
345
+ language: import_zod.z.string().optional().describe('Primary BCP-47 language tag (e.g. "hi-IN", "en-US").'),
346
+ supported_languages: import_zod.z.array(import_zod.z.string()).optional().describe('All BCP-47 languages the agent should handle, e.g. ["hi-IN","en-US"] for a bilingual agent. The primary `language` is added automatically if omitted here.'),
347
+ llm_provider: import_zod.z.enum(["gemini", "openai", "anthropic", "sarvam"]).optional(),
348
+ llm_model: import_zod.z.string().optional().describe('LLM model id from list_llm_options (e.g. "gemini-2.5-flash", "sarvam-105b").'),
349
+ llm_fallback_provider: import_zod.z.string().nullable().optional().describe("Fallback LLM provider; null clears it."),
350
+ llm_fallback_model: import_zod.z.string().nullable().optional().describe("Fallback LLM model; null clears it."),
351
+ stt_provider: import_zod.z.string().optional().describe('Speech-to-text provider (e.g. "deepgram", "sarvam").'),
352
+ stt_model: import_zod.z.string().optional(),
353
+ stt_fallback_provider: import_zod.z.string().nullable().optional(),
354
+ stt_fallback_model: import_zod.z.string().nullable().optional(),
355
+ tts_provider: import_zod.z.string().optional().describe('Text-to-speech provider (e.g. "elevenlabs", "sarvam", "cartesia").'),
356
+ tts_model: import_zod.z.string().optional(),
357
+ tts_fallback_provider: import_zod.z.string().nullable().optional(),
358
+ tts_fallback_model: import_zod.z.string().nullable().optional(),
359
+ custom_llm_url: import_zod.z.string().nullable().optional().describe("Self-hosted LLM endpoint (https:// or wss://). Enterprise only; null clears."),
360
+ custom_voice_url: import_zod.z.string().nullable().optional().describe("Self-hosted TTS endpoint (https:// or wss://). Enterprise only; null clears."),
361
+ custom_voice_mode: import_zod.z.enum(["rymi", "openai-compat"]).optional().describe("Wire format for custom_voice_url."),
362
+ custom_transcriber_url: import_zod.z.string().nullable().optional().describe("Self-hosted STT endpoint (https:// or wss://). Enterprise only; null clears."),
363
+ prompt_mode: import_zod.z.enum(["builder", "raw"]).optional().describe('How the system prompt is interpreted: "builder" (server structures persona/playbook from system_prompt) or "raw" (use system_prompt verbatim \u2014 requires system_prompt). Defaults to "builder".'),
364
+ persona: import_zod.z.record(import_zod.z.any()).optional().describe("Persona object. persona.name is the name the agent speaks and introduces itself as (also shown on share/tester pages); it defaults to the label when omitted. Note: callerPersonas must be objects of shape {type, approach, detectedWhen}, NOT plain strings."),
365
+ playbook: import_zod.z.record(import_zod.z.any()).optional().describe("Playbook configuration object"),
366
+ advanced: import_zod.z.record(import_zod.z.any()).optional(),
367
+ features: import_zod.z.record(import_zod.z.any()).optional(),
368
+ post_call: import_zod.z.record(import_zod.z.any()).optional()
369
+ };
370
+ function foldPromptMode(params) {
371
+ const { prompt_mode, ...rest } = params;
372
+ if (!prompt_mode) return rest;
373
+ return { ...rest, advanced: { ...rest.advanced || {}, prompt_mode } };
149
374
  }
150
- );
151
- if (!isReadOnly) {
152
- server.tool(
153
- "create_agent",
154
- 'Create a new Rymi AI voice agent. Call list_llm_options first to discover valid voice and model values. For a multi-language agent, set supported_languages (e.g. ["hi-IN","en-US"]).',
155
- {
156
- name: import_zod.z.string().min(1).describe("Agent display name"),
157
- ...agentConfigFields
158
- },
159
- async (params) => {
160
- try {
161
- const result = await rymi.agents.create(foldPromptMode(params));
162
- return ok(result);
163
- } catch (err) {
164
- return fail(err);
375
+ var LLM_PROVIDER_ALIASES = {
376
+ google: "gemini",
377
+ googleai: "gemini",
378
+ "google-genai": "gemini",
379
+ "google-ai": "gemini",
380
+ vertex: "gemini",
381
+ vertexai: "gemini",
382
+ "vertex-ai": "gemini",
383
+ claude: "anthropic",
384
+ gpt: "openai",
385
+ "azure-openai": "openai",
386
+ azure: "openai"
387
+ };
388
+ function normalizeLlmProvider(params) {
389
+ const p = params.llm_provider;
390
+ if (!p) return params;
391
+ const alias = LLM_PROVIDER_ALIASES[p.toLowerCase()];
392
+ return alias ? { ...params, llm_provider: alias } : params;
393
+ }
394
+ var agentTools = [
395
+ defineTool({
396
+ name: "list_agents",
397
+ description: "List all your Rymi AI voice agents.",
398
+ scope: "agent",
399
+ risk: "read",
400
+ input: {
401
+ limit: import_zod.z.number().int().min(1).max(500).optional().describe("Max agents to return (default 50)"),
402
+ offset: import_zod.z.number().int().min(0).optional().describe("Pagination offset")
403
+ },
404
+ run: (client, { limit, offset }) => client.agents.list({ limit, offset })
405
+ }),
406
+ defineTool({
407
+ name: "get_agent",
408
+ description: "Retrieve a single Rymi voice agent by ID.",
409
+ scope: "agent",
410
+ risk: "read",
411
+ input: { agent_id: import_zod.z.string().uuid().describe("The agent UUID") },
412
+ run: (client, { agent_id }) => client.agents.retrieve(agent_id)
413
+ }),
414
+ defineTool({
415
+ name: "list_llm_options",
416
+ description: "Fetch all available LLM models and voices you can use when creating or updating an agent. Always call this before create_agent to pick valid values.",
417
+ scope: "agent",
418
+ risk: "read",
419
+ input: {},
420
+ run: (client) => client.agents.llmOptions()
421
+ }),
422
+ defineTool({
423
+ name: "list_voices",
424
+ description: "List available agent/TTS voices, optionally filtered by provider or model. Much smaller payload than list_llm_options \u2014 use this to pick a `voice` value for create_agent/update_agent.",
425
+ scope: "agent",
426
+ risk: "read",
427
+ input: {
428
+ provider: import_zod.z.string().optional().describe('Filter to one voice provider, e.g. "elevenlabs", "sarvam", "gemini", "cartesia", "deepgram".'),
429
+ model_id: import_zod.z.string().optional().describe("Filter to voices compatible with a given model id (matched against the voice's supported_model_ids).")
430
+ },
431
+ run: async (client, { provider, model_id }) => {
432
+ const { voices } = await client.agents.llmOptions();
433
+ let filtered = Array.isArray(voices) ? voices : [];
434
+ if (provider) {
435
+ const p = provider.toLowerCase();
436
+ filtered = filtered.filter((v) => String(v?.provider || "").toLowerCase() === p);
437
+ }
438
+ if (model_id) {
439
+ filtered = filtered.filter((v) => Array.isArray(v?.supported_model_ids) && v.supported_model_ids.includes(model_id));
440
+ }
441
+ const trimmed = filtered.map((v) => ({
442
+ id: v?.id,
443
+ provider: v?.provider,
444
+ name: v?.name,
445
+ label: v?.label,
446
+ gender: v?.gender,
447
+ supported_model_ids: v?.supported_model_ids,
448
+ byok_required: v?.byok_required,
449
+ preview_url: v?.preview_url
450
+ }));
451
+ return { voices: trimmed, total: trimmed.length };
165
452
  }
166
- }
167
- );
168
- server.tool(
169
- "update_agent",
170
- "Update an existing Rymi voice agent's configuration. Only the fields you pass are changed; nullable fields accept null to clear them.",
171
- {
172
- agent_id: import_zod.z.string().uuid().describe("The agent UUID to update"),
173
- name: import_zod.z.string().optional(),
174
- ...agentConfigFields,
175
- chat_summary: import_zod.z.string().nullable().optional()
176
- },
177
- async ({ agent_id, ...params }) => {
178
- try {
179
- const result = await rymi.agents.update(agent_id, foldPromptMode(params));
180
- return ok(result);
181
- } catch (err) {
182
- return fail(err);
453
+ }),
454
+ defineTool({
455
+ name: "preview_stack",
456
+ description: "Preview the resolved per-language model stack (STT/LLM/TTS), blockers, warnings, and model diffs for a set of supported languages \u2014 without saving. Use before create/update to confirm a multi-language setup is valid.",
457
+ scope: "agent",
458
+ risk: "read",
459
+ input: {
460
+ supported_languages: import_zod.z.array(import_zod.z.string()).min(1).describe('BCP-47 languages to resolve a stack for, e.g. ["hi-IN","en-US"].'),
461
+ language: import_zod.z.string().optional().describe("Primary BCP-47 language (defaults to the first supported language)."),
462
+ current_provider_config: import_zod.z.record(import_zod.z.any()).optional().describe("Existing provider_config to diff against, if any.")
463
+ },
464
+ run: (client, params) => client.agents.previewStack(params)
465
+ }),
466
+ defineTool({
467
+ name: "list_calls_for_agent",
468
+ description: "List calls made with a specific agent.",
469
+ scope: "agent",
470
+ risk: "read",
471
+ input: {
472
+ agent_id: import_zod.z.string().uuid(),
473
+ limit: import_zod.z.number().int().min(1).max(200).optional(),
474
+ offset: import_zod.z.number().int().min(0).optional(),
475
+ status: import_zod.z.string().optional().describe("Filter by call status")
476
+ },
477
+ run: (client, { agent_id, ...params }) => client.agents.listCalls(agent_id, params)
478
+ }),
479
+ defineTool({
480
+ name: "validate_agent_publish",
481
+ description: "Check whether an agent is ready to go live. Returns a validation report without persisting any changes.",
482
+ scope: "agent",
483
+ risk: "read",
484
+ input: {
485
+ agent_id: import_zod.z.string().uuid().optional().describe("Merge validation with a persisted agent"),
486
+ name: import_zod.z.string().optional(),
487
+ voice: import_zod.z.string().optional(),
488
+ persona: import_zod.z.record(import_zod.z.any()).optional(),
489
+ playbook: import_zod.z.record(import_zod.z.any()).optional()
490
+ },
491
+ run: (client, params) => client.agents.validatePublish(params)
492
+ }),
493
+ defineTool({
494
+ name: "apply_agent_changes",
495
+ description: "Validate and resolve a flat key/value change-set against the AgentConfig field registry. Does NOT persist \u2014 follow up with update_agent.",
496
+ scope: "agent",
497
+ risk: "read",
498
+ input: {
499
+ agent_id: import_zod.z.string().uuid().describe("Agent to load currentConfig from (recommended)"),
500
+ changes: import_zod.z.array(import_zod.z.object({ key: import_zod.z.string(), value: import_zod.z.any() })).describe("Array of {key, value} field changes"),
501
+ mode: import_zod.z.enum(["create", "edit"]).describe("create = new agent, edit = updating existing"),
502
+ lenient: import_zod.z.boolean().optional().describe("Skip unknown-field hard-fail (not recommended)")
503
+ },
504
+ run: async (client, { agent_id, changes, mode, lenient }) => {
505
+ const current = await client.agents.retrieve(agent_id);
506
+ const normalizedChanges = changes.map(({ key, value }) => ({ key, value }));
507
+ return client.agents.applyChanges({
508
+ currentConfig: current,
509
+ changes: normalizedChanges,
510
+ mode,
511
+ lenient
512
+ });
183
513
  }
514
+ }),
515
+ defineTool({
516
+ name: "enrich_company",
517
+ description: "Use AI + Google Search grounding to auto-generate a company description from a website URL, suitable for an agent's persona.",
518
+ scope: "agent",
519
+ risk: "read",
520
+ input: {
521
+ company_name: import_zod.z.string().min(1),
522
+ website_url: import_zod.z.string().url()
523
+ },
524
+ run: (client, { company_name, website_url }) => client.agents.enrichCompany({ companyName: company_name, websiteUrl: website_url })
525
+ }),
526
+ defineTool({
527
+ name: "generate_agent_draft",
528
+ description: "Use AI to generate an agent configuration draft from a plain-text description.",
529
+ scope: "agent",
530
+ risk: "read",
531
+ input: {
532
+ prompt: import_zod.z.string().min(1).describe("Description of the agent you want to create"),
533
+ mode: import_zod.z.enum(["create", "edit"]).optional().default("create"),
534
+ current_config: import_zod.z.record(import_zod.z.any()).optional().describe("Existing config when editing"),
535
+ options: import_zod.z.record(import_zod.z.any()).optional().describe("Override hints e.g. {voice, llm_provider}")
536
+ },
537
+ run: (client, params) => client.agents.generate({ prompt: params.prompt, options: params.options })
538
+ }),
539
+ defineTool({
540
+ name: "create_agent",
541
+ description: 'Create a new Rymi AI voice agent. Call list_llm_options first to discover valid voice and model values. For a multi-language agent, set supported_languages (e.g. ["hi-IN","en-US"]).',
542
+ scope: "agent",
543
+ risk: "write",
544
+ input: {
545
+ name: import_zod.z.string().min(1).describe("Studio label: the name shown in the sidebar/agent list only. The name the agent speaks is persona.name."),
546
+ ...agentConfigFields
547
+ },
548
+ run: (client, params) => client.agents.create(normalizeLlmProvider(foldPromptMode(params)))
549
+ }),
550
+ defineTool({
551
+ name: "update_agent",
552
+ description: "Update an existing Rymi voice agent's configuration. Only the fields you pass are changed; nullable fields accept null to clear them.",
553
+ scope: "agent",
554
+ risk: "write",
555
+ input: {
556
+ agent_id: import_zod.z.string().uuid().describe("The agent UUID to update"),
557
+ name: import_zod.z.string().optional().describe("Studio label (sidebar/agent list only). To change the spoken name, set persona.name."),
558
+ ...agentConfigFields,
559
+ chat_summary: import_zod.z.string().nullable().optional()
560
+ },
561
+ run: async (client, { agent_id, ...params }) => client.agents.update(await resolveAgentId(client, agent_id), normalizeLlmProvider(foldPromptMode(params)))
562
+ }),
563
+ defineTool({
564
+ name: "delete_agent",
565
+ description: "Permanently delete a Rymi voice agent.",
566
+ scope: "agent",
567
+ risk: "sensitive",
568
+ input: { agent_id: import_zod.z.string().uuid().describe("The agent UUID to delete") },
569
+ run: async (client, { agent_id }) => client.agents.delete(await resolveAgentId(client, agent_id))
570
+ }),
571
+ defineTool({
572
+ name: "clone_agent",
573
+ description: 'Duplicate an existing agent. The copy gets " (Copy)" appended to its name.',
574
+ scope: "agent",
575
+ risk: "write",
576
+ input: { agent_id: import_zod.z.string().uuid().describe("The agent UUID to clone") },
577
+ run: async (client, { agent_id }) => client.agents.clone(await resolveAgentId(client, agent_id))
578
+ })
579
+ ];
580
+ var import_zod2 = require("zod");
581
+ var callTools = [
582
+ defineTool({
583
+ name: "list_calls",
584
+ description: "List previous and active calls across your account.",
585
+ scope: "agent",
586
+ risk: "read",
587
+ input: {
588
+ limit: import_zod2.z.number().int().min(1).max(200).optional().describe("Max calls to return"),
589
+ offset: import_zod2.z.number().int().min(0).optional().describe("Pagination offset"),
590
+ cursor: import_zod2.z.string().optional().describe("Opaque cursor for keyset pagination"),
591
+ status: import_zod2.z.string().optional().describe("Filter by call status (e.g. completed, in_progress, failed)")
592
+ },
593
+ run: (client, params) => client.calls.list(params)
594
+ }),
595
+ defineTool({
596
+ name: "list_active_calls",
597
+ description: "List calls currently in progress.",
598
+ scope: "agent",
599
+ risk: "read",
600
+ input: {
601
+ limit: import_zod2.z.number().int().min(1).max(200).optional(),
602
+ offset: import_zod2.z.number().int().min(0).optional()
603
+ },
604
+ run: (client, params) => client.calls.active(params)
605
+ }),
606
+ defineTool({
607
+ name: "get_call",
608
+ description: "Retrieve details, participants, status, duration, and cost for a single call.",
609
+ scope: "agent",
610
+ risk: "read",
611
+ input: { call_id: import_zod2.z.string().describe("The call ID") },
612
+ run: (client, { call_id }) => client.calls.retrieve(call_id)
613
+ }),
614
+ defineTool({
615
+ name: "get_call_summary",
616
+ description: "Retrieve the post-call summary for a call.",
617
+ scope: "agent",
618
+ risk: "read",
619
+ input: { call_id: import_zod2.z.string().describe("The call ID") },
620
+ run: (client, { call_id }) => client.calls.summary(call_id)
621
+ }),
622
+ defineTool({
623
+ name: "get_call_transcript",
624
+ description: "Retrieve the full transcript for a call. May contain personal data \u2014 handle accordingly.",
625
+ scope: "agent",
626
+ risk: "read",
627
+ input: { call_id: import_zod2.z.string().describe("The call ID") },
628
+ run: (client, { call_id }) => client.calls.transcript(call_id)
629
+ }),
630
+ defineTool({
631
+ name: "get_call_recording",
632
+ description: "Retrieve recording metadata (e.g. playback URL) for a call, when recording is enabled.",
633
+ scope: "agent",
634
+ risk: "read",
635
+ input: { call_id: import_zod2.z.string().describe("The call ID") },
636
+ run: (client, { call_id }) => client.calls.recording(call_id)
637
+ }),
638
+ defineTool({
639
+ name: "get_call_queue_stats",
640
+ description: "Retrieve current outbound call queue statistics.",
641
+ scope: "agent",
642
+ risk: "read",
643
+ input: {},
644
+ run: (client) => client.calls.queueStats()
645
+ }),
646
+ defineTool({
647
+ name: "reprocess_call",
648
+ description: "Re-run post-call intelligence (summary, extraction, evaluation) for a call.",
649
+ scope: "agent",
650
+ risk: "write",
651
+ input: { call_id: import_zod2.z.string().describe("The call ID to reprocess") },
652
+ run: (client, { call_id }) => client.calls.reprocess(call_id)
653
+ }),
654
+ defineTool({
655
+ name: "create_call",
656
+ description: "WARNING: places a real, billable PSTN call. `from_number` is the caller ID shown to the recipient and must be a phone number registered to your account \u2014 omit it to use the agent's attached number. `identity` is the destination in strict E.164 (e.g. +15555550123).",
657
+ scope: "agent",
658
+ risk: "sensitive",
659
+ input: {
660
+ agent_id: import_zod2.z.string().uuid().describe("The agent that will handle the call"),
661
+ participants: import_zod2.z.array(import_zod2.z.object({
662
+ transport: import_zod2.z.enum(["webrtc", "pstn"]),
663
+ identity: import_zod2.z.string().describe("Destination phone number (pstn) or participant identity (webrtc)"),
664
+ from_number: import_zod2.z.string().optional().describe("Caller ID / from number for pstn"),
665
+ metadata: import_zod2.z.record(import_zod2.z.any()).optional()
666
+ })).min(1),
667
+ metadata: import_zod2.z.record(import_zod2.z.any()).optional(),
668
+ variables: import_zod2.z.record(import_zod2.z.any()).optional().describe("Playbook variables to seed the call"),
669
+ post_call: import_zod2.z.record(import_zod2.z.any()).optional()
670
+ },
671
+ run: (client, params) => client.calls.create(params)
672
+ }),
673
+ defineTool({
674
+ name: "batch_call",
675
+ description: "Queue up to 500 outbound PSTN recipients in one request. WARNING: places real phone calls and incurs per-minute charges. `from_number` is the caller ID shown to the recipient and must be a phone number registered to your account.",
676
+ scope: "agent",
677
+ risk: "sensitive",
678
+ input: {
679
+ agent_id: import_zod2.z.string().uuid().describe("The agent that will handle the calls"),
680
+ to: import_zod2.z.array(import_zod2.z.string()).optional().describe("Simple list of destination phone numbers"),
681
+ recipients: import_zod2.z.array(import_zod2.z.object({
682
+ to: import_zod2.z.string().optional(),
683
+ from_number: import_zod2.z.string().optional(),
684
+ metadata: import_zod2.z.record(import_zod2.z.any()).optional()
685
+ })).optional().describe("Per-recipient targets with optional from_number/metadata"),
686
+ from_number: import_zod2.z.string().optional().describe("Default caller ID / from number"),
687
+ batch_id: import_zod2.z.string().optional(),
688
+ metadata: import_zod2.z.record(import_zod2.z.any()).optional(),
689
+ variables: import_zod2.z.record(import_zod2.z.any()).optional(),
690
+ post_call: import_zod2.z.record(import_zod2.z.any()).optional()
691
+ },
692
+ run: (client, params) => client.calls.batch(params)
693
+ }),
694
+ defineTool({
695
+ name: "end_call",
696
+ description: "WARNING: immediately terminates an in-progress call. The call transitions to completed and post-call processing runs.",
697
+ scope: "agent",
698
+ risk: "sensitive",
699
+ input: { call_id: import_zod2.z.string().describe("The call ID to end") },
700
+ run: (client, { call_id }) => client.calls.end(call_id)
701
+ }),
702
+ defineTool({
703
+ name: "add_call_participant",
704
+ description: "WARNING: dials/adds participants to a live call (warm transfer / conference). PSTN additions are billable.",
705
+ scope: "agent",
706
+ risk: "sensitive",
707
+ input: {
708
+ call_id: import_zod2.z.string().describe("The in-progress call ID"),
709
+ participants: import_zod2.z.array(import_zod2.z.object({
710
+ transport: import_zod2.z.enum(["webrtc", "pstn"]),
711
+ identity: import_zod2.z.string().describe("Destination phone number (pstn, E.164) or participant identity (webrtc)"),
712
+ from_number: import_zod2.z.string().optional().describe("Caller ID for pstn"),
713
+ metadata: import_zod2.z.record(import_zod2.z.any()).optional()
714
+ })).min(1)
715
+ },
716
+ run: (client, { call_id, participants }) => client.calls.addParticipants(call_id, { participants })
717
+ })
718
+ ];
719
+ var import_zod3 = require("zod");
720
+ var numberTools = [
721
+ defineTool({
722
+ name: "list_numbers",
723
+ description: "List all phone numbers on your Rymi account and which agent each is attached to.",
724
+ scope: "account",
725
+ risk: "read",
726
+ input: {
727
+ limit: import_zod3.z.number().int().min(1).max(500).optional(),
728
+ offset: import_zod3.z.number().int().min(0).optional()
729
+ },
730
+ run: (client, params) => client.numbers.list(params)
731
+ }),
732
+ defineTool({
733
+ name: "register_number",
734
+ description: "Register a phone number on your account, optionally attaching it to an agent.",
735
+ scope: "account",
736
+ risk: "write",
737
+ input: {
738
+ number: import_zod3.z.string().describe("Phone number in E.164 format (e.g. +14155550123)"),
739
+ agent_id: import_zod3.z.string().uuid().optional().describe("Agent to attach the number to on registration")
740
+ },
741
+ run: (client, { number, agent_id }) => client.numbers.register(number, agent_id ? { agent_id } : {})
742
+ }),
743
+ defineTool({
744
+ name: "attach_number",
745
+ description: "Attach an existing number to an agent so inbound calls route to it.",
746
+ scope: "account",
747
+ risk: "write",
748
+ input: {
749
+ number: import_zod3.z.string().describe("Phone number in E.164 format"),
750
+ agent_id: import_zod3.z.string().uuid().describe("Agent to route this number to")
751
+ },
752
+ run: (client, { number, agent_id }) => client.numbers.attach(number, agent_id)
753
+ }),
754
+ defineTool({
755
+ name: "remove_number",
756
+ description: "Remove a phone number from your account. The number stops routing to any agent.",
757
+ scope: "account",
758
+ risk: "sensitive",
759
+ input: { number: import_zod3.z.string().describe("Phone number in E.164 format") },
760
+ run: (client, { number }) => client.numbers.remove(number)
761
+ })
762
+ ];
763
+ var telephonyTools = [
764
+ defineTool({
765
+ name: "telephony_status",
766
+ description: "Report whether a telephony carrier is connected, and which provider/account.",
767
+ scope: "account",
768
+ risk: "read",
769
+ input: {},
770
+ run: (client) => client.telephony.status()
771
+ }),
772
+ defineTool({
773
+ name: "list_telephony_numbers",
774
+ description: "List numbers available on the connected telephony carrier account.",
775
+ scope: "account",
776
+ risk: "read",
777
+ input: {},
778
+ run: (client) => client.telephony.numbers()
779
+ })
780
+ ];
781
+ var keyTools = [
782
+ defineTool({
783
+ name: "list_publishable_keys",
784
+ description: "List publishable (browser-safe) keys and which agent/channels each is scoped to. Returns key prefixes only, never full secrets.",
785
+ scope: "account",
786
+ risk: "read",
787
+ input: {},
788
+ run: (client) => client.keys.listPublishable()
789
+ })
790
+ ];
791
+ var import_zod4 = require("zod");
792
+ var knowledgeTools = [
793
+ defineTool({
794
+ name: "list_knowledge_sources",
795
+ description: "List the knowledge sources (RAG context) attached to an agent.",
796
+ scope: "agent",
797
+ risk: "read",
798
+ input: { agent_id: import_zod4.z.string().uuid().describe("The agent UUID") },
799
+ run: (client, { agent_id }) => client.agents.listKnowledgeSources(agent_id)
800
+ }),
801
+ defineTool({
802
+ name: "list_agent_changes",
803
+ description: "List recorded configuration changes for an agent (for auditing or before an undo).",
804
+ scope: "agent",
805
+ risk: "read",
806
+ input: {
807
+ agent_id: import_zod4.z.string().uuid().describe("The agent UUID"),
808
+ since: import_zod4.z.string().optional().describe("ISO timestamp \u2014 only return changes after this time")
809
+ },
810
+ run: (client, { agent_id, since }) => client.agents.listChanges(agent_id, since ? { since } : {})
811
+ }),
812
+ defineTool({
813
+ name: "add_knowledge_source",
814
+ description: "Add a knowledge source to an agent from raw text or a URL (the URL is fetched and ingested).",
815
+ scope: "agent",
816
+ risk: "write",
817
+ input: {
818
+ agent_id: import_zod4.z.string().uuid().describe("The agent UUID"),
819
+ kind: import_zod4.z.enum(["text", "url"]).describe("Source type"),
820
+ title: import_zod4.z.string().min(1).describe("Human-readable title for the source"),
821
+ text: import_zod4.z.string().optional().describe('Required when kind="text": the raw content to ingest'),
822
+ url: import_zod4.z.string().url().optional().describe('Required when kind="url": the page to fetch and ingest')
823
+ },
824
+ run: (client, { agent_id, kind, title, text, url }) => {
825
+ const data = kind === "text" ? { kind: "text", title, text: text ?? "" } : { kind: "url", title, url: url ?? "" };
826
+ return client.agents.addKnowledgeSource(agent_id, data);
827
+ }
828
+ }),
829
+ defineTool({
830
+ name: "delete_knowledge_source",
831
+ description: "Delete a knowledge source from an agent.",
832
+ scope: "agent",
833
+ risk: "sensitive",
834
+ input: {
835
+ agent_id: import_zod4.z.string().uuid().describe("The agent UUID"),
836
+ source_id: import_zod4.z.string().describe("The knowledge source ID to delete")
837
+ },
838
+ run: (client, { agent_id, source_id }) => client.agents.deleteKnowledgeSource(agent_id, source_id)
839
+ }),
840
+ defineTool({
841
+ name: "undo_agent_change",
842
+ description: "Undo a single recorded configuration change, reverting that field to its previous value.",
843
+ scope: "agent",
844
+ risk: "write",
845
+ input: {
846
+ agent_id: import_zod4.z.string().uuid().describe("The agent UUID"),
847
+ change_id: import_zod4.z.string().describe("The change_id to undo (from list_agent_changes)")
848
+ },
849
+ run: (client, { agent_id, change_id }) => client.agents.undoChange(agent_id, change_id)
850
+ })
851
+ ];
852
+ var import_zod5 = require("zod");
853
+ var insightTools = [
854
+ defineTool({
855
+ name: "get_usage_summary",
856
+ description: "Get this account's usage summary: remaining voice-runtime MINUTES, Studio AI unit usage, and post-call intelligence usage. Voice balance is reported in minutes, not dollars.",
857
+ scope: "account",
858
+ risk: "read",
859
+ input: {},
860
+ run: (client) => client.billing.usageSummary()
861
+ }),
862
+ defineTool({
863
+ name: "list_agent_templates",
864
+ description: "List published agent templates. Use a template's `defaults` as the starting config for create_agent.",
865
+ scope: "agent",
866
+ risk: "read",
867
+ input: {},
868
+ run: (client) => client.templates.list()
869
+ }),
870
+ defineTool({
871
+ name: "list_eval_runs",
872
+ description: "List previous evaluation runs for an agent.",
873
+ scope: "agent",
874
+ risk: "read",
875
+ input: { agent_id: import_zod5.z.string().uuid().describe("The agent UUID") },
876
+ run: (client, { agent_id }) => client.agents.listEvalRuns(agent_id)
877
+ }),
878
+ defineTool({
879
+ name: "get_eval_run",
880
+ description: "Retrieve a single evaluation run, including per-scenario scores.",
881
+ scope: "agent",
882
+ risk: "read",
883
+ input: {
884
+ agent_id: import_zod5.z.string().uuid().describe("The agent UUID"),
885
+ run_id: import_zod5.z.string().describe("The evaluation run ID")
886
+ },
887
+ run: (client, { agent_id, run_id }) => client.agents.getEvalRun(agent_id, run_id)
888
+ }),
889
+ defineTool({
890
+ name: "run_evals",
891
+ description: 'Run the evaluation suite for an agent. mode="synthetic" (default) uses the offline scorer; mode="live" runs the model-driven runner (consumes Studio AI units). Set judge=true to supplement the synthetic heuristics with an opt-in LLM judge (requires a Gemini key; consumes Studio AI units).',
892
+ scope: "agent",
893
+ risk: "write",
894
+ input: {
895
+ agent_id: import_zod5.z.string().uuid().describe("The agent UUID to evaluate"),
896
+ mode: import_zod5.z.enum(["synthetic", "live"]).optional().describe("Evaluation mode (default synthetic)"),
897
+ judge: import_zod5.z.boolean().optional().describe("Supplement synthetic heuristics with the LLM judge (default false)")
898
+ },
899
+ run: (client, { agent_id, mode, judge }) => {
900
+ const params = {};
901
+ if (mode) params.mode = mode;
902
+ if (judge) params.judge = judge;
903
+ return client.agents.runEvals(agent_id, params);
904
+ }
905
+ }),
906
+ defineTool({
907
+ name: "run_eval_suite",
908
+ description: "Run the eval SUITE across many agents at once (agents \xD7 seeded scenarios) with bounded concurrency. One eval run is persisted per agent (visible in the per-agent eval UI); the aggregate report is returned. Set judge=true to add the LLM judge (requires a Gemini key; consumes Studio AI units).",
909
+ scope: "agent",
910
+ risk: "write",
911
+ input: {
912
+ agent_ids: import_zod5.z.array(import_zod5.z.string().uuid()).min(1).describe("Agent UUIDs to evaluate"),
913
+ scenario_ids: import_zod5.z.array(import_zod5.z.string()).optional().describe("Optional subset of seeded scenario ids; omit to run all"),
914
+ concurrency: import_zod5.z.number().int().positive().optional().describe("Max agents evaluated in parallel (clamped to a safe ceiling)"),
915
+ judge: import_zod5.z.boolean().optional().describe("Supplement synthetic heuristics with the LLM judge (default false)")
916
+ },
917
+ run: (client, { agent_ids, scenario_ids, concurrency, judge }) => client.agents.runEvalSuite({
918
+ agentIds: agent_ids,
919
+ ...scenario_ids ? { scenarioIds: scenario_ids } : {},
920
+ ...concurrency ? { concurrency } : {},
921
+ ...judge ? { judge } : {}
922
+ })
923
+ })
924
+ ];
925
+ var import_zod6 = require("zod");
926
+ var publishTools = [
927
+ defineTool({
928
+ name: "publish_agent",
929
+ description: "Publish a Rymi voice agent to make its current saved configuration live. IMPORTANT: this immediately makes the agent callable by end users. Returns published:false with a blockers list when the config has unresolved issues.",
930
+ scope: "agent",
931
+ risk: "sensitive",
932
+ input: {
933
+ agent_id: import_zod6.z.string().uuid().describe("The agent UUID to publish")
934
+ },
935
+ // Returns the raw { published, blockers? } result. Unlike the old MCP
936
+ // handler, a published:false-with-blockers response is NOT flagged as a
937
+ // protocol error — blockers are valid data every consumer (MCP client,
938
+ // studio harness) reads from the result directly.
939
+ run: async (client, { agent_id }) => client.agents.publish(await resolveAgentId(client, agent_id))
940
+ })
941
+ ];
942
+ var import_zod7 = require("zod");
943
+ var dncTools = [
944
+ defineTool({
945
+ name: "list_dnc",
946
+ description: "List all numbers on your Do-Not-Call registry.",
947
+ scope: "account",
948
+ risk: "read",
949
+ input: {
950
+ limit: import_zod7.z.number().int().min(1).max(500).optional(),
951
+ offset: import_zod7.z.number().int().min(0).optional()
952
+ },
953
+ run: (client, params) => client.dnc.list(params)
954
+ }),
955
+ defineTool({
956
+ name: "check_dnc",
957
+ description: "Check whether phone numbers are on the Do-Not-Call registry. Read-only \u2014 adds nothing.",
958
+ scope: "account",
959
+ risk: "read",
960
+ input: { phone_numbers: import_zod7.z.array(import_zod7.z.string()).min(1).max(500).describe("E.164 numbers to check") },
961
+ run: (client, { phone_numbers }) => client.dnc.check({ phone_numbers })
962
+ }),
963
+ defineTool({
964
+ name: "add_dnc",
965
+ description: "Add a phone number to the Do-Not-Call registry so outbound calls to it are blocked.",
966
+ scope: "account",
967
+ risk: "write",
968
+ input: {
969
+ phone_number: import_zod7.z.string().describe("Phone number (any format; normalized to E.164)"),
970
+ reason: import_zod7.z.string().optional()
971
+ },
972
+ run: (client, { phone_number, reason }) => client.dnc.add({ phone_number, reason })
973
+ }),
974
+ defineTool({
975
+ name: "add_dnc_batch",
976
+ description: "Add up to 1000 numbers to the Do-Not-Call registry. Invalid numbers are skipped and returned in `invalid`.",
977
+ scope: "account",
978
+ risk: "write",
979
+ input: {
980
+ phone_numbers: import_zod7.z.array(import_zod7.z.string()).min(1).max(1e3),
981
+ reason: import_zod7.z.string().optional()
982
+ },
983
+ run: (client, { phone_numbers, reason }) => client.dnc.addBatch({ phone_numbers, reason })
984
+ }),
985
+ defineTool({
986
+ name: "remove_dnc",
987
+ description: "WARNING: removes a number from the Do-Not-Call registry, re-enabling outbound calls to it.",
988
+ scope: "account",
989
+ risk: "sensitive",
990
+ input: { phone: import_zod7.z.string().describe("Phone number to remove") },
991
+ run: (client, { phone }) => client.dnc.remove(phone)
992
+ })
993
+ ];
994
+ var import_zod8 = require("zod");
995
+ var import_crypto = require("crypto");
996
+ var webhookTools = [
997
+ defineTool({
998
+ name: "list_webhooks",
999
+ description: "List your registered webhook endpoints. Signing secrets are never returned.",
1000
+ scope: "account",
1001
+ risk: "read",
1002
+ input: {
1003
+ limit: import_zod8.z.number().int().min(1).max(500).optional(),
1004
+ offset: import_zod8.z.number().int().min(0).optional()
1005
+ },
1006
+ run: (client, params) => client.webhooks.list(params)
1007
+ }),
1008
+ defineTool({
1009
+ name: "create_webhook",
1010
+ description: "Register a webhook to receive call lifecycle events (e.g. call.completed, transcript.ready). URL must be public https. If you omit `secret`, one is generated and shown ONCE in the response \u2014 store it to verify signatures.",
1011
+ scope: "account",
1012
+ risk: "write",
1013
+ input: {
1014
+ url: import_zod8.z.string().url().describe("Public https endpoint"),
1015
+ events: import_zod8.z.array(import_zod8.z.string()).min(1).describe("Event names to subscribe to"),
1016
+ secret: import_zod8.z.string().min(16).max(256).optional().describe("Signing secret; auto-generated if omitted"),
1017
+ alert_email: import_zod8.z.string().email().optional()
1018
+ },
1019
+ run: async (client, { url, events, secret, alert_email }) => {
1020
+ const finalSecret = secret ?? (0, import_crypto.randomBytes)(24).toString("hex");
1021
+ const result = await client.webhooks.create({ url, events, secret: finalSecret, alert_email });
1022
+ return secret ? result : { ...result, secret: finalSecret, secret_notice: "Store this secret now \u2014 it cannot be retrieved later." };
1023
+ }
1024
+ }),
1025
+ defineTool({
1026
+ name: "update_webhook",
1027
+ description: "Update a webhook endpoint. Only provided fields change. Pass `secret` to rotate it.",
1028
+ scope: "account",
1029
+ risk: "write",
1030
+ input: {
1031
+ id: import_zod8.z.string(),
1032
+ url: import_zod8.z.string().url().optional(),
1033
+ events: import_zod8.z.array(import_zod8.z.string()).min(1).optional(),
1034
+ secret: import_zod8.z.string().min(16).max(256).optional(),
1035
+ alert_email: import_zod8.z.string().email().nullable().optional()
1036
+ },
1037
+ run: (client, { id, ...rest }) => client.webhooks.update(id, rest)
1038
+ }),
1039
+ defineTool({
1040
+ name: "delete_webhook",
1041
+ description: "WARNING: stops event delivery to this endpoint.",
1042
+ scope: "account",
1043
+ risk: "sensitive",
1044
+ input: { id: import_zod8.z.string() },
1045
+ run: (client, { id }) => client.webhooks.delete(id)
1046
+ })
1047
+ ];
1048
+ var import_zod9 = require("zod");
1049
+ var billingTools = [
1050
+ defineTool({
1051
+ name: "estimate_call_cost",
1052
+ description: "Estimate how much balance a call will consume for a custom model stack and duration. Results are usage estimates; surface remaining balance to customers in minutes.",
1053
+ scope: "account",
1054
+ risk: "read",
1055
+ input: {
1056
+ stt_model: import_zod9.z.string().optional(),
1057
+ llm_model: import_zod9.z.string().optional(),
1058
+ tts_model: import_zod9.z.string().optional(),
1059
+ duration_seconds: import_zod9.z.number().min(0).optional()
1060
+ },
1061
+ run: (client, params) => client.billing.estimate(params)
1062
+ }),
1063
+ defineTool({
1064
+ name: "set_auto_recharge",
1065
+ description: "Configure auto-recharge. `pack_usd` must exceed `threshold_usd` or the API rejects it (recharge-loop guard).",
1066
+ scope: "account",
1067
+ risk: "sensitive",
1068
+ input: {
1069
+ enabled: import_zod9.z.boolean().optional(),
1070
+ pack_usd: import_zod9.z.number().min(1).max(1e3).optional(),
1071
+ threshold_usd: import_zod9.z.number().min(0).max(100).optional()
1072
+ },
1073
+ run: (client, params) => client.billing.setAutoRecharge(params)
1074
+ }),
1075
+ defineTool({
1076
+ name: "set_spend_alerts",
1077
+ description: "Configure spend-alert thresholds and low-balance / email preferences.",
1078
+ scope: "account",
1079
+ risk: "write",
1080
+ input: {
1081
+ thresholds_usd: import_zod9.z.array(import_zod9.z.number()).max(10).optional(),
1082
+ low_balance_pct: import_zod9.z.number().min(0).max(100).optional(),
1083
+ email_enabled: import_zod9.z.boolean().optional()
1084
+ },
1085
+ run: (client, params) => client.billing.setAlerts(params)
1086
+ })
1087
+ ];
1088
+ var import_zod10 = require("zod");
1089
+ var campaignTools = [
1090
+ // ─── reads ───────────────────────────────────────────────────────────────
1091
+ defineTool({
1092
+ name: "list_campaigns",
1093
+ description: "List campaigns for the authenticated tenant, paginated.",
1094
+ scope: "agent",
1095
+ risk: "read",
1096
+ input: {
1097
+ limit: import_zod10.z.number().int().min(1).max(200).optional().describe("Max campaigns to return"),
1098
+ offset: import_zod10.z.number().int().min(0).optional().describe("Pagination offset"),
1099
+ status: import_zod10.z.enum(["draft", "scheduled", "running", "paused", "completed", "failed", "archived"]).optional().describe("Filter by campaign status"),
1100
+ type: import_zod10.z.enum(["outbound", "inbound"]).optional().describe("Filter by campaign type"),
1101
+ agent_id: import_zod10.z.string().uuid().optional().describe("Filter by agent")
1102
+ },
1103
+ run: (client, params) => client.campaigns.list(params)
1104
+ }),
1105
+ defineTool({
1106
+ name: "get_campaign",
1107
+ description: "Retrieve a single campaign by id, including its goal, policies, status, and stat counters.",
1108
+ scope: "agent",
1109
+ risk: "read",
1110
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID") },
1111
+ run: (client, { campaign_id }) => client.campaigns.get(campaign_id)
1112
+ }),
1113
+ defineTool({
1114
+ name: "get_campaign_report",
1115
+ description: "Retrieve the campaign report: stat_* summary counters plus outcome/sentiment/hourly/snapshot distributions. Cost is reported in credits, durations in minutes.",
1116
+ scope: "agent",
1117
+ risk: "read",
1118
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID") },
1119
+ run: (client, { campaign_id }) => client.campaigns.report(campaign_id)
1120
+ }),
1121
+ defineTool({
1122
+ name: "list_campaign_attempts",
1123
+ description: "List a campaign's dial attempts, paginated.",
1124
+ scope: "agent",
1125
+ risk: "read",
1126
+ input: {
1127
+ campaign_id: import_zod10.z.string().describe("The campaign ID"),
1128
+ limit: import_zod10.z.number().int().min(1).max(200).optional(),
1129
+ offset: import_zod10.z.number().int().min(0).optional(),
1130
+ status: import_zod10.z.string().optional().describe("Filter by attempt status (e.g. completed, failed, skipped)")
1131
+ },
1132
+ run: (client, { campaign_id, ...params }) => client.campaigns.attempts(campaign_id, params)
1133
+ }),
1134
+ defineTool({
1135
+ name: "list_campaign_suggestions",
1136
+ description: "List a campaign's improvement suggestions (from the detectors + LLM proposal engine), paginated.",
1137
+ scope: "agent",
1138
+ risk: "read",
1139
+ input: {
1140
+ campaign_id: import_zod10.z.string().describe("The campaign ID"),
1141
+ limit: import_zod10.z.number().int().min(1).max(200).optional(),
1142
+ offset: import_zod10.z.number().int().min(0).optional(),
1143
+ status: import_zod10.z.string().optional().describe("Filter by suggestion status (proposed, accepted, dismissed)")
1144
+ },
1145
+ run: (client, { campaign_id, ...params }) => client.campaigns.suggestions(campaign_id, params)
1146
+ }),
1147
+ defineTool({
1148
+ name: "list_contacts",
1149
+ description: "List tenant-level contacts, paginated.",
1150
+ scope: "agent",
1151
+ risk: "read",
1152
+ input: {
1153
+ limit: import_zod10.z.number().int().min(1).max(200).optional(),
1154
+ offset: import_zod10.z.number().int().min(0).optional()
1155
+ },
1156
+ run: (client, params) => client.contacts.list(params)
1157
+ }),
1158
+ // ─── writes ──────────────────────────────────────────────────────────────
1159
+ defineTool({
1160
+ name: "create_campaign",
1161
+ description: "Create a new campaign in draft status against a published agent. Does not launch it or place any calls.",
1162
+ scope: "agent",
1163
+ risk: "write",
1164
+ input: {
1165
+ agent_id: import_zod10.z.string().uuid().describe("The agent that will handle campaign calls"),
1166
+ type: import_zod10.z.enum(["outbound", "inbound"]).describe("Campaign type"),
1167
+ name: import_zod10.z.string().describe("Human-readable campaign name"),
1168
+ goal: import_zod10.z.object({
1169
+ goal_type: import_zod10.z.string(),
1170
+ success_field: import_zod10.z.string(),
1171
+ success_when: import_zod10.z.record(import_zod10.z.any()),
1172
+ secondary_fields: import_zod10.z.array(import_zod10.z.string()).optional(),
1173
+ human_label: import_zod10.z.string()
1174
+ }).optional().describe("The success goal this campaign is measured against"),
1175
+ schedule_policy: import_zod10.z.record(import_zod10.z.any()).optional(),
1176
+ retry_policy: import_zod10.z.record(import_zod10.z.any()).optional(),
1177
+ concurrency_policy: import_zod10.z.record(import_zod10.z.any()).optional(),
1178
+ automation_policy: import_zod10.z.record(import_zod10.z.any()).optional(),
1179
+ reporting_policy: import_zod10.z.record(import_zod10.z.any()).optional(),
1180
+ metadata: import_zod10.z.record(import_zod10.z.any()).optional()
1181
+ },
1182
+ run: (client, params) => client.campaigns.create(params)
1183
+ }),
1184
+ defineTool({
1185
+ name: "import_campaign_contacts",
1186
+ description: "Import inline contact rows (JSON or CSV) into a campaign: creates/merges contacts and attaches them as campaign members in one call.",
1187
+ scope: "agent",
1188
+ risk: "write",
1189
+ input: {
1190
+ campaign_id: import_zod10.z.string().describe("The campaign ID"),
1191
+ contacts: import_zod10.z.array(import_zod10.z.record(import_zod10.z.any())).optional().describe("Inline contact rows as JSON objects"),
1192
+ csv: import_zod10.z.string().optional().describe("Contact rows as CSV text")
1193
+ },
1194
+ run: (client, { campaign_id, ...data }) => client.campaigns.members.import(campaign_id, data)
1195
+ }),
1196
+ defineTool({
1197
+ name: "launch_campaign",
1198
+ description: "WARNING: launches a campaign, which places real outbound PSTN calls at scale and incurs per-minute charges for every recipient dialed. Validates launch blockers first (published snapshot, valid members, caller ID, DNC, credits, follow-up connectors); if blockers fail, no calls are placed.",
1199
+ scope: "agent",
1200
+ risk: "sensitive",
1201
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID to launch") },
1202
+ run: (client, { campaign_id }) => client.campaigns.launch(campaign_id)
1203
+ }),
1204
+ defineTool({
1205
+ name: "pause_campaign",
1206
+ description: "Pause a running campaign. In-flight calls finish naturally; no new attempts are scheduled until resumed.",
1207
+ scope: "agent",
1208
+ risk: "write",
1209
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID to pause") },
1210
+ run: (client, { campaign_id }) => client.campaigns.pause(campaign_id)
1211
+ }),
1212
+ defineTool({
1213
+ name: "resume_campaign",
1214
+ description: "Resume a paused campaign. WARNING: resuming re-enables outbound dialing and will incur further per-minute charges as attempts continue.",
1215
+ scope: "agent",
1216
+ risk: "sensitive",
1217
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID to resume") },
1218
+ run: (client, { campaign_id }) => client.campaigns.resume(campaign_id)
1219
+ }),
1220
+ defineTool({
1221
+ name: "accept_campaign_suggestion",
1222
+ description: "Accept an improvement suggestion. Agent-editing suggestions create an agent-changes draft for review; campaign-policy suggestions patch the campaign directly.",
1223
+ scope: "agent",
1224
+ risk: "write",
1225
+ input: {
1226
+ campaign_id: import_zod10.z.string().describe("The campaign ID"),
1227
+ suggestion_id: import_zod10.z.string().describe("The suggestion ID to accept")
1228
+ },
1229
+ run: (client, { campaign_id, suggestion_id }) => client.campaigns.acceptSuggestion(campaign_id, suggestion_id)
1230
+ })
1231
+ ];
1232
+ var import_zod11 = require("zod");
1233
+ var import_agentTools = (init_agentTools(), __toCommonJS(agentTools_exports));
1234
+ var import_safeUrl = (init_safeUrl(), __toCommonJS(safeUrl_exports));
1235
+ var DEFAULT_NAME = "call_webhook";
1236
+ var METHODS = ["GET", "POST", "PUT", "PATCH"];
1237
+ var FIELD_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
1238
+ var RESERVED_HEADERS = /* @__PURE__ */ new Set(["host", "content-length", "connection", "transfer-encoding"]);
1239
+ var LIVE_NOTE = "Saved to the live agent row: the voice runtime uses it from the next call. There is no separate draft; publish_agent is not required. Undo with list_agent_changes \u2192 undo_agent_change.";
1240
+ var fieldSchema = import_zod11.z.object({
1241
+ name: import_zod11.z.string().describe('Argument name the agent fills in: letters, digits, underscores; not a Python keyword or "params".'),
1242
+ type: import_zod11.z.enum(["string", "number", "integer", "boolean"]).optional().describe('Default "string".'),
1243
+ description: import_zod11.z.string().optional().describe("What the value is, for the agent."),
1244
+ required: import_zod11.z.boolean().optional(),
1245
+ enum: import_zod11.z.array(import_zod11.z.string()).optional().describe("Allowed values, if fixed.")
1246
+ });
1247
+ var settingsShape = {
1248
+ url: import_zod11.z.string().describe("Public https endpoint. Private, loopback and non-https hosts are refused."),
1249
+ method: import_zod11.z.enum(METHODS).optional().describe("Default POST. GET sends fields as query parameters."),
1250
+ purpose: import_zod11.z.string().optional().describe("When the agent should call this tool. Without it the agent rarely fires it."),
1251
+ fields: import_zod11.z.array(fieldSchema).optional().describe("Arguments the agent supplies. Replaces the whole list on update."),
1252
+ headers: import_zod11.z.record(import_zod11.z.string()).optional().describe("Request headers. Credentials must be a {{secrets.NAME}} reference (see set_tool_secret), never a literal key."),
1253
+ send_metadata: import_zod11.z.boolean().optional().describe("Include call metadata (call id, agent id, caller phone). Default true."),
1254
+ response_fields: import_zod11.z.array(import_zod11.z.string()).optional().describe("Dot paths kept from the JSON response; default keeps the (truncated) body."),
1255
+ timeout_ms: import_zod11.z.number().int().min(1e3).max(1e4).optional().describe("Per-request timeout, 1000\u201310000 ms. Default 6000."),
1256
+ enabled: import_zod11.z.boolean().optional().describe("Default true.")
1257
+ };
1258
+ function bindingName(b) {
1259
+ const n = b?.provider_settings?.name;
1260
+ return typeof n === "string" && n.trim() ? n.trim() : DEFAULT_NAME;
1261
+ }
1262
+ var isApiTool = (b) => b?.tool_id === "call_webhook";
1263
+ function webhookBindingErrors(b, opts) {
1264
+ const errors = [];
1265
+ const s = b.provider_settings ?? {};
1266
+ const name = bindingName(b);
1267
+ if (!import_agentTools.API_TOOL_NAME_PATTERN.test(name)) errors.push(`name "${name}": lowercase letters, digits and underscores, starting with a letter, 3\u201341 characters.`);
1268
+ if (opts.otherNames.includes(name)) errors.push(`name "${name}" is already used by another API tool on this agent.`);
1269
+ const url = typeof s.url === "string" ? s.url.trim() : "";
1270
+ if (!url) errors.push("url is required.");
1271
+ else if (!/^https:\/\//i.test(url)) errors.push("url must be https \u2014 the request carries caller details.");
1272
+ else if (!(0, import_safeUrl.isSafeUrl)(url, { protocols: ["https:"] })) errors.push("url must be a valid public host; private and loopback addresses are refused at call time.");
1273
+ if (!METHODS.includes(s.method)) errors.push(`method must be one of ${METHODS.join(", ")}.`);
1274
+ if (!Number.isInteger(b.timeout_ms) || b.timeout_ms < 1e3 || b.timeout_ms > 1e4) errors.push("timeout_ms must be an integer from 1000 to 10000.");
1275
+ const seen = /* @__PURE__ */ new Set();
1276
+ for (const f of Array.isArray(s.fields) ? s.fields : []) {
1277
+ const fname = typeof f?.name === "string" ? f.name.trim() : "";
1278
+ if (!FIELD_NAME.test(fname)) errors.push(`field "${fname}": letters, digits and underscores only, not starting with a digit.`);
1279
+ else if (import_agentTools.API_TOOL_RESERVED_FIELD_NAMES.has(fname)) errors.push(`field "${fname}" is reserved; use e.g. ${fname}_value.`);
1280
+ else if (seen.has(fname)) errors.push(`field "${fname}" is duplicated.`);
1281
+ seen.add(fname);
184
1282
  }
185
- );
186
- server.tool(
187
- "delete_agent",
188
- "Permanently delete a Rymi voice agent.",
189
- { agent_id: import_zod.z.string().uuid().describe("The agent UUID to delete") },
190
- async ({ agent_id }) => {
191
- const result = await rymi.agents.delete(agent_id);
192
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
193
- }
194
- );
195
- server.tool(
196
- "clone_agent",
197
- 'Duplicate an existing agent. The copy gets " (Copy)" appended to its name.',
198
- { agent_id: import_zod.z.string().uuid().describe("The agent UUID to clone") },
199
- async ({ agent_id }) => {
200
- const result = await rymi.agents.clone(agent_id);
201
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1283
+ const headers = s.headers ?? {};
1284
+ for (const key of opts.checkHeaders) {
1285
+ const value = headers[key];
1286
+ if (RESERVED_HEADERS.has(key.trim().toLowerCase())) errors.push(`header "${key}" is set by the runtime and cannot be overridden.`);
1287
+ else if (typeof value !== "string") errors.push(`header "${key}" must be a string.`);
1288
+ else if (import_agentTools.API_TOOL_AUTH_HEADER_PATTERN.test(key.trim()) && !import_agentTools.API_TOOL_SECRET_REF_PATTERN.test(value)) {
1289
+ errors.push(`header "${key}" holds a literal credential. Store it with set_tool_secret and use "{{secrets.NAME}}" instead.`);
1290
+ }
202
1291
  }
203
- );
204
- }
205
- server.tool(
206
- "list_calls_for_agent",
207
- "List calls made with a specific agent.",
208
- {
209
- agent_id: import_zod.z.string().uuid(),
210
- limit: import_zod.z.number().int().min(1).max(200).optional(),
211
- offset: import_zod.z.number().int().min(0).optional(),
212
- status: import_zod.z.string().optional().describe("Filter by call status")
213
- },
214
- async ({ agent_id, ...params }) => {
215
- const result = await rymi.agents.listCalls(agent_id, params);
216
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1292
+ return errors;
217
1293
  }
218
- );
219
- server.tool(
220
- "validate_agent_publish",
221
- "Check whether an agent is ready to go live. Returns a validation report without persisting any changes.",
222
- {
223
- agent_id: import_zod.z.string().uuid().optional().describe("Merge validation with a persisted agent"),
224
- name: import_zod.z.string().optional(),
225
- voice: import_zod.z.string().optional(),
226
- persona: import_zod.z.record(import_zod.z.any()).optional(),
227
- playbook: import_zod.z.record(import_zod.z.any()).optional()
228
- },
229
- async (params) => {
230
- const result = await rymi.agents.validatePublish(params);
231
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1294
+ function assertValid(b, tools, self, checkHeaders) {
1295
+ const otherNames = tools.filter((t) => isApiTool(t) && t !== self).map(bindingName);
1296
+ const errors = webhookBindingErrors(b, { otherNames, checkHeaders });
1297
+ if (errors.length) throw new Error(`Invalid API tool binding: ${errors.join(" ")}`);
232
1298
  }
233
- );
234
- server.tool(
235
- "apply_agent_changes",
236
- "Validate and resolve a flat key/value change-set against the AgentConfig field registry. Does NOT persist \u2014 follow up with update_agent.",
237
- {
238
- agent_id: import_zod.z.string().uuid().describe("Agent to load currentConfig from (recommended)"),
239
- changes: import_zod.z.array(import_zod.z.object({ key: import_zod.z.string(), value: import_zod.z.any() })).describe("Array of {key, value} field changes"),
240
- mode: import_zod.z.enum(["create", "edit"]).describe("create = new agent, edit = updating existing"),
241
- lenient: import_zod.z.boolean().optional().describe("Skip unknown-field hard-fail (not recommended)")
242
- },
243
- async ({ agent_id, changes, mode, lenient }) => {
244
- const current = await rymi.agents.retrieve(agent_id);
245
- const normalizedChanges = changes.map(({ key, value }) => ({ key, value }));
246
- const result = await rymi.agents.applyChanges({
247
- currentConfig: current,
248
- changes: normalizedChanges,
249
- mode,
250
- lenient
251
- });
252
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1299
+ async function writeTools(client, agentRef, mutate) {
1300
+ const agentId = await resolveAgentId(client, agentRef);
1301
+ const agent = await client.agents.retrieve(agentId);
1302
+ if (!Array.isArray(agent.tools)) throw new Error("The API did not return this agent's tool bindings, so a write could delete them. Refusing.");
1303
+ const tools = mutate(agent.tools);
1304
+ await client.agents.update(agentId, { tools });
1305
+ return { agentId, tools };
253
1306
  }
254
- );
255
- server.tool(
256
- "enrich_company",
257
- "Use AI + Google Search grounding to auto-generate a company description from a website URL, suitable for an agent's persona.",
258
- {
259
- company_name: import_zod.z.string().min(1),
260
- website_url: import_zod.z.string().url()
261
- },
262
- async ({ company_name, website_url }) => {
263
- const result = await rymi.agents.enrichCompany({ companyName: company_name, websiteUrl: website_url });
264
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1307
+ function findApiTool(tools, name) {
1308
+ const found = tools.find((t) => isApiTool(t) && bindingName(t) === name);
1309
+ if (!found) {
1310
+ const names2 = tools.filter(isApiTool).map(bindingName);
1311
+ throw new Error(`No API tool named "${name}" on this agent. Existing: ${names2.length ? names2.join(", ") : "none"}.`);
1312
+ }
1313
+ return found;
265
1314
  }
266
- );
267
- server.tool(
268
- "generate_agent_draft",
269
- "Use AI to generate an agent configuration draft from a plain-text description.",
270
- {
271
- prompt: import_zod.z.string().min(1).describe("Description of the agent you want to create"),
272
- mode: import_zod.z.enum(["create", "edit"]).optional().default("create"),
273
- current_config: import_zod.z.record(import_zod.z.any()).optional().describe("Existing config when editing"),
274
- options: import_zod.z.record(import_zod.z.any()).optional().describe("Override hints e.g. {voice, llm_provider}")
275
- },
276
- async (params) => {
277
- const result = await rymi.agents.generate({ prompt: params.prompt, options: params.options });
278
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1315
+ var addShape = { agent_id: import_zod11.z.string().describe("The agent UUID"), name: import_zod11.z.string().describe('LLM-facing tool name, e.g. "lookup_reservation". Unique per agent.'), ...settingsShape };
1316
+ var updateShape = {
1317
+ agent_id: import_zod11.z.string().describe("The agent UUID"),
1318
+ name: import_zod11.z.string().describe("Current name of the API tool to change."),
1319
+ new_name: import_zod11.z.string().optional().describe("Rename the tool."),
1320
+ ...settingsShape,
1321
+ url: settingsShape.url.optional(),
1322
+ headers: import_zod11.z.record(import_zod11.z.string().nullable()).optional().describe("Merged into the existing headers by name; null removes one. Headers you omit \u2014 including ones shown as [redacted] \u2014 are kept unchanged.")
1323
+ };
1324
+ var agentToolBindingTools = [
1325
+ defineTool({
1326
+ name: "list_agent_tools",
1327
+ description: "List an agent's tool bindings (API tools / call_webhook, handoff, calendar, \u2026). Header values are shown only as {{secrets.NAME}} references; literal values are [redacted].",
1328
+ scope: "agent",
1329
+ risk: "read",
1330
+ input: { agent_id: import_zod11.z.string().describe("The agent UUID") },
1331
+ run: async (client, { agent_id }) => {
1332
+ const agentId = await resolveAgentId(client, agent_id);
1333
+ const agent = await client.agents.retrieve(agentId);
1334
+ return { agent_id: agentId, tools: agent.tools ?? [] };
1335
+ }
1336
+ }),
1337
+ defineTool({
1338
+ name: "add_agent_tool",
1339
+ description: `Add an API tool (call_webhook) to an agent, keeping its existing tools. The agent can then call the endpoint mid-call with the fields you define. ${LIVE_NOTE}`,
1340
+ scope: "agent",
1341
+ risk: "write",
1342
+ input: addShape,
1343
+ run: async (client, params) => {
1344
+ const p = import_zod11.z.object(addShape).parse(params);
1345
+ const binding = {
1346
+ tool_id: "call_webhook",
1347
+ enabled: p.enabled ?? true,
1348
+ credential_ref: null,
1349
+ timeout_ms: p.timeout_ms ?? 6e3,
1350
+ side_effect: (p.method ?? "POST") === "GET" ? "read" : "write",
1351
+ provider_settings: {
1352
+ name: p.name.trim(),
1353
+ url: p.url.trim(),
1354
+ method: p.method ?? "POST",
1355
+ send_metadata: p.send_metadata ?? true,
1356
+ ...p.purpose ? { purpose: p.purpose } : {},
1357
+ ...p.fields?.length ? { fields: p.fields } : {},
1358
+ ...p.headers && Object.keys(p.headers).length ? { headers: p.headers } : {},
1359
+ ...p.response_fields?.length ? { response_fields: p.response_fields } : {}
1360
+ }
1361
+ };
1362
+ const { agentId } = await writeTools(client, p.agent_id, (tools) => {
1363
+ assertValid(binding, tools, null, Object.keys(p.headers ?? {}));
1364
+ return [...tools, binding];
1365
+ });
1366
+ return { status: "added", agent_id: agentId, tool: binding, note: LIVE_NOTE };
1367
+ }
1368
+ }),
1369
+ defineTool({
1370
+ name: "update_agent_tool",
1371
+ description: `Change one API tool (call_webhook) on an agent by name. Only the settings you pass change; fields replaces the whole list, headers merge by name. ${LIVE_NOTE}`,
1372
+ scope: "agent",
1373
+ risk: "write",
1374
+ input: updateShape,
1375
+ run: async (client, params) => {
1376
+ const p = import_zod11.z.object(updateShape).parse(params);
1377
+ let updated = null;
1378
+ const { agentId } = await writeTools(client, p.agent_id, (tools) => {
1379
+ const current = findApiTool(tools, p.name);
1380
+ const prev = current.provider_settings ?? {};
1381
+ const headers = { ...prev.headers ?? {} };
1382
+ for (const [k, v] of Object.entries(p.headers ?? {})) {
1383
+ if (v === null) delete headers[k];
1384
+ else headers[k] = v;
1385
+ }
1386
+ const settings = { ...prev, name: (p.new_name ?? bindingName(current)).trim() };
1387
+ for (const key of ["url", "method", "purpose", "fields", "send_metadata", "response_fields"]) {
1388
+ if (p[key] !== void 0) settings[key] = key === "url" ? p.url.trim() : p[key];
1389
+ }
1390
+ settings.method ??= "POST";
1391
+ if (Object.keys(headers).length) settings.headers = headers;
1392
+ else delete settings.headers;
1393
+ const next = {
1394
+ ...current,
1395
+ enabled: p.enabled ?? current.enabled,
1396
+ timeout_ms: p.timeout_ms ?? current.timeout_ms,
1397
+ side_effect: settings.method === "GET" ? "read" : "write",
1398
+ provider_settings: settings
1399
+ };
1400
+ const touched = Object.entries(p.headers ?? {}).filter(([, v]) => v !== null).map(([k]) => k);
1401
+ assertValid(next, tools, current, touched);
1402
+ updated = next;
1403
+ return tools.map((t) => t === current ? next : t);
1404
+ });
1405
+ return { status: "updated", agent_id: agentId, tool: updated, note: LIVE_NOTE };
1406
+ }
1407
+ }),
1408
+ defineTool({
1409
+ name: "remove_agent_tool",
1410
+ description: `Remove one API tool (call_webhook) from an agent by name, keeping its other tools. ${LIVE_NOTE}`,
1411
+ scope: "agent",
1412
+ risk: "write",
1413
+ input: { agent_id: import_zod11.z.string().describe("The agent UUID"), name: import_zod11.z.string().describe("Name of the API tool to remove.") },
1414
+ run: async (client, { agent_id, name }) => {
1415
+ const { agentId, tools } = await writeTools(client, agent_id, (tools2) => {
1416
+ const target = findApiTool(tools2, name);
1417
+ return tools2.filter((t) => t !== target);
1418
+ });
1419
+ return { status: "removed", agent_id: agentId, name, remaining: tools.filter(isApiTool).map(bindingName), note: LIVE_NOTE };
1420
+ }
1421
+ }),
1422
+ defineTool({
1423
+ name: "list_tool_secrets",
1424
+ description: "List the workspace's API-tool secret names and the host each is pinned to. Values are never returned.",
1425
+ scope: "account",
1426
+ risk: "read",
1427
+ input: {},
1428
+ run: (client) => client.toolSecrets.list()
1429
+ }),
1430
+ defineTool({
1431
+ name: "set_tool_secret",
1432
+ description: 'Create or replace a workspace secret for API-tool headers, referenced as "{{secrets.NAME}}" (e.g. header x-api-key: "{{secrets.RESERVATIONS_API_KEY}}"). The value is encrypted, write-only, and only ever sent to `host`. Replacing an existing name changes it for every agent that references it.',
1433
+ scope: "account",
1434
+ risk: "sensitive",
1435
+ input: {
1436
+ name: import_zod11.z.string().describe("UPPER_SNAKE_CASE, 2\u201364 characters."),
1437
+ value: import_zod11.z.string().min(1).max(4096).describe("The secret value. Never echoed back."),
1438
+ host: import_zod11.z.string().describe('Public hostname the secret may be sent to, e.g. "api.example.com".')
1439
+ },
1440
+ run: async (client, { name, value, host }) => {
1441
+ if (!import_agentTools.SECRET_NAME_PATTERN.test(name)) throw new Error("Secret names are UPPER_SNAKE_CASE, 2\u201364 characters, starting with a letter.");
1442
+ await client.toolSecrets.set(name, { value, host });
1443
+ return { status: "saved", name, reference: `{{secrets.${name}}}` };
1444
+ }
1445
+ })
1446
+ ];
1447
+ var import_zod12 = require("zod");
1448
+ var shareLinkTools = [
1449
+ defineTool({
1450
+ name: "get_share_link",
1451
+ description: "Get an agent's public share link (URL, limits, minutes used). Returns link: null if none exists yet.",
1452
+ scope: "agent",
1453
+ risk: "read",
1454
+ input: { agent_id: import_zod12.z.string().uuid().describe("The agent UUID") },
1455
+ run: (client, { agent_id }) => client.agents.getShareLink(agent_id)
1456
+ }),
1457
+ defineTool({
1458
+ name: "set_share_link",
1459
+ description: "Create an agent's public share link, or update its settings. Anyone with the URL can talk to the agent in a browser, billed to this workspace. Defaults on create: 30-minute pool, 300 s per call, 2 concurrent calls, 3 calls per IP per hour. Set enabled=false to switch the link off.",
1460
+ scope: "agent",
1461
+ risk: "sensitive",
1462
+ input: {
1463
+ agent_id: import_zod12.z.string().uuid().describe("The agent UUID"),
1464
+ enabled: import_zod12.z.boolean().optional().describe("Turn the link on or off"),
1465
+ minutes_limit: import_zod12.z.number().int().min(0).max(6e3).optional().describe("Total talk-time pool in minutes"),
1466
+ max_call_seconds: import_zod12.z.number().int().min(60).max(1800).optional().describe("Per-call cap in seconds"),
1467
+ max_concurrent: import_zod12.z.number().int().min(1).max(10).optional().describe("Simultaneous calls"),
1468
+ calls_per_ip_hour: import_zod12.z.number().int().min(1).max(30).optional().describe("Calls per caller IP per hour")
1469
+ },
1470
+ run: (client, { agent_id, ...settings }) => client.agents.setShareLink(agent_id, settings)
1471
+ }),
1472
+ defineTool({
1473
+ name: "regenerate_share_link",
1474
+ description: "Issue a new URL for an agent's share link. The old URL stops working immediately.",
1475
+ scope: "agent",
1476
+ risk: "sensitive",
1477
+ input: { agent_id: import_zod12.z.string().uuid().describe("The agent UUID") },
1478
+ run: (client, { agent_id }) => client.agents.regenerateShareLink(agent_id)
1479
+ })
1480
+ ];
1481
+ var import_agentTools2 = (init_agentTools(), __toCommonJS(agentTools_exports));
1482
+ var REDACTED = "[redacted]";
1483
+ function redactToolSecrets(value) {
1484
+ if (Array.isArray(value)) return value.map(redactToolSecrets);
1485
+ if (!value || typeof value !== "object") return value;
1486
+ const out = {};
1487
+ for (const [k, v] of Object.entries(value)) out[k] = redactToolSecrets(v);
1488
+ const headers = out.tool_id && out.provider_settings?.headers;
1489
+ if (headers && typeof headers === "object" && !Array.isArray(headers)) {
1490
+ out.provider_settings = {
1491
+ ...out.provider_settings,
1492
+ headers: Object.fromEntries(Object.entries(headers).map(([k, v]) => [
1493
+ k,
1494
+ typeof v === "string" && import_agentTools2.API_TOOL_SECRET_REF_PATTERN.test(v) ? v : REDACTED
1495
+ ]))
1496
+ };
1497
+ }
1498
+ return out;
279
1499
  }
280
- );
281
- }
1500
+ var opsToolCatalog2 = [
1501
+ ...agentTools,
1502
+ ...agentToolBindingTools,
1503
+ ...callTools,
1504
+ ...numberTools,
1505
+ ...telephonyTools,
1506
+ ...keyTools,
1507
+ ...knowledgeTools,
1508
+ ...insightTools,
1509
+ ...publishTools,
1510
+ ...dncTools,
1511
+ ...webhookTools,
1512
+ ...billingTools,
1513
+ ...campaignTools,
1514
+ ...shareLinkTools
1515
+ ].map((t) => ({ ...t, run: async (client, params) => redactToolSecrets(await t.run(client, params)) }));
1516
+ var names = /* @__PURE__ */ new Set();
1517
+ for (const tool of opsToolCatalog2) {
1518
+ if (names.has(tool.name)) throw new Error(`Duplicate ops tool name: ${tool.name}`);
1519
+ names.add(tool.name);
1520
+ }
1521
+ }
1522
+ });
282
1523
 
283
- // src/tools/calls.ts
284
- var import_zod2 = require("zod");
1524
+ // src/server.ts
1525
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
1526
+ var import_node = __toESM(require("@rymi/node"));
1527
+ var import_ops_tools = __toESM(require_dist());
285
1528
 
286
1529
  // src/utils/errors.ts
287
1530
  function handleMcpError(err) {
@@ -301,578 +1544,10 @@ function withToolErrors(handler) {
301
1544
  };
302
1545
  }
303
1546
 
304
- // src/tools/calls.ts
305
- function registerCallTools(server, rymi, isReadOnly = false) {
306
- server.tool(
307
- "list_calls",
308
- "List previous and active calls across your account.",
309
- {
310
- limit: import_zod2.z.number().int().min(1).max(200).optional().describe("Max calls to return"),
311
- offset: import_zod2.z.number().int().min(0).optional().describe("Pagination offset"),
312
- cursor: import_zod2.z.string().optional().describe("Opaque cursor for keyset pagination"),
313
- status: import_zod2.z.string().optional().describe("Filter by call status (e.g. completed, in_progress, failed)")
314
- },
315
- async (params) => {
316
- const result = await rymi.calls.list(params);
317
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
318
- }
319
- );
320
- server.tool(
321
- "list_active_calls",
322
- "List calls currently in progress.",
323
- {
324
- limit: import_zod2.z.number().int().min(1).max(200).optional(),
325
- offset: import_zod2.z.number().int().min(0).optional()
326
- },
327
- async (params) => {
328
- const result = await rymi.calls.active(params);
329
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
330
- }
331
- );
332
- server.tool(
333
- "get_call",
334
- "Retrieve details, participants, status, duration, and cost for a single call.",
335
- { call_id: import_zod2.z.string().describe("The call ID") },
336
- async ({ call_id }) => {
337
- const result = await rymi.calls.retrieve(call_id);
338
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
339
- }
340
- );
341
- server.tool(
342
- "get_call_summary",
343
- "Retrieve the post-call summary for a call.",
344
- { call_id: import_zod2.z.string().describe("The call ID") },
345
- async ({ call_id }) => {
346
- const result = await rymi.calls.summary(call_id);
347
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
348
- }
349
- );
350
- server.tool(
351
- "get_call_transcript",
352
- "Retrieve the full transcript for a call. May contain personal data \u2014 handle accordingly.",
353
- { call_id: import_zod2.z.string().describe("The call ID") },
354
- async ({ call_id }) => {
355
- const result = await rymi.calls.transcript(call_id);
356
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
357
- }
358
- );
359
- server.tool(
360
- "get_call_recording",
361
- "Retrieve recording metadata (e.g. playback URL) for a call, when recording is enabled.",
362
- { call_id: import_zod2.z.string().describe("The call ID") },
363
- async ({ call_id }) => {
364
- const result = await rymi.calls.recording(call_id);
365
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
366
- }
367
- );
368
- server.tool(
369
- "get_call_queue_stats",
370
- "Retrieve current outbound call queue statistics.",
371
- {},
372
- async () => {
373
- const result = await rymi.calls.queueStats();
374
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
375
- }
376
- );
377
- if (!isReadOnly) {
378
- server.tool(
379
- "reprocess_call",
380
- "Re-run post-call intelligence (summary, extraction, evaluation) for a call.",
381
- { call_id: import_zod2.z.string().describe("The call ID to reprocess") },
382
- async ({ call_id }) => {
383
- const result = await rymi.calls.reprocess(call_id);
384
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
385
- }
386
- );
387
- }
388
- }
389
- function registerOutboundCallTools(server, rymi) {
390
- server.tool(
391
- "create_call",
392
- "WARNING: places a real, billable PSTN call. `from_number` is the caller ID shown to the recipient and must be a phone number registered to your account \u2014 omit it to use the agent's attached number. `identity` is the destination in strict E.164 (e.g. +15555550123).",
393
- {
394
- agent_id: import_zod2.z.string().uuid().describe("The agent that will handle the call"),
395
- participants: import_zod2.z.array(import_zod2.z.object({
396
- transport: import_zod2.z.enum(["webrtc", "pstn"]),
397
- identity: import_zod2.z.string().describe("Destination phone number (pstn) or participant identity (webrtc)"),
398
- from_number: import_zod2.z.string().optional().describe("Caller ID / from number for pstn"),
399
- metadata: import_zod2.z.record(import_zod2.z.any()).optional()
400
- })).min(1),
401
- metadata: import_zod2.z.record(import_zod2.z.any()).optional(),
402
- variables: import_zod2.z.record(import_zod2.z.any()).optional().describe("Playbook variables to seed the call"),
403
- post_call: import_zod2.z.record(import_zod2.z.any()).optional()
404
- },
405
- withToolErrors(async (params) => {
406
- const result = await rymi.calls.create(params);
407
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
408
- })
409
- );
410
- server.tool(
411
- "batch_call",
412
- "Queue up to 500 outbound PSTN recipients in one request. WARNING: places real phone calls and incurs per-minute charges. `from_number` is the caller ID shown to the recipient and must be a phone number registered to your account.",
413
- {
414
- agent_id: import_zod2.z.string().uuid().describe("The agent that will handle the calls"),
415
- to: import_zod2.z.array(import_zod2.z.string()).optional().describe("Simple list of destination phone numbers"),
416
- recipients: import_zod2.z.array(import_zod2.z.object({
417
- to: import_zod2.z.string().optional(),
418
- from_number: import_zod2.z.string().optional(),
419
- metadata: import_zod2.z.record(import_zod2.z.any()).optional()
420
- })).optional().describe("Per-recipient targets with optional from_number/metadata"),
421
- from_number: import_zod2.z.string().optional().describe("Default caller ID / from number"),
422
- batch_id: import_zod2.z.string().optional(),
423
- metadata: import_zod2.z.record(import_zod2.z.any()).optional(),
424
- variables: import_zod2.z.record(import_zod2.z.any()).optional(),
425
- post_call: import_zod2.z.record(import_zod2.z.any()).optional()
426
- },
427
- withToolErrors(async (params) => {
428
- const result = await rymi.calls.batch(params);
429
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
430
- })
431
- );
432
- }
433
- function registerCallControlTools(server, rymi) {
434
- server.tool(
435
- "end_call",
436
- "WARNING: immediately terminates an in-progress call. The call transitions to completed and post-call processing runs.",
437
- { call_id: import_zod2.z.string().describe("The call ID to end") },
438
- withToolErrors(async ({ call_id }) => {
439
- const result = await rymi.calls.end(call_id);
440
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
441
- })
442
- );
443
- server.tool(
444
- "add_call_participant",
445
- "WARNING: dials/adds participants to a live call (warm transfer / conference). PSTN additions are billable.",
446
- {
447
- call_id: import_zod2.z.string().describe("The in-progress call ID"),
448
- participants: import_zod2.z.array(import_zod2.z.object({
449
- transport: import_zod2.z.enum(["webrtc", "pstn"]),
450
- identity: import_zod2.z.string().describe("Destination phone number (pstn, E.164) or participant identity (webrtc)"),
451
- from_number: import_zod2.z.string().optional().describe("Caller ID for pstn"),
452
- metadata: import_zod2.z.record(import_zod2.z.any()).optional()
453
- })).min(1)
454
- },
455
- withToolErrors(async ({ call_id, participants }) => {
456
- const result = await rymi.calls.addParticipants(call_id, { participants });
457
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
458
- })
459
- );
460
- }
461
-
462
- // src/tools/numbers.ts
463
- var import_zod3 = require("zod");
464
- function registerNumberTools(server, rymi, isReadOnly = false) {
465
- server.tool(
466
- "list_numbers",
467
- "List all phone numbers on your Rymi account and which agent each is attached to.",
468
- {
469
- limit: import_zod3.z.number().int().min(1).max(500).optional(),
470
- offset: import_zod3.z.number().int().min(0).optional()
471
- },
472
- async (params) => {
473
- const result = await rymi.numbers.list(params);
474
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
475
- }
476
- );
477
- if (!isReadOnly) {
478
- server.tool(
479
- "register_number",
480
- "Register a phone number on your account, optionally attaching it to an agent.",
481
- {
482
- number: import_zod3.z.string().describe("Phone number in E.164 format (e.g. +14155550123)"),
483
- agent_id: import_zod3.z.string().uuid().optional().describe("Agent to attach the number to on registration")
484
- },
485
- async ({ number, agent_id }) => {
486
- const result = await rymi.numbers.register(number, agent_id ? { agent_id } : {});
487
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
488
- }
489
- );
490
- server.tool(
491
- "attach_number",
492
- "Attach an existing number to an agent so inbound calls route to it.",
493
- {
494
- number: import_zod3.z.string().describe("Phone number in E.164 format"),
495
- agent_id: import_zod3.z.string().uuid().describe("Agent to route this number to")
496
- },
497
- async ({ number, agent_id }) => {
498
- const result = await rymi.numbers.attach(number, agent_id);
499
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
500
- }
501
- );
502
- server.tool(
503
- "remove_number",
504
- "Remove a phone number from your account. The number stops routing to any agent.",
505
- { number: import_zod3.z.string().describe("Phone number in E.164 format") },
506
- async ({ number }) => {
507
- const result = await rymi.numbers.remove(number);
508
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
509
- }
510
- );
511
- }
512
- }
513
-
514
- // src/tools/telephony.ts
515
- function registerTelephonyTools(server, rymi, isReadOnly = false) {
516
- server.tool(
517
- "telephony_status",
518
- "Report whether a telephony carrier is connected, and which provider/account.",
519
- {},
520
- async () => {
521
- const result = await rymi.telephony.status();
522
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
523
- }
524
- );
525
- server.tool(
526
- "list_telephony_numbers",
527
- "List numbers available on the connected telephony carrier account.",
528
- {},
529
- async () => {
530
- const result = await rymi.telephony.numbers();
531
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
532
- }
533
- );
534
- }
535
-
536
- // src/tools/keys.ts
537
- function registerKeyTools(server, rymi, isReadOnly = false) {
538
- server.tool(
539
- "list_publishable_keys",
540
- "List publishable (browser-safe) keys and which agent/channels each is scoped to. Returns key prefixes only, never full secrets.",
541
- {},
542
- async () => {
543
- const result = await rymi.keys.listPublishable();
544
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
545
- }
546
- );
547
- }
548
-
549
- // src/tools/knowledge.ts
550
- var import_zod4 = require("zod");
551
- function registerKnowledgeTools(server, rymi, isReadOnly = false) {
552
- server.tool(
553
- "list_knowledge_sources",
554
- "List the knowledge sources (RAG context) attached to an agent.",
555
- { agent_id: import_zod4.z.string().uuid().describe("The agent UUID") },
556
- async ({ agent_id }) => {
557
- const result = await rymi.agents.listKnowledgeSources(agent_id);
558
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
559
- }
560
- );
561
- server.tool(
562
- "list_agent_changes",
563
- "List recorded configuration changes for an agent (for auditing or before an undo).",
564
- {
565
- agent_id: import_zod4.z.string().uuid().describe("The agent UUID"),
566
- since: import_zod4.z.string().optional().describe("ISO timestamp \u2014 only return changes after this time")
567
- },
568
- async ({ agent_id, since }) => {
569
- const result = await rymi.agents.listChanges(agent_id, since ? { since } : {});
570
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
571
- }
572
- );
573
- if (!isReadOnly) {
574
- server.tool(
575
- "add_knowledge_source",
576
- "Add a knowledge source to an agent from raw text or a URL (the URL is fetched and ingested).",
577
- {
578
- agent_id: import_zod4.z.string().uuid().describe("The agent UUID"),
579
- kind: import_zod4.z.enum(["text", "url"]).describe("Source type"),
580
- title: import_zod4.z.string().min(1).describe("Human-readable title for the source"),
581
- text: import_zod4.z.string().optional().describe('Required when kind="text": the raw content to ingest'),
582
- url: import_zod4.z.string().url().optional().describe('Required when kind="url": the page to fetch and ingest')
583
- },
584
- async ({ agent_id, kind, title, text, url }) => {
585
- const data = kind === "text" ? { kind: "text", title, text: text ?? "" } : { kind: "url", title, url: url ?? "" };
586
- const result = await rymi.agents.addKnowledgeSource(agent_id, data);
587
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
588
- }
589
- );
590
- server.tool(
591
- "delete_knowledge_source",
592
- "Delete a knowledge source from an agent.",
593
- {
594
- agent_id: import_zod4.z.string().uuid().describe("The agent UUID"),
595
- source_id: import_zod4.z.string().describe("The knowledge source ID to delete")
596
- },
597
- async ({ agent_id, source_id }) => {
598
- const result = await rymi.agents.deleteKnowledgeSource(agent_id, source_id);
599
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
600
- }
601
- );
602
- server.tool(
603
- "undo_agent_change",
604
- "Undo a single recorded configuration change, reverting that field to its previous value.",
605
- {
606
- agent_id: import_zod4.z.string().uuid().describe("The agent UUID"),
607
- change_id: import_zod4.z.string().describe("The change_id to undo (from list_agent_changes)")
608
- },
609
- async ({ agent_id, change_id }) => {
610
- const result = await rymi.agents.undoChange(agent_id, change_id);
611
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
612
- }
613
- );
614
- }
615
- }
616
-
617
- // src/tools/insights.ts
618
- var import_zod5 = require("zod");
619
- function registerInsightTools(server, rymi, isReadOnly = false) {
620
- server.tool(
621
- "get_usage_summary",
622
- "Get this account's usage summary: remaining voice-runtime MINUTES, Studio AI unit usage, and post-call intelligence usage. Voice balance is reported in minutes, not dollars.",
623
- {},
624
- async () => {
625
- const result = await rymi.billing.usageSummary();
626
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
627
- }
628
- );
629
- server.tool(
630
- "list_agent_templates",
631
- "List published agent templates. Use a template's `defaults` as the starting config for create_agent.",
632
- {},
633
- async () => {
634
- const result = await rymi.templates.list();
635
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
636
- }
637
- );
638
- server.tool(
639
- "list_eval_runs",
640
- "List previous evaluation runs for an agent.",
641
- { agent_id: import_zod5.z.string().uuid().describe("The agent UUID") },
642
- async ({ agent_id }) => {
643
- const result = await rymi.agents.listEvalRuns(agent_id);
644
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
645
- }
646
- );
647
- server.tool(
648
- "get_eval_run",
649
- "Retrieve a single evaluation run, including per-scenario scores.",
650
- {
651
- agent_id: import_zod5.z.string().uuid().describe("The agent UUID"),
652
- run_id: import_zod5.z.string().describe("The evaluation run ID")
653
- },
654
- async ({ agent_id, run_id }) => {
655
- const result = await rymi.agents.getEvalRun(agent_id, run_id);
656
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
657
- }
658
- );
659
- if (!isReadOnly) {
660
- server.tool(
661
- "run_evals",
662
- 'Run the evaluation suite for an agent. mode="synthetic" (default) uses the offline scorer; mode="live" runs the model-driven runner (consumes Studio AI units).',
663
- {
664
- agent_id: import_zod5.z.string().uuid().describe("The agent UUID to evaluate"),
665
- mode: import_zod5.z.enum(["synthetic", "live"]).optional().describe("Evaluation mode (default synthetic)")
666
- },
667
- async ({ agent_id, mode }) => {
668
- const result = await rymi.agents.runEvals(agent_id, mode ? { mode } : {});
669
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
670
- }
671
- );
672
- }
673
- }
674
-
675
- // src/tools/publish.ts
676
- var import_zod6 = require("zod");
677
- function registerPublishTool(server, rymi) {
678
- server.tool(
679
- "publish_agent",
680
- "Publish a Rymi voice agent to make it live. IMPORTANT: this immediately makes the agent callable by end users.",
681
- {
682
- agent_id: import_zod6.z.string().uuid().describe("The agent UUID to publish")
683
- },
684
- async ({ agent_id }) => {
685
- const validation = await rymi.agents.validatePublish({ agent_id });
686
- if (!validation.valid) {
687
- return {
688
- content: [{
689
- type: "text",
690
- text: JSON.stringify({ published: false, reason: "Validation failed", details: validation }, null, 2)
691
- }]
692
- };
693
- }
694
- return {
695
- content: [{
696
- type: "text",
697
- text: JSON.stringify({
698
- published: false,
699
- reason: "publish_agent requires PUT /agents/:id status field (coming soon). Validation passed \u2014 agent is ready to publish from the Rymi dashboard.",
700
- agent_id,
701
- validation
702
- }, null, 2)
703
- }]
704
- };
705
- }
706
- );
707
- }
708
-
709
- // src/tools/dnc.ts
710
- var import_zod7 = require("zod");
711
- function registerDncTools(server, rymi, isReadOnly) {
712
- server.tool(
713
- "list_dnc",
714
- "List all numbers on your Do-Not-Call registry.",
715
- {
716
- limit: import_zod7.z.number().int().min(1).max(500).optional(),
717
- offset: import_zod7.z.number().int().min(0).optional()
718
- },
719
- withToolErrors(async (params) => {
720
- const result = await rymi.dnc.list(params);
721
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
722
- })
723
- );
724
- server.tool(
725
- "check_dnc",
726
- "Check whether phone numbers are on the Do-Not-Call registry. Read-only \u2014 adds nothing.",
727
- { phone_numbers: import_zod7.z.array(import_zod7.z.string()).min(1).max(500).describe("E.164 numbers to check") },
728
- withToolErrors(async ({ phone_numbers }) => {
729
- const result = await rymi.dnc.check({ phone_numbers });
730
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
731
- })
732
- );
733
- if (isReadOnly) return;
734
- server.tool(
735
- "add_dnc",
736
- "Add a phone number to the Do-Not-Call registry so outbound calls to it are blocked.",
737
- {
738
- phone_number: import_zod7.z.string().describe("Phone number (any format; normalized to E.164)"),
739
- reason: import_zod7.z.string().optional()
740
- },
741
- withToolErrors(async ({ phone_number, reason }) => {
742
- const result = await rymi.dnc.add({ phone_number, reason });
743
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
744
- })
745
- );
746
- server.tool(
747
- "add_dnc_batch",
748
- "Add up to 1000 numbers to the Do-Not-Call registry. Invalid numbers are skipped and returned in `invalid`.",
749
- {
750
- phone_numbers: import_zod7.z.array(import_zod7.z.string()).min(1).max(1e3),
751
- reason: import_zod7.z.string().optional()
752
- },
753
- withToolErrors(async ({ phone_numbers, reason }) => {
754
- const result = await rymi.dnc.addBatch({ phone_numbers, reason });
755
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
756
- })
757
- );
758
- server.tool(
759
- "remove_dnc",
760
- "WARNING: removes a number from the Do-Not-Call registry, re-enabling outbound calls to it.",
761
- { phone: import_zod7.z.string().describe("Phone number to remove") },
762
- withToolErrors(async ({ phone }) => {
763
- const result = await rymi.dnc.remove(phone);
764
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
765
- })
766
- );
767
- }
768
-
769
- // src/tools/webhooks.ts
770
- var import_zod8 = require("zod");
771
- var import_crypto = require("crypto");
772
- function registerWebhookTools(server, rymi, isReadOnly) {
773
- server.tool(
774
- "list_webhooks",
775
- "List your registered webhook endpoints. Signing secrets are never returned.",
776
- {
777
- limit: import_zod8.z.number().int().min(1).max(500).optional(),
778
- offset: import_zod8.z.number().int().min(0).optional()
779
- },
780
- withToolErrors(async (params) => {
781
- const result = await rymi.webhooks.list(params);
782
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
783
- })
784
- );
785
- if (isReadOnly) return;
786
- server.tool(
787
- "create_webhook",
788
- "Register a webhook to receive call lifecycle events (e.g. call.completed, transcript.ready). URL must be public https. If you omit `secret`, one is generated and shown ONCE in the response \u2014 store it to verify signatures.",
789
- {
790
- url: import_zod8.z.string().url().describe("Public https endpoint"),
791
- events: import_zod8.z.array(import_zod8.z.string()).min(1).describe("Event names to subscribe to"),
792
- secret: import_zod8.z.string().min(16).max(256).optional().describe("Signing secret; auto-generated if omitted"),
793
- alert_email: import_zod8.z.string().email().optional()
794
- },
795
- withToolErrors(async ({ url, events, secret, alert_email }) => {
796
- const finalSecret = secret ?? (0, import_crypto.randomBytes)(24).toString("hex");
797
- const result = await rymi.webhooks.create({ url, events, secret: finalSecret, alert_email });
798
- const echoed = secret ? result : { ...result, secret: finalSecret, secret_notice: "Store this secret now \u2014 it cannot be retrieved later." };
799
- return { content: [{ type: "text", text: JSON.stringify(echoed, null, 2) }] };
800
- })
801
- );
802
- server.tool(
803
- "update_webhook",
804
- "Update a webhook endpoint. Only provided fields change. Pass `secret` to rotate it.",
805
- {
806
- id: import_zod8.z.string(),
807
- url: import_zod8.z.string().url().optional(),
808
- events: import_zod8.z.array(import_zod8.z.string()).min(1).optional(),
809
- secret: import_zod8.z.string().min(16).max(256).optional(),
810
- alert_email: import_zod8.z.string().email().nullable().optional()
811
- },
812
- withToolErrors(async ({ id, ...rest }) => {
813
- const result = await rymi.webhooks.update(id, rest);
814
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
815
- })
816
- );
817
- server.tool(
818
- "delete_webhook",
819
- "WARNING: stops event delivery to this endpoint.",
820
- { id: import_zod8.z.string() },
821
- withToolErrors(async ({ id }) => {
822
- const result = await rymi.webhooks.delete(id);
823
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
824
- })
825
- );
826
- }
827
-
828
- // src/tools/billing.ts
829
- var import_zod9 = require("zod");
830
- function registerBillingControlTools(server, rymi, isReadOnly) {
831
- server.tool(
832
- "estimate_call_cost",
833
- "Estimate how much balance a call will consume for a given tier and duration. Results are usage estimates; surface remaining balance to customers in minutes.",
834
- {
835
- tier: import_zod9.z.enum(["operator", "specialist", "executive", "concierge"]).optional(),
836
- duration_seconds: import_zod9.z.number().min(0).optional()
837
- },
838
- withToolErrors(async (params) => {
839
- const result = await rymi.billing.estimate(params);
840
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
841
- })
842
- );
843
- if (isReadOnly) return;
844
- server.tool(
845
- "set_auto_recharge",
846
- "Configure auto-recharge. `pack_usd` must exceed `threshold_usd` or the API rejects it (recharge-loop guard).",
847
- {
848
- enabled: import_zod9.z.boolean().optional(),
849
- pack_usd: import_zod9.z.number().min(1).max(1e3).optional(),
850
- threshold_usd: import_zod9.z.number().min(0).max(100).optional()
851
- },
852
- withToolErrors(async (params) => {
853
- const result = await rymi.billing.setAutoRecharge(params);
854
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
855
- })
856
- );
857
- server.tool(
858
- "set_spend_alerts",
859
- "Configure spend-alert thresholds and low-balance / email preferences.",
860
- {
861
- thresholds_usd: import_zod9.z.array(import_zod9.z.number()).max(10).optional(),
862
- low_balance_pct: import_zod9.z.number().min(0).max(100).optional(),
863
- email_enabled: import_zod9.z.boolean().optional()
864
- },
865
- withToolErrors(async (params) => {
866
- const result = await rymi.billing.setAlerts(params);
867
- return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
868
- })
869
- );
870
- }
871
-
872
1547
  // package.json
873
1548
  var package_default = {
874
1549
  name: "@rymi/mcp",
875
- version: "0.6.2",
1550
+ version: "2.1.0",
876
1551
  description: "Rymi MCP server \u2014 manage AI voice agents via Model Context Protocol",
877
1552
  license: "MIT",
878
1553
  author: "Rymi AI <engineering@rymi.live>",
@@ -920,6 +1595,7 @@ var package_default = {
920
1595
  zod: "^3.23.0"
921
1596
  },
922
1597
  devDependencies: {
1598
+ "@rymi/ops-tools": "workspace:*",
923
1599
  tsup: "^8.0.0",
924
1600
  typescript: "^5.0.0",
925
1601
  vitest: "^1.6.0"
@@ -932,85 +1608,55 @@ var package_default = {
932
1608
  };
933
1609
 
934
1610
  // src/server.ts
935
- function createServer(apiKey) {
936
- const rymi = new import_node.default({ apiKey });
937
- const server = new import_mcp.McpServer({
1611
+ function createServer(apiKey2) {
1612
+ const rymi = new import_node.default({ apiKey: apiKey2 });
1613
+ const server2 = new import_mcp.McpServer({
938
1614
  name: "rymi",
939
1615
  version: package_default.version
940
1616
  });
941
1617
  const isReadOnly = process.env.RYMI_MCP_READONLY === "1";
942
- registerAgentTools(server, rymi, isReadOnly);
943
- registerCallTools(server, rymi, isReadOnly);
944
- registerNumberTools(server, rymi, isReadOnly);
945
- registerTelephonyTools(server, rymi, isReadOnly);
946
- registerKeyTools(server, rymi, isReadOnly);
947
- registerKnowledgeTools(server, rymi, isReadOnly);
948
- registerInsightTools(server, rymi, isReadOnly);
949
- registerDncTools(server, rymi, isReadOnly);
950
- registerWebhookTools(server, rymi, isReadOnly);
951
- registerBillingControlTools(server, rymi, isReadOnly);
952
- if (!isReadOnly) {
953
- registerCallControlTools(server, rymi);
954
- registerOutboundCallTools(server, rymi);
955
- registerPublishTool(server, rymi);
1618
+ for (const tool of import_ops_tools.opsToolCatalog) {
1619
+ if (isReadOnly && tool.risk !== "read") continue;
1620
+ server2.tool(
1621
+ tool.name,
1622
+ tool.description,
1623
+ tool.input,
1624
+ withToolErrors(async (params) => {
1625
+ const result = await tool.run(rymi, params);
1626
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1627
+ })
1628
+ );
956
1629
  }
957
- return server;
1630
+ return server2;
958
1631
  }
959
1632
 
960
1633
  // src/transport/stdio.ts
961
1634
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
962
- async function runStdio(server) {
1635
+ async function runStdio(server2) {
963
1636
  const transport2 = new import_stdio.StdioServerTransport();
964
- await server.connect(transport2);
965
- }
966
-
967
- // src/transport/http.ts
968
- var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
969
- var import_http = require("http");
970
- var PORT = parseInt(process.env.RYMI_MCP_PORT || "8787", 10);
971
- async function runHttp() {
972
- const httpServer = (0, import_http.createServer)(async (req, res) => {
973
- if (req.url === "/health") {
974
- res.writeHead(200, { "Content-Type": "application/json" });
975
- res.end(JSON.stringify({ ok: true }));
976
- return;
977
- }
978
- const authHeader = req.headers["authorization"] || "";
979
- const apiKey = authHeader.startsWith("Bearer ") ? authHeader.slice(7).trim() : "";
980
- if (!apiKey) {
981
- res.writeHead(401, { "Content-Type": "application/json" });
982
- res.end(JSON.stringify({ error: "Missing Authorization: Bearer <RYMI_API_KEY>" }));
983
- return;
984
- }
985
- const server = createServer(apiKey);
986
- const transport2 = new import_streamableHttp.StreamableHTTPServerTransport({ sessionIdGenerator: void 0 });
987
- await server.connect(transport2);
988
- await transport2.handleRequest(req, res);
989
- });
990
- httpServer.listen(PORT, () => {
991
- process.stderr.write(`Rymi MCP HTTP server listening on port ${PORT}
992
- `);
993
- });
1637
+ await server2.connect(transport2);
994
1638
  }
995
1639
 
996
1640
  // src/index.ts
997
1641
  var transport = process.argv.includes("--transport") ? process.argv[process.argv.indexOf("--transport") + 1] : "stdio";
998
- if (transport === "http") {
999
- runHttp().catch((err) => {
1000
- process.stderr.write(`Fatal: ${err.message}
1001
- `);
1002
- process.exit(1);
1003
- });
1004
- } else {
1005
- const apiKey = process.env.RYMI_API_KEY || "";
1006
- if (!apiKey) {
1007
- process.stderr.write("Error: RYMI_API_KEY environment variable is required.\n");
1008
- process.exit(1);
1009
- }
1010
- const server = createServer(apiKey);
1011
- runStdio(server).catch((err) => {
1012
- process.stderr.write(`Fatal: ${err.message}
1013
- `);
1014
- process.exit(1);
1015
- });
1642
+ if (transport !== "stdio") {
1643
+ process.stderr.write(
1644
+ `Error: --transport ${transport} was removed in @rymi/mcp 2.0.0; this package is stdio-only.
1645
+ For an HTTP endpoint use the hosted one: https://mcp.rymi.live/mcp
1646
+ \u2014 add it as a custom connector (OAuth, no key), or pass a rymi_ secret key as a Bearer token.
1647
+ See https://docs.rymi.live/api/mcp
1648
+ `
1649
+ );
1650
+ process.exit(1);
1651
+ }
1652
+ var apiKey = process.env.RYMI_API_KEY || "";
1653
+ if (!apiKey) {
1654
+ process.stderr.write("Error: RYMI_API_KEY environment variable is required.\n");
1655
+ process.exit(1);
1016
1656
  }
1657
+ var server = createServer(apiKey);
1658
+ runStdio(server).catch((err) => {
1659
+ process.stderr.write(`Fatal: ${err.message}
1660
+ `);
1661
+ process.exit(1);
1662
+ });