@rymi/mcp 2.1.0 → 2.3.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 +28 -4
  2. package/dist/index.js +225 -66
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -73,11 +73,11 @@ Run the server on your own machine — it speaks **stdio** by default and talks
73
73
  <tr><th>Group</th><th>Tools</th></tr>
74
74
  <tr>
75
75
  <td><b>Agents</b></td>
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>
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> · <code>publish_agent</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>
79
+ <td><b>Agent tools & secrets</b></td>
80
+ <td><code>set_agent_tools</code> · <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
81
  </tr>
82
82
  <tr>
83
83
  <td><b>Share link</b></td>
@@ -93,13 +93,17 @@ Run the server on your own machine — it speaks **stdio** by default and talks
93
93
  </tr>
94
94
  <tr>
95
95
  <td><b>Insight</b></td>
96
- <td><code>get_usage_summary</code> (minutes-based) · <code>list_agent_templates</code> · <code>run_evals</code> · <code>list_eval_runs</code> · <code>get_eval_run</code></td>
96
+ <td><code>get_usage_summary</code> (minutes-based) · <code>list_agent_templates</code> · <code>run_evals</code> · <code>run_eval_suite</code> · <code>list_eval_runs</code> · <code>get_eval_run</code></td>
97
97
  </tr>
98
98
  <tr>
99
99
  <td><b>Calls</b> <sub>(read-only)</sub></td>
100
100
  <td><code>list_calls</code> · <code>list_active_calls</code> · <code>get_call</code> · <code>get_call_summary</code> · <code>get_call_transcript</code> · <code>get_call_recording</code> · <code>get_call_queue_stats</code> · <code>reprocess_call</code> · <code>list_calls_for_agent</code></td>
101
101
  </tr>
102
102
  <tr>
103
+ <td><b>Call control</b></td>
104
+ <td><code>create_call</code> · <code>batch_call</code> · <code>end_call</code> · <code>add_call_participant</code></td>
105
+ </tr>
106
+ <tr>
103
107
  <td><b>Numbers</b></td>
104
108
  <td><code>list_numbers</code> · <code>register_number</code> · <code>attach_number</code> · <code>remove_number</code></td>
105
109
  </tr>
@@ -111,6 +115,26 @@ Run the server on your own machine — it speaks **stdio** by default and talks
111
115
  <td><b>Keys</b> <sub>(read-only)</sub></td>
112
116
  <td><code>list_publishable_keys</code></td>
113
117
  </tr>
118
+ <tr>
119
+ <td><b>Campaigns</b></td>
120
+ <td><code>list_campaigns</code> · <code>get_campaign</code> · <code>get_campaign_report</code> · <code>list_campaign_attempts</code> · <code>list_campaign_suggestions</code> · <code>list_contacts</code> · <code>create_campaign</code> · <code>import_campaign_contacts</code> · <code>launch_campaign</code> · <code>pause_campaign</code> · <code>resume_campaign</code> · <code>accept_campaign_suggestion</code></td>
121
+ </tr>
122
+ <tr>
123
+ <td><b>Lead intake</b></td>
124
+ <td><code>get_campaign_intake</code> · <code>set_campaign_intake</code> · <code>rotate_campaign_intake</code> · <code>disable_campaign_intake</code></td>
125
+ </tr>
126
+ <tr>
127
+ <td><b>Do-Not-Call</b></td>
128
+ <td><code>list_dnc</code> · <code>check_dnc</code> · <code>add_dnc</code> · <code>add_dnc_batch</code> · <code>remove_dnc</code></td>
129
+ </tr>
130
+ <tr>
131
+ <td><b>Webhooks</b></td>
132
+ <td><code>list_webhooks</code> · <code>create_webhook</code> · <code>update_webhook</code> · <code>delete_webhook</code></td>
133
+ </tr>
134
+ <tr>
135
+ <td><b>Billing & cost</b></td>
136
+ <td><code>estimate_call_cost</code> · <code>set_auto_recharge</code> · <code>set_spend_alerts</code></td>
137
+ </tr>
114
138
  </table>
