@speakai/mcp-server 1.13.7 → 1.14.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 (2) hide show
  1. package/dist/index.js +561 -23
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -2449,7 +2449,7 @@ function register5(server, client) {
2449
2449
  name: import_zod6.z.string().describe("Display name for the recorder"),
2450
2450
  ...recorderConfigShape,
2451
2451
  clientInformation: import_zod6.z.record(import_zod6.z.unknown()).optional().describe(
2452
- "Respondent info & questions: { name:boolean, email:boolean, questions:[{ question, isRequired, answerType, options?, includeOther?, fieldId? }], consent?:{ isEnabled, title, description, yesButtonLabel, noButtonLabel, isRequired, fieldId? } }"
2452
+ `Respondent info & questions: { name:boolean, email:boolean, questions:[\u2026], consent?:{ isEnabled, title, description, yesButtonLabel, noButtonLabel, isRequired, fieldId? } }. Question shape \u2014 ${QUESTION_SHAPE_DESC}`
2453
2453
  )
2454
2454
  },
2455
2455
  {
@@ -2661,7 +2661,7 @@ function register5(server, client) {
2661
2661
  name: import_zod6.z.boolean().optional().describe("Whether to collect the respondent's name"),
2662
2662
  email: import_zod6.z.boolean().optional().describe("Whether to collect the respondent's email"),
2663
2663
  questions: import_zod6.z.array(import_zod6.z.record(import_zod6.z.unknown())).describe(
2664
- "Survey questions. Each: { question, isRequired, answerType, options?, includeOther?, fieldId?, id? }"
2664
+ `Survey questions. ${QUESTION_SHAPE_DESC} (id? may also be passed to update an existing question.)`
2665
2665
  ),
2666
2666
  consent: import_zod6.z.record(import_zod6.z.unknown()).optional().describe(
2667
2667
  "Consent screen: { isEnabled, title, description, yesButtonLabel, noButtonLabel, isRequired, fieldId? }"
@@ -2717,13 +2717,24 @@ function register5(server, client) {
2717
2717
  }
2718
2718
  );
2719
2719
  }
2720
- var import_zod6, recorderConfigShape;
2720
+ var import_zod6, RECORDER_ANSWER_TYPES, QUESTION_SHAPE_DESC, recorderConfigShape;
2721
2721
  var init_recorder3 = __esm({
2722
2722
  "src/tools/recorder.ts"() {
2723
2723
  "use strict";
2724
2724
  import_zod6 = require("zod");
2725
2725
  init_helpers();
2726
2726
  init_client();
2727
+ RECORDER_ANSWER_TYPES = [
2728
+ "single",
2729
+ "multiple",
2730
+ "checkbox",
2731
+ "radiobutton",
2732
+ "dropdownlist",
2733
+ "date",
2734
+ "time",
2735
+ "datetime"
2736
+ ];
2737
+ QUESTION_SHAPE_DESC = `Each: { question, isRequired, answerType, options?, includeOther?, fieldId? }. answerType must be one of: ${RECORDER_ANSWER_TYPES.map((t) => `"${t}"`).join(", ")}. Choice types (single, multiple, checkbox, radiobutton, dropdownlist) take options:string[] and includeOther:boolean (adds a free-text "Other"). date/time/datetime take no options. There is no free-text/rating/number answerType.`;
2727
2738
  recorderConfigShape = {
2728
2739
  description: import_zod6.z.string().optional().describe("Recorder description"),
2729
2740
  sourceLanguage: import_zod6.z.string().optional().describe("Transcription language code (e.g. en-US)"),
@@ -2944,22 +2955,14 @@ function register7(server, client) {
2944
2955
  );
2945
2956
  registerSpeakTool(
2946
2957
  server,
2947
- "ask_magic_prompt",
2948
- `[Deprecated \u2014 use ask_ai_chat] ${askAiChatDescription}`,
2949
- askAiChatInputSchema,
2950
- askAiChatAnnotations,
2951
- askAiChatHandler
2952
- );
2953
- registerSpeakTool(
2954
- server,
2955
- "retry_magic_prompt",
2958
+ "retry_ai_chat",
2956
2959
  "Retry a failed or incomplete AI Chat response. Use when a previous ask_ai_chat call returned an error or incomplete answer.",
2957
2960
  {
2958
2961
  promptId: import_zod8.z.string().min(1).describe("ID of the conversation containing the failed message"),
2959
2962
  messageId: import_zod8.z.string().min(1).describe("ID of the specific message to retry")
2960
2963
  },
2961
2964
  {
2962
- title: "Retry AI Question",
2965
+ title: "Retry AI Chat",
2963
2966
  readOnlyHint: false,
2964
2967
  destructiveHint: false,
2965
2968
  idempotentHint: false,
@@ -3668,11 +3671,87 @@ var init_fields2 = __esm({
3668
3671
  }
3669
3672
  });
3670
3673
 
3674
+ // src/tools/inbound-webhook-utils.ts
3675
+ function unwrapData(payload) {
3676
+ const p = payload;
3677
+ return p && typeof p === "object" && "status" in p && "data" in p ? p.data : p;
3678
+ }
3679
+ function narrowPathsToChildKey(paths, childKey) {
3680
+ if (!childKey) return paths;
3681
+ const prefix = `${childKey}.`;
3682
+ const narrowed = paths.filter((p) => p.startsWith(prefix)).map((p) => p.slice(prefix.length)).filter(Boolean);
3683
+ return narrowed.length ? narrowed : paths;
3684
+ }
3685
+ function buildHowToUse(inboundUrl, sampleCaptured) {
3686
+ const url = inboundUrl ?? "<inboundUrl>";
3687
+ const lines = [
3688
+ `Send events with: curl -X POST '${url}' -H 'Content-Type: application/json' -d '{"url": "https://example.com/file.mp3", "name": "My recording"}'`,
3689
+ `To capture/refresh a sample payload WITHOUT running the automation, POST to '${url}?test=1'.`,
3690
+ "Reference payload values in step configs with {{trigger.payload.<path>}} tokens \u2014 see mappableTokens."
3691
+ ];
3692
+ if (!sampleCaptured) {
3693
+ lines.unshift(
3694
+ "No sample payload captured yet \u2014 send a test request first (see below) so payload paths become discoverable for mapping, then call get_inbound_webhook again."
3695
+ );
3696
+ }
3697
+ return lines;
3698
+ }
3699
+ async function fetchInboundWebhookInfo(api, webhookId, childKey) {
3700
+ const res = await api.get(`/v1/webhook/${webhookId}`);
3701
+ const payload = unwrapData(res.data);
3702
+ const wh = payload?.webhookData ?? payload ?? {};
3703
+ const flattened = Array.isArray(wh.flattenedPaths) ? wh.flattenedPaths : [];
3704
+ const sample = wh.samplePayload ?? null;
3705
+ const sampleCaptured = sample != null && (typeof sample !== "object" || Object.keys(sample).length > 0);
3706
+ const inboundUrl = typeof wh.inboundUrl === "string" ? wh.inboundUrl : null;
3707
+ return {
3708
+ webhookId,
3709
+ inboundUrl,
3710
+ sampleCaptured,
3711
+ samplePayload: sample,
3712
+ mappableTokens: narrowPathsToChildKey(flattened, childKey).map(
3713
+ (p) => `{{trigger.payload.${p}}}`
3714
+ ),
3715
+ ...childKey ? { childKey } : {},
3716
+ howToUse: buildHowToUse(inboundUrl, sampleCaptured)
3717
+ };
3718
+ }
3719
+ async function resolveAutomationInboundWebhook(api, automationId) {
3720
+ const res = await api.get(`/v1/automations/${automationId}`);
3721
+ const automation = unwrapData(res.data);
3722
+ const trigger = automation?.trigger ?? {};
3723
+ return {
3724
+ webhookId: typeof trigger.webhookId === "string" && trigger.webhookId ? trigger.webhookId : void 0,
3725
+ childKey: typeof trigger.childKey === "string" && trigger.childKey ? trigger.childKey : void 0
3726
+ };
3727
+ }
3728
+ function isInboundWebhookTrigger(trigger) {
3729
+ const t = trigger;
3730
+ return !!t && (t.triggerSlug === "inbound_webhook" || t.type === "webhook" || !!t.webhookId);
3731
+ }
3732
+ var init_inbound_webhook_utils = __esm({
3733
+ "src/tools/inbound-webhook-utils.ts"() {
3734
+ "use strict";
3735
+ }
3736
+ });
3737
+
3671
3738
  // src/tools/automations.ts
3672
3739
  var automations_exports = {};
3673
3740
  __export(automations_exports, {
3674
3741
  register: () => register10
3675
3742
  });
3743
+ async function withInboundWebhookInfo(api, responseData, automationId) {
3744
+ try {
3745
+ if (!automationId) return responseData;
3746
+ const { webhookId, childKey } = await resolveAutomationInboundWebhook(api, automationId);
3747
+ if (!webhookId) return responseData;
3748
+ const inboundWebhook = await fetchInboundWebhookInfo(api, webhookId, childKey);
3749
+ const base = responseData && typeof responseData === "object" ? responseData : { data: responseData };
3750
+ return { ...base, inboundWebhook };
3751
+ } catch {
3752
+ return responseData;
3753
+ }
3754
+ }
3676
3755
  function register10(server, client) {
3677
3756
  const api = client ?? speakClient;
3678
3757
  registerSpeakTool(
@@ -3797,7 +3876,7 @@ function register10(server, client) {
3797
3876
  registerSpeakTool(
3798
3877
  server,
3799
3878
  "create_automation",
3800
- "Create a new automation rule using the V2 graph model (trigger + ordered steps). Fetch valid step/trigger options with list_automation_triggers / list_automation_actions if unsure.",
3879
+ "Create a new automation rule using the V2 graph model (trigger + ordered steps). Fetch valid step/trigger options with list_automation_triggers / list_automation_actions if unsure. For inbound-webhook automations the response includes inboundWebhook.inboundUrl (where to POST payloads) \u2014 recommended flow: create, send a test payload to the URL with ?test=1, call get_inbound_webhook to see mappable payload tokens, then update_automation to wire tokens/fieldsMap.",
3801
3880
  writeSchema,
3802
3881
  {
3803
3882
  title: "Create Automation",
@@ -3809,8 +3888,13 @@ function register10(server, client) {
3809
3888
  async (body) => {
3810
3889
  try {
3811
3890
  const result = await api.post("/v1/automations/", body);
3891
+ let data = result.data;
3892
+ if (isInboundWebhookTrigger(body.trigger)) {
3893
+ const automationId = unwrapData(result.data)?.automationId;
3894
+ data = await withInboundWebhookInfo(api, data, automationId);
3895
+ }
3812
3896
  return {
3813
- content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }]
3897
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
3814
3898
  };
3815
3899
  } catch (err) {
3816
3900
  return {
@@ -3838,8 +3922,12 @@ function register10(server, client) {
3838
3922
  async ({ automationId, ...body }) => {
3839
3923
  try {
3840
3924
  const result = await api.put(`/v1/automations/${automationId}`, body);
3925
+ let data = result.data;
3926
+ if (isInboundWebhookTrigger(body.trigger)) {
3927
+ data = await withInboundWebhookInfo(api, data, automationId);
3928
+ }
3841
3929
  return {
3842
- content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }]
3930
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
3843
3931
  };
3844
3932
  } catch (err) {
3845
3933
  return {
@@ -4075,18 +4163,27 @@ function register10(server, client) {
4075
4163
  }
4076
4164
  );
4077
4165
  }
4078
- var import_zod11, STEPS_DESCRIPTION, TRIGGER_DESCRIPTION, writeSchema;
4166
+ var import_zod11, TOKEN_SYNTAX_NOTE, STEPS_DESCRIPTION, TRIGGER_DESCRIPTION, OR_TRIGGERS_DESCRIPTION, writeSchema;
4079
4167
  var init_automations = __esm({
4080
4168
  "src/tools/automations.ts"() {
4081
4169
  "use strict";
4082
4170
  import_zod11 = require("zod");
4083
4171
  init_helpers();
4084
4172
  init_client();
4085
- STEPS_DESCRIPTION = 'Ordered array of graph steps (1-20). Each step is an object: { stepId: string (unique within the array), stepType: one of "magic-prompt" | "translation" | "filter" | "composio-action" | "speak-upload", dependsOn?: string[] (stepIds this step runs after) } plus ONE payload key matching stepType:\n- magic-prompt -> magicPrompt: { prompt (required unless fieldIds given), title?, assistantType? ("general"|"researcher"|"marketer"|"sales"|"recruiter"|"custom", default "general"), assistantTemplateId? (required if assistantType="custom"), fieldIds?: string[] (max 10, for field extraction) }\n- translation -> translation: { targetLanguage: BCP-47 code, e.g. "es" }\n- filter -> filter: { logic: "AND"|"OR", rules: [{ field, op: "eq"|"neq"|"contains"|"ncontains"|"startsWith"|"gt"|"lt"|"exists", value? }] }\n- composio-action -> composio: { app, action, connectedAccountId?, argsTemplate? } (Composio is currently behind a server flag and may be unavailable)\n- speak-upload -> speakUpload: { folderId, sourceMode: "url"|"file", sourceUrl? (required when sourceMode="url"), name?, fieldsMap?, language? }';
4086
- TRIGGER_DESCRIPTION = 'Trigger object. For folder-based automations: { type: "folders", folderIds: string[] } (at least one folder is required). Note: only "folders" is currently supported for multi-step automations via the API \u2014 "tags"/"keywords" are rejected, and "composio"/"webhook" triggers (provider, app, triggerSlug, webhookId, childKey, connectedAccountId) are gated by server flags.';
4173
+ init_inbound_webhook_utils();
4174
+ TOKEN_SYNTAX_NOTE = "Token syntax (usable in fields marked 'tokens allowed'): {{trigger.payload.<path>}} reads the inbound webhook payload (dot paths and [n] array indices; paths are relative to trigger.childKey when set \u2014 discover valid paths with get_inbound_webhook after sending a test payload); {{step.<index>.<path>}} or {{step.<stepId>.<path>}} reads a previous step's output (speak-upload -> mediaId, magic-prompt -> answer, outbound-webhook -> status/response).";
4175
+ STEPS_DESCRIPTION = 'Ordered array of graph steps (1-20). Each step is an object: { stepId: string (unique within the array), stepType: one of "speak-upload" | "magic-prompt" | "translation" | "filter" | "condition" | "notify" | "outbound-webhook" | "composio-action", dependsOn?: string[] (stepIds this step runs after), branch?: "true"|"false" (which outcome of an upstream condition step this step belongs to) } plus ONE config key matching stepType:\n- speak-upload -> speakUpload: { sourceMode: "url"|"file", sourceUrl (required when sourceMode="url"; tokens allowed \u2014 if the token resolves to an object, the first http(s) URL inside it is used), folderId (required, unless folderRouting.mode="dynamic" where it becomes the optional fallback), name? (tokens allowed, mixable with static text), language? (language code or token), fieldsMap?: { <customFieldId>: "<value>" } (writes payload values into Speak custom fields on the uploaded media; values are usually {{trigger.payload.<path>}} tokens \u2014 get field ids from list_fields), folderRouting?: { mode: "static"|"dynamic", sourceKey (payload key holding the destination folder name, required when dynamic), onNoMatch: "create"|"default" (create a folder named after the value, or fall back to folderId) } }\n- magic-prompt -> magicPrompt: { prompt (required unless fieldIds given, max 20000), title?, assistantType? ("general"|"researcher"|"marketer"|"sales"|"recruiter"|"custom", default "general"), assistantTemplateId? (required if assistantType="custom"), fieldIds?: string[] (max 10 \u2014 extract answers into these custom fields) }\n- translation -> translation: { targetLanguage: region-qualified locale code, e.g. "es-ES", "fr-FR" (bare codes like "es" are rejected) }\n- filter -> filter: { logic: "AND"|"OR" (default "AND"), rules: [{ field, op, value? }] (1-20) } \u2014 the run continues only when the rules match, otherwise it stops silently\n- condition -> condition: same { logic, rules } shape as filter, but instead of stopping it routes: downstream steps marked branch:"true"/"false" run according to the outcome\n- notify -> notify: { channel: "in_app"|"email"|"slack", target?, message (required, tokens allowed) }\n- outbound-webhook -> outboundWebhook: { url (required, tokens allowed), method? ("GET"|"POST"|"PUT"|"PATCH"|"DELETE", default "POST"), headers?: { <name>: <value> }, bodyTemplate?: string | object (tokens allowed) }\n- composio-action -> composio: { app, action, connectedAccountId?, argsTemplate? } (Composio is currently behind a server flag and may be unavailable)\nFilter/condition rule fields depend on what flows into the step: MEDIA -> name|duration|sourceLanguage|tags|transcript|speakers or a custom field id; INSIGHT -> answer; inbound-webhook DATA -> any payload path (e.g. "contact.status"). Ops by field type \u2014 text: eq|neq|contains|ncontains|startsWith|exists; number: eq|neq|gt|lt|exists; array: contains|ncontains|exists ("exists" takes no value; gt/lt values are numbers).\n' + TOKEN_SYNTAX_NOTE;
4176
+ TRIGGER_DESCRIPTION = `Trigger object (the automation's root). Always include triggerSlug. Supported shapes:
4177
+ - Media analyzed in folder(s): { type: "folders", triggerSlug: "media_analyzed", folderIds: string[] (min 1) }
4178
+ - Inbound webhook (receive external payloads): { type: "folders", triggerSlug: "inbound_webhook", webhookId? (from provision_inbound_webhook; omit to auto-provision a new one on create), childKey? (dot-path narrowing which part of the payload feeds the automation, e.g. "data") }. The create/update response includes inboundWebhook.inboundUrl \u2014 the public URL to POST payloads to.
4179
+ - Custom field updated: { type: "folders", triggerSlug: "field_updated", values: string[] (watched custom field ids, min 1), fieldValueMatches?: [{ fieldId, values: string[] }] (fire only when the field changes TO one of these values; empty values = any change), fieldMatchLogic?: "AND"|"OR" (how multiple fieldValueMatches combine, default "OR") }
4180
+ - Composio app event: { type: "composio", provider: "composio", app, triggerSlug, connectedAccountId } (requires a connected account; may be behind a server flag)
4181
+ Notes: "tags"/"keywords" trigger types are rejected for graph automations. The server stores inbound-webhook triggers with type "webhook" internally \u2014 send type "folders" plus the slug as shown above.`;
4182
+ OR_TRIGGERS_DESCRIPTION = 'Optional additional "Or" triggers (max 10): the automation runs when ANY of them fires, sharing the same steps. Each entry mirrors the trigger shapes above but cannot be an inbound webhook and carries no webhookId/childKey. Example: [{ type: "folders", triggerSlug: "field_updated", values: ["<fieldId>"] }]';
4087
4183
  writeSchema = {
4088
4184
  name: import_zod11.z.string().min(1).max(150).describe("Display name for the automation"),
4089
4185
  trigger: import_zod11.z.record(import_zod11.z.unknown()).describe(TRIGGER_DESCRIPTION),
4186
+ triggers: import_zod11.z.array(import_zod11.z.record(import_zod11.z.unknown())).max(10).optional().describe(OR_TRIGGERS_DESCRIPTION),
4090
4187
  steps: import_zod11.z.array(import_zod11.z.record(import_zod11.z.unknown())).min(1).max(20).describe(STEPS_DESCRIPTION),
4091
4188
  description: import_zod11.z.string().max(1e3).optional().describe("Optional description"),
4092
4189
  isActive: import_zod11.z.boolean().optional().describe("Whether the automation is active (defaults to true)"),
@@ -4192,6 +4289,126 @@ function register11(server, client) {
4192
4289
  }
4193
4290
  }
4194
4291
  );
4292
+ registerSpeakTool(
4293
+ server,
4294
+ "provision_inbound_webhook",
4295
+ "Provision a standalone inbound webhook and get its public receive URL (inboundUrl) BEFORE creating an automation. Webhook-first flow: provision, send a test payload to the URL (append ?test=1 to only capture a sample without running anything), inspect mappable payload paths with get_inbound_webhook, then pass the webhookId as trigger.webhookId to create_automation.",
4296
+ {},
4297
+ {
4298
+ title: "Provision Inbound Webhook",
4299
+ readOnlyHint: false,
4300
+ destructiveHint: false,
4301
+ idempotentHint: false,
4302
+ openWorldHint: true
4303
+ },
4304
+ async () => {
4305
+ try {
4306
+ const result = await api.post("/v1/webhook/inbound/provision", {});
4307
+ const payload = unwrapData(result.data) ?? {};
4308
+ const inboundUrl = typeof payload.inboundUrl === "string" ? payload.inboundUrl : "<inboundUrl>";
4309
+ const data = {
4310
+ ...payload,
4311
+ nextSteps: [
4312
+ `Capture a sample payload (does not run anything): curl -X POST '${inboundUrl}?test=1' -H 'Content-Type: application/json' -d '{"url": "https://example.com/file.mp3", "name": "Test"}'`,
4313
+ "Call get_inbound_webhook with this webhookId to see the captured sample and mappable {{trigger.payload.*}} tokens.",
4314
+ 'Create the automation with create_automation, passing this webhookId in trigger.webhookId (triggerSlug: "inbound_webhook").'
4315
+ ]
4316
+ };
4317
+ return {
4318
+ content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
4319
+ };
4320
+ } catch (err) {
4321
+ return {
4322
+ content: [{ type: "text", text: `Error: ${formatAxiosError(err)}` }],
4323
+ isError: true
4324
+ };
4325
+ }
4326
+ }
4327
+ );
4328
+ registerSpeakTool(
4329
+ server,
4330
+ "get_inbound_webhook",
4331
+ "Get an inbound webhook's public receive URL, captured sample payload, and the ready-to-paste {{trigger.payload.*}} tokens for mapping payload values into automation steps (speak-upload name/sourceUrl, fieldsMap custom-field values, notify/outbound-webhook templates). Pass either the webhookId or the automationId of an inbound-webhook automation. If no sample has been captured yet, send a test payload to the inboundUrl first (append ?test=1 to capture without running the automation).",
4332
+ {
4333
+ webhookId: import_zod12.z.string().min(1).optional().describe("Inbound webhook id (from provision_inbound_webhook or an automation's trigger.webhookId)"),
4334
+ automationId: import_zod12.z.string().min(1).optional().describe("Automation id \u2014 resolves the bound webhookId and childKey automatically"),
4335
+ childKey: import_zod12.z.string().optional().describe("Override the dot-path used to narrow mappable payload paths (defaults to the automation's trigger.childKey)")
4336
+ },
4337
+ {
4338
+ title: "Get Inbound Webhook",
4339
+ readOnlyHint: true,
4340
+ destructiveHint: false,
4341
+ idempotentHint: true,
4342
+ openWorldHint: false
4343
+ },
4344
+ async ({ webhookId, automationId, childKey }) => {
4345
+ try {
4346
+ let resolvedWebhookId = webhookId;
4347
+ let resolvedChildKey = childKey;
4348
+ if (!resolvedWebhookId && automationId) {
4349
+ const resolved = await resolveAutomationInboundWebhook(api, automationId);
4350
+ if (!resolved.webhookId) {
4351
+ return {
4352
+ content: [
4353
+ {
4354
+ type: "text",
4355
+ text: `Error: automation ${automationId} has no inbound webhook bound to its trigger (it is not an inbound-webhook automation).`
4356
+ }
4357
+ ],
4358
+ isError: true
4359
+ };
4360
+ }
4361
+ resolvedWebhookId = resolved.webhookId;
4362
+ resolvedChildKey = resolvedChildKey ?? resolved.childKey;
4363
+ }
4364
+ if (!resolvedWebhookId) {
4365
+ return {
4366
+ content: [{ type: "text", text: "Error: provide either webhookId or automationId." }],
4367
+ isError: true
4368
+ };
4369
+ }
4370
+ const info = await fetchInboundWebhookInfo(api, resolvedWebhookId, resolvedChildKey);
4371
+ return {
4372
+ content: [{ type: "text", text: JSON.stringify(info, null, 2) }]
4373
+ };
4374
+ } catch (err) {
4375
+ return {
4376
+ content: [{ type: "text", text: `Error: ${formatAxiosError(err)}` }],
4377
+ isError: true
4378
+ };
4379
+ }
4380
+ }
4381
+ );
4382
+ registerSpeakTool(
4383
+ server,
4384
+ "get_webhook_attempts",
4385
+ "Get the delivery log for an inbound webhook: each received request with its HTTP acknowledgement status (200 = sample captured, 202 = accepted and run started, 401/403 = rejected) and the automation run it started. Use get_automation_runs for the run outcomes themselves.",
4386
+ {
4387
+ webhookId: import_zod12.z.string().min(1).describe("Unique identifier of the inbound webhook"),
4388
+ page: import_zod12.z.number().int().min(0).optional().describe("0-based page index"),
4389
+ pageSize: import_zod12.z.number().int().min(1).max(100).optional().describe("Results per page")
4390
+ },
4391
+ {
4392
+ title: "Get Webhook Attempts",
4393
+ readOnlyHint: true,
4394
+ destructiveHint: false,
4395
+ idempotentHint: true,
4396
+ openWorldHint: false
4397
+ },
4398
+ async ({ webhookId, ...params }) => {
4399
+ try {
4400
+ const result = await api.get(`/v1/webhook/${webhookId}/attempts`, { params });
4401
+ return {
4402
+ content: [{ type: "text", text: JSON.stringify(result.data, null, 2) }]
4403
+ };
4404
+ } catch (err) {
4405
+ return {
4406
+ content: [{ type: "text", text: `Error: ${formatAxiosError(err)}` }],
4407
+ isError: true
4408
+ };
4409
+ }
4410
+ }
4411
+ );
4195
4412
  registerSpeakTool(
4196
4413
  server,
4197
4414
  "delete_webhook",
@@ -4228,6 +4445,7 @@ var init_webhooks = __esm({
4228
4445
  import_zod12 = require("zod");
4229
4446
  init_helpers();
4230
4447
  init_client();
4448
+ init_inbound_webhook_utils();
4231
4449
  }
4232
4450
  });
4233
4451
 
@@ -4494,8 +4712,292 @@ var workflows_exports = {};
4494
4712
  __export(workflows_exports, {
4495
4713
  register: () => register14
4496
4714
  });
4715
+ function tokenize(value) {
4716
+ if (typeof value !== "string") return value;
4717
+ if (value.includes("{{")) return value;
4718
+ if (value.startsWith("payload.")) return `{{trigger.payload.${value.slice("payload.".length)}}}`;
4719
+ return value;
4720
+ }
4721
+ async function loadFolders(api) {
4722
+ const res = await api.get("/v1/folder", { params: { pageSize: 500 } });
4723
+ const data = unwrapData(res.data) ?? {};
4724
+ const list = data.folderList ?? data.folders ?? (Array.isArray(data) ? data : []);
4725
+ return list.map((f) => ({
4726
+ folderId: String(f.folderId ?? f.id ?? ""),
4727
+ name: String(f.name ?? "")
4728
+ }));
4729
+ }
4730
+ async function loadFields(api) {
4731
+ const res = await api.get("/v1/fields");
4732
+ const data = unwrapData(res.data) ?? [];
4733
+ return data.map((f) => ({
4734
+ id: String(f.id ?? ""),
4735
+ name: String(f.name ?? "")
4736
+ }));
4737
+ }
4738
+ async function resolveFolder(api, ref, folders, createdFolders) {
4739
+ const byId = folders.find((f) => f.folderId === ref);
4740
+ if (byId) return byId.folderId;
4741
+ const byName = folders.find((f) => f.name.toLowerCase() === ref.toLowerCase());
4742
+ if (byName) return byName.folderId;
4743
+ if (ID_PATTERN.test(ref)) {
4744
+ throw new Error(`Folder id "${ref}" not found in this workspace (and it looks like an id, so it was not created as a folder name)`);
4745
+ }
4746
+ const res = await api.post("/v1/folder", { name: ref });
4747
+ const folderId = unwrapData(res.data)?.folderId;
4748
+ if (!folderId) throw new Error(`Could not create folder "${ref}"`);
4749
+ folders.push({ folderId, name: ref });
4750
+ createdFolders.push(`${ref} (${folderId})`);
4751
+ return folderId;
4752
+ }
4753
+ function resolveField(ref, fields) {
4754
+ const byId = fields.find((f) => f.id === ref);
4755
+ if (byId) return byId.id;
4756
+ const byName = fields.find((f) => f.name.toLowerCase() === ref.toLowerCase());
4757
+ if (byName) return byName.id;
4758
+ const available = fields.map((f) => f.name).slice(0, 25).join(", ");
4759
+ throw new Error(`Unknown custom field "${ref}". Available fields: ${available || "(none \u2014 create one with create_field)"}`);
4760
+ }
4497
4761
  function register14(server, client) {
4498
4762
  const api = client ?? speakClient;
4763
+ registerSpeakTool(
4764
+ server,
4765
+ "build_automation",
4766
+ "High-level automation builder: create (or update) a Speak automation from a friendly spec without knowing the wire format. Accepts folder/custom-field NAMES (resolved to ids; missing folders are auto-created), payload.<path> shorthand for webhook tokens, and simple step types (filter, branch, upload, ai_chat, translate, notify, call_webhook). For inbound-webhook automations the result includes the receive URL and mappable payload tokens. Prefer this over create_automation unless you need raw control.",
4767
+ buildAutomationSchema,
4768
+ {
4769
+ title: "Build Automation",
4770
+ readOnlyHint: false,
4771
+ destructiveHint: false,
4772
+ idempotentHint: false,
4773
+ openWorldHint: true
4774
+ },
4775
+ async (args2) => {
4776
+ const { name, trigger, steps, automationId, description, isActive, orTriggers } = args2;
4777
+ const createdFolders = [];
4778
+ const dataFlowFilterFields = [];
4779
+ try {
4780
+ let folders = null;
4781
+ let fields = null;
4782
+ const getFolders = async () => folders ?? (folders = await loadFolders(api));
4783
+ const getFields = async () => fields ?? (fields = await loadFields(api));
4784
+ const buildTrigger = async (spec, allowWebhook) => {
4785
+ const on = String(spec.on ?? "");
4786
+ if (on === "media_analyzed") {
4787
+ const refs = spec.folders ?? [];
4788
+ if (!refs.length) throw new Error("media_analyzed trigger requires `folders` (names or ids)");
4789
+ const folderIds = [];
4790
+ for (const ref of refs) folderIds.push(await resolveFolder(api, String(ref), await getFolders(), createdFolders));
4791
+ return { type: "folders", provider: "speak", app: "speak", triggerSlug: "media_analyzed", folderIds };
4792
+ }
4793
+ if (on === "inbound_webhook") {
4794
+ if (!allowWebhook) throw new Error("inbound_webhook cannot be used as an Or-trigger \u2014 make it the primary trigger");
4795
+ const t = { type: "folders", provider: "speak", app: "speak", triggerSlug: "inbound_webhook", folderIds: [] };
4796
+ if (spec.webhookId) t.webhookId = spec.webhookId;
4797
+ if (spec.childKey) t.childKey = spec.childKey;
4798
+ return t;
4799
+ }
4800
+ if (on === "field_updated") {
4801
+ const watch = spec.watchFields ?? [];
4802
+ if (!watch.length) throw new Error("field_updated trigger requires `watchFields`: [{ field, values? }]");
4803
+ const fieldList = await getFields();
4804
+ const values = [];
4805
+ const fieldValueMatches = [];
4806
+ for (const w of watch) {
4807
+ const fieldId = resolveField(String(w.field), fieldList);
4808
+ values.push(fieldId);
4809
+ if (Array.isArray(w.values) && w.values.length) {
4810
+ fieldValueMatches.push({ fieldId, values: w.values.map(String) });
4811
+ }
4812
+ }
4813
+ const t = { type: "folders", provider: "speak", app: "speak", triggerSlug: "field_updated", folderIds: [], values };
4814
+ if (fieldValueMatches.length) t.fieldValueMatches = fieldValueMatches;
4815
+ if (spec.matchLogic === "AND") t.fieldMatchLogic = "AND";
4816
+ return t;
4817
+ }
4818
+ throw new Error(`Unknown trigger \`on\`: "${on}". Use media_analyzed, inbound_webhook, or field_updated.`);
4819
+ };
4820
+ const isWebhookAutomation = trigger.on === "inbound_webhook";
4821
+ const buildRules = async (rules, flowing2, isFilterStep) => {
4822
+ const out = [];
4823
+ for (const r of rules) {
4824
+ let field = String(r.field ?? "");
4825
+ if (flowing2 === "media" && !CANONICAL_FILTER_FIELDS.has(field) && !field.includes(".")) {
4826
+ field = resolveField(field, await getFields());
4827
+ } else if (isFilterStep && flowing2 === "data" && !CANONICAL_FILTER_FIELDS.has(field)) {
4828
+ dataFlowFilterFields.push(field);
4829
+ }
4830
+ const op = String(r.op ?? "eq");
4831
+ const rule = { field, op };
4832
+ if (r.value !== void 0) {
4833
+ rule.value = (op === "gt" || op === "lt") && Number.isFinite(Number(r.value)) ? Number(r.value) : r.value;
4834
+ }
4835
+ out.push(rule);
4836
+ }
4837
+ return out;
4838
+ };
4839
+ const wireSteps = [];
4840
+ let lastBranchStepId = null;
4841
+ let flowing = isWebhookAutomation ? "data" : "media";
4842
+ for (let i = 0; i < steps.length; i++) {
4843
+ const spec = steps[i];
4844
+ const stepId = `s${i + 1}`;
4845
+ const doType = String(spec.do ?? "");
4846
+ const step = { stepId };
4847
+ if (spec.runWhen === "true" || spec.runWhen === "false") {
4848
+ if (!lastBranchStepId) throw new Error(`Step ${i + 1}: runWhen requires an earlier branch step`);
4849
+ step.branch = spec.runWhen;
4850
+ step.dependsOn = [lastBranchStepId];
4851
+ }
4852
+ if (doType === "filter" || doType === "branch") {
4853
+ step.stepType = doType === "filter" ? "filter" : "condition";
4854
+ const block = {
4855
+ logic: spec.logic === "OR" ? "OR" : "AND",
4856
+ rules: await buildRules(spec.rules ?? [], flowing, doType === "filter")
4857
+ };
4858
+ step[doType === "filter" ? "filter" : "condition"] = block;
4859
+ if (doType === "branch") lastBranchStepId = stepId;
4860
+ } else if (doType === "upload") {
4861
+ if (!spec.source) throw new Error(`Step ${i + 1} (upload): \`source\` is required (URL or payload.<path>)`);
4862
+ const upload = {
4863
+ sourceMode: "url",
4864
+ sourceUrl: tokenize(spec.source),
4865
+ // Always defer until the uploaded media is PROCESSED so downstream
4866
+ // steps (ai_chat, translate) see the transcript — the web canvas
4867
+ // hardcodes this too.
4868
+ waitForProcessing: true
4869
+ };
4870
+ if (spec.name) upload.name = tokenize(spec.name);
4871
+ if (spec.language) upload.language = tokenize(spec.language);
4872
+ if (spec.folderFromPayload) {
4873
+ const rawKey = String(spec.folderFromPayload);
4874
+ const sourceKey = rawKey.includes("{{") ? rawKey : `{{trigger.payload.${rawKey.startsWith("payload.") ? rawKey.slice("payload.".length) : rawKey}}}`;
4875
+ upload.folderRouting = {
4876
+ mode: "dynamic",
4877
+ sourceKey,
4878
+ onNoMatch: spec.onNoFolderMatch === "default" ? "default" : "create"
4879
+ };
4880
+ if (spec.folder) upload.folderId = await resolveFolder(api, String(spec.folder), await getFolders(), createdFolders);
4881
+ } else {
4882
+ if (!spec.folder) throw new Error(`Step ${i + 1} (upload): provide \`folder\` (name or id) or \`folderFromPayload\``);
4883
+ upload.folderId = await resolveFolder(api, String(spec.folder), await getFolders(), createdFolders);
4884
+ }
4885
+ if (spec.mapFields && typeof spec.mapFields === "object") {
4886
+ const fieldList = await getFields();
4887
+ const fieldsMap = {};
4888
+ for (const [ref, value] of Object.entries(spec.mapFields)) {
4889
+ if (value !== null && typeof value === "object") {
4890
+ throw new Error(`Step ${i + 1} (upload): mapFields["${ref}"] must be a string or number, not an object`);
4891
+ }
4892
+ fieldsMap[resolveField(ref, fieldList)] = tokenize(String(value));
4893
+ }
4894
+ if (Object.keys(fieldsMap).length) upload.fieldsMap = fieldsMap;
4895
+ }
4896
+ step.stepType = "speak-upload";
4897
+ step.speakUpload = upload;
4898
+ flowing = "media";
4899
+ } else if (doType === "ai_chat") {
4900
+ const hasSaveToFields = Array.isArray(spec.saveToFields) && spec.saveToFields.length > 0;
4901
+ if (!spec.prompt && !hasSaveToFields) {
4902
+ throw new Error(`Step ${i + 1} (ai_chat): provide \`prompt\`, \`saveToFields\`, or both`);
4903
+ }
4904
+ const magicPrompt = { prompt: spec.prompt ?? "", assistantType: "general" };
4905
+ if (spec.title) magicPrompt.title = spec.title;
4906
+ if (spec.model) magicPrompt.modelId = spec.model;
4907
+ if (Array.isArray(spec.saveToFields) && spec.saveToFields.length) {
4908
+ if (spec.saveToFields.length > 10) {
4909
+ throw new Error(`Step ${i + 1} (ai_chat): saveToFields supports at most 10 fields`);
4910
+ }
4911
+ const fieldList = await getFields();
4912
+ magicPrompt.fieldIds = spec.saveToFields.map((ref) => resolveField(String(ref), fieldList));
4913
+ }
4914
+ step.stepType = "magic-prompt";
4915
+ step.magicPrompt = magicPrompt;
4916
+ flowing = "insight";
4917
+ } else if (doType === "translate") {
4918
+ if (!spec.language) throw new Error(`Step ${i + 1} (translate): \`language\` is required (e.g. "es-ES")`);
4919
+ step.stepType = "translation";
4920
+ step.translation = { targetLanguage: spec.language };
4921
+ flowing = "media";
4922
+ } else if (doType === "notify") {
4923
+ if (!spec.message) throw new Error(`Step ${i + 1} (notify): \`message\` is required`);
4924
+ const channel = spec.channel === void 0 ? "in_app" : String(spec.channel);
4925
+ if (!["in_app", "email", "slack"].includes(channel)) {
4926
+ throw new Error(`Step ${i + 1} (notify): channel must be "in_app", "email", or "slack" (got "${channel}")`);
4927
+ }
4928
+ const notify = { channel, message: tokenize(spec.message) };
4929
+ if (spec.target) notify.target = String(spec.target);
4930
+ step.stepType = "notify";
4931
+ step.notify = notify;
4932
+ flowing = "data";
4933
+ } else if (doType === "call_webhook") {
4934
+ if (!spec.url) throw new Error(`Step ${i + 1} (call_webhook): \`url\` is required`);
4935
+ const method = spec.method === void 0 ? "POST" : String(spec.method).toUpperCase();
4936
+ if (!["GET", "POST", "PUT", "PATCH", "DELETE"].includes(method)) {
4937
+ throw new Error(`Step ${i + 1} (call_webhook): method must be GET, POST, PUT, PATCH, or DELETE (got "${spec.method}")`);
4938
+ }
4939
+ const outbound = {
4940
+ url: tokenize(spec.url),
4941
+ method
4942
+ };
4943
+ if (spec.headers && typeof spec.headers === "object") outbound.headers = spec.headers;
4944
+ if (spec.body !== void 0) {
4945
+ outbound.bodyTemplate = typeof spec.body === "string" ? tokenize(spec.body) : spec.body;
4946
+ }
4947
+ step.stepType = "outbound-webhook";
4948
+ step.outboundWebhook = outbound;
4949
+ flowing = "data";
4950
+ } else {
4951
+ throw new Error(
4952
+ `Step ${i + 1}: unknown \`do\`: "${doType}". Use filter, branch, upload, ai_chat, translate, notify, or call_webhook.`
4953
+ );
4954
+ }
4955
+ wireSteps.push(step);
4956
+ }
4957
+ const body = {
4958
+ name,
4959
+ trigger: await buildTrigger(trigger, true),
4960
+ steps: wireSteps
4961
+ };
4962
+ if (description) body.description = description;
4963
+ if (isActive !== void 0) body.isActive = isActive;
4964
+ if (orTriggers?.length) {
4965
+ const entries = [];
4966
+ for (const spec of orTriggers) entries.push(await buildTrigger(spec, false));
4967
+ body.triggers = entries;
4968
+ }
4969
+ const result = automationId ? await api.put(`/v1/automations/${automationId}`, body) : await api.post("/v1/automations/", body);
4970
+ const resolvedId = unwrapData(result.data)?.automationId ?? automationId;
4971
+ const response = {
4972
+ ...typeof result.data === "object" ? result.data : { data: result.data }
4973
+ };
4974
+ if (createdFolders.length) response.createdFolders = createdFolders;
4975
+ if (isWebhookAutomation && resolvedId) {
4976
+ try {
4977
+ const resolved = await resolveAutomationInboundWebhook(api, resolvedId);
4978
+ if (resolved.webhookId) {
4979
+ response.inboundWebhook = await fetchInboundWebhookInfo(api, resolved.webhookId, resolved.childKey);
4980
+ }
4981
+ } catch {
4982
+ }
4983
+ }
4984
+ return {
4985
+ content: [{ type: "text", text: JSON.stringify(response, null, 2) }]
4986
+ };
4987
+ } catch (err) {
4988
+ let message = formatAxiosError(err);
4989
+ if (dataFlowFilterFields.length && message.includes("fieldIds do not belong")) {
4990
+ message += `
4991
+
4992
+ Likely cause: this server rejects filter rules on webhook payload fields (${dataFlowFilterFields.join(", ")}) at publish time (known server-side validation gap). Workarounds: move the filter AFTER the upload step and filter on media/custom fields instead, or filter in the sending system before it posts to the webhook.`;
4993
+ }
4994
+ return {
4995
+ content: [{ type: "text", text: `Error: ${message}` }],
4996
+ isError: true
4997
+ };
4998
+ }
4999
+ }
5000
+ );
4499
5001
  registerSpeakTool(
4500
5002
  server,
4501
5003
  "upload_and_analyze",
@@ -4650,7 +5152,7 @@ ${JSON.stringify(signedRes.data, null, 2)}` }],
4650
5152
  }
4651
5153
  );
4652
5154
  }
4653
- var import_zod15, fs, path2;
5155
+ var import_zod15, fs, path2, CANONICAL_FILTER_FIELDS, ID_PATTERN, TRIGGER_SPEC_DESCRIPTION, STEP_SPEC_DESCRIPTION, buildAutomationSchema;
4654
5156
  var init_workflows = __esm({
4655
5157
  "src/tools/workflows.ts"() {
4656
5158
  "use strict";
@@ -4661,6 +5163,34 @@ var init_workflows = __esm({
4661
5163
  fs = __toESM(require("fs"));
4662
5164
  path2 = __toESM(require("path"));
4663
5165
  init_media_utils();
5166
+ init_inbound_webhook_utils();
5167
+ CANONICAL_FILTER_FIELDS = /* @__PURE__ */ new Set([
5168
+ "name",
5169
+ "duration",
5170
+ "sourceLanguage",
5171
+ "tags",
5172
+ "transcript",
5173
+ "speakers",
5174
+ "answer",
5175
+ // server-side aliases
5176
+ "title",
5177
+ "language",
5178
+ "speakersCount"
5179
+ ]);
5180
+ ID_PATTERN = /^[0-9a-f]{12}$/;
5181
+ TRIGGER_SPEC_DESCRIPTION = 'What starts the automation. Object with:\n- on (required): "media_analyzed" | "inbound_webhook" | "field_updated"\n- folders: array of folder names or ids (required for media_analyzed; missing folders are created)\n- childKey: dot-path narrowing the webhook payload root, e.g. "data" (inbound_webhook only)\n- webhookId: reuse a webhook from provision_inbound_webhook (inbound_webhook only; omit to auto-provision)\n- watchFields: array of { field: name-or-id, values?: string[] } (required for field_updated \u2014 fires when the field changes; values restricts to specific new values)\n- matchLogic: "AND"|"OR" for combining multiple watchFields value matches (default OR)';
5182
+ STEP_SPEC_DESCRIPTION = 'Ordered actions. Each step is an object with a `do` key plus its options. String values may be literals, "payload.<path>" shorthand (converted to {{trigger.payload.<path>}} only when it is the ENTIRE value), or raw {{...}} tokens \u2014 inside longer text, write the full {{trigger.payload.<path>}} form.\n- { do: "filter", rules: [{ field, op, value? }], logic?: "AND"|"OR" } \u2014 continue only if rules match. Fields: media flows use name|duration|sourceLanguage|tags|transcript|speakers or a custom field name; webhook payloads use payload paths like "contact.status". Ops: eq|neq|contains|ncontains|startsWith|gt|lt|exists\n- { do: "branch", rules, logic? } \u2014 like filter but routes instead of stopping; later steps with runWhen: "true"|"false" only run on that outcome. NOTE: branch routing requires the server\'s DAG runner (feature-flagged); when it is off, steps run in order and runWhen markers are ignored \u2014 prefer filter for guaranteed gating\n- { do: "upload", source (URL or payload.<path>, required), name?, language? (e.g. "en-US"), folder? (name or id; created if missing), folderFromPayload? (payload key holding the destination folder name \u2014 dynamic routing), onNoFolderMatch?: "create"|"default", mapFields?: { <field name or id>: <value or payload.<path>> } (writes payload values into custom fields on the uploaded media) }\n- { do: "ai_chat", prompt? (required unless saveToFields given), title?, saveToFields?: [field names or ids] (max 10 \u2014 values are extracted into these custom fields; prompt may be omitted for extraction-only steps), model? (a Speak-supported LLM id, e.g. "gemini-2.5-flash", "claude-sonnet-4-6"; omit for the workspace default) }\n- { do: "translate", language: region-qualified code like "es-ES", "fr-FR" }\n- { do: "notify", message (required, tokens allowed), channel?: "in_app"|"email"|"slack" (default in_app; email currently falls back to an in-app notification), target? (reserved \u2014 not yet used for delivery) }\n- { do: "call_webhook", url (required), method?, headers?, body? (string or object template, tokens allowed) }\nSteps may also set runWhen (after a branch step). Composio app actions (Google Drive, Slack apps, \u2026) are not supported by this builder yet \u2014 use create_automation directly for those.';
5183
+ buildAutomationSchema = {
5184
+ name: import_zod15.z.string().min(1).max(150).describe("Display name for the automation"),
5185
+ trigger: import_zod15.z.record(import_zod15.z.unknown()).describe(TRIGGER_SPEC_DESCRIPTION),
5186
+ steps: import_zod15.z.array(import_zod15.z.record(import_zod15.z.unknown())).min(1).max(20).describe(STEP_SPEC_DESCRIPTION),
5187
+ automationId: import_zod15.z.string().optional().describe("Update this existing automation instead of creating a new one (full replace)"),
5188
+ description: import_zod15.z.string().max(1e3).optional().describe("Optional description"),
5189
+ isActive: import_zod15.z.boolean().optional().describe("Whether the automation is active (default true)"),
5190
+ orTriggers: import_zod15.z.array(import_zod15.z.record(import_zod15.z.unknown())).max(10).optional().describe(
5191
+ 'Additional "Or" triggers (same shape as trigger, but inbound_webhook is not allowed here). The automation runs when ANY trigger fires.'
5192
+ )
5193
+ };
4664
5194
  }
4665
5195
  });
4666
5196
 
@@ -5692,7 +6222,6 @@ var init_tool_names = __esm({
5692
6222
  "get_live_meeting_transcript",
5693
6223
  // prompt
5694
6224
  "ask_ai_chat",
5695
- "ask_magic_prompt",
5696
6225
  "list_prompts",
5697
6226
  "get_favorite_prompts",
5698
6227
  "toggle_prompt_favorite",
@@ -5701,7 +6230,7 @@ var init_tool_names = __esm({
5701
6230
  "update_chat_title",
5702
6231
  "delete_chat_message",
5703
6232
  "submit_chat_feedback",
5704
- "retry_magic_prompt",
6233
+ "retry_ai_chat",
5705
6234
  "export_chat_answer",
5706
6235
  "get_chat_statistics",
5707
6236
  // recorder
@@ -5724,8 +6253,12 @@ var init_tool_names = __esm({
5724
6253
  "list_webhooks",
5725
6254
  "create_webhook",
5726
6255
  "update_webhook",
6256
+ "provision_inbound_webhook",
6257
+ "get_inbound_webhook",
6258
+ "get_webhook_attempts",
5727
6259
  "delete_webhook",
5728
- // workflows (high-level wrappers around media + upload tools)
6260
+ // workflows (high-level wrappers around media + upload + automation tools)
6261
+ "build_automation",
5729
6262
  "upload_and_analyze",
5730
6263
  "upload_local_file",
5731
6264
  // users / team management
@@ -6914,6 +7447,11 @@ var cliCommands = [
6914
7447
  "clips",
6915
7448
  "clip",
6916
7449
  "schedule-meeting",
7450
+ "list-meeting-events",
7451
+ "live-transcript",
7452
+ "move",
7453
+ "tools",
7454
+ "call",
6917
7455
  "help"
6918
7456
  ];
6919
7457
  var isCliMode = args.length > 0 && (args[0].startsWith("-") || cliCommands.includes(args[0]));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@speakai/mcp-server",
3
- "version": "1.13.7",
3
+ "version": "1.14.0",
4
4
  "mcpName": "io.github.speakai/mcp-server",
5
5
  "description": "Official Speak AI MCP Server — capture meetings, search thousands of recordings, run async voice and video surveys, create clips, and automate workflows from your AI assistant.",
6
6
  "homepage": "https://mcp.speakai.co",