@rymi/mcp 0.6.2 → 2.0.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 +98 -35
  2. package/dist/index.js +970 -888
  3. package/package.json +3 -2
package/dist/index.js CHANGED
@@ -6,6 +6,9 @@ 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 __commonJS = (cb, mod) => function __require() {
10
+ return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
+ };
9
12
  var __copyProps = (to, from, except, desc) => {
10
13
  if (from && typeof from === "object" || typeof from === "function") {
11
14
  for (let key of __getOwnPropNames(from))
@@ -23,265 +26,941 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
23
26
  mod
24
27
  ));
25
28
 
26
- // src/server.ts
27
- var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
28
- var import_node = __toESM(require("@rymi/node"));
29
-
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) }] };
63
- }
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) }] };
70
- }
71
- function foldPromptMode(params) {
72
- const { prompt_mode, ...rest } = params;
73
- if (!prompt_mode) return rest;
74
- return { ...rest, advanced: { ...rest.advanced || {}, prompt_mode } };
75
- }
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) }] };
96
- }
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) }] };
105
- }
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);
29
+ // ../ops-tools/dist/index.js
30
+ var require_dist = __commonJS({
31
+ "../ops-tools/dist/index.js"(exports2, module2) {
32
+ "use strict";
33
+ var __defProp2 = Object.defineProperty;
34
+ var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
35
+ var __getOwnPropNames2 = Object.getOwnPropertyNames;
36
+ var __hasOwnProp2 = Object.prototype.hasOwnProperty;
37
+ var __export = (target, all) => {
38
+ for (var name in all)
39
+ __defProp2(target, name, { get: all[name], enumerable: true });
40
+ };
41
+ var __copyProps2 = (to, from, except, desc) => {
42
+ if (from && typeof from === "object" || typeof from === "function") {
43
+ for (let key of __getOwnPropNames2(from))
44
+ if (!__hasOwnProp2.call(to, key) && key !== except)
45
+ __defProp2(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc2(from, key)) || desc.enumerable });
120
46
  }
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) }] };
47
+ return to;
48
+ };
49
+ var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
50
+ var index_exports = {};
51
+ __export(index_exports, {
52
+ defineTool: () => defineTool,
53
+ opsToolCatalog: () => opsToolCatalog2,
54
+ resolveToolDisposition: () => resolveToolDisposition
55
+ });
56
+ module2.exports = __toCommonJS(index_exports);
57
+ function defineTool(def) {
58
+ return def;
135
59
  }
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) }] };
60
+ var PRIVILEGED = /* @__PURE__ */ new Set(["owner", "admin"]);
61
+ function resolveToolDisposition(p) {
62
+ if (p.risk === "read") return "execute";
63
+ if (p.scope === "account" && !PRIVILEGED.has(p.role)) return "deny";
64
+ return p.risk === "write" ? "propose" : "confirm";
149
65
  }
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);
66
+ var import_zod = require("zod");
67
+ var agentConfigFields = {
68
+ system_prompt: import_zod.z.string().optional().describe("Full system prompt for the agent. If given without persona/playbook, the server auto-structures it."),
69
+ 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.'),
70
+ language: import_zod.z.string().optional().describe('Primary BCP-47 language tag (e.g. "hi-IN", "en-US").'),
71
+ 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.'),
72
+ llm_provider: import_zod.z.enum(["gemini", "openai", "anthropic", "sarvam"]).optional(),
73
+ llm_model: import_zod.z.string().optional().describe('LLM model id from list_llm_options (e.g. "gemini-2.5-flash", "sarvam-105b").'),
74
+ llm_fallback_provider: import_zod.z.string().nullable().optional().describe("Fallback LLM provider; null clears it."),
75
+ llm_fallback_model: import_zod.z.string().nullable().optional().describe("Fallback LLM model; null clears it."),
76
+ stt_provider: import_zod.z.string().optional().describe('Speech-to-text provider (e.g. "deepgram", "sarvam").'),
77
+ stt_model: import_zod.z.string().optional(),
78
+ stt_fallback_provider: import_zod.z.string().nullable().optional(),
79
+ stt_fallback_model: import_zod.z.string().nullable().optional(),
80
+ tts_provider: import_zod.z.string().optional().describe('Text-to-speech provider (e.g. "elevenlabs", "sarvam", "cartesia").'),
81
+ tts_model: import_zod.z.string().optional(),
82
+ tts_fallback_provider: import_zod.z.string().nullable().optional(),
83
+ tts_fallback_model: import_zod.z.string().nullable().optional(),
84
+ custom_llm_url: import_zod.z.string().nullable().optional().describe("Self-hosted LLM endpoint (https:// or wss://). Enterprise only; null clears."),
85
+ custom_voice_url: import_zod.z.string().nullable().optional().describe("Self-hosted TTS endpoint (https:// or wss://). Enterprise only; null clears."),
86
+ custom_voice_mode: import_zod.z.enum(["rymi", "openai-compat"]).optional().describe("Wire format for custom_voice_url."),
87
+ custom_transcriber_url: import_zod.z.string().nullable().optional().describe("Self-hosted STT endpoint (https:// or wss://). Enterprise only; null clears."),
88
+ 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".'),
89
+ 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."),
90
+ playbook: import_zod.z.record(import_zod.z.any()).optional().describe("Playbook configuration object"),
91
+ advanced: import_zod.z.record(import_zod.z.any()).optional(),
92
+ features: import_zod.z.record(import_zod.z.any()).optional(),
93
+ post_call: import_zod.z.record(import_zod.z.any()).optional()
94
+ };
95
+ function foldPromptMode(params) {
96
+ const { prompt_mode, ...rest } = params;
97
+ if (!prompt_mode) return rest;
98
+ return { ...rest, advanced: { ...rest.advanced || {}, prompt_mode } };
99
+ }
100
+ var agentTools = [
101
+ defineTool({
102
+ name: "list_agents",
103
+ description: "List all your Rymi AI voice agents.",
104
+ scope: "agent",
105
+ risk: "read",
106
+ input: {
107
+ limit: import_zod.z.number().int().min(1).max(500).optional().describe("Max agents to return (default 50)"),
108
+ offset: import_zod.z.number().int().min(0).optional().describe("Pagination offset")
109
+ },
110
+ run: (client, { limit, offset }) => client.agents.list({ limit, offset })
111
+ }),
112
+ defineTool({
113
+ name: "get_agent",
114
+ description: "Retrieve a single Rymi voice agent by ID.",
115
+ scope: "agent",
116
+ risk: "read",
117
+ input: { agent_id: import_zod.z.string().uuid().describe("The agent UUID") },
118
+ run: (client, { agent_id }) => client.agents.retrieve(agent_id)
119
+ }),
120
+ defineTool({
121
+ name: "list_llm_options",
122
+ 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.",
123
+ scope: "agent",
124
+ risk: "read",
125
+ input: {},
126
+ run: (client) => client.agents.llmOptions()
127
+ }),
128
+ defineTool({
129
+ name: "list_voices",
130
+ 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.",
131
+ scope: "agent",
132
+ risk: "read",
133
+ input: {
134
+ provider: import_zod.z.string().optional().describe('Filter to one voice provider, e.g. "elevenlabs", "sarvam", "gemini", "cartesia", "deepgram".'),
135
+ 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).")
136
+ },
137
+ run: async (client, { provider, model_id }) => {
138
+ const { voices } = await client.agents.llmOptions();
139
+ let filtered = Array.isArray(voices) ? voices : [];
140
+ if (provider) {
141
+ const p = provider.toLowerCase();
142
+ filtered = filtered.filter((v) => String(v?.provider || "").toLowerCase() === p);
143
+ }
144
+ if (model_id) {
145
+ filtered = filtered.filter((v) => Array.isArray(v?.supported_model_ids) && v.supported_model_ids.includes(model_id));
146
+ }
147
+ const trimmed = filtered.map((v) => ({
148
+ id: v?.id,
149
+ provider: v?.provider,
150
+ name: v?.name,
151
+ label: v?.label,
152
+ gender: v?.gender,
153
+ supported_model_ids: v?.supported_model_ids,
154
+ byok_required: v?.byok_required,
155
+ preview_url: v?.preview_url
156
+ }));
157
+ return { voices: trimmed, total: trimmed.length };
165
158
  }
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);
159
+ }),
160
+ defineTool({
161
+ name: "preview_stack",
162
+ 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.",
163
+ scope: "agent",
164
+ risk: "read",
165
+ input: {
166
+ 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"].'),
167
+ language: import_zod.z.string().optional().describe("Primary BCP-47 language (defaults to the first supported language)."),
168
+ current_provider_config: import_zod.z.record(import_zod.z.any()).optional().describe("Existing provider_config to diff against, if any.")
169
+ },
170
+ run: (client, params) => client.agents.previewStack(params)
171
+ }),
172
+ defineTool({
173
+ name: "list_calls_for_agent",
174
+ description: "List calls made with a specific agent.",
175
+ scope: "agent",
176
+ risk: "read",
177
+ input: {
178
+ agent_id: import_zod.z.string().uuid(),
179
+ limit: import_zod.z.number().int().min(1).max(200).optional(),
180
+ offset: import_zod.z.number().int().min(0).optional(),
181
+ status: import_zod.z.string().optional().describe("Filter by call status")
182
+ },
183
+ run: (client, { agent_id, ...params }) => client.agents.listCalls(agent_id, params)
184
+ }),
185
+ defineTool({
186
+ name: "validate_agent_publish",
187
+ description: "Check whether an agent is ready to go live. Returns a validation report without persisting any changes.",
188
+ scope: "agent",
189
+ risk: "read",
190
+ input: {
191
+ agent_id: import_zod.z.string().uuid().optional().describe("Merge validation with a persisted agent"),
192
+ name: import_zod.z.string().optional(),
193
+ voice: import_zod.z.string().optional(),
194
+ persona: import_zod.z.record(import_zod.z.any()).optional(),
195
+ playbook: import_zod.z.record(import_zod.z.any()).optional()
196
+ },
197
+ run: (client, params) => client.agents.validatePublish(params)
198
+ }),
199
+ defineTool({
200
+ name: "apply_agent_changes",
201
+ description: "Validate and resolve a flat key/value change-set against the AgentConfig field registry. Does NOT persist \u2014 follow up with update_agent.",
202
+ scope: "agent",
203
+ risk: "read",
204
+ input: {
205
+ agent_id: import_zod.z.string().uuid().describe("Agent to load currentConfig from (recommended)"),
206
+ 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"),
207
+ mode: import_zod.z.enum(["create", "edit"]).describe("create = new agent, edit = updating existing"),
208
+ lenient: import_zod.z.boolean().optional().describe("Skip unknown-field hard-fail (not recommended)")
209
+ },
210
+ run: async (client, { agent_id, changes, mode, lenient }) => {
211
+ const current = await client.agents.retrieve(agent_id);
212
+ const normalizedChanges = changes.map(({ key, value }) => ({ key, value }));
213
+ return client.agents.applyChanges({
214
+ currentConfig: current,
215
+ changes: normalizedChanges,
216
+ mode,
217
+ lenient
218
+ });
183
219
  }
184
- }
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) }] };
202
- }
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) }] };
217
- }
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) }] };
232
- }
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) }] };
253
- }
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) }] };
265
- }
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) }] };
220
+ }),
221
+ defineTool({
222
+ name: "enrich_company",
223
+ description: "Use AI + Google Search grounding to auto-generate a company description from a website URL, suitable for an agent's persona.",
224
+ scope: "agent",
225
+ risk: "read",
226
+ input: {
227
+ company_name: import_zod.z.string().min(1),
228
+ website_url: import_zod.z.string().url()
229
+ },
230
+ run: (client, { company_name, website_url }) => client.agents.enrichCompany({ companyName: company_name, websiteUrl: website_url })
231
+ }),
232
+ defineTool({
233
+ name: "generate_agent_draft",
234
+ description: "Use AI to generate an agent configuration draft from a plain-text description.",
235
+ scope: "agent",
236
+ risk: "read",
237
+ input: {
238
+ prompt: import_zod.z.string().min(1).describe("Description of the agent you want to create"),
239
+ mode: import_zod.z.enum(["create", "edit"]).optional().default("create"),
240
+ current_config: import_zod.z.record(import_zod.z.any()).optional().describe("Existing config when editing"),
241
+ options: import_zod.z.record(import_zod.z.any()).optional().describe("Override hints e.g. {voice, llm_provider}")
242
+ },
243
+ run: (client, params) => client.agents.generate({ prompt: params.prompt, options: params.options })
244
+ }),
245
+ defineTool({
246
+ name: "create_agent",
247
+ 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"]).',
248
+ scope: "agent",
249
+ risk: "write",
250
+ input: {
251
+ name: import_zod.z.string().min(1).describe("Agent display name"),
252
+ ...agentConfigFields
253
+ },
254
+ run: (client, params) => client.agents.create(foldPromptMode(params))
255
+ }),
256
+ defineTool({
257
+ name: "update_agent",
258
+ description: "Update an existing Rymi voice agent's configuration. Only the fields you pass are changed; nullable fields accept null to clear them.",
259
+ scope: "agent",
260
+ risk: "write",
261
+ input: {
262
+ agent_id: import_zod.z.string().uuid().describe("The agent UUID to update"),
263
+ name: import_zod.z.string().optional(),
264
+ ...agentConfigFields,
265
+ chat_summary: import_zod.z.string().nullable().optional()
266
+ },
267
+ run: (client, { agent_id, ...params }) => client.agents.update(agent_id, foldPromptMode(params))
268
+ }),
269
+ defineTool({
270
+ name: "delete_agent",
271
+ description: "Permanently delete a Rymi voice agent.",
272
+ scope: "agent",
273
+ risk: "sensitive",
274
+ input: { agent_id: import_zod.z.string().uuid().describe("The agent UUID to delete") },
275
+ run: (client, { agent_id }) => client.agents.delete(agent_id)
276
+ }),
277
+ defineTool({
278
+ name: "clone_agent",
279
+ description: 'Duplicate an existing agent. The copy gets " (Copy)" appended to its name.',
280
+ scope: "agent",
281
+ risk: "write",
282
+ input: { agent_id: import_zod.z.string().uuid().describe("The agent UUID to clone") },
283
+ run: (client, { agent_id }) => client.agents.clone(agent_id)
284
+ })
285
+ ];
286
+ var import_zod2 = require("zod");
287
+ var callTools = [
288
+ defineTool({
289
+ name: "list_calls",
290
+ description: "List previous and active calls across your account.",
291
+ scope: "agent",
292
+ risk: "read",
293
+ input: {
294
+ limit: import_zod2.z.number().int().min(1).max(200).optional().describe("Max calls to return"),
295
+ offset: import_zod2.z.number().int().min(0).optional().describe("Pagination offset"),
296
+ cursor: import_zod2.z.string().optional().describe("Opaque cursor for keyset pagination"),
297
+ status: import_zod2.z.string().optional().describe("Filter by call status (e.g. completed, in_progress, failed)")
298
+ },
299
+ run: (client, params) => client.calls.list(params)
300
+ }),
301
+ defineTool({
302
+ name: "list_active_calls",
303
+ description: "List calls currently in progress.",
304
+ scope: "agent",
305
+ risk: "read",
306
+ input: {
307
+ limit: import_zod2.z.number().int().min(1).max(200).optional(),
308
+ offset: import_zod2.z.number().int().min(0).optional()
309
+ },
310
+ run: (client, params) => client.calls.active(params)
311
+ }),
312
+ defineTool({
313
+ name: "get_call",
314
+ description: "Retrieve details, participants, status, duration, and cost for a single call.",
315
+ scope: "agent",
316
+ risk: "read",
317
+ input: { call_id: import_zod2.z.string().describe("The call ID") },
318
+ run: (client, { call_id }) => client.calls.retrieve(call_id)
319
+ }),
320
+ defineTool({
321
+ name: "get_call_summary",
322
+ description: "Retrieve the post-call summary for a call.",
323
+ scope: "agent",
324
+ risk: "read",
325
+ input: { call_id: import_zod2.z.string().describe("The call ID") },
326
+ run: (client, { call_id }) => client.calls.summary(call_id)
327
+ }),
328
+ defineTool({
329
+ name: "get_call_transcript",
330
+ description: "Retrieve the full transcript for a call. May contain personal data \u2014 handle accordingly.",
331
+ scope: "agent",
332
+ risk: "read",
333
+ input: { call_id: import_zod2.z.string().describe("The call ID") },
334
+ run: (client, { call_id }) => client.calls.transcript(call_id)
335
+ }),
336
+ defineTool({
337
+ name: "get_call_recording",
338
+ description: "Retrieve recording metadata (e.g. playback URL) for a call, when recording is enabled.",
339
+ scope: "agent",
340
+ risk: "read",
341
+ input: { call_id: import_zod2.z.string().describe("The call ID") },
342
+ run: (client, { call_id }) => client.calls.recording(call_id)
343
+ }),
344
+ defineTool({
345
+ name: "get_call_queue_stats",
346
+ description: "Retrieve current outbound call queue statistics.",
347
+ scope: "agent",
348
+ risk: "read",
349
+ input: {},
350
+ run: (client) => client.calls.queueStats()
351
+ }),
352
+ defineTool({
353
+ name: "reprocess_call",
354
+ description: "Re-run post-call intelligence (summary, extraction, evaluation) for a call.",
355
+ scope: "agent",
356
+ risk: "write",
357
+ input: { call_id: import_zod2.z.string().describe("The call ID to reprocess") },
358
+ run: (client, { call_id }) => client.calls.reprocess(call_id)
359
+ }),
360
+ defineTool({
361
+ name: "create_call",
362
+ 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).",
363
+ scope: "agent",
364
+ risk: "sensitive",
365
+ input: {
366
+ agent_id: import_zod2.z.string().uuid().describe("The agent that will handle the call"),
367
+ participants: import_zod2.z.array(import_zod2.z.object({
368
+ transport: import_zod2.z.enum(["webrtc", "pstn"]),
369
+ identity: import_zod2.z.string().describe("Destination phone number (pstn) or participant identity (webrtc)"),
370
+ from_number: import_zod2.z.string().optional().describe("Caller ID / from number for pstn"),
371
+ metadata: import_zod2.z.record(import_zod2.z.any()).optional()
372
+ })).min(1),
373
+ metadata: import_zod2.z.record(import_zod2.z.any()).optional(),
374
+ variables: import_zod2.z.record(import_zod2.z.any()).optional().describe("Playbook variables to seed the call"),
375
+ post_call: import_zod2.z.record(import_zod2.z.any()).optional()
376
+ },
377
+ run: (client, params) => client.calls.create(params)
378
+ }),
379
+ defineTool({
380
+ name: "batch_call",
381
+ 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.",
382
+ scope: "agent",
383
+ risk: "sensitive",
384
+ input: {
385
+ agent_id: import_zod2.z.string().uuid().describe("The agent that will handle the calls"),
386
+ to: import_zod2.z.array(import_zod2.z.string()).optional().describe("Simple list of destination phone numbers"),
387
+ recipients: import_zod2.z.array(import_zod2.z.object({
388
+ to: import_zod2.z.string().optional(),
389
+ from_number: import_zod2.z.string().optional(),
390
+ metadata: import_zod2.z.record(import_zod2.z.any()).optional()
391
+ })).optional().describe("Per-recipient targets with optional from_number/metadata"),
392
+ from_number: import_zod2.z.string().optional().describe("Default caller ID / from number"),
393
+ batch_id: import_zod2.z.string().optional(),
394
+ metadata: import_zod2.z.record(import_zod2.z.any()).optional(),
395
+ variables: import_zod2.z.record(import_zod2.z.any()).optional(),
396
+ post_call: import_zod2.z.record(import_zod2.z.any()).optional()
397
+ },
398
+ run: (client, params) => client.calls.batch(params)
399
+ }),
400
+ defineTool({
401
+ name: "end_call",
402
+ description: "WARNING: immediately terminates an in-progress call. The call transitions to completed and post-call processing runs.",
403
+ scope: "agent",
404
+ risk: "sensitive",
405
+ input: { call_id: import_zod2.z.string().describe("The call ID to end") },
406
+ run: (client, { call_id }) => client.calls.end(call_id)
407
+ }),
408
+ defineTool({
409
+ name: "add_call_participant",
410
+ description: "WARNING: dials/adds participants to a live call (warm transfer / conference). PSTN additions are billable.",
411
+ scope: "agent",
412
+ risk: "sensitive",
413
+ input: {
414
+ call_id: import_zod2.z.string().describe("The in-progress call ID"),
415
+ participants: import_zod2.z.array(import_zod2.z.object({
416
+ transport: import_zod2.z.enum(["webrtc", "pstn"]),
417
+ identity: import_zod2.z.string().describe("Destination phone number (pstn, E.164) or participant identity (webrtc)"),
418
+ from_number: import_zod2.z.string().optional().describe("Caller ID for pstn"),
419
+ metadata: import_zod2.z.record(import_zod2.z.any()).optional()
420
+ })).min(1)
421
+ },
422
+ run: (client, { call_id, participants }) => client.calls.addParticipants(call_id, { participants })
423
+ })
424
+ ];
425
+ var import_zod3 = require("zod");
426
+ var numberTools = [
427
+ defineTool({
428
+ name: "list_numbers",
429
+ description: "List all phone numbers on your Rymi account and which agent each is attached to.",
430
+ scope: "account",
431
+ risk: "read",
432
+ input: {
433
+ limit: import_zod3.z.number().int().min(1).max(500).optional(),
434
+ offset: import_zod3.z.number().int().min(0).optional()
435
+ },
436
+ run: (client, params) => client.numbers.list(params)
437
+ }),
438
+ defineTool({
439
+ name: "register_number",
440
+ description: "Register a phone number on your account, optionally attaching it to an agent.",
441
+ scope: "account",
442
+ risk: "write",
443
+ input: {
444
+ number: import_zod3.z.string().describe("Phone number in E.164 format (e.g. +14155550123)"),
445
+ agent_id: import_zod3.z.string().uuid().optional().describe("Agent to attach the number to on registration")
446
+ },
447
+ run: (client, { number, agent_id }) => client.numbers.register(number, agent_id ? { agent_id } : {})
448
+ }),
449
+ defineTool({
450
+ name: "attach_number",
451
+ description: "Attach an existing number to an agent so inbound calls route to it.",
452
+ scope: "account",
453
+ risk: "write",
454
+ input: {
455
+ number: import_zod3.z.string().describe("Phone number in E.164 format"),
456
+ agent_id: import_zod3.z.string().uuid().describe("Agent to route this number to")
457
+ },
458
+ run: (client, { number, agent_id }) => client.numbers.attach(number, agent_id)
459
+ }),
460
+ defineTool({
461
+ name: "remove_number",
462
+ description: "Remove a phone number from your account. The number stops routing to any agent.",
463
+ scope: "account",
464
+ risk: "sensitive",
465
+ input: { number: import_zod3.z.string().describe("Phone number in E.164 format") },
466
+ run: (client, { number }) => client.numbers.remove(number)
467
+ })
468
+ ];
469
+ var telephonyTools = [
470
+ defineTool({
471
+ name: "telephony_status",
472
+ description: "Report whether a telephony carrier is connected, and which provider/account.",
473
+ scope: "account",
474
+ risk: "read",
475
+ input: {},
476
+ run: (client) => client.telephony.status()
477
+ }),
478
+ defineTool({
479
+ name: "list_telephony_numbers",
480
+ description: "List numbers available on the connected telephony carrier account.",
481
+ scope: "account",
482
+ risk: "read",
483
+ input: {},
484
+ run: (client) => client.telephony.numbers()
485
+ })
486
+ ];
487
+ var keyTools = [
488
+ defineTool({
489
+ name: "list_publishable_keys",
490
+ description: "List publishable (browser-safe) keys and which agent/channels each is scoped to. Returns key prefixes only, never full secrets.",
491
+ scope: "account",
492
+ risk: "read",
493
+ input: {},
494
+ run: (client) => client.keys.listPublishable()
495
+ })
496
+ ];
497
+ var import_zod4 = require("zod");
498
+ var knowledgeTools = [
499
+ defineTool({
500
+ name: "list_knowledge_sources",
501
+ description: "List the knowledge sources (RAG context) attached to an agent.",
502
+ scope: "agent",
503
+ risk: "read",
504
+ input: { agent_id: import_zod4.z.string().uuid().describe("The agent UUID") },
505
+ run: (client, { agent_id }) => client.agents.listKnowledgeSources(agent_id)
506
+ }),
507
+ defineTool({
508
+ name: "list_agent_changes",
509
+ description: "List recorded configuration changes for an agent (for auditing or before an undo).",
510
+ scope: "agent",
511
+ risk: "read",
512
+ input: {
513
+ agent_id: import_zod4.z.string().uuid().describe("The agent UUID"),
514
+ since: import_zod4.z.string().optional().describe("ISO timestamp \u2014 only return changes after this time")
515
+ },
516
+ run: (client, { agent_id, since }) => client.agents.listChanges(agent_id, since ? { since } : {})
517
+ }),
518
+ defineTool({
519
+ name: "add_knowledge_source",
520
+ description: "Add a knowledge source to an agent from raw text or a URL (the URL is fetched and ingested).",
521
+ scope: "agent",
522
+ risk: "write",
523
+ input: {
524
+ agent_id: import_zod4.z.string().uuid().describe("The agent UUID"),
525
+ kind: import_zod4.z.enum(["text", "url"]).describe("Source type"),
526
+ title: import_zod4.z.string().min(1).describe("Human-readable title for the source"),
527
+ text: import_zod4.z.string().optional().describe('Required when kind="text": the raw content to ingest'),
528
+ url: import_zod4.z.string().url().optional().describe('Required when kind="url": the page to fetch and ingest')
529
+ },
530
+ run: (client, { agent_id, kind, title, text, url }) => {
531
+ const data = kind === "text" ? { kind: "text", title, text: text ?? "" } : { kind: "url", title, url: url ?? "" };
532
+ return client.agents.addKnowledgeSource(agent_id, data);
533
+ }
534
+ }),
535
+ defineTool({
536
+ name: "delete_knowledge_source",
537
+ description: "Delete a knowledge source from an agent.",
538
+ scope: "agent",
539
+ risk: "sensitive",
540
+ input: {
541
+ agent_id: import_zod4.z.string().uuid().describe("The agent UUID"),
542
+ source_id: import_zod4.z.string().describe("The knowledge source ID to delete")
543
+ },
544
+ run: (client, { agent_id, source_id }) => client.agents.deleteKnowledgeSource(agent_id, source_id)
545
+ }),
546
+ defineTool({
547
+ name: "undo_agent_change",
548
+ description: "Undo a single recorded configuration change, reverting that field to its previous value.",
549
+ scope: "agent",
550
+ risk: "write",
551
+ input: {
552
+ agent_id: import_zod4.z.string().uuid().describe("The agent UUID"),
553
+ change_id: import_zod4.z.string().describe("The change_id to undo (from list_agent_changes)")
554
+ },
555
+ run: (client, { agent_id, change_id }) => client.agents.undoChange(agent_id, change_id)
556
+ })
557
+ ];
558
+ var import_zod5 = require("zod");
559
+ var insightTools = [
560
+ defineTool({
561
+ name: "get_usage_summary",
562
+ 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.",
563
+ scope: "account",
564
+ risk: "read",
565
+ input: {},
566
+ run: (client) => client.billing.usageSummary()
567
+ }),
568
+ defineTool({
569
+ name: "list_agent_templates",
570
+ description: "List published agent templates. Use a template's `defaults` as the starting config for create_agent.",
571
+ scope: "agent",
572
+ risk: "read",
573
+ input: {},
574
+ run: (client) => client.templates.list()
575
+ }),
576
+ defineTool({
577
+ name: "list_eval_runs",
578
+ description: "List previous evaluation runs for an agent.",
579
+ scope: "agent",
580
+ risk: "read",
581
+ input: { agent_id: import_zod5.z.string().uuid().describe("The agent UUID") },
582
+ run: (client, { agent_id }) => client.agents.listEvalRuns(agent_id)
583
+ }),
584
+ defineTool({
585
+ name: "get_eval_run",
586
+ description: "Retrieve a single evaluation run, including per-scenario scores.",
587
+ scope: "agent",
588
+ risk: "read",
589
+ input: {
590
+ agent_id: import_zod5.z.string().uuid().describe("The agent UUID"),
591
+ run_id: import_zod5.z.string().describe("The evaluation run ID")
592
+ },
593
+ run: (client, { agent_id, run_id }) => client.agents.getEvalRun(agent_id, run_id)
594
+ }),
595
+ defineTool({
596
+ name: "run_evals",
597
+ 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).',
598
+ scope: "agent",
599
+ risk: "write",
600
+ input: {
601
+ agent_id: import_zod5.z.string().uuid().describe("The agent UUID to evaluate"),
602
+ mode: import_zod5.z.enum(["synthetic", "live"]).optional().describe("Evaluation mode (default synthetic)"),
603
+ judge: import_zod5.z.boolean().optional().describe("Supplement synthetic heuristics with the LLM judge (default false)")
604
+ },
605
+ run: (client, { agent_id, mode, judge }) => {
606
+ const params = {};
607
+ if (mode) params.mode = mode;
608
+ if (judge) params.judge = judge;
609
+ return client.agents.runEvals(agent_id, params);
610
+ }
611
+ }),
612
+ defineTool({
613
+ name: "run_eval_suite",
614
+ 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).",
615
+ scope: "agent",
616
+ risk: "write",
617
+ input: {
618
+ agent_ids: import_zod5.z.array(import_zod5.z.string().uuid()).min(1).describe("Agent UUIDs to evaluate"),
619
+ scenario_ids: import_zod5.z.array(import_zod5.z.string()).optional().describe("Optional subset of seeded scenario ids; omit to run all"),
620
+ concurrency: import_zod5.z.number().int().positive().optional().describe("Max agents evaluated in parallel (clamped to a safe ceiling)"),
621
+ judge: import_zod5.z.boolean().optional().describe("Supplement synthetic heuristics with the LLM judge (default false)")
622
+ },
623
+ run: (client, { agent_ids, scenario_ids, concurrency, judge }) => client.agents.runEvalSuite({
624
+ agentIds: agent_ids,
625
+ ...scenario_ids ? { scenarioIds: scenario_ids } : {},
626
+ ...concurrency ? { concurrency } : {},
627
+ ...judge ? { judge } : {}
628
+ })
629
+ })
630
+ ];
631
+ var import_zod6 = require("zod");
632
+ var publishTools = [
633
+ defineTool({
634
+ name: "publish_agent",
635
+ 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.",
636
+ scope: "agent",
637
+ risk: "sensitive",
638
+ input: {
639
+ agent_id: import_zod6.z.string().uuid().describe("The agent UUID to publish")
640
+ },
641
+ // Returns the raw { published, blockers? } result. Unlike the old MCP
642
+ // handler, a published:false-with-blockers response is NOT flagged as a
643
+ // protocol error — blockers are valid data every consumer (MCP client,
644
+ // studio harness) reads from the result directly.
645
+ run: (client, { agent_id }) => client.agents.publish(agent_id)
646
+ })
647
+ ];
648
+ var import_zod7 = require("zod");
649
+ var dncTools = [
650
+ defineTool({
651
+ name: "list_dnc",
652
+ description: "List all numbers on your Do-Not-Call registry.",
653
+ scope: "account",
654
+ risk: "read",
655
+ input: {
656
+ limit: import_zod7.z.number().int().min(1).max(500).optional(),
657
+ offset: import_zod7.z.number().int().min(0).optional()
658
+ },
659
+ run: (client, params) => client.dnc.list(params)
660
+ }),
661
+ defineTool({
662
+ name: "check_dnc",
663
+ description: "Check whether phone numbers are on the Do-Not-Call registry. Read-only \u2014 adds nothing.",
664
+ scope: "account",
665
+ risk: "read",
666
+ input: { phone_numbers: import_zod7.z.array(import_zod7.z.string()).min(1).max(500).describe("E.164 numbers to check") },
667
+ run: (client, { phone_numbers }) => client.dnc.check({ phone_numbers })
668
+ }),
669
+ defineTool({
670
+ name: "add_dnc",
671
+ description: "Add a phone number to the Do-Not-Call registry so outbound calls to it are blocked.",
672
+ scope: "account",
673
+ risk: "write",
674
+ input: {
675
+ phone_number: import_zod7.z.string().describe("Phone number (any format; normalized to E.164)"),
676
+ reason: import_zod7.z.string().optional()
677
+ },
678
+ run: (client, { phone_number, reason }) => client.dnc.add({ phone_number, reason })
679
+ }),
680
+ defineTool({
681
+ name: "add_dnc_batch",
682
+ description: "Add up to 1000 numbers to the Do-Not-Call registry. Invalid numbers are skipped and returned in `invalid`.",
683
+ scope: "account",
684
+ risk: "write",
685
+ input: {
686
+ phone_numbers: import_zod7.z.array(import_zod7.z.string()).min(1).max(1e3),
687
+ reason: import_zod7.z.string().optional()
688
+ },
689
+ run: (client, { phone_numbers, reason }) => client.dnc.addBatch({ phone_numbers, reason })
690
+ }),
691
+ defineTool({
692
+ name: "remove_dnc",
693
+ description: "WARNING: removes a number from the Do-Not-Call registry, re-enabling outbound calls to it.",
694
+ scope: "account",
695
+ risk: "sensitive",
696
+ input: { phone: import_zod7.z.string().describe("Phone number to remove") },
697
+ run: (client, { phone }) => client.dnc.remove(phone)
698
+ })
699
+ ];
700
+ var import_zod8 = require("zod");
701
+ var import_crypto = require("crypto");
702
+ var webhookTools = [
703
+ defineTool({
704
+ name: "list_webhooks",
705
+ description: "List your registered webhook endpoints. Signing secrets are never returned.",
706
+ scope: "account",
707
+ risk: "read",
708
+ input: {
709
+ limit: import_zod8.z.number().int().min(1).max(500).optional(),
710
+ offset: import_zod8.z.number().int().min(0).optional()
711
+ },
712
+ run: (client, params) => client.webhooks.list(params)
713
+ }),
714
+ defineTool({
715
+ name: "create_webhook",
716
+ 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.",
717
+ scope: "account",
718
+ risk: "write",
719
+ input: {
720
+ url: import_zod8.z.string().url().describe("Public https endpoint"),
721
+ events: import_zod8.z.array(import_zod8.z.string()).min(1).describe("Event names to subscribe to"),
722
+ secret: import_zod8.z.string().min(16).max(256).optional().describe("Signing secret; auto-generated if omitted"),
723
+ alert_email: import_zod8.z.string().email().optional()
724
+ },
725
+ run: async (client, { url, events, secret, alert_email }) => {
726
+ const finalSecret = secret ?? (0, import_crypto.randomBytes)(24).toString("hex");
727
+ const result = await client.webhooks.create({ url, events, secret: finalSecret, alert_email });
728
+ return secret ? result : { ...result, secret: finalSecret, secret_notice: "Store this secret now \u2014 it cannot be retrieved later." };
729
+ }
730
+ }),
731
+ defineTool({
732
+ name: "update_webhook",
733
+ description: "Update a webhook endpoint. Only provided fields change. Pass `secret` to rotate it.",
734
+ scope: "account",
735
+ risk: "write",
736
+ input: {
737
+ id: import_zod8.z.string(),
738
+ url: import_zod8.z.string().url().optional(),
739
+ events: import_zod8.z.array(import_zod8.z.string()).min(1).optional(),
740
+ secret: import_zod8.z.string().min(16).max(256).optional(),
741
+ alert_email: import_zod8.z.string().email().nullable().optional()
742
+ },
743
+ run: (client, { id, ...rest }) => client.webhooks.update(id, rest)
744
+ }),
745
+ defineTool({
746
+ name: "delete_webhook",
747
+ description: "WARNING: stops event delivery to this endpoint.",
748
+ scope: "account",
749
+ risk: "sensitive",
750
+ input: { id: import_zod8.z.string() },
751
+ run: (client, { id }) => client.webhooks.delete(id)
752
+ })
753
+ ];
754
+ var import_zod9 = require("zod");
755
+ var billingTools = [
756
+ defineTool({
757
+ name: "estimate_call_cost",
758
+ 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.",
759
+ scope: "account",
760
+ risk: "read",
761
+ input: {
762
+ stt_model: import_zod9.z.string().optional(),
763
+ llm_model: import_zod9.z.string().optional(),
764
+ tts_model: import_zod9.z.string().optional(),
765
+ duration_seconds: import_zod9.z.number().min(0).optional()
766
+ },
767
+ run: (client, params) => client.billing.estimate(params)
768
+ }),
769
+ defineTool({
770
+ name: "set_auto_recharge",
771
+ description: "Configure auto-recharge. `pack_usd` must exceed `threshold_usd` or the API rejects it (recharge-loop guard).",
772
+ scope: "account",
773
+ risk: "sensitive",
774
+ input: {
775
+ enabled: import_zod9.z.boolean().optional(),
776
+ pack_usd: import_zod9.z.number().min(1).max(1e3).optional(),
777
+ threshold_usd: import_zod9.z.number().min(0).max(100).optional()
778
+ },
779
+ run: (client, params) => client.billing.setAutoRecharge(params)
780
+ }),
781
+ defineTool({
782
+ name: "set_spend_alerts",
783
+ description: "Configure spend-alert thresholds and low-balance / email preferences.",
784
+ scope: "account",
785
+ risk: "write",
786
+ input: {
787
+ thresholds_usd: import_zod9.z.array(import_zod9.z.number()).max(10).optional(),
788
+ low_balance_pct: import_zod9.z.number().min(0).max(100).optional(),
789
+ email_enabled: import_zod9.z.boolean().optional()
790
+ },
791
+ run: (client, params) => client.billing.setAlerts(params)
792
+ })
793
+ ];
794
+ var import_zod10 = require("zod");
795
+ var campaignTools = [
796
+ // ─── reads ───────────────────────────────────────────────────────────────
797
+ defineTool({
798
+ name: "list_campaigns",
799
+ description: "List campaigns for the authenticated tenant, paginated.",
800
+ scope: "agent",
801
+ risk: "read",
802
+ input: {
803
+ limit: import_zod10.z.number().int().min(1).max(200).optional().describe("Max campaigns to return"),
804
+ offset: import_zod10.z.number().int().min(0).optional().describe("Pagination offset"),
805
+ status: import_zod10.z.enum(["draft", "scheduled", "running", "paused", "completed", "failed", "archived"]).optional().describe("Filter by campaign status"),
806
+ type: import_zod10.z.enum(["outbound", "inbound"]).optional().describe("Filter by campaign type"),
807
+ agent_id: import_zod10.z.string().uuid().optional().describe("Filter by agent")
808
+ },
809
+ run: (client, params) => client.campaigns.list(params)
810
+ }),
811
+ defineTool({
812
+ name: "get_campaign",
813
+ description: "Retrieve a single campaign by id, including its goal, policies, status, and stat counters.",
814
+ scope: "agent",
815
+ risk: "read",
816
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID") },
817
+ run: (client, { campaign_id }) => client.campaigns.get(campaign_id)
818
+ }),
819
+ defineTool({
820
+ name: "get_campaign_report",
821
+ description: "Retrieve the campaign report: stat_* summary counters plus outcome/sentiment/hourly/snapshot distributions. Cost is reported in credits, durations in minutes.",
822
+ scope: "agent",
823
+ risk: "read",
824
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID") },
825
+ run: (client, { campaign_id }) => client.campaigns.report(campaign_id)
826
+ }),
827
+ defineTool({
828
+ name: "list_campaign_attempts",
829
+ description: "List a campaign's dial attempts, paginated.",
830
+ scope: "agent",
831
+ risk: "read",
832
+ input: {
833
+ campaign_id: import_zod10.z.string().describe("The campaign ID"),
834
+ limit: import_zod10.z.number().int().min(1).max(200).optional(),
835
+ offset: import_zod10.z.number().int().min(0).optional(),
836
+ status: import_zod10.z.string().optional().describe("Filter by attempt status (e.g. completed, failed, skipped)")
837
+ },
838
+ run: (client, { campaign_id, ...params }) => client.campaigns.attempts(campaign_id, params)
839
+ }),
840
+ defineTool({
841
+ name: "list_campaign_suggestions",
842
+ description: "List a campaign's improvement suggestions (from the detectors + LLM proposal engine), paginated.",
843
+ scope: "agent",
844
+ risk: "read",
845
+ input: {
846
+ campaign_id: import_zod10.z.string().describe("The campaign ID"),
847
+ limit: import_zod10.z.number().int().min(1).max(200).optional(),
848
+ offset: import_zod10.z.number().int().min(0).optional(),
849
+ status: import_zod10.z.string().optional().describe("Filter by suggestion status (proposed, accepted, dismissed)")
850
+ },
851
+ run: (client, { campaign_id, ...params }) => client.campaigns.suggestions(campaign_id, params)
852
+ }),
853
+ defineTool({
854
+ name: "list_contacts",
855
+ description: "List tenant-level contacts, paginated.",
856
+ scope: "agent",
857
+ risk: "read",
858
+ input: {
859
+ limit: import_zod10.z.number().int().min(1).max(200).optional(),
860
+ offset: import_zod10.z.number().int().min(0).optional()
861
+ },
862
+ run: (client, params) => client.contacts.list(params)
863
+ }),
864
+ // ─── writes ──────────────────────────────────────────────────────────────
865
+ defineTool({
866
+ name: "create_campaign",
867
+ description: "Create a new campaign in draft status against a published agent. Does not launch it or place any calls.",
868
+ scope: "agent",
869
+ risk: "write",
870
+ input: {
871
+ agent_id: import_zod10.z.string().uuid().describe("The agent that will handle campaign calls"),
872
+ type: import_zod10.z.enum(["outbound", "inbound"]).describe("Campaign type"),
873
+ name: import_zod10.z.string().describe("Human-readable campaign name"),
874
+ goal: import_zod10.z.object({
875
+ goal_type: import_zod10.z.string(),
876
+ success_field: import_zod10.z.string(),
877
+ success_when: import_zod10.z.record(import_zod10.z.any()),
878
+ secondary_fields: import_zod10.z.array(import_zod10.z.string()).optional(),
879
+ human_label: import_zod10.z.string()
880
+ }).optional().describe("The success goal this campaign is measured against"),
881
+ schedule_policy: import_zod10.z.record(import_zod10.z.any()).optional(),
882
+ retry_policy: import_zod10.z.record(import_zod10.z.any()).optional(),
883
+ concurrency_policy: import_zod10.z.record(import_zod10.z.any()).optional(),
884
+ automation_policy: import_zod10.z.record(import_zod10.z.any()).optional(),
885
+ reporting_policy: import_zod10.z.record(import_zod10.z.any()).optional(),
886
+ metadata: import_zod10.z.record(import_zod10.z.any()).optional()
887
+ },
888
+ run: (client, params) => client.campaigns.create(params)
889
+ }),
890
+ defineTool({
891
+ name: "import_campaign_contacts",
892
+ description: "Import inline contact rows (JSON or CSV) into a campaign: creates/merges contacts and attaches them as campaign members in one call.",
893
+ scope: "agent",
894
+ risk: "write",
895
+ input: {
896
+ campaign_id: import_zod10.z.string().describe("The campaign ID"),
897
+ contacts: import_zod10.z.array(import_zod10.z.record(import_zod10.z.any())).optional().describe("Inline contact rows as JSON objects"),
898
+ csv: import_zod10.z.string().optional().describe("Contact rows as CSV text")
899
+ },
900
+ run: (client, { campaign_id, ...data }) => client.campaigns.members.import(campaign_id, data)
901
+ }),
902
+ defineTool({
903
+ name: "launch_campaign",
904
+ 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.",
905
+ scope: "agent",
906
+ risk: "sensitive",
907
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID to launch") },
908
+ run: (client, { campaign_id }) => client.campaigns.launch(campaign_id)
909
+ }),
910
+ defineTool({
911
+ name: "pause_campaign",
912
+ description: "Pause a running campaign. In-flight calls finish naturally; no new attempts are scheduled until resumed.",
913
+ scope: "agent",
914
+ risk: "write",
915
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID to pause") },
916
+ run: (client, { campaign_id }) => client.campaigns.pause(campaign_id)
917
+ }),
918
+ defineTool({
919
+ name: "resume_campaign",
920
+ description: "Resume a paused campaign. WARNING: resuming re-enables outbound dialing and will incur further per-minute charges as attempts continue.",
921
+ scope: "agent",
922
+ risk: "sensitive",
923
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID to resume") },
924
+ run: (client, { campaign_id }) => client.campaigns.resume(campaign_id)
925
+ }),
926
+ defineTool({
927
+ name: "accept_campaign_suggestion",
928
+ description: "Accept an improvement suggestion. Agent-editing suggestions create an agent-changes draft for review; campaign-policy suggestions patch the campaign directly.",
929
+ scope: "agent",
930
+ risk: "write",
931
+ input: {
932
+ campaign_id: import_zod10.z.string().describe("The campaign ID"),
933
+ suggestion_id: import_zod10.z.string().describe("The suggestion ID to accept")
934
+ },
935
+ run: (client, { campaign_id, suggestion_id }) => client.campaigns.acceptSuggestion(campaign_id, suggestion_id)
936
+ })
937
+ ];
938
+ var opsToolCatalog2 = [
939
+ ...agentTools,
940
+ ...callTools,
941
+ ...numberTools,
942
+ ...telephonyTools,
943
+ ...keyTools,
944
+ ...knowledgeTools,
945
+ ...insightTools,
946
+ ...publishTools,
947
+ ...dncTools,
948
+ ...webhookTools,
949
+ ...billingTools,
950
+ ...campaignTools
951
+ ];
952
+ var names = /* @__PURE__ */ new Set();
953
+ for (const tool of opsToolCatalog2) {
954
+ if (names.has(tool.name)) throw new Error(`Duplicate ops tool name: ${tool.name}`);
955
+ names.add(tool.name);
279
956
  }