115
139
 
116
140
  ### ⚠️ Gated tools
package/dist/index.js CHANGED
@@ -34,6 +34,68 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
34
34
  ));
35
35
  var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
36
36
 
37
+ // ../shared-types/dist/safeUrl.js
38
+ var safeUrl_exports = {};
39
+ __export(safeUrl_exports, {
40
+ isSafeUrl: () => isSafeUrl
41
+ });
42
+ function unmapIpv4(v6) {
43
+ const dotted = /^::ffff:(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})$/.exec(v6);
44
+ if (dotted)
45
+ return dotted[1];
46
+ const hex = /^::ffff:([0-9a-f]{1,4}):([0-9a-f]{1,4})$/.exec(v6);
47
+ if (!hex)
48
+ return null;
49
+ const high = parseInt(hex[1], 16);
50
+ const low = parseInt(hex[2], 16);
51
+ return [high >> 8, high & 255, low >> 8, low & 255].join(".");
52
+ }
53
+ function isBlockedHost(host) {
54
+ const bare = host.startsWith("[") && host.endsWith("]") ? host.slice(1, -1) : host;
55
+ if (bare === "localhost" || bare === "0.0.0.0" || bare === "::")
56
+ return true;
57
+ if (bare.endsWith(".local") || bare.endsWith(".internal") || bare.endsWith(".localhost"))
58
+ return true;
59
+ if (/^127\./.test(bare))
60
+ return true;
61
+ if (/^10\./.test(bare))
62
+ return true;
63
+ if (/^192\.168\./.test(bare))
64
+ return true;
65
+ if (/^172\.(1[6-9]|2\d|3[01])\./.test(bare))
66
+ return true;
67
+ if (/^169\.254\./.test(bare))
68
+ return true;
69
+ const v6 = bare.toLowerCase();
70
+ if (v6 === "::1")
71
+ return true;
72
+ if (/^f[cd][0-9a-f]{2}:/.test(v6))
73
+ return true;
74
+ if (/^fe[89ab][0-9a-f]:/.test(v6))
75
+ return true;
76
+ const mapped = unmapIpv4(v6);
77
+ if (mapped)
78
+ return isBlockedHost(mapped);
79
+ return false;
80
+ }
81
+ function isSafeUrl(raw, options = {}) {
82
+ const protocols = options.protocols ?? ["http:", "https:"];
83
+ let url;
84
+ try {
85
+ url = new URL(raw);
86
+ } catch {
87
+ return false;
88
+ }
89
+ if (!protocols.includes(url.protocol))
90
+ return false;
91
+ return !isBlockedHost(url.hostname.toLowerCase());
92
+ }
93
+ var init_safeUrl = __esm({
94
+ "../shared-types/dist/safeUrl.js"() {
95
+ "use strict";
96
+ }
97
+ });
98
+
37
99
  // ../shared-types/dist/agentTools.js
38
100
  var agentTools_exports = {};
