@rymi/mcp 2.0.0 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -0
  2. package/dist/index.js +579 -15
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -76,6 +76,14 @@ Run the server on your own machine — it speaks **stdio** by default and talks
76
76
  <td><code>list_agents</code> · <code>get_agent</code> · <code>create_agent</code> · <code>update_agent</code> · <code>delete_agent</code> · <code>clone_agent</code> · <code>apply_agent_changes</code> · <code>generate_agent_draft</code> · <code>enrich_company</code> · <code>validate_agent_publish</code> · <code>preview_stack</code></td>
77
77
  </tr>
78
78
  <tr>
79
+ <td><b>API tools & secrets</b></td>
80
+ <td><code>list_agent_tools</code> · <code>add_agent_tool</code> · <code>update_agent_tool</code> · <code>remove_agent_tool</code> · <code>list_tool_secrets</code> · <code>set_tool_secret</code></td>
81
+ </tr>
82
+ <tr>
83
+ <td><b>Share link</b></td>
84
+ <td><code>get_share_link</code> · <code>set_share_link</code> · <code>regenerate_share_link</code></td>
85
+ </tr>
86
+ <tr>
79
87
  <td><b>Discovery</b></td>
80
88
  <td><code>list_llm_options</code> · <code>list_voices</code></td>
81
89
  </tr>
package/dist/index.js CHANGED
@@ -6,9 +6,16 @@ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
6
6
  var __getOwnPropNames = Object.getOwnPropertyNames;
7
7
  var __getProtoOf = Object.getPrototypeOf;
8
8
  var __hasOwnProp = Object.prototype.hasOwnProperty;
9
+ var __esm = (fn, res) => function __init() {
10
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
11
+ };
9
12
  var __commonJS = (cb, mod) => function __require() {
10
13
  return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
11
14
  };
15
+ var __export = (target, all) => {
16
+ for (var name in all)
17
+ __defProp(target, name, { get: all[name], enumerable: true });
18
+ };
12
19
  var __copyProps = (to, from, except, desc) => {
13
20
  if (from && typeof from === "object" || typeof from === "function") {
14
21
  for (let key of __getOwnPropNames(from))
@@ -25,6 +32,260 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
25
32
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
33
  mod
27
34
  ));