280
- );
281
- }
957
+ }
958
+ });
282
959
 
283
- // src/tools/calls.ts
284
- var import_zod2 = require("zod");
960
+ // src/server.ts
961
+ var import_mcp = require("@modelcontextprotocol/sdk/server/mcp.js");
962
+ var import_node = __toESM(require("@rymi/node"));
963
+ var import_ops_tools = __toESM(require_dist());
285
964
 
286
965
  // src/utils/errors.ts
287
966
  function handleMcpError(err) {
@@ -301,578 +980,10 @@ function withToolErrors(handler) {
301
980
  };
302
981
  }
303
982
 
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
983
  // package.json
873
984
  var package_default = {
874
985
  name: "@rymi/mcp",
875
- version: "0.6.2",
986
+ version: "2.0.0",
876
987
  description: "Rymi MCP server \u2014 manage AI voice agents via Model Context Protocol",
877
988
  license: "MIT",
878
989
  author: "Rymi AI <engineering@rymi.live>",
@@ -920,6 +1031,7 @@ var package_default = {
920
1031
  zod: "^3.23.0"
921
1032
  },
922
1033
  devDependencies: {
1034
+ "@rymi/ops-tools": "workspace:*",
923
1035
  tsup: "^8.0.0",
924
1036
  typescript: "^5.0.0",
925
1037
  vitest: "^1.6.0"
@@ -932,85 +1044,55 @@ var package_default = {
932
1044
  };
933
1045
 
934
1046
  // src/server.ts
935
- function createServer(apiKey) {
936
- const rymi = new import_node.default({ apiKey });
937
- const server = new import_mcp.McpServer({
1047
+ function createServer(apiKey2) {
1048
+ const rymi = new import_node.default({ apiKey: apiKey2 });
1049
+ const server2 = new import_mcp.McpServer({
938
1050
  name: "rymi",
939
1051
  version: package_default.version
940
1052
  });
941
1053
  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);
1054
+ for (const tool of import_ops_tools.opsToolCatalog) {
1055
+ if (isReadOnly && tool.risk !== "read") continue;
1056
+ server2.tool(
1057
+ tool.name,
1058
+ tool.description,
1059
+ tool.input,
1060
+ withToolErrors(async (params) => {
1061
+ const result = await tool.run(rymi, params);
1062
+ return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
1063
+ })
1064
+ );
956
1065
  }
957
- return server;
1066
+ return server2;
958
1067
  }
959
1068
 
960
1069
  // src/transport/stdio.ts
961
1070
  var import_stdio = require("@modelcontextprotocol/sdk/server/stdio.js");
962
- async function runStdio(server) {
1071
+ async function runStdio(server2) {
963
1072
  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
- });
1073
+ await server2.connect(transport2);
994
1074
  }
995
1075
 
996
1076
  // src/index.ts
997
1077
  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
- });
1078
+ if (transport !== "stdio") {
1079
+ process.stderr.write(
1080
+ `Error: --transport ${transport} was removed in @rymi/mcp 2.0.0; this package is stdio-only.
1081
+ For an HTTP endpoint use the hosted one: https://mcp.rymi.live/mcp
1082
+ \u2014 add it as a custom connector (OAuth, no key), or pass a rymi_ secret key as a Bearer token.
1083
+ See https://docs.rymi.live/api/mcp
1084
+ `
1085
+ );
1086
+ process.exit(1);
1016
1087
  }
1088
+ var apiKey = process.env.RYMI_API_KEY || "";
1089
+ if (!apiKey) {
1090
+ process.stderr.write("Error: RYMI_API_KEY environment variable is required.\n");
1091
+ process.exit(1);
1092
+ }
1093
+ var server = createServer(apiKey);
1094
+ runStdio(server).catch((err) => {
1095
+ process.stderr.write(`Fatal: ${err.message}
1096
+ `);
1097
+ process.exit(1);
1098
+ });