@motorical/mcp 1.3.0 → 1.4.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@motorical/mcp",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "MCP server for Motorical transactional email API — dry-run/send, mint public tokens, list Motor Blocks, inspect delivery events",
5
5
  "type": "module",
6
6
  "bin": {
package/src/client.js CHANGED
@@ -293,6 +293,59 @@ export class MotoricalClient {
293
293
  return this.request('GET', this._scoped(path + qs, motorBlockId), { bearer });
294
294
  }
295
295
 
296
+ async webhookList({ motorBlockId } = {}) {
297
+ const bearer = await this.getBearer({ motorBlockId });
298
+ const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/webhooks`;
299
+ return this.request('GET', this._scoped(path, motorBlockId), { bearer });
300
+ }
301
+
302
+ async webhookCreate({ motorBlockId, url, events } = {}) {
303
+ if (!url) throw new Error('url is required');
304
+ const bearer = await this.getBearer({ motorBlockId });
305
+ const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/webhooks`;
306
+ const body = events !== undefined ? { url, events } : { url };
307
+ return this.request('POST', this._scoped(path, motorBlockId), { bearer, body });
308
+ }
309
+
310
+ async webhookUpdate({ motorBlockId, webhookId, url, events, enabled } = {}) {
311
+ if (!webhookId) throw new Error('webhookId is required');
312
+ const bearer = await this.getBearer({ motorBlockId });
313
+ const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/webhooks/${encodeURIComponent(webhookId)}`;
314
+ const body = {};
315
+ if (url !== undefined) body.url = url;
316
+ if (events !== undefined) body.events = events;
317
+ if (enabled !== undefined) body.enabled = enabled;
318
+ return this.request('PUT', this._scoped(path, motorBlockId), { bearer, body });
319
+ }
320
+
321
+ async webhookDelete({ motorBlockId, webhookId } = {}) {
322
+ if (!webhookId) throw new Error('webhookId is required');
323
+ const bearer = await this.getBearer({ motorBlockId });
324
+ const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/webhooks/${encodeURIComponent(webhookId)}`;
325
+ return this.request('DELETE', this._scoped(path, motorBlockId), { bearer });
326
+ }
327
+
328
+ async webhookTest({ motorBlockId, webhookId } = {}) {
329
+ if (!webhookId) throw new Error('webhookId is required');
330
+ const bearer = await this.getBearer({ motorBlockId });
331
+ const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/webhooks/${encodeURIComponent(webhookId)}/test`;
332
+ return this.request('POST', this._scoped(path, motorBlockId), { bearer });
333
+ }
334
+
335
+ async webhookGetDeliveries({ motorBlockId, webhookId, limit } = {}) {
336
+ if (!webhookId) throw new Error('webhookId is required');
337
+ const bearer = await this.getBearer({ motorBlockId });
338
+ const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/webhooks/${encodeURIComponent(webhookId)}/deliveries`;
339
+ return this.request('GET', this._scoped(path + this._qs({ limit }), motorBlockId), { bearer });
340
+ }
341
+
342
+ async webhookGetStats({ motorBlockId, webhookId, hours } = {}) {
343
+ if (!webhookId) throw new Error('webhookId is required');
344
+ const bearer = await this.getBearer({ motorBlockId });
345
+ const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/webhooks/${encodeURIComponent(webhookId)}/stats`;
346
+ return this.request('GET', this._scoped(path + this._qs({ hours }), motorBlockId), { bearer });
347
+ }
348
+
296
349
  // Account-wide: the route is mounted { accountScoped: true } and reads only
297
350
  // the token's user, so _accountPath omits the block rather than throwing.
298
351
  // Using _scoped() here would demand a Motor Block for an account-wide route.
package/src/server.js CHANGED
@@ -556,6 +556,63 @@ export function createMotoricalMcpServer(options = {}) {
556
556
  }
557
557
  }, async (args) => { try { return jsonResult(await client.getDomainHealth(args)); } catch (err) { return errorResult(err); } });
558
558
 
559
+ const webhookIdArg = z.string().describe('The webhook endpoint id, from motorical_webhook_list or the create response');
560
+
561
+ registerTool('motorical_webhook_list', {
562
+ description: 'List webhook endpoints registered on one Motor Block (GET /api/public/v1/motor-blocks/{id}/webhooks).',
563
+ inputSchema: { motorBlockId: blockSelector }
564
+ }, async (args) => { try { return jsonResult(await client.webhookList(args)); } catch (err) { return errorResult(err); } });
565
+
566
+ registerTool('motorical_webhook_create', {
567
+ description:
568
+ 'Register a new webhook endpoint on one Motor Block (POST /api/public/v1/motor-blocks/{id}/webhooks). '
569
+ + 'The response includes the full signing secret exactly once — store it immediately, it is only masked on every later read.',
570
+ inputSchema: {
571
+ motorBlockId: blockSelector,
572
+ url: z.string().url().describe('HTTPS endpoint that will receive webhook deliveries'),
573
+ events: z.array(z.string()).optional().describe('Event types to subscribe to; defaults to all event types when omitted')
574
+ }
575
+ }, async (args) => { try { return jsonResult(await client.webhookCreate(args)); } catch (err) { return errorResult(err); } });
576
+
577
+ registerTool('motorical_webhook_update', {
578
+ description: 'Update a webhook endpoint\'s url, events, or enabled state (PUT /api/public/v1/motor-blocks/{id}/webhooks/{webhookId}). Only the fields you pass are changed.',
579
+ inputSchema: {
580
+ motorBlockId: blockSelector,
581
+ webhookId: webhookIdArg,
582
+ url: z.string().url().optional(),
583
+ events: z.array(z.string()).optional(),
584
+ enabled: z.boolean().optional()
585
+ }
586
+ }, async (args) => { try { return jsonResult(await client.webhookUpdate(args)); } catch (err) { return errorResult(err); } });
587
+
588
+ registerTool('motorical_webhook_delete', {
589
+ description: 'Delete a webhook endpoint (DELETE /api/public/v1/motor-blocks/{id}/webhooks/{webhookId}).',
590
+ inputSchema: { motorBlockId: blockSelector, webhookId: webhookIdArg }
591
+ }, async (args) => { try { return jsonResult(await client.webhookDelete(args)); } catch (err) { return errorResult(err); } });
592
+
593
+ registerTool('motorical_webhook_test', {
594
+ description: 'Send a synthetic test delivery to a webhook endpoint (POST /api/public/v1/motor-blocks/{id}/webhooks/{webhookId}/test).',
595
+ inputSchema: { motorBlockId: blockSelector, webhookId: webhookIdArg }
596
+ }, async (args) => { try { return jsonResult(await client.webhookTest(args)); } catch (err) { return errorResult(err); } });
597
+
598
+ registerTool('motorical_webhook_get_deliveries', {
599
+ description: 'Recent delivery attempts for one webhook endpoint (GET /api/public/v1/motor-blocks/{id}/webhooks/{webhookId}/deliveries).',
600
+ inputSchema: {
601
+ motorBlockId: blockSelector,
602
+ webhookId: webhookIdArg,
603
+ limit: z.number().int().positive().max(200).optional().describe('Default 50, max 200')
604
+ }
605
+ }, async (args) => { try { return jsonResult(await client.webhookGetDeliveries(args)); } catch (err) { return errorResult(err); } });
606
+
607
+ registerTool('motorical_webhook_get_stats', {
608
+ description: 'Delivery success/failure counts and average latency for one webhook endpoint over a time window (GET /api/public/v1/motor-blocks/{id}/webhooks/{webhookId}/stats).',
609
+ inputSchema: {
610
+ motorBlockId: blockSelector,
611
+ webhookId: webhookIdArg,
612
+ hours: z.number().int().positive().max(168).optional().describe('Default 24, max 168 (7 days)')
613
+ }
614
+ }, async (args) => { try { return jsonResult(await client.webhookGetStats(args)); } catch (err) { return errorResult(err); } });
615
+
559
616
  server.registerResource(
560
617
  'motorical-llms',
561
618
  'motorical://docs/llms.txt',