35
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
+
37
+ // ../shared-types/dist/agentTools.js
38
+ var agentTools_exports = {};
39
+ __export(agentTools_exports, {
40
+ API_SECRETS_PROVIDER: () => API_SECRETS_PROVIDER,
41
+ API_TOOL_AUTH_HEADER_PATTERN: () => API_TOOL_AUTH_HEADER_PATTERN,
42
+ API_TOOL_NAME_PATTERN: () => API_TOOL_NAME_PATTERN,
43
+ API_TOOL_RESERVED_FIELD_NAMES: () => API_TOOL_RESERVED_FIELD_NAMES,
44
+ API_TOOL_SECRET_REF_PATTERN: () => API_TOOL_SECRET_REF_PATTERN,
45
+ BUILTIN_TOOL_CATALOG: () => BUILTIN_TOOL_CATALOG,
46
+ SECRET_NAME_PATTERN: () => SECRET_NAME_PATTERN,
47
+ toToolCapabilitySafeView: () => toToolCapabilitySafeView
48
+ });
49
+ function toToolCapabilitySafeView(state) {
50
+ return {
51
+ toolId: state.toolId,
52
+ available: state.authorized && state.runtimeRegistered,
53
+ unavailableReason: state.unavailableReason,
54
+ sideEffect: state.sideEffect
55
+ };
56
+ }
57
+ var API_TOOL_NAME_PATTERN, SECRET_NAME_PATTERN, API_SECRETS_PROVIDER, API_TOOL_RESERVED_FIELD_NAMES, API_TOOL_AUTH_HEADER_PATTERN, API_TOOL_SECRET_REF_PATTERN, BUILTIN_TOOL_CATALOG;
58
+ var init_agentTools = __esm({
59
+ "../shared-types/dist/agentTools.js"() {
60
+ "use strict";
61
+ API_TOOL_NAME_PATTERN = /^[a-z][a-z0-9_]{2,40}$/;
62
+ SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]{1,63}$/;
63
+ API_SECRETS_PROVIDER = "api_secrets";
64
+ API_TOOL_RESERVED_FIELD_NAMES = /* @__PURE__ */ new Set([
65
+ "False",
66
+ "None",
67
+ "True",
68
+ "and",
69
+ "as",
70
+ "assert",
71
+ "async",
72
+ "await",
73
+ "break",
74
+ "class",
75
+ "continue",
76
+ "def",
77
+ "del",
78
+ "elif",
79
+ "else",
80
+ "except",
81
+ "finally",
82
+ "for",
83
+ "from",
84
+ "global",
85
+ "if",
86
+ "import",
87
+ "in",
88
+ "is",
89
+ "lambda",
90
+ "nonlocal",
91
+ "not",
92
+ "or",
93
+ "pass",
94
+ "raise",
95
+ "return",
96
+ "try",
97
+ "while",
98
+ "with",
99
+ "yield",
100
+ "params"
101
+ ]);
102
+ API_TOOL_AUTH_HEADER_PATTERN = /^(authorization|x-api-key|api[-_]?key|.*token.*|.*secret.*)$/i;
103
+ API_TOOL_SECRET_REF_PATTERN = /\{\{\s*secrets\.[A-Z][A-Z0-9_]{1,63}\s*\}\}/;
104
+ BUILTIN_TOOL_CATALOG = [
105
+ {
106
+ tool_id: "handoff_to_human",
107
+ label: "Human handoff",
108
+ description: "Transfer the live call to the configured human destination when escalation is required.",
109
+ category: "call_control",
110
+ side_effect: "write",
111
+ default_timeout_ms: 1e3,
112
+ requires_credential: false
113
+ },
114
+ {
115
+ tool_id: "check_calendar_availability",
116
+ label: "Check calendar availability",
117
+ description: "Look up open slots in the configured calendar provider.",
118
+ category: "calendar",
119
+ side_effect: "read",
120
+ default_timeout_ms: 4e3,
121
+ requires_credential: true
122
+ },
123
+ {
124
+ tool_id: "list_calendar_events",
125
+ label: "List calendar events",
126
+ description: "Read scheduled events from the configured calendar provider.",
127
+ category: "calendar",
128
+ side_effect: "read",
129
+ default_timeout_ms: 4e3,
130
+ requires_credential: true
131
+ },
132
+ {
133
+ tool_id: "create_calendar_event",
134
+ label: "Create calendar event",
135
+ description: "Create a calendar event after the caller explicitly confirms the details.",
136
+ category: "calendar",
137
+ side_effect: "write",
138
+ default_timeout_ms: 6e3,
139
+ requires_credential: true
140
+ },
141
+ {
142
+ tool_id: "update_calendar_event",
143
+ label: "Update calendar event",
144
+ description: "Move or edit an existing calendar event after explicit caller confirmation.",
145
+ category: "calendar",
146
+ side_effect: "write",
147
+ default_timeout_ms: 6e3,
148
+ requires_credential: true
149
+ },
150
+ {
151
+ tool_id: "delete_calendar_event",
152
+ label: "Delete calendar event",
153
+ description: "Cancel an existing calendar event after explicit caller confirmation.",
154
+ category: "calendar",
155
+ side_effect: "write",
156
+ default_timeout_ms: 6e3,
157
+ requires_credential: true
158
+ },
159
+ {
160
+ tool_id: "lookup_customer",
161
+ label: "Look up customer",
162
+ description: "Find a customer record by phone, email, or external id.",
163
+ category: "crm",
164
+ side_effect: "read",
165
+ default_timeout_ms: 4e3,
166
+ requires_credential: true
167
+ },
168
+ {
169
+ tool_id: "send_whatsapp_message",
170
+ label: "Send WhatsApp message",
171
+ description: "Send a WhatsApp message to the caller mid-call (e.g. confirmation link, address, brochure URL). Outside the 24h customer-care window an approved template must be configured.",
172
+ category: "messaging",
173
+ side_effect: "write",
174
+ default_timeout_ms: 6e3,
175
+ requires_credential: true
176
+ },
177
+ {
178
+ tool_id: "send_telegram_message",
179
+ label: "Send Telegram message",
180
+ description: "Send a Telegram bot message to the caller mid-call. Requires the caller to have an established chat_id with the tenant's bot (no cold-send by phone).",
181
+ category: "messaging",
182
+ side_effect: "write",
183
+ default_timeout_ms: 6e3,
184
+ requires_credential: true
185
+ },
186
+ {
187
+ tool_id: "send_asset",
188
+ label: "Send asset",
189
+ description: "Send a configured shareable asset to the caller when they explicitly request a document, brochure, or guide.",
190
+ category: "messaging",
191
+ side_effect: "write",
192
+ default_timeout_ms: 6e3,
193
+ requires_credential: true
194
+ },
195
+ {
196
+ tool_id: "lookup_ticket",
197
+ label: "Look up ticket",
198
+ description: "Find a support ticket by its number, or the caller's most recent ticket, and read back its status.",
199
+ category: "ticketing",
200
+ side_effect: "read",
201
+ default_timeout_ms: 4e3,
202
+ requires_credential: true
203
+ },
204
+ {
205
+ tool_id: "create_ticket",
206
+ label: "Create ticket",
207
+ description: "Raise a support ticket from the call after the caller confirms the details, and read the ticket number back to them.",
208
+ category: "ticketing",
209
+ side_effect: "write",
210
+ default_timeout_ms: 6e3,
211
+ requires_credential: true
212
+ },
213
+ {
214
+ tool_id: "call_webhook",
215
+ label: "API request",
216
+ description: "Call your own HTTPS API mid-call, to look something up or to send details. Each API tool has its own name, purpose, typed parameters and headers; header secrets stay encrypted.",
217
+ category: "webhook",
218
+ side_effect: "read",
219
+ default_timeout_ms: 6e3,
220
+ // The endpoint lives in the binding's provider_settings and its secrets in
221
+ // the tenant's api_secrets row — nothing to connect in Settings → Connectors.
222
+ requires_credential: false
223
+ }
224
+ ];
225
+ }
226
+ });
227
+
228
+ // ../shared-types/dist/safeUrl.js
229
+ var safeUrl_exports = {};
230
+ __export(safeUrl_exports, {
231
+ isSafeUrl: () => isSafeUrl
232
+ });
233
+ function unmapIpv4(v6) {
234
+ const dotted = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(v6);
235
+ if (dotted)
236
+ return dotted[1];
237
+ const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(v6);
238
+ if (!hex)
239
+ return null;
240
+ const high = parseInt(hex[1], 16);
241
+ const low = parseInt(hex[2], 16);
242
+ return [high >> 8, high & 255, low >> 8, low & 255].join(".");
243
+ }
244
+ function isBlockedHost(host) {
245
+ const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
246
+ if (bare === "localhost" || bare === "0.0.0.0" || bare === "::")
247
+ return true;
248
+ if (bare.endsWith(".local") || bare.endsWith(".internal") || bare.endsWith(".localhost"))
249
+ return true;
250
+ if (/^127\./.test(bare))
251
+ return true;
252
+ if (/^10\./.test(bare))
253
+ return true;
254
+ if (/^192\.168\./.test(bare))
255
+ return true;
256
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(bare))
257
+ return true;
258
+ if (/^169\.254\./.test(bare))
259
+ return true;
260
+ const v6 = bare.toLowerCase();
261
+ if (v6 === "::1")
262
+ return true;
263
+ if (/^f[cd][0-9a-f]{2}:/.test(v6))
264
+ return true;
265
+ if (/^fe[89ab][0-9a-f]:/.test(v6))
266
+ return true;
267
+ const mapped = unmapIpv4(v6);
268
+ if (mapped)
269
+ return isBlockedHost(mapped);
270
+ return false;
271
+ }
272
+ function isSafeUrl(raw, options = {}) {
273
+ const protocols = options.protocols ?? ["http:", "https:"];
274
+ let url;
275
+ try {
276
+ url = new URL(raw);
277
+ } catch {
278
+ return false;
279
+ }
280
+ if (!protocols.includes(url.protocol))
281
+ return false;
282
+ return !isBlockedHost(url.hostname.toLowerCase());
283
+ }
284
+ var init_safeUrl = __esm({
285
+ "../shared-types/dist/safeUrl.js"() {
286
+ "use strict";
287
+ }
288
+ });
28
289
 
