@anychat-ai/mcp 0.1.1 → 0.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.
@@ -5,34 +5,44 @@ const zod_1 = require("zod");
5
5
  const tool_1 = require("../tool");
6
6
  /**
7
7
  * Capabilities tool group — tool bindings, OAuth-runtime tool status,
8
- * outreach config + preview, outreach links, and knowledge documents.
8
+ * custom-integration contracts, scheduling config, People field
9
+ * definitions, outreach config + preview, outreach links, product catalog
10
+ * and knowledge documents.
9
11
  *
10
12
  * Every tool is a 1:1 projection of a `/v1` route (API reference:
11
- * "Tool bindings", "OAuth-runtime tools", "Outreach", "Outreach links";
12
- * knowledge documents are the two read routes under
13
- * `/knowledge/documents`, guarded by `knowledge:read`).
13
+ * "Tool bindings", "OAuth-runtime tools", "Custom tools", "Scheduling",
14
+ * "People fields", "Outreach", "Outreach links", "Products"; knowledge
15
+ * documents are the read route under `/knowledge/documents`, guarded by
16
+ * `knowledge:read`).
17
+ *
18
+ * The capability-config resources (`/scheduling`, `/people-fields`,
19
+ * `/products`, `/custom-tools`) are exactly the `capabilities.*` slices
20
+ * of the export document: GET ≡ export view, PUT accepts what import
21
+ * accepts.
14
22
  *
15
23
  * Deliberately NOT here:
16
24
  * - `POST …/tools/{toolId}/oauth/start` — the grant completes in the
17
25
  * operator's browser; hosts can't drive that flow from a tool.
18
- * - Sending outreach — there is no `/v1` send route (sends are a console /
19
- * gateway operation), so nothing to project.
26
+ * - Sending outreach to a person — lives in the People group
27
+ * (`send_outreach` / `send_outreach_follow_up`).
20
28
  * - Human escalation — no dedicated route; it's the identity contact
21
29
  * fields (`contactEmail`, `contactPhone`, `websiteUrl`,
22
30
  * `contactAvailability`), edited via the identity tools.
23
31
  */
24
- const agentId = zod_1.z
25
- .string()
26
- .describe('Agent id (URL-safe slug, e.g. agent-acme-bot-pj7r2qx4)');
32
+ const agentId = zod_1.z.string();
27
33
  const toolId = zod_1.z
28
34
  .string()
29
- .describe('Qualified catalog tool id, e.g. "@anychat/tool.custom-http-webhook", or the id of one of the agent\'s custom tools. Passed raw — the tool URL-encodes it.');
35
+ .describe('Catalog tool id, e.g. "@anychat/tool.custom-http-webhook", or one of the agent\'s custom tool ids');
30
36
  const bindingsBase = (agent) => `/agents/${encodeURIComponent(agent)}/tool-bindings`;
31
37
  const bindingPath = (agent, tool) => `${bindingsBase(agent)}/${encodeURIComponent(tool)}`;
32
38
  const oauthPath = (agent, tool, action) => `/agents/${encodeURIComponent(agent)}/tools/${encodeURIComponent(tool)}/oauth/${action}`;
33
39
  const outreachPath = (agent) => `/agents/${encodeURIComponent(agent)}/outreach`;
34
40
  const outreachLinksPath = (agent) => `/agents/${encodeURIComponent(agent)}/outreach-links`;
35
41
  const knowledgeDocsPath = (agent) => `/agents/${encodeURIComponent(agent)}/knowledge/documents`;
42
+ const schedulingPath = (agent) => `/agents/${encodeURIComponent(agent)}/scheduling`;
43
+ const peopleFieldsPath = (agent) => `/agents/${encodeURIComponent(agent)}/people-fields`;
44
+ const productsPath = (agent) => `/agents/${encodeURIComponent(agent)}/products`;
45
+ const customToolsPath = (agent, tool) => `/agents/${encodeURIComponent(agent)}/custom-tools${tool === undefined ? '' : `/${encodeURIComponent(tool)}`}`;
36
46
  /**
37
47
  * Tool-binding `config`: shape is defined by the catalog tool's
38
48
  * `configSchema` (see the tools catalog), so it's an open record. Secret
@@ -41,38 +51,129 @@ const knowledgeDocsPath = (agent) => `/agents/${encodeURIComponent(agent)}/knowl
41
51
  */
42
52
  const bindingConfig = zod_1.z
43
53
  .record(zod_1.z.string(), zod_1.z.any())
44
- .describe("Tool configuration object; shape per the catalog tool's configSchema (e.g. { webhookUrl, authHeader } for the Custom HTTP Webhook tool, a calendar id for Google Calendar). For write-only secret fields (API keys, webhook URLs, auth headers): send a non-empty string to set/replace, an empty string to clear, or omit to leave the stored secret untouched. Reads return { set, last4 } metadata in place of secrets.");
54
+ .describe('Config per the catalog tool\'s configSchema (e.g. { webhookUrl, authHeader }). Write-only secret fields: non-empty string sets, "" clears, omit leaves the stored secret; reads show { set, last4 }.');
45
55
  const followUpStep = zod_1.z