39
101
  __export(agentTools_exports, {
@@ -43,9 +105,32 @@ __export(agentTools_exports, {
43
105
  API_TOOL_RESERVED_FIELD_NAMES: () => API_TOOL_RESERVED_FIELD_NAMES,
44
106
  API_TOOL_SECRET_REF_PATTERN: () => API_TOOL_SECRET_REF_PATTERN,
45
107
  BUILTIN_TOOL_CATALOG: () => BUILTIN_TOOL_CATALOG,
108
+ MCP_CONNECT_TIMEOUT_MS: () => MCP_CONNECT_TIMEOUT_MS,
109
+ MCP_SERVER_TOOL_ID: () => MCP_SERVER_TOOL_ID,
46
110
  SECRET_NAME_PATTERN: () => SECRET_NAME_PATTERN,
111
+ connectionStatus: () => connectionStatus,
112
+ isAgentToolEnabled: () => isAgentToolEnabled,
113
+ readMcpServerBinding: () => readMcpServerBinding,
47
114
  toToolCapabilitySafeView: () => toToolCapabilitySafeView
48
115
  });
116
+ function readMcpServerBinding(binding) {
117
+ const raw = binding?.provider_settings ?? {};
118
+ const url = typeof raw.url === "string" ? raw.url.trim() : "";
119
+ if (!isSafeUrl(url, { protocols: ["https:"] }))
120
+ return null;
121
+ const allowlist = Array.isArray(raw.allowlist) ? [...new Set(raw.allowlist.filter((t) => typeof t === "string" && t.trim() !== "").map((t) => t.trim()))].slice(0, MAX_MCP_ALLOWLIST) : [];
122
+ if (allowlist.length === 0)
123
+ return null;
124
+ const headers = {};
125
+ if (raw.headers && typeof raw.headers === "object" && !Array.isArray(raw.headers)) {
126
+ for (const [key, value] of Object.entries(raw.headers)) {
127
+ if (key.trim() && typeof value === "string")
128
+ headers[key.trim()] = value;
129
+ }
130
+ }
131
+ const name = typeof raw.name === "string" && raw.name.trim() ? raw.name.trim() : new URL(url).hostname;
132
+ return { name, url, headers, allowlist };
133
+ }
49
134
  function toToolCapabilitySafeView(state) {
50
135
  return {
51
136
  toolId: state.toolId,
@@ -54,10 +139,27 @@ function toToolCapabilitySafeView(state) {
54
139
  sideEffect: state.sideEffect
55
140
  };
56
141
  }
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;
142
+ function isAgentToolEnabled(bindings, defaultOn) {
143
+ if (bindings?.some((binding) => binding.enabled === true))
144
+ return true;
145
+ if (bindings?.some((binding) => binding.enabled === false))
146
+ return false;
147
+ return defaultOn;
148
+ }
149
+ function connectionStatus(row) {
150
+ if (row.is_active === false)
151
+ return "error";
152
+ const creds = row.credentials || {};
153
+ const hasRefreshToken = typeof creds.refresh_token === "string" && creds.refresh_token.trim().length > 0;
154
+ const expiresAt = typeof creds.expires_at === "string" ? new Date(creds.expires_at) : null;
155
+ const accessTokenExpired = expiresAt && !isNaN(expiresAt.getTime()) && expiresAt.getTime() < Date.now();
156
+ return accessTokenExpired && !hasRefreshToken ? "expired" : "connected";
157
+ }
158
+ 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, MCP_SERVER_TOOL_ID, MCP_CONNECT_TIMEOUT_MS, MAX_MCP_ALLOWLIST, BUILTIN_TOOL_CATALOG;
58
159
  var init_agentTools = __esm({
59
160
  "../shared-types/dist/agentTools.js"() {
60
161
  "use strict";
162
+ init_safeUrl();
61
163
  API_TOOL_NAME_PATTERN = /^[a-z][a-z0-9_]{2,40}$/;
62
164
  SECRET_NAME_PATTERN = /^[A-Z][A-Z0-9_]{1,63}$/;
63
165
  API_SECRETS_PROVIDER = "api_secrets";
@@ -101,6 +203,9 @@ var init_agentTools = __esm({
101
203
  ]);
102
204
  API_TOOL_AUTH_HEADER_PATTERN = /^(authorization|x-api-key|api[-_]?key|.*token.*|.*secret.*)$/i;
103
205
  API_TOOL_SECRET_REF_PATTERN = /\{\{\s*secrets\.[A-Z][A-Z0-9_]{1,63}\s*\}\}/;
206
+ MCP_SERVER_TOOL_ID = "mcp_server";
207
+ MCP_CONNECT_TIMEOUT_MS = 3e3;
208
+ MAX_MCP_ALLOWLIST = 50;
104
209
  BUILTIN_TOOL_CATALOG = [
105
210
  {
106
211
  tool_id: "handoff_to_human",
@@ -111,6 +216,15 @@ var init_agentTools = __esm({
111
216
  default_timeout_ms: 1e3,
112
217
  requires_credential: false
113
218
  },
219
+ {
220
+ tool_id: "schedule_callback",
221
+ label: "Scheduled callbacks",
222
+ description: "Let the agent schedule a call back to the caller at a time they choose, and place it itself at that time (Indian mobile numbers, 9 am to 9 pm IST). Needs a phone number attached to the agent or the workspace; no calendar or messaging connection is required.",
223
+ category: "call_control",
224
+ side_effect: "write",
225
+ default_timeout_ms: 4e3,
226
+ requires_credential: false
227
+ },
114
228
  {
115
229
  tool_id: "check_calendar_availability",
116
230
  label: "Check calendar availability",
@@ -220,73 +334,20 @@ var init_agentTools = __esm({
220
334
  // The endpoint lives in the binding's provider_settings and its secrets in
221
335
  // the tenant's api_secrets row — nothing to connect in Settings → Connectors.
222
336
  requires_credential: false
337
+ },
338
+ {
339
+ tool_id: "mcp_server",
340
+ label: "MCP server",
341
+ description: "Give the agent tools from your own MCP server. Only the tools you allowlist are offered; header secrets stay encrypted and are sent only to that server.",
342
+ category: "webhook",
343
+ side_effect: "write",
344
+ default_timeout_ms: MCP_CONNECT_TIMEOUT_MS,
345
+ requires_credential: false
223
346
  }
224
347
  ];
225
348
  }
226
349
  });
227
350
 
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
- });
289
-
290
351
  // ../ops-tools/dist/index.js
291
352
  var require_dist = __commonJS({
292
353
  "../ops-tools/dist/index.js"(exports2, module2) {
@@ -1227,11 +1288,50 @@ var require_dist = __commonJS({
1227
1288
  suggestion_id: import_zod10.z.string().describe("The suggestion ID to accept")
1228
1289
  },
1229
1290
  run: (client, { campaign_id, suggestion_id }) => client.campaigns.acceptSuggestion(campaign_id, suggestion_id)
1291
+ }),
1292
+ defineTool({
1293
+ name: "get_campaign_intake",
1294
+ description: "Get a campaign's lead-intake URL settings (consent and default-country handling, last used). Returns intake: null if none. The URL itself is only shown when created or rotated.",
1295
+ scope: "agent",
1296
+ risk: "read",
1297
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID") },
1298
+ run: (client, { campaign_id }) => client.campaigns.intake.get(campaign_id)
1299
+ }),
1300
+ defineTool({
1301
+ name: "set_campaign_intake",
1302
+ description: 'Create a lead-intake URL for a campaign, or update its settings. Lead sources (website forms, Typeform, Google Apps Script, Zapier/Make) POST leads to the URL with no API key; a running campaign dials them within its calling window. The URL is returned ONCE on create (url: null on later updates) and anyone holding it can add numbers to the campaign. assume_voice_consent=true treats every posted lead as consenting to calls; default_country (e.g. "IN") makes local numbers without +<code> dialable.',
1303
+ scope: "agent",
1304
+ risk: "sensitive",
1305
+ input: {
1306
+ campaign_id: import_zod10.z.string().describe("The campaign ID"),
1307
+ assume_voice_consent: import_zod10.z.boolean().optional().describe("Treat every posted lead as having voice consent"),
1308
+ default_country: import_zod10.z.string().regex(/^[A-Z]{2}$/).nullable().optional().describe('ISO-3166 alpha-2 country for numbers posted without +<code>, e.g. "IN"')
1309
+ },
1310
+ run: (client, { campaign_id, ...settings }) => client.campaigns.intake.set(campaign_id, settings)
1311
+ }),
1312
+ defineTool({
1313
+ name: "rotate_campaign_intake",
1314
+ description: "Issue a new lead-intake URL for a campaign. The old URL stops accepting leads immediately, so every lead source must be updated.",
1315
+ scope: "agent",
1316
+ risk: "sensitive",
1317
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID") },
1318
+ run: (client, { campaign_id }) => client.campaigns.intake.rotate(campaign_id)
1319
+ }),
1320
+ defineTool({
1321
+ name: "disable_campaign_intake",
1322
+ description: "Remove a campaign's lead-intake URL. Leads already accepted stay in the campaign.",
1323
+ scope: "agent",
1324
+ risk: "write",
1325
+ input: { campaign_id: import_zod10.z.string().describe("The campaign ID") },
1326
+ run: async (client, { campaign_id }) => {
1327
+ await client.campaigns.intake.disable(campaign_id);
1328
+ return { status: "disabled", campaign_id };
1329
+ }
1230
1330
  })
1231
1331
  ];
1232
1332
  var import_zod11 = require("zod");
1233
1333
  var import_agentTools = (init_agentTools(), __toCommonJS(agentTools_exports));
1234
- var import_safeUrl = (init_safeUrl(), __toCommonJS(safeUrl_exports));
1334
+ var import_safeUrl2 = (init_safeUrl(), __toCommonJS(safeUrl_exports));
1235
1335
  var DEFAULT_NAME = "call_webhook";
1236
1336
  var METHODS = ["GET", "POST", "PUT", "PATCH"];
1237
1337
  var FIELD_NAME = /^[a-zA-Z_][a-zA-Z0-9_]*$/;
@@ -1269,7 +1369,7 @@ var require_dist = __commonJS({
1269
1369
  const url = typeof s.url === "string" ? s.url.trim() : "";
1270
1370
  if (!url) errors.push("url is required.");
1271
1371
  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.");
1372
+ else if (!(0, import_safeUrl2.isSafeUrl)(url, { protocols: ["https:"] })) errors.push("url must be a valid public host; private and loopback addresses are refused at call time.");
1273
1373
  if (!METHODS.includes(s.method)) errors.push(`method must be one of ${METHODS.join(", ")}.`);
1274
1374
  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
1375
  const seen = /* @__PURE__ */ new Set();
@@ -1312,6 +1412,49 @@ var require_dist = __commonJS({
1312
1412
  }
1313
1413
  return found;
1314
1414
  }
1415
+ var ALWAYS_ON_TOOL_IDS = ["datetime_now", "knowledge_search", "end_call", "get_caller_history", "forget_me"];
1416
+ var SWITCHABLE_CATALOG = import_agentTools.BUILTIN_TOOL_CATALOG.filter((t) => t.tool_id !== "call_webhook" && t.tool_id !== "mcp_server");
1417
+ var TOOL_GROUPS = {
1418
+ calendar: [
1419
+ "check_calendar_availability",
1420
+ "list_calendar_events",
1421
+ "create_calendar_event",
1422
+ "update_calendar_event",
1423
+ "delete_calendar_event"
1424
+ ],
1425
+ tickets: ["lookup_ticket", "create_ticket"]
1426
+ };
1427
+ var SWITCHABLE_IDS = [...ALWAYS_ON_TOOL_IDS, ...SWITCHABLE_CATALOG.map((t) => t.tool_id)];
1428
+ function expandToolIds(requested) {
1429
+ const ids = /* @__PURE__ */ new Set();
1430
+ const unknown = [];
1431
+ for (const raw of requested) {
1432
+ const name = raw.trim();
1433
+ if (TOOL_GROUPS[name]) TOOL_GROUPS[name].forEach((id) => ids.add(id));
1434
+ else if (SWITCHABLE_IDS.includes(name)) ids.add(name);
1435
+ else unknown.push(name);
1436
+ }
1437
+ if (unknown.length) {
1438
+ throw new Error(`Unknown tool ${unknown.map((u) => `"${u}"`).join(", ")}. Use one of: ${[...Object.keys(TOOL_GROUPS), ...SWITCHABLE_IDS].join(", ")}. API tools use add/update/remove_agent_tool.`);
1439
+ }
1440
+ return [...ids];
1441
+ }
1442
+ function setToolsEnabled(tools, toolIds, enabled) {
1443
+ const next = tools.map((t) => toolIds.includes(t.tool_id) ? { ...t, enabled } : t);
1444
+ for (const toolId of toolIds) {
1445
+ if (next.some((t) => t.tool_id === toolId)) continue;
1446
+ const entry = SWITCHABLE_CATALOG.find((t) => t.tool_id === toolId);
1447
+ next.push({
1448
+ tool_id: toolId,
1449
+ enabled,
1450
+ credential_ref: null,
1451
+ timeout_ms: entry?.default_timeout_ms ?? 3e3,
1452
+ side_effect: entry?.side_effect ?? (toolId === "end_call" || toolId === "forget_me" ? "write" : "read"),
1453
+ provider_settings: {}
1454
+ });
1455
+ }
1456
+ return next;
1457
+ }
1315
1458
  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
1459
  var updateShape = {
1317
1460
  agent_id: import_zod11.z.string().describe("The agent UUID"),
@@ -1419,6 +1562,22 @@ var require_dist = __commonJS({
1419
1562
  return { status: "removed", agent_id: agentId, name, remaining: tools.filter(isApiTool).map(bindingName), note: LIVE_NOTE };
1420
1563
  }
1421
1564
  }),
1565
+ defineTool({
1566
+ name: "set_agent_tools",
1567
+ description: `Switch built-in tools on or off for one agent: calendar (check/list/create/update/delete), lookup_customer, lookup_ticket/create_ticket, send_whatsapp_message, send_telegram_message, send_asset, handoff_to_human, schedule_callback, and the always-on ones (end_call, forget_me, knowledge_search, get_caller_history, datetime_now). Groups: "calendar" (all five calendar tools), "tickets". Connected-app tools are opt-in per agent: connecting the app for the workspace makes them available, and an agent gets one only when switched on here or in Studio; it also needs the app connected (Settings \u2192 Connectors) to work on calls. Other agents are not affected. ${LIVE_NOTE}`,
1568
+ scope: "agent",
1569
+ risk: "write",
1570
+ input: {
1571
+ agent_id: import_zod11.z.string().describe("The agent UUID"),
1572
+ tools: import_zod11.z.array(import_zod11.z.string()).min(1).describe('Tool ids or groups, e.g. ["calendar"] or ["send_whatsapp_message", "send_asset"].'),
1573
+ enabled: import_zod11.z.boolean().describe("true switches them on for this agent, false switches them off.")
1574
+ },
1575
+ run: async (client, { agent_id, tools: requested, enabled }) => {
1576
+ const toolIds = expandToolIds(requested);
1577
+ const { agentId } = await writeTools(client, agent_id, (tools) => setToolsEnabled(tools, toolIds, enabled));
1578
+ return { status: enabled ? "enabled" : "disabled", agent_id: agentId, tools: toolIds, note: LIVE_NOTE };
1579
+ }
1580
+ }),
1422
1581
  defineTool({
1423
1582
  name: "list_tool_secrets",
1424
1583
  description: "List the workspace's API-tool secret names and the host each is pinned to. Values are never returned.",
@@ -1547,7 +1706,7 @@ function withToolErrors(handler) {
1547
1706
  // package.json
1548
1707
  var package_default = {
1549
1708
  name: "@rymi/mcp",
1550
- version: "2.1.0",
1709
+ version: "2.3.0",
1551
1710
  description: "Rymi MCP server \u2014 manage AI voice agents via Model Context Protocol",
1552
1711
  license: "MIT",
1553
1712
  author: "Rymi AI <engineering@rymi.live>",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rymi/mcp",
3
- "version": "2.1.0",
3
+ "version": "2.3.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.1.0",
47
+ "@rymi/node": "^2.2.0",
48
48
  "zod": "^3.23.0"
49
49
  },
50
50
  "devDependencies": {