29
290
  // ../ops-tools/dist/index.js
30
291
  var require_dist = __commonJS({
@@ -34,7 +295,7 @@ var require_dist = __commonJS({
34
295
  var __getOwnPropDesc2 = Object.getOwnPropertyDescriptor;
35
296
  var __getOwnPropNames2 = Object.getOwnPropertyNames;
36
297
  var __hasOwnProp2 = Object.prototype.hasOwnProperty;
37
- var __export = (target, all) => {
298
+ var __export2 = (target, all) => {
38
299
  for (var name in all)
39
300
  __defProp2(target, name, { get: all[name], enumerable: true });
40
301
  };
@@ -46,14 +307,14 @@ var require_dist = __commonJS({
46
307
  }
47
308
  return to;
48
309
  };
49
- var __toCommonJS = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
310
+ var __toCommonJS2 = (mod) => __copyProps2(__defProp2({}, "__esModule", { value: true }), mod);
50
311
  var index_exports = {};
51
- __export(index_exports, {
312
+ __export2(index_exports, {
52
313
  defineTool: () => defineTool,
53
314
  opsToolCatalog: () => opsToolCatalog2,
54
315
  resolveToolDisposition: () => resolveToolDisposition
55
316
  });
56
- module2.exports = __toCommonJS(index_exports);
317
+ module2.exports = __toCommonJS2(index_exports);
57
318
  function defineTool(def) {
58
319
  return def;
59
320
  }
@@ -64,6 +325,20 @@ var require_dist = __commonJS({
64
325
  return p.risk === "write" ? "propose" : "confirm";
65
326
  }
66
327
  var import_zod = require("zod");
328
+ var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
329
+ var norm = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, "");
330
+ async function resolveAgentId(client, agentId) {
331
+ const raw = String(agentId ?? "").trim();
332
+ if (UUID_RE.test(raw)) return raw;
333
+ const { agents } = await client.agents.list({ limit: 500 });
334
+ const target = norm(raw);
335
+ const matches = agents.filter((a) => norm(a.name ?? "") === target);
336
+ if (matches.length === 1) return matches[0].id;
337
+ if (matches.length > 1) {
338
+ throw new Error(`"${raw}" matches ${matches.length} agents \u2014 call list_agents and pass the exact agent UUID.`);
339
+ }
340
+ throw new Error(`No agent named "${raw}" found \u2014 call list_agents to get a valid agent UUID.`);
341
+ }
67
342
  var agentConfigFields = {
68
343
  system_prompt: import_zod.z.string().optional().describe("Full system prompt for the agent. If given without persona/playbook, the server auto-structures it."),
69
344
  voice: import_zod.z.string().optional().describe('Voice ID (e.g. "Aoede", "Charon"). Call list_llm_options for valid values. Leave empty for realtime LLMs and Deepgram TTS, which carry their own voice.'),
@@ -86,7 +361,7 @@ var require_dist = __commonJS({
86
361
  custom_voice_mode: import_zod.z.enum(["rymi", "openai-compat"]).optional().describe("Wire format for custom_voice_url."),
87
362
  custom_transcriber_url: import_zod.z.string().nullable().optional().describe("Self-hosted STT endpoint (https:// or wss://). Enterprise only; null clears."),
88
363
  prompt_mode: import_zod.z.enum(["builder", "raw"]).optional().describe('How the system prompt is interpreted: "builder" (server structures persona/playbook from system_prompt) or "raw" (use system_prompt verbatim \u2014 requires system_prompt). Defaults to "builder".'),
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."),
364
+ persona: import_zod.z.record(import_zod.z.any()).optional().describe("Persona object. persona.name is the name the agent speaks and introduces itself as (also shown on share/tester pages); it defaults to the label when omitted. Note: callerPersonas must be objects of shape {type, approach, detectedWhen}, NOT plain strings."),
90
365
  playbook: import_zod.z.record(import_zod.z.any()).optional().describe("Playbook configuration object"),
91
366
  advanced: import_zod.z.record(import_zod.z.any()).optional(),
92
367
  features: import_zod.z.record(import_zod.z.any()).optional(),
@@ -97,6 +372,25 @@ var require_dist = __commonJS({
97
372
  if (!prompt_mode) return rest;
98
373
  return { ...rest, advanced: { ...rest.advanced || {}, prompt_mode } };
99
374
  }
375
+ var LLM_PROVIDER_ALIASES = {
376
+ google: "gemini",
377
+ googleai: "gemini",
378
+ "google-genai": "gemini",
379
+ "google-ai": "gemini",
380
+ vertex: "gemini",
381
+ vertexai: "gemini",
382
+ "vertex-ai": "gemini",
383
+ claude: "anthropic",
384
+ gpt: "openai",
385
+ "azure-openai": "openai",
386
+ azure: "openai"
387
+ };
388
+ function normalizeLlmProvider(params) {
389
+ const p = params.llm_provider;
390
+ if (!p) return params;
391
+ const alias = LLM_PROVIDER_ALIASES[p.toLowerCase()];
392
+ return alias ? { ...params, llm_provider: alias } : params;
393
+ }
100
394
  var agentTools = [
101
395
  defineTool({
102
396
  name: "list_agents",
@@ -248,10 +542,10 @@ var require_dist = __commonJS({
248
542
  scope: "agent",
249
543
  risk: "write",
250
544
  input: {
251
- name: import_zod.z.string().min(1).describe("Agent display name"),
545
+ name: import_zod.z.string().min(1).describe("Studio label: the name shown in the sidebar/agent list only. The name the agent speaks is persona.name."),
252
546
  ...agentConfigFields
253
547
  },
254
- run: (client, params) => client.agents.create(foldPromptMode(params))
548
+ run: (client, params) => client.agents.create(normalizeLlmProvider(foldPromptMode(params)))
255
549
  }),
256
550
  defineTool({
257
551
  name: "update_agent",
@@ -260,11 +554,11 @@ var require_dist = __commonJS({
260
554
  risk: "write",
261
555
  input: {
262
556
  agent_id: import_zod.z.string().uuid().describe("The agent UUID to update"),
263
- name: import_zod.z.string().optional(),
557
+ name: import_zod.z.string().optional().describe("Studio label (sidebar/agent list only). To change the spoken name, set persona.name."),
264
558
  ...agentConfigFields,
265
559
  chat_summary: import_zod.z.string().nullable().optional()
266
560
  },
267
- run: (client, { agent_id, ...params }) => client.agents.update(agent_id, foldPromptMode(params))
561
+ run: async (client, { agent_id, ...params }) => client.agents.update(await resolveAgentId(client, agent_id), normalizeLlmProvider(foldPromptMode(params)))
268
562
  }),
269
563
  defineTool({
270
564
  name: "delete_agent",
@@ -272,7 +566,7 @@ var require_dist = __commonJS({
272
566
  scope: "agent",
273
567
  risk: "sensitive",
274
568
  input: { agent_id: import_zod.z.string().uuid().describe("The agent UUID to delete") },
275
- run: (client, { agent_id }) => client.agents.delete(agent_id)
569
+ run: async (client, { agent_id }) => client.agents.delete(await resolveAgentId(client, agent_id))
276
570
  }),
277
571
  defineTool({
278
572
  name: "clone_agent",
@@ -280,7 +574,7 @@ var require_dist = __commonJS({
280
574
  scope: "agent",
281
575
  risk: "write",
282
576
  input: { agent_id: import_zod.z.string().uuid().describe("The agent UUID to clone") },
283
- run: (client, { agent_id }) => client.agents.clone(agent_id)
577
+ run: async (client, { agent_id }) => client.agents.clone(await resolveAgentId(client, agent_id))
284
578
  })
285
579
  ];
286
580
  var import_zod2 = require("zod");
@@ -642,7 +936,7 @@ var require_dist = __commonJS({
642
936
  // handler, a published:false-with-blockers response is NOT flagged as a
643
937
  // protocol error — blockers are valid data every consumer (MCP client,
644
938
  // studio harness) reads from the result directly.
645
- run: (client, { agent_id }) => client.agents.publish(agent_id)
939
+ run: async (client, { agent_id }) => client.agents.publish(await resolveAgentId(client, agent_id))
646
940
  })
647
941
  ];
648
942
  var import_zod7 = require("zod");
@@ -935,8 +1229,277 @@ var require_dist = __commonJS({
935
1229
  run: (client, { campaign_id, suggestion_id }) => client.campaigns.acceptSuggestion(campaign_id, suggestion_id)
936
1230
  })
937
1231
  ];
1232
+ var import_zod11 = require("zod");
1233
+ var import_agentTools = (init_agentTools(), __toCommonJS(agentTools_exports));
1234
+ var import_safeUrl = (init_safeUrl(), __toCommonJS(safeUrl_exports));
1235
+ var DEFAULT_NAME = "call_webhook";
1236
+ var METHODS = ["GET", "POST", "PUT", "PATCH"];
1237
+ var FIELD_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
1238
+ var RESERVED_HEADERS = /* @__PURE__ */ new Set(["host", "content-length", "connection", "transfer-encoding"]);
1239
+ var LIVE_NOTE = "Saved to the live agent row: the voice runtime uses it from the next call. There is no separate draft; publish_agent is not required. Undo with list_agent_changes \u2192 undo_agent_change.";
1240
+ var fieldSchema = import_zod11.z.object({
1241
+ name: import_zod11.z.string().describe('Argument name the agent fills in: letters, digits, underscores; not a Python keyword or "params".'),
1242
+ type: import_zod11.z.enum(["string", "number", "integer", "boolean"]).optional().describe('Default "string".'),
1243
+ description: import_zod11.z.string().optional().describe("What the value is, for the agent."),
1244
+ required: import_zod11.z.boolean().optional(),
1245
+ enum: import_zod11.z.array(import_zod11.z.string()).optional().describe("Allowed values, if fixed.")
1246
+ });
1247
+ var settingsShape = {
1248
+ url: import_zod11.z.string().describe("Public https endpoint. Private, loopback and non-https hosts are refused."),
1249
+ method: import_zod11.z.enum(METHODS).optional().describe("Default POST. GET sends fields as query parameters."),
1250
+ purpose: import_zod11.z.string().optional().describe("When the agent should call this tool. Without it the agent rarely fires it."),
1251
+ fields: import_zod11.z.array(fieldSchema).optional().describe("Arguments the agent supplies. Replaces the whole list on update."),
1252
+ headers: import_zod11.z.record(import_zod11.z.string()).optional().describe("Request headers. Credentials must be a {{secrets.NAME}} reference (see set_tool_secret), never a literal key."),
1253
+ send_metadata: import_zod11.z.boolean().optional().describe("Include call metadata (call id, agent id, caller phone). Default true."),
1254
+ response_fields: import_zod11.z.array(import_zod11.z.string()).optional().describe("Dot paths kept from the JSON response; default keeps the (truncated) body."),
1255
+ timeout_ms: import_zod11.z.number().int().min(1e3).max(1e4).optional().describe("Per-request timeout, 1000\u201310000 ms. Default 6000."),
1256
+ enabled: import_zod11.z.boolean().optional().describe("Default true.")
1257
+ };
1258
+ function bindingName(b) {
1259
+ const n = b?.provider_settings?.name;
1260
+ return typeof n === "string" && n.trim() ? n.trim() : DEFAULT_NAME;
1261
+ }
1262
+ var isApiTool = (b) => b?.tool_id === "call_webhook";
1263
+ function webhookBindingErrors(b, opts) {
1264
+ const errors = [];
1265
+ const s = b.provider_settings ?? {};
1266
+ const name = bindingName(b);
1267
+ if (!import_agentTools.API_TOOL_NAME_PATTERN.test(name)) errors.push(`name "${name}": lowercase letters, digits and underscores, starting with a letter, 3\u201341 characters.`);
1268
+ if (opts.otherNames.includes(name)) errors.push(`name "${name}" is already used by another API tool on this agent.`);
1269
+ const url = typeof s.url === "string" ? s.url.trim() : "";
1270
+ if (!url) errors.push("url is required.");
1271
+ else if (!/^https:\/\//i.test(url)) errors.push("url must be https \u2014 the request carries caller details.");
1272
+ else if (!(0, import_safeUrl.isSafeUrl)(url, { protocols: ["https:"] })) errors.push("url must be a valid public host; private and loopback addresses are refused at call time.");
1273
+ if (!METHODS.includes(s.method)) errors.push(`method must be one of ${METHODS.join(", ")}.`);
1274
+ if (!Number.isInteger(b.timeout_ms) || b.timeout_ms < 1e3 || b.timeout_ms > 1e4) errors.push("timeout_ms must be an integer from 1000 to 10000.");
1275
+ const seen = /* @__PURE__ */ new Set();
1276
+ for (const f of Array.isArray(s.fields) ? s.fields : []) {
1277
+ const fname = typeof f?.name === "string" ? f.name.trim() : "";
1278
+ if (!FIELD_NAME.test(fname)) errors.push(`field "${fname}": letters, digits and underscores only, not starting with a digit.`);
1279
+ else if (import_agentTools.API_TOOL_RESERVED_FIELD_NAMES.has(fname)) errors.push(`field "${fname}" is reserved; use e.g. ${fname}_value.`);
1280
+ else if (seen.has(fname)) errors.push(`field "${fname}" is duplicated.`);
1281
+ seen.add(fname);
1282
+ }
1283
+ const headers = s.headers ?? {};
1284
+ for (const key of opts.checkHeaders) {
1285
+ const value = headers[key];
1286
+ if (RESERVED_HEADERS.has(key.trim().toLowerCase())) errors.push(`header "${key}" is set by the runtime and cannot be overridden.`);
1287
+ else if (typeof value !== "string") errors.push(`header "${key}" must be a string.`);
1288
+ else if (import_agentTools.API_TOOL_AUTH_HEADER_PATTERN.test(key.trim()) && !import_agentTools.API_TOOL_SECRET_REF_PATTERN.test(value)) {
1289
+ errors.push(`header "${key}" holds a literal credential. Store it with set_tool_secret and use "{{secrets.NAME}}" instead.`);
1290
+ }
1291
+ }
1292
+ return errors;
1293
+ }
1294
+ function assertValid(b, tools, self, checkHeaders) {
1295
+ const otherNames = tools.filter((t) => isApiTool(t) && t !== self).map(bindingName);
1296
+ const errors = webhookBindingErrors(b, { otherNames, checkHeaders });
1297
+ if (errors.length) throw new Error(`Invalid API tool binding: ${errors.join(" ")}`);
1298
+ }
1299
+ async function writeTools(client, agentRef, mutate) {
1300
+ const agentId = await resolveAgentId(client, agentRef);
1301
+ const agent = await client.agents.retrieve(agentId);
1302
+ if (!Array.isArray(agent.tools)) throw new Error("The API did not return this agent's tool bindings, so a write could delete them. Refusing.");
1303
+ const tools = mutate(agent.tools);
1304
+ await client.agents.update(agentId, { tools });
1305
+ return { agentId, tools };
1306
+ }
1307
+ function findApiTool(tools, name) {
1308
+ const found = tools.find((t) => isApiTool(t) && bindingName(t) === name);
1309
+ if (!found) {
1310
+ const names2 = tools.filter(isApiTool).map(bindingName);
1311
+ throw new Error(`No API tool named "${name}" on this agent. Existing: ${names2.length ? names2.join(", ") : "none"}.`);
1312
+ }
1313
+ return found;
1314
+ }
1315
+ var addShape = { agent_id: import_zod11.z.string().describe("The agent UUID"), name: import_zod11.z.string().describe('LLM-facing tool name, e.g. "lookup_reservation". Unique per agent.'), ...settingsShape };
1316
+ var updateShape = {
1317
+ agent_id: import_zod11.z.string().describe("The agent UUID"),
1318
+ name: import_zod11.z.string().describe("Current name of the API tool to change."),
1319
+ new_name: import_zod11.z.string().optional().describe("Rename the tool."),
1320
+ ...settingsShape,
1321
+ url: settingsShape.url.optional(),
1322
+ headers: import_zod11.z.record(import_zod11.z.string().nullable()).optional().describe("Merged into the existing headers by name; null removes one. Headers you omit \u2014 including ones shown as [redacted] \u2014 are kept unchanged.")
1323
+ };
1324
+ var agentToolBindingTools = [
1325
+ defineTool({
1326
+ name: "list_agent_tools",
1327
+ description: "List an agent's tool bindings (API tools / call_webhook, handoff, calendar, \u2026). Header values are shown only as {{secrets.NAME}} references; literal values are [redacted].",
1328
+ scope: "agent",
1329
+ risk: "read",
1330
+ input: { agent_id: import_zod11.z.string().describe("The agent UUID") },
1331
+ run: async (client, { agent_id }) => {
1332
+ const agentId = await resolveAgentId(client, agent_id);
1333
+ const agent = await client.agents.retrieve(agentId);
1334
+ return { agent_id: agentId, tools: agent.tools ?? [] };
1335
+ }
1336
+ }),
1337
+ defineTool({
1338
+ name: "add_agent_tool",
1339
+ description: `Add an API tool (call_webhook) to an agent, keeping its existing tools. The agent can then call the endpoint mid-call with the fields you define. ${LIVE_NOTE}`,
1340
+ scope: "agent",
1341
+ risk: "write",
1342
+ input: addShape,
1343
+ run: async (client, params) => {
1344
+ const p = import_zod11.z.object(addShape).parse(params);
1345
+ const binding = {
1346
+ tool_id: "call_webhook",
1347
+ enabled: p.enabled ?? true,
1348
+ credential_ref: null,
1349
+ timeout_ms: p.timeout_ms ?? 6e3,
1350
+ side_effect: (p.method ?? "POST") === "GET" ? "read" : "write",
1351
+ provider_settings: {
1352
+ name: p.name.trim(),
1353
+ url: p.url.trim(),
1354
+ method: p.method ?? "POST",
1355
+ send_metadata: p.send_metadata ?? true,
1356
+ ...p.purpose ? { purpose: p.purpose } : {},
1357
+ ...p.fields?.length ? { fields: p.fields } : {},
1358
+ ...p.headers && Object.keys(p.headers).length ? { headers: p.headers } : {},
1359
+ ...p.response_fields?.length ? { response_fields: p.response_fields } : {}
1360
+ }
1361
+ };
1362
+ const { agentId } = await writeTools(client, p.agent_id, (tools) => {
1363
+ assertValid(binding, tools, null, Object.keys(p.headers ?? {}));
1364
+ return [...tools, binding];
1365
+ });
1366
+ return { status: "added", agent_id: agentId, tool: binding, note: LIVE_NOTE };
1367
+ }
1368
+ }),
1369
+ defineTool({
1370
+ name: "update_agent_tool",
1371
+ description: `Change one API tool (call_webhook) on an agent by name. Only the settings you pass change; fields replaces the whole list, headers merge by name. ${LIVE_NOTE}`,
1372
+ scope: "agent",
1373
+ risk: "write",
1374
+ input: updateShape,
1375
+ run: async (client, params) => {
1376
+ const p = import_zod11.z.object(updateShape).parse(params);
1377
+ let updated = null;
1378
+ const { agentId } = await writeTools(client, p.agent_id, (tools) => {
1379
+ const current = findApiTool(tools, p.name);
1380
+ const prev = current.provider_settings ?? {};
1381
+ const headers = { ...prev.headers ?? {} };
1382
+ for (const [k, v] of Object.entries(p.headers ?? {})) {
1383
+ if (v === null) delete headers[k];
1384
+ else headers[k] = v;
1385
+ }
1386
+ const settings = { ...prev, name: (p.new_name ?? bindingName(current)).trim() };
1387
+ for (const key of ["url", "method", "purpose", "fields", "send_metadata", "response_fields"]) {
1388
+ if (p[key] !== void 0) settings[key] = key === "url" ? p.url.trim() : p[key];
1389
+ }
1390
+ settings.method ??= "POST";
1391
+ if (Object.keys(headers).length) settings.headers = headers;
1392
+ else delete settings.headers;
1393
+ const next = {
1394
+ ...current,
1395
+ enabled: p.enabled ?? current.enabled,
1396
+ timeout_ms: p.timeout_ms ?? current.timeout_ms,
1397
+ side_effect: settings.method === "GET" ? "read" : "write",
1398
+ provider_settings: settings
1399
+ };
1400
+ const touched = Object.entries(p.headers ?? {}).filter(([, v]) => v !== null).map(([k]) => k);
1401
+ assertValid(next, tools, current, touched);
1402
+ updated = next;
1403
+ return tools.map((t) => t === current ? next : t);
1404
+ });
1405
+ return { status: "updated", agent_id: agentId, tool: updated, note: LIVE_NOTE };
1406
+ }
1407
+ }),
1408
+ defineTool({
1409
+ name: "remove_agent_tool",
1410
+ description: `Remove one API tool (call_webhook) from an agent by name, keeping its other tools. ${LIVE_NOTE}`,
1411
+ scope: "agent",
1412
+ risk: "write",
1413
+ input: { agent_id: import_zod11.z.string().describe("The agent UUID"), name: import_zod11.z.string().describe("Name of the API tool to remove.") },
1414
+ run: async (client, { agent_id, name }) => {
1415
+ const { agentId, tools } = await writeTools(client, agent_id, (tools2) => {
1416
+ const target = findApiTool(tools2, name);
1417
+ return tools2.filter((t) => t !== target);
1418
+ });
1419
+ return { status: "removed", agent_id: agentId, name, remaining: tools.filter(isApiTool).map(bindingName), note: LIVE_NOTE };
1420
+ }
1421
+ }),
1422
+ defineTool({
1423
+ name: "list_tool_secrets",
1424
+ description: "List the workspace's API-tool secret names and the host each is pinned to. Values are never returned.",
1425
+ scope: "account",
1426
+ risk: "read",
1427
+ input: {},
1428
+ run: (client) => client.toolSecrets.list()
1429
+ }),
1430
+ defineTool({
1431
+ name: "set_tool_secret",
1432
+ description: 'Create or replace a workspace secret for API-tool headers, referenced as "{{secrets.NAME}}" (e.g. header x-api-key: "{{secrets.RESERVATIONS_API_KEY}}"). The value is encrypted, write-only, and only ever sent to `host`. Replacing an existing name changes it for every agent that references it.',
1433
+ scope: "account",
1434
+ risk: "sensitive",
1435
+ input: {
1436
+ name: import_zod11.z.string().describe("UPPER_SNAKE_CASE, 2\u201364 characters."),
1437
+ value: import_zod11.z.string().min(1).max(4096).describe("The secret value. Never echoed back."),
1438
+ host: import_zod11.z.string().describe('Public hostname the secret may be sent to, e.g. "api.example.com".')
1439
+ },
1440
+ run: async (client, { name, value, host }) => {
1441
+ if (!import_agentTools.SECRET_NAME_PATTERN.test(name)) throw new Error("Secret names are UPPER_SNAKE_CASE, 2\u201364 characters, starting with a letter.");
1442
+ await client.toolSecrets.set(name, { value, host });
1443
+ return { status: "saved", name, reference: `{{secrets.${name}}}` };
1444
+ }
1445
+ })
1446
+ ];
1447
+ var import_zod12 = require("zod");
1448
+ var shareLinkTools = [
1449
+ defineTool({
1450
+ name: "get_share_link",
1451
+ description: "Get an agent's public share link (URL, limits, minutes used). Returns link: null if none exists yet.",
1452
+ scope: "agent",
1453
+ risk: "read",
1454
+ input: { agent_id: import_zod12.z.string().uuid().describe("The agent UUID") },
1455
+ run: (client, { agent_id }) => client.agents.getShareLink(agent_id)
1456
+ }),
1457
+ defineTool({
1458
+ name: "set_share_link",
1459
+ description: "Create an agent's public share link, or update its settings. Anyone with the URL can talk to the agent in a browser, billed to this workspace. Defaults on create: 30-minute pool, 300 s per call, 2 concurrent calls, 3 calls per IP per hour. Set enabled=false to switch the link off.",
1460
+ scope: "agent",
1461
+ risk: "sensitive",
1462
+ input: {
1463
+ agent_id: import_zod12.z.string().uuid().describe("The agent UUID"),
1464
+ enabled: import_zod12.z.boolean().optional().describe("Turn the link on or off"),
1465
+ minutes_limit: import_zod12.z.number().int().min(0).max(6e3).optional().describe("Total talk-time pool in minutes"),
1466
+ max_call_seconds: import_zod12.z.number().int().min(60).max(1800).optional().describe("Per-call cap in seconds"),
1467
+ max_concurrent: import_zod12.z.number().int().min(1).max(10).optional().describe("Simultaneous calls"),
1468
+ calls_per_ip_hour: import_zod12.z.number().int().min(1).max(30).optional().describe("Calls per caller IP per hour")
1469
+ },
1470
+ run: (client, { agent_id, ...settings }) => client.agents.setShareLink(agent_id, settings)
1471
+ }),
1472
+ defineTool({
1473
+ name: "regenerate_share_link",
1474
+ description: "Issue a new URL for an agent's share link. The old URL stops working immediately.",
1475
+ scope: "agent",
1476
+ risk: "sensitive",
1477
+ input: { agent_id: import_zod12.z.string().uuid().describe("The agent UUID") },
1478
+ run: (client, { agent_id }) => client.agents.regenerateShareLink(agent_id)
1479
+ })
1480
+ ];
1481
+ var import_agentTools2 = (init_agentTools(), __toCommonJS(agentTools_exports));
1482
+ var REDACTED = "[redacted]";
1483
+ function redactToolSecrets(value) {
1484
+ if (Array.isArray(value)) return value.map(redactToolSecrets);
1485
+ if (!value || typeof value !== "object") return value;
1486
+ const out = {};
1487
+ for (const [k, v] of Object.entries(value)) out[k] = redactToolSecrets(v);
1488
+ const headers = out.tool_id && out.provider_settings?.headers;
1489
+ if (headers && typeof headers === "object" && !Array.isArray(headers)) {
1490
+ out.provider_settings = {
1491
+ ...out.provider_settings,
1492
+ headers: Object.fromEntries(Object.entries(headers).map(([k, v]) => [
1493
+ k,
1494
+ typeof v === "string" && import_agentTools2.API_TOOL_SECRET_REF_PATTERN.test(v) ? v : REDACTED
1495
+ ]))
1496
+ };
1497
+ }
1498
+ return out;
1499
+ }
938
1500
  var opsToolCatalog2 = [
939
1501
  ...agentTools,
1502
+ ...agentToolBindingTools,
940
1503
  ...callTools,
941
1504
  ...numberTools,
942
1505
  ...telephonyTools,
@@ -947,8 +1510,9 @@ var require_dist = __commonJS({
947
1510
  ...dncTools,
948
1511
  ...webhookTools,
949
1512
  ...billingTools,
950
- ...campaignTools
951
- ];
1513
+ ...campaignTools,
1514
+ ...shareLinkTools
1515
+ ].map((t) => ({ ...t, run: async (client, params) => redactToolSecrets(await t.run(client, params)) }));
952
1516
  var names = /* @__PURE__ */ new Set();
953
1517
  for (const tool of opsToolCatalog2) {
954
1518
  if (names.has(tool.name)) throw new Error(`Duplicate ops tool name: ${tool.name}`);
@@ -983,7 +1547,7 @@ function withToolErrors(handler) {
983
1547
  // package.json
984
1548
  var package_default = {
985
1549
  name: "@rymi/mcp",
986
- version: "2.0.0",
1550
+ version: "2.1.0",
987
1551
  description: "Rymi MCP server \u2014 manage AI voice agents via Model Context Protocol",
988
1552
  license: "MIT",
989
1553
  author: "Rymi AI <engineering@rymi.live>",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rymi/mcp",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "description": "Rymi MCP server — manage AI voice agents via Model Context Protocol",
5
5
  "license": "MIT",
6
6
  "author": "Rymi AI <engineering@rymi.live>",
@@ -44,7 +44,7 @@
44
44
  },
45
45
  "dependencies": {
46
46
  "@modelcontextprotocol/sdk": "^1.11.1",
47
- "@rymi/node": "^2.0.0",
47
+ "@rymi/node": "^2.1.0",
48
48
  "zod": "^3.23.0"
49
49
  },
50
50
  "devDependencies": {