46
56
  .object({
47
- id: zod_1.z.string().min(1).describe('Step id (non-empty string, operator-chosen)'),
57
+ id: zod_1.z.string().min(1).describe('Operator-chosen step id'),
48
58
  delay: zod_1.z
49
59
  .object({
50
- value: zod_1.z.number().positive().describe('Wait since the initial outreach (> 0)'),
51
- unit: zod_1.z.enum(['hours', 'days']).describe('Unit for value'),
60
+ value: zod_1.z.number().positive(),
61
+ unit: zod_1.z.enum(['hours', 'days']),
52
62
  })
53
- .describe('How long after the outreach to send the nudge'),
54
- messageTemplateId: zod_1.z
55
- .string()
56
- .min(1)
57
- .describe("Id of one of the agent's message templates (must exist, else 404)"),
63
+ .describe('Wait after the initial outreach'),
64
+ messageTemplateId: zod_1.z.string().min(1).describe('Existing message template id'),
58
65
  })
59
66
  .describe('The single automatic follow-up step');
67
+ // ── Capability-config shapes (mirror the API's sanitizers) ─────────────
68
+ const timeHHMM = zod_1.z.string().describe('"HH:MM" 24h');
69
+ const schedulingWindow = zod_1.z.object({
70
+ dayOfWeek: zod_1.z.number().int().min(0).max(6).describe('0 = Sun … 6 = Sat'),
71
+ startTime: timeHHMM,
72
+ endTime: timeHHMM,
73
+ });
74
+ const representative = zod_1.z
75
+ .object({
76
+ id: zod_1.z.string().min(1).describe('Stable per-agent id, unique'),
77
+ name: zod_1.z.string().min(1),
78
+ title: zod_1.z.string().optional(),
79
+ phone: zod_1.z.string().optional(),
80
+ email: zod_1.z.string().optional().describe('Gets appointment emails'),
81
+ type: zod_1.z
82
+ .string()
83
+ .optional()
84
+ .describe('sales (default) | advisor | clinician | support'),
85
+ availability: zod_1.z
86
+ .object({
87
+ timezone: zod_1.z.string().optional().describe('IANA'),
88
+ workingHours: zod_1.z.array(schedulingWindow).optional(),
89
+ })
90
+ .optional()
91
+ .describe('Omit → agent-level availableHours'),
92
+ })
93
+ .loose();
94
+ /** `SchedulingConfig` — the `capabilities.scheduling` slice; never carries googleCalendar. */
95
+ const schedulingConfigShape = {
96
+ timezone: zod_1.z.string().optional().describe('IANA'),
97
+ availableHours: zod_1.z
98
+ .array(schedulingWindow)
99
+ .optional()
100
+ .describe('Weekly bookable windows'),
101
+ blackoutDates: zod_1.z.array(zod_1.z.string()).optional().describe('YYYY-MM-DD'),
102
+ disallowSameDayBooking: zod_1.z.boolean().optional(),
103
+ defaultDurationMinutes: zod_1.z.number().int().min(1).max(1440).optional(),
104
+ notificationRecipients: zod_1.z
105
+ .array(zod_1.z.string())
106
+ .optional()
107
+ .describe('Extra emails for appointment mail'),
108
+ reminders: zod_1.z
109
+ .object({
110
+ enabled: zod_1.z.boolean().optional().describe('Unset = on'),
111
+ leadMinutes: zod_1.z.number().int().min(1).max(10080).optional(),
112
+ includeContactCard: zod_1.z.boolean().optional().describe('Attach rep .vcf'),
113
+ })
114
+ .optional()
115
+ .describe('Pre-call reminder to the prospect'),
116
+ representatives: zod_1.z.array(representative).optional(),
117
+ defaultRepresentativeIdentity: zod_1.z
118
+ .string()
119
+ .optional()
120
+ .describe('Fallback when no rep assigned, e.g. "a sales representative"'),
121
+ };
122
+ const personFieldDef = zod_1.z
123
+ .object({
124
+ key: zod_1.z.string().describe('^[a-zA-Z][a-zA-Z0-9_]*$, unique; Person.customFields[key]'),
125
+ label: zod_1.z.string().optional(),
126
+ description: zod_1.z.string().optional().describe('What it means / when to fill it'),
127
+ type: zod_1.z
128
+ .enum(['string', 'number', 'date', 'boolean', 'enum'])
129
+ .optional()
130
+ .describe('Default string'),
131
+ enum: zod_1.z.array(zod_1.z.string()).optional().describe('Allowed values when type = enum'),
132
+ examples: zod_1.z.array(zod_1.z.string()).optional(),
133
+ required: zod_1.z.boolean().optional().describe('Advisory (intake forms); never blocks'),
134
+ intake: zod_1.z
135
+ .object({ aliases: zod_1.z.array(zod_1.z.string()).optional() })
136
+ .optional()
137
+ .describe('Import/lead-form column aliases (case-insensitive)'),
138
+ })
139
+ .loose();
140
+ const agentProduct = zod_1.z
141
+ .object({
142
+ id: zod_1.z.string().min(1).describe('Stable per-agent id, unique'),
143
+ name: zod_1.z.string().min(1),
144
+ description: zod_1.z.string().optional(),
145
+ imageUrl: zod_1.z.string().optional(),
146
+ productPageUrl: zod_1.z.string().optional(),
147
+ metadata: zod_1.z
148
+ .record(zod_1.z.string(), zod_1.z.string())
149
+ .optional()
150
+ .describe('Free string attributes (price, category …)'),
151
+ })
152
+ .loose();
153
+ const customToolId = zod_1.z
154
+ .string()
155
+ .describe('Custom tool id = the name the agent calls it by (^[a-zA-Z0-9_-]{1,64}$), e.g. "lookupOrder"');
156
+ /** Drop undefined keys so a PUT body carries only what the caller set. */
157
+ function stripUndefined(o) {
158
+ return Object.fromEntries(Object.entries(o).filter(([, v]) => v !== undefined));
159
+ }
60
160
  exports.capabilityTools = [
61
161
  // ── Tool bindings ────────────────────────────────────────────────────
62
162
  (0, tool_1.defineTool)({
63
163
  name: 'list_tool_bindings',
64
164
  title: 'List tool bindings',
65
- description: "List the tools bound to an agent (integrations such as Google Calendar, Slack, Custom HTTP Webhook, or the agent's own custom tools) with each binding's config (secrets redacted to { set, last4 }), enabled flag and configStatus (complete | incomplete | invalid). Use to see which capabilities an agent has wired up before configuring or troubleshooting one. Maps to GET /v1/orgs/{org}/agents/{agent}/tool-bindings.",
165
+ description: 'List the tools bound to an agent (Google Calendar, Slack, Custom HTTP Webhook, custom tools …) with config (secrets redacted to { set, last4 }), enabled flag and configStatus (complete | incomplete | invalid). Maps to GET /v1/orgs/{org}/agents/{agent}/tool-bindings.',
66
166
  kind: 'read',
67
167
  scope: 'agents:read',
68
168
  roleFloor: 'viewer',
169
+ profile: 'core',
69
170
  input: { agentId },
70
171
  run: async ({ client }, { agentId }) => client.get(await client.orgPath(bindingsBase(agentId))),
71
172
  }),
72
173
  (0, tool_1.defineTool)({
73
174
  name: 'get_tool_binding',
74
175
  title: 'Get a tool binding',
75
- description: 'Read one tool binding on an agent: { toolId, config (secrets redacted), enabled, configStatus }. Use to inspect how a specific integration is configured. Maps to GET /v1/orgs/{org}/agents/{agent}/tool-bindings/{toolId}.',
176
+ description: 'Read one tool binding: { toolId, config (secrets redacted), enabled, configStatus }. Maps to GET /v1/orgs/{org}/agents/{agent}/tool-bindings/{toolId}.',
76
177
  kind: 'read',
77
178
  scope: 'agents:read',
78
179
  roleFloor: 'viewer',
@@ -82,10 +183,11 @@ exports.capabilityTools = [
82
183
  (0, tool_1.defineTool)({
83
184
  name: 'upsert_tool_binding',
84
185
  title: 'Create or replace a tool binding',
85
- description: "Bind a catalog tool (or one of the agent's custom tools) to the agent, or replace an existing binding's config wholesale. The tool must exist in the catalog (see the tools catalog for its configSchema) — unknown tools are rejected with 404. Returns the saved binding with configStatus. Use update_tool_binding to merge a partial change instead. Maps to PUT /v1/orgs/{org}/agents/{agent}/tool-bindings/{toolId}.",
186
+ description: "Bind a catalog tool (or a custom tool) to the agent, or replace an existing binding's config wholesale (see the tools catalog for configSchema; unknown tool → 404). Returns the binding with configStatus. Use update_tool_binding for a partial merge. Maps to PUT /v1/orgs/{org}/agents/{agent}/tool-bindings/{toolId}.",
86
187
  kind: 'write',
87
188
  scope: 'agents:write',
88
189
  roleFloor: 'editor',
190
+ profile: 'core',
89
191
  input: {
90
192
  agentId,
91
193
  toolId,
@@ -93,7 +195,7 @@ exports.capabilityTools = [
93
195
  enabled: zod_1.z
94
196
  .boolean()
95
197
  .optional()
96
- .describe('Whether the agent may use the tool (default: keep the existing value, or true for a new binding)'),
198
+ .describe('Default: existing value, or true for a new binding'),
97
199
  },
98
200
  run: async ({ client }, { agentId, toolId, config, enabled }) => client.put(await client.orgPath(bindingPath(agentId, toolId)), {
99
201
  config,
@@ -103,7 +205,7 @@ exports.capabilityTools = [
103
205
  (0, tool_1.defineTool)({
104
206
  name: 'update_tool_binding',
105
207
  title: 'Update a tool binding (merge)',
106
- description: "Merge changes into an existing tool binding's config (fields you omit are left as they are). The binding must already exist (else 404). Returns the saved binding with configStatus. Maps to PATCH /v1/orgs/{org}/agents/{agent}/tool-bindings/{toolId}.",
208
+ description: "Merge changes into an existing tool binding's config (omitted fields unchanged; binding must exist). Maps to PATCH /v1/orgs/{org}/agents/{agent}/tool-bindings/{toolId}.",
107
209
  kind: 'write',
108
210
  scope: 'agents:write',
109
211
  roleFloor: 'editor',
@@ -111,10 +213,7 @@ exports.capabilityTools = [
111
213
  agentId,
112
214
  toolId,
113
215
  config: bindingConfig.optional(),
114
- enabled: zod_1.z
115
- .boolean()
116
- .optional()
117
- .describe('Set the enabled flag as part of the same update'),
216
+ enabled: zod_1.z.boolean().optional(),
118
217
  },
119
218
  run: async ({ client }, { agentId, toolId, config, enabled }) => client.patch(await client.orgPath(bindingPath(agentId, toolId)), {
120
219
  ...(config !== undefined ? { config } : {}),
@@ -124,21 +223,21 @@ exports.capabilityTools = [
124
223
  (0, tool_1.defineTool)({
125
224
  name: 'set_tool_binding_enabled',
126
225
  title: 'Enable or disable a tool binding',
127
- description: 'Turn a bound tool on or off for the agent without touching its config — a disabled binding keeps its configuration and any stored secrets. Returns the updated binding. Maps to POST /v1/orgs/{org}/agents/{agent}/tool-bindings/{toolId}/_enable (enabled: true) or …/_disable (enabled: false).',
226
+ description: 'Turn a bound tool on or off without touching its config or secrets. Maps to POST /v1/orgs/{org}/agents/{agent}/tool-bindings/{toolId}/_enable or …/_disable.',
128
227
  kind: 'write',
129
228
  scope: 'agents:write',
130
229
  roleFloor: 'editor',
131
230
  input: {
132
231
  agentId,
133
232
  toolId,
134
- enabled: zod_1.z.boolean().describe('true → _enable, false → _disable'),
233
+ enabled: zod_1.z.boolean(),
135
234
  },
136
235
  run: async ({ client }, { agentId, toolId, enabled }) => client.post(await client.orgPath(`${bindingPath(agentId, toolId)}/${enabled ? '_enable' : '_disable'}`)),
137
236
  }),
138
237
  (0, tool_1.defineTool)({
139
238
  name: 'delete_tool_binding',
140
239
  title: 'Delete a tool binding',
141
- description: 'Unbind a tool from the agent and clear any stored secrets for the binding (irreversible — the config and secrets are gone; the agent stops offering the tool). To merely pause a tool use set_tool_binding_enabled; to drop only an OAuth grant use disconnect_oauth. Returns { ok: true, deleted: toolId } (the API replies 204). Maps to DELETE /v1/orgs/{org}/agents/{agent}/tool-bindings/{toolId}.',
240
+ description: 'Unbind a tool and clear its stored secrets (irreversible). To pause use set_tool_binding_enabled; to drop only an OAuth grant use disconnect_oauth. Maps to DELETE /v1/orgs/{org}/agents/{agent}/tool-bindings/{toolId}.',
142
241
  kind: 'destructive',
143
242
  scope: 'agents:write',
144
243
  roleFloor: 'editor',
@@ -152,7 +251,7 @@ exports.capabilityTools = [
152
251
  (0, tool_1.defineTool)({
153
252
  name: 'get_oauth_status',
154
253
  title: 'Get OAuth connection status',
155
- description: 'Report the OAuth credential state for an OAuth-runtime tool binding (Google Calendar, Slack, …): { state: "not-connected" | "connected" | "expired" | "revoked" | "error", connectedAt?, lastError? }. Use to check whether an integration is connected or needs the operator to reconnect. Connecting requires the operator\'s browser (the console\'s Connect button, or POST …/oauth/start) and is not exposed as a tool. Maps to GET /v1/orgs/{org}/agents/{agent}/tools/{toolId}/oauth/status.',
254
+ description: "OAuth credential state for an OAuth-runtime tool binding (Google Calendar, Slack …): { state: not-connected | connected | expired | revoked | error, connectedAt?, lastError? }. Connecting needs the operator's browser (console Connect button) and is not a tool. Maps to GET /v1/orgs/{org}/agents/{agent}/tools/{toolId}/oauth/status.",
156
255
  kind: 'read',
157
256
  scope: 'agents:read',
158
257
  roleFloor: 'viewer',
@@ -162,60 +261,165 @@ exports.capabilityTools = [
162
261
  (0, tool_1.defineTool)({
163
262
  name: 'disconnect_oauth',
164
263
  title: 'Disconnect an OAuth grant',
165
- description: 'Revoke the OAuth grant at the provider and delete the stored credential for an OAuth-runtime tool binding. The agent immediately loses access to the connected account (e.g. the calendar) until an operator reconnects in a browser; the binding itself stays so reconnecting is one click. Confirm with the user before running. Maps to POST /v1/orgs/{org}/agents/{agent}/tools/{toolId}/oauth/disconnect.',
264
+ description: 'Revoke the OAuth grant at the provider and delete the stored credential; the agent loses access until an operator reconnects in a browser. The binding stays. Maps to POST /v1/orgs/{org}/agents/{agent}/tools/{toolId}/oauth/disconnect.',
166
265
  kind: 'destructive',
167
266
  scope: 'agents:write',
168
267
  roleFloor: 'editor',
169
268
  input: { agentId, toolId },
170
269
  run: async ({ client }, { agentId, toolId }) => client.post(await client.orgPath(oauthPath(agentId, toolId, 'disconnect'))),
171
270
  }),
271
+ // ── Custom tools (integration contracts) ─────────────────────────────
272
+ (0, tool_1.defineTool)({
273
+ name: 'list_custom_tools',
274
+ title: 'List custom tools',
275
+ description: "List the agent's custom-integration contracts: { data: [{ id, name?, description, parameters }] }. The HTTP connection (url, method, auth) is the separate tool binding with the same id. Maps to GET /v1/orgs/{org}/agents/{agent}/custom-tools.",
276
+ kind: 'read',
277
+ scope: 'agents:read',
278
+ roleFloor: 'viewer',
279
+ input: { agentId },
280
+ run: async ({ client }, { agentId }) => client.get(await client.orgPath(customToolsPath(agentId))),
281
+ }),
282
+ (0, tool_1.defineTool)({
283
+ name: 'get_custom_tool',
284
+ title: 'Get a custom tool',
285
+ description: 'Read one custom-tool contract { id, name?, description, parameters } (404 tool.not_found). Maps to GET /v1/orgs/{org}/agents/{agent}/custom-tools/{toolId}.',
286
+ kind: 'read',
287
+ scope: 'agents:read',
288
+ roleFloor: 'viewer',
289
+ input: { agentId, toolId: customToolId },
290
+ run: async ({ client }, { agentId, toolId }) => client.get(await client.orgPath(customToolsPath(agentId, toolId))),
291
+ }),
292
+ (0, tool_1.defineTool)({
293
+ name: 'upsert_custom_tool',
294
+ title: 'Create or replace a custom tool',
295
+ description: 'Create (201) or replace (200) a custom-tool contract — what the agent calls it, what it does, and its argument schema. Contracts never carry credentials: bind the URL/method/auth afterwards with upsert_tool_binding using the same id. Maps to PUT /v1/orgs/{org}/agents/{agent}/custom-tools/{toolId}.',
296
+ kind: 'write',
297
+ scope: 'agents:write',
298
+ roleFloor: 'editor',
299
+ input: {
300
+ agentId,
301
+ toolId: customToolId,
302
+ name: zod_1.z.string().optional().describe('Operator-facing label; defaults to id'),
303
+ description: zod_1.z
304
+ .string()
305
+ .min(1)
306
+ .describe('What the tool does and when the agent should use it'),
307
+ parameters: zod_1.z
308
+ .record(zod_1.z.string(), zod_1.z.any())
309
+ .optional()
310
+ .describe('JSON Schema for the call arguments (object with properties); default {}'),
311
+ },
312
+ run: async ({ client }, { agentId, toolId, name, description, parameters }) => client.put(await client.orgPath(customToolsPath(agentId, toolId)), {
313
+ ...(name !== undefined ? { name } : {}),
314
+ description,
315
+ ...(parameters !== undefined ? { parameters } : {}),
316
+ }),
317
+ }),
318
+ (0, tool_1.defineTool)({
319
+ name: 'delete_custom_tool',
320
+ title: 'Delete a custom tool',
321
+ description: 'Remove a custom-tool contract (404 tool.not_found). Its tool binding is left alone — delete_tool_binding it too when retiring the integration. Maps to DELETE /v1/orgs/{org}/agents/{agent}/custom-tools/{toolId}.',
322
+ kind: 'destructive',
323
+ scope: 'agents:write',
324
+ roleFloor: 'editor',
325
+ input: { agentId, toolId: customToolId },
326
+ run: async ({ client }, { agentId, toolId }) => {
327
+ await client.delete(await client.orgPath(customToolsPath(agentId, toolId)));
328
+ return { ok: true, deleted: toolId };
329
+ },
330
+ }),
331
+ // ── Scheduling config ────────────────────────────────────────────────
332
+ (0, tool_1.defineTool)({
333
+ name: 'get_scheduling_config',
334
+ title: 'Get scheduling config',
335
+ description: "Read the agent's Scheduling capability config: timezone, availableHours, blackoutDates, disallowSameDayBooking, defaultDurationMinutes, notificationRecipients, reminders, representatives (always present), defaultRepresentativeIdentity. Never includes the Google Calendar link. Maps to GET /v1/orgs/{org}/agents/{agent}/scheduling.",
336
+ kind: 'read',
337
+ scope: 'agents:read',
338
+ roleFloor: 'viewer',
339
+ profile: 'core',
340
+ input: { agentId },
341
+ run: async ({ client }, { agentId }) => client.get(await client.orgPath(schedulingPath(agentId))),
342
+ }),
343
+ (0, tool_1.defineTool)({
344
+ name: 'replace_scheduling_config',
345
+ title: 'Replace scheduling config',
346
+ description: 'Replace the whole scheduling config — omitted keys are cleared (representatives → [], defaultRepresentativeIdentity unset), so read first with get_scheduling_config and send everything. googleCalendar is ignored and preserved (connect via console). 422 names the bad path. Maps to PUT /v1/orgs/{org}/agents/{agent}/scheduling.',
347
+ kind: 'write',
348
+ scope: 'agents:write',
349
+ roleFloor: 'editor',
350
+ profile: 'core',
351
+ input: { agentId, ...schedulingConfigShape },
352
+ run: async ({ client }, { agentId, ...config }) => client.put(await client.orgPath(schedulingPath(agentId)), stripUndefined(config)),
353
+ }),
354
+ // ── People fields ────────────────────────────────────────────────────
355
+ (0, tool_1.defineTool)({
356
+ name: 'get_people_fields',
357
+ title: 'Get people field definitions',
358
+ description: "Read the agent's declared People fields (the structured facts tracked in Person.customFields): { data: [{ key, label, description, type, enum, examples, required, intake }] }. Maps to GET /v1/orgs/{org}/agents/{agent}/people-fields.",
359
+ kind: 'read',
360
+ scope: 'agents:read',
361
+ roleFloor: 'viewer',
362
+ profile: 'core',
363
+ input: { agentId },
364
+ run: async ({ client }, { agentId }) => client.get(await client.orgPath(peopleFieldsPath(agentId))),
365
+ }),
366
+ (0, tool_1.defineTool)({
367
+ name: 'replace_people_fields',
368
+ title: 'Replace people field definitions',
369
+ description: 'Replace the whole list of declared People fields (read first with get_people_fields; a dropped key hides that customFields value from list filters and the agent). Keys must be unique. Maps to PUT /v1/orgs/{org}/agents/{agent}/people-fields.',
370
+ kind: 'write',
371
+ scope: 'agents:write',
372
+ roleFloor: 'editor',
373
+ profile: 'core',
374
+ input: { agentId, fields: zod_1.z.array(personFieldDef) },
375
+ run: async ({ client }, { agentId, fields }) => client.put(await client.orgPath(peopleFieldsPath(agentId)), { fields }),
376
+ }),
172
377
  // ── Outreach config ──────────────────────────────────────────────────
173
378
  (0, tool_1.defineTool)({
174
379
  name: 'get_outreach_config',
175
380
  title: 'Get outreach config',
176
- description: "Read the agent's outreach configuration: greetingTemplateId (the message template used as the first message when the agent initiates a conversation) and followUps (the single automatic nudge for non-responders: enabled, steps[1], quietHours). Returns {} when nothing is configured (outreach then falls back to an LLM-composed opener). Maps to GET /v1/orgs/{org}/agents/{agent}/outreach.",
381
+ description: "Read the agent's outreach config: greetingTemplateId (first message when the agent initiates) and followUps (single automatic nudge for non-responders: enabled, steps[1], quietHours). {} when unset (LLM-composed opener). Maps to GET /v1/orgs/{org}/agents/{agent}/outreach.",
177
382
  kind: 'read',
178
383
  scope: 'agents:read',
179
384
  roleFloor: 'viewer',
385
+ profile: 'core',
180
386
  input: { agentId },
181
387
  run: async ({ client }, { agentId }) => client.get(await client.orgPath(outreachPath(agentId))),
182
388
  }),
183
389
  (0, tool_1.defineTool)({
184
390
  name: 'replace_outreach_config',
185
391
  title: 'Replace outreach config',
186
- description: "Replace the agent's whole outreach configuration (PUT semantics: fields you omit are cleared — read it first with get_outreach_config). greetingTemplateId must be an existing message template on the agent (else 404); followUps.steps holds exactly one step (more → 422); pass {} to clear everything. Enabling follow-ups only enrolls people contacted afterwards — never existing contacts. Returns the saved config. Maps to PUT /v1/orgs/{org}/agents/{agent}/outreach.",
392
+ description: 'Replace the whole outreach config (omitted fields are cleared — read first with get_outreach_config). greetingTemplateId must exist (404); followUps.steps holds exactly one step (422); {} clears everything. Enabling follow-ups only enrolls people contacted afterwards. Maps to PUT /v1/orgs/{org}/agents/{agent}/outreach.',
187
393
  kind: 'write',
188
394
  scope: 'agents:write',
189
395
  roleFloor: 'editor',
396
+ profile: 'core',
190
397
  input: {
191
398
  agentId,
192
399
  greetingTemplateId: zod_1.z
193
400
  .string()
194
401
  .optional()
195
- .describe("Id of one of the agent's message templates to use as the outreach greeting; omit to fall back to an LLM-composed opener"),
402
+ .describe('Message template id for the greeting; omit for an LLM-composed opener'),
196
403
  followUps: zod_1.z
197
404
  .object({
198
- enabled: zod_1.z.boolean().describe('Master switch for automatic follow-up sending'),
199
- steps: zod_1.z
200
- .array(followUpStep)
201
- .max(1)
202
- .describe('Exactly one follow-up step (or empty)'),
405
+ enabled: zod_1.z.boolean(),
406
+ steps: zod_1.z.array(followUpStep).max(1),
203
407
  quietHours: zod_1.z
204
408
  .object({
205
- startHour: zod_1.z.number().int().min(0).max(23).describe('0–23, inclusive'),
409
+ startHour: zod_1.z.number().int().min(0).max(23),
206
410
  endHour: zod_1.z
207
411
  .number()
208
412
  .int()
209
413
  .min(0)
210
414
  .max(23)
211
- .describe('0–23, exclusive; may wrap midnight'),
212
- timezone: zod_1.z.string().min(1).describe('IANA zone, e.g. "America/Chicago"'),
415
+ .describe('Exclusive; may wrap midnight'),
416
+ timezone: zod_1.z.string().min(1).describe('IANA zone'),
213
417
  })
214
418
  .optional()
215
- .describe('Suppress the automatic send during these local hours'),
419
+ .describe('Suppress sends during these local hours'),
216
420
  })
217
421
  .optional()
218
- .describe('Automatic follow-up (nudge) config; omit to clear follow-ups'),
422
+ .describe('Automatic follow-up config; omit to clear'),
219
423
  },
220
424
  run: async ({ client }, { agentId, greetingTemplateId, followUps }) => client.put(await client.orgPath(outreachPath(agentId)), {
221
425
  ...(greetingTemplateId !== undefined ? { greetingTemplateId } : {}),
@@ -225,15 +429,13 @@ exports.capabilityTools = [
225
429
  (0, tool_1.defineTool)({
226
430
  name: 'preview_outreach',
227
431
  title: 'Preview outreach greeting',
228
- description: 'Render the configured outreach greeting for a specific person and return what would be sent: { greeting: { renderedText, missingSlots[], blocked } }. Use to check slot substitution before sending outreach. Returns 404 when no greeting template is configured. Maps to GET /v1/orgs/{org}/agents/{agent}/outreach/preview?personId=….',
432
+ description: 'Render the configured outreach greeting for a person: { greeting: { renderedText, missingSlots[], blocked } }. Sends nothing; 404 when no greeting template is configured. Maps to GET /v1/orgs/{org}/agents/{agent}/outreach/preview?personId=….',
229
433
  kind: 'read',
230
434
  scope: 'agents:read',
231
435
  roleFloor: 'viewer',
232
436
  input: {
233
437
  agentId,
234
- personId: zod_1.z
235
- .string()
236
- .describe('Id of a Person on this agent to render the greeting against'),
438
+ personId: zod_1.z.string(),
237
439
  },
238
440
  run: async ({ client }, { agentId, personId }) => client.get(await client.orgPath(`${outreachPath(agentId)}/preview`), { personId }),
239
441
  }),
@@ -241,7 +443,7 @@ exports.capabilityTools = [
241
443
  (0, tool_1.defineTool)({
242
444
  name: 'list_outreach_links',
243
445
  title: 'List outreach links',
244
- description: "List the agent's outreach links — shareable public phone-entry pages that let a prospect start an RCS conversation with the agent. Each has { id, code, url, label, maxUses, usesRemaining, redemptionCount, expiresAt, disabled, createdAt }. Maps to GET /v1/orgs/{org}/agents/{agent}/outreach-links.",
446
+ description: "List the agent's outreach links — shareable public phone-entry pages that start an RCS conversation: { id, code, url, label, maxUses, usesRemaining, redemptionCount, expiresAt, disabled, createdAt }. Maps to GET /v1/orgs/{org}/agents/{agent}/outreach-links.",
245
447
  kind: 'read',
246
448
  scope: 'agents:read',
247
449
  roleFloor: 'viewer',
@@ -251,27 +453,19 @@ exports.capabilityTools = [
251
453
  (0, tool_1.defineTool)({
252
454
  name: 'create_outreach_link',
253
455
  title: 'Create an outreach link',
254
- description: "Mint a new use-limited, time-limited outreach link against one of the agent's RCS channel bindings (find the binding id with the channel tools). Returns the link including its shareable url. Validation failures return 400 with a human-readable error. Maps to POST /v1/orgs/{org}/agents/{agent}/outreach-links.",
456
+ description: "Mint a use- and time-limited outreach link against one of the agent's RCS channel bindings (see list_channels). Returns the link incl. its shareable url. Maps to POST /v1/orgs/{org}/agents/{agent}/outreach-links.",
255
457
  kind: 'write',
256
458
  scope: 'agents:write',
257
459
  roleFloor: 'editor',
258
460
  input: {
259
461
  agentId,
260
- bindingId: zod_1.z
261
- .string()
262
- .describe('Id of an RCS channel binding on this agent (required)'),
263
- label: zod_1.z.string().optional().describe('Operator-facing note, e.g. "Trade-show QR"'),
264
- maxUses: zod_1.z
265
- .number()
266
- .int()
267
- .min(1)
268
- .max(500)
269
- .optional()
270
- .describe('Redemption cap, 1–500 (default 25)'),
462
+ bindingId: zod_1.z.string().describe('RCS channel binding id'),
463
+ label: zod_1.z.string().optional().describe('Operator note, e.g. "Trade-show QR"'),
464
+ maxUses: zod_1.z.number().int().min(1).max(500).optional().describe('Default 25'),
271
465
  expiresAt: zod_1.z
272
466
  .string()
273
467
  .optional()
274
- .describe('ISO-8601 expiry, at most 30 days out (default 14 days from now)'),
468
+ .describe('ISO-8601, at most 30 days out (default 14 days)'),
275
469
  },
276
470
  run: async ({ client }, { agentId, bindingId, label, maxUses, expiresAt }) => client.post(await client.orgPath(outreachLinksPath(agentId)), {
277
471
  bindingId,
@@ -283,38 +477,60 @@ exports.capabilityTools = [
283
477
  (0, tool_1.defineTool)({
284
478
  name: 'update_outreach_link',
285
479
  title: 'Pause or resume an outreach link',
286
- description: "Set an outreach link's disabled flag — pause (disabled: true) or resume (false) without losing its remaining uses. This is the only mutable field. Returns the updated link. Maps to PATCH /v1/orgs/{org}/agents/{agent}/outreach-links/{id}.",
480
+ description: 'Pause (disabled: true) or resume (false) an outreach link without losing remaining uses — the only mutable field. Maps to PATCH /v1/orgs/{org}/agents/{agent}/outreach-links/{id}.',
287
481
  kind: 'write',
288
482
  scope: 'agents:write',
289
483
  roleFloor: 'editor',
290
484
  input: {
291
485
  agentId,
292
- linkId: zod_1.z.string().describe('Outreach link id (from list_outreach_links)'),
293
- disabled: zod_1.z.boolean().describe('true to pause the link, false to resume it'),
486
+ linkId: zod_1.z.string(),
487
+ disabled: zod_1.z.boolean(),
294
488
  },
295
489
  run: async ({ client }, { agentId, linkId, disabled }) => client.patch(await client.orgPath(`${outreachLinksPath(agentId)}/${encodeURIComponent(linkId)}`), { disabled }),
296
490
  }),
297
491
  (0, tool_1.defineTool)({
298
492
  name: 'delete_outreach_link',
299
493
  title: 'Delete an outreach link',
300
- description: 'Permanently remove an outreach link — anyone holding the URL can no longer use it and its redemption count is lost. Prefer update_outreach_link (disabled: true) to pause instead. Returns { ok: true, deleted: linkId } (the API replies 204). Maps to DELETE /v1/orgs/{org}/agents/{agent}/outreach-links/{id}.',
494
+ description: 'Permanently remove an outreach link (URL stops working; redemption count lost). Prefer update_outreach_link to pause. Maps to DELETE /v1/orgs/{org}/agents/{agent}/outreach-links/{id}.',
301
495
  kind: 'destructive',
302
496
  scope: 'agents:write',
303
497
  roleFloor: 'editor',
304
498
  input: {
305
499
  agentId,
306
- linkId: zod_1.z.string().describe('Outreach link id (from list_outreach_links)'),
500
+ linkId: zod_1.z.string(),
307
501
  },
308
502
  run: async ({ client }, { agentId, linkId }) => {
309
503
  await client.delete(await client.orgPath(`${outreachLinksPath(agentId)}/${encodeURIComponent(linkId)}`));
310
504
  return { ok: true, deleted: linkId };
311
505
  },
312
506
  }),
507
+ // ── Products (Knowledge capability) ──────────────────────────────────
508
+ (0, tool_1.defineTool)({
509
+ name: 'get_products',
510
+ title: 'Get product catalog',
511
+ description: "Read the agent's product catalog (Knowledge capability): { data: [{ id, name, description, imageUrl, productPageUrl, metadata }] }. Maps to GET /v1/orgs/{org}/agents/{agent}/products.",
512
+ kind: 'read',
513
+ scope: 'agents:read',
514
+ roleFloor: 'viewer',
515
+ profile: 'core',
516
+ input: { agentId },
517
+ run: async ({ client }, { agentId }) => client.get(await client.orgPath(productsPath(agentId))),
518
+ }),
519
+ (0, tool_1.defineTool)({
520
+ name: 'replace_products',
521
+ title: 'Replace product catalog',
522
+ description: 'Replace the whole product catalog (read first with get_products and send every product you want kept). Ids must be unique. Maps to PUT /v1/orgs/{org}/agents/{agent}/products.',
523
+ kind: 'write',
524
+ scope: 'agents:write',
525
+ roleFloor: 'editor',
526
+ input: { agentId, products: zod_1.z.array(agentProduct) },
527
+ run: async ({ client }, { agentId, products }) => client.put(await client.orgPath(productsPath(agentId)), { products }),
528
+ }),
313
529
  // ── Knowledge documents (read-only) ──────────────────────────────────
314
530
  (0, tool_1.defineTool)({
315
531
  name: 'list_knowledge_documents',
316
532
  title: 'List knowledge documents',
317
- description: "List the documents in the agent's knowledge base (metadata only: id, fileName, mimeType, title, status, uploadedAt, createdAt, updatedAt — never the extracted text). Use to see what the agent can draw on for answers. Uploading and deleting documents is console-only today. Maps to GET /v1/orgs/{org}/agents/{agent}/knowledge/documents.",
533
+ description: "List the agent's knowledge-base documents (metadata only: id, fileName, mimeType, title, status, timestamps — never the extracted text). Upload/delete is console-only. Maps to GET /v1/orgs/{org}/agents/{agent}/knowledge/documents.",
318
534
  kind: 'read',
319
535
  scope: 'knowledge:read',
320
536
  roleFloor: 'viewer',