@motorical/mcp 1.2.1 → 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 +1 -1
- package/src/client.js +175 -0
- package/src/server.js +210 -1
package/package.json
CHANGED
package/src/client.js
CHANGED
|
@@ -208,6 +208,181 @@ export class MotoricalClient {
|
|
|
208
208
|
return `${path}${sep}motorBlockId=${encodeURIComponent(id)}`;
|
|
209
209
|
}
|
|
210
210
|
|
|
211
|
+
/**
|
|
212
|
+
* Query string from a params object, skipping anything the caller didn't
|
|
213
|
+
* supply. Returns '' when nothing survives, so it composes with _scoped()
|
|
214
|
+
* and _accountPath() — both of which detect an existing '?' and switch to
|
|
215
|
+
* '&' when appending motorBlockId.
|
|
216
|
+
*/
|
|
217
|
+
_qs(params = {}) {
|
|
218
|
+
const q = new URLSearchParams();
|
|
219
|
+
for (const [k, v] of Object.entries(params)) {
|
|
220
|
+
if (v === undefined || v === null || v === '') continue;
|
|
221
|
+
q.set(k, String(v));
|
|
222
|
+
}
|
|
223
|
+
const s = q.toString();
|
|
224
|
+
return s ? `?${s}` : '';
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
async getOverview({ motorBlockId, from, to } = {}) {
|
|
228
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
229
|
+
const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/overview`;
|
|
230
|
+
return this.request('GET', this._scoped(path + this._qs({ from, to }), motorBlockId), { bearer });
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
async getDailySummary({ motorBlockId, days } = {}) {
|
|
234
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
235
|
+
const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/daily-summary`;
|
|
236
|
+
return this.request('GET', this._scoped(path + this._qs({ days }), motorBlockId), { bearer });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
async getMetrics({ motorBlockId, from, to, interval } = {}) {
|
|
240
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
241
|
+
const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/metrics`;
|
|
242
|
+
return this.request('GET', this._scoped(path + this._qs({ from, to, interval }), motorBlockId), { bearer });
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
async getDeliverability({ motorBlockId, from, to, limit } = {}) {
|
|
246
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
247
|
+
const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/deliverability`;
|
|
248
|
+
return this.request('GET', this._scoped(path + this._qs({ from, to, limit }), motorBlockId), { bearer });
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async getReputation({ motorBlockId } = {}) {
|
|
252
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
253
|
+
const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/reputation`;
|
|
254
|
+
return this.request('GET', this._scoped(path, motorBlockId), { bearer });
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
async getAnomalies({ motorBlockId } = {}) {
|
|
258
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
259
|
+
const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/anomalies`;
|
|
260
|
+
return this.request('GET', this._scoped(path, motorBlockId), { bearer });
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
async getProviders({ motorBlockId, from, to, limit } = {}) {
|
|
264
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
265
|
+
const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/providers`;
|
|
266
|
+
return this.request('GET', this._scoped(path + this._qs({ from, to, limit }), motorBlockId), { bearer });
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async getErrorCodes({ motorBlockId, from, to, limit } = {}) {
|
|
270
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
271
|
+
const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/error-codes`;
|
|
272
|
+
return this.request('GET', this._scoped(path + this._qs({ from, to, limit }), motorBlockId), { bearer });
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async getRateLimits({ motorBlockId } = {}) {
|
|
276
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
277
|
+
const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/rate-limits`;
|
|
278
|
+
return this.request('GET', this._scoped(path, motorBlockId), { bearer });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
async getConfig({ motorBlockId } = {}) {
|
|
282
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
283
|
+
const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/config`;
|
|
284
|
+
return this.request('GET', this._scoped(path, motorBlockId), { bearer });
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// refresh is sent only when true: the route reads it as "re-run the live DNS
|
|
288
|
+
// check now", so refresh=false is not the same request as omitting it.
|
|
289
|
+
async getDomainHealth({ motorBlockId, refresh } = {}) {
|
|
290
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
291
|
+
const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/domain-health`;
|
|
292
|
+
const qs = this._qs({ refresh: refresh === true ? 'true' : undefined });
|
|
293
|
+
return this.request('GET', this._scoped(path + qs, motorBlockId), { bearer });
|
|
294
|
+
}
|
|
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
|
+
|
|
349
|
+
// Account-wide: the route is mounted { accountScoped: true } and reads only
|
|
350
|
+
// the token's user, so _accountPath omits the block rather than throwing.
|
|
351
|
+
// Using _scoped() here would demand a Motor Block for an account-wide route.
|
|
352
|
+
async getAccountRateLimits({ motorBlockId } = {}) {
|
|
353
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
354
|
+
return this.request(
|
|
355
|
+
'GET',
|
|
356
|
+
this._accountPath('/api/public/v1/account/rate-limits', motorBlockId),
|
|
357
|
+
{ bearer }
|
|
358
|
+
);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
async getLogs({
|
|
362
|
+
motorBlockId, from, to, currentOutcome, query, limit, cursor, includePII,
|
|
363
|
+
} = {}) {
|
|
364
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
365
|
+
const path = `/api/public/v1/motor-blocks/${encodeURIComponent(motorBlockId)}/logs`;
|
|
366
|
+
const qs = this._qs({
|
|
367
|
+
from, to, currentOutcome, query, limit, cursor,
|
|
368
|
+
includePII: includePII === true ? 'true' : undefined,
|
|
369
|
+
});
|
|
370
|
+
return this.request('GET', this._scoped(path + qs, motorBlockId), { bearer });
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// GET /messages is a LOOKUP, not a listing: it 400s without an exact
|
|
374
|
+
// smtpMessageId and resolves at most one match. This is a second lookup key
|
|
375
|
+
// alongside getMessage()'s internal UUID, never a way to browse.
|
|
376
|
+
async getMessageBySmtpId({ smtpMessageId, motorBlockId, includePII } = {}) {
|
|
377
|
+
if (!smtpMessageId) throw new Error('smtpMessageId is required');
|
|
378
|
+
const bearer = await this.getBearer({ motorBlockId });
|
|
379
|
+
const qs = this._qs({
|
|
380
|
+
smtpMessageId,
|
|
381
|
+
includePII: includePII === true ? 'true' : undefined,
|
|
382
|
+
});
|
|
383
|
+
return this.request('GET', this._scoped(`/api/public/v1/messages${qs}`, motorBlockId), { bearer });
|
|
384
|
+
}
|
|
385
|
+
|
|
211
386
|
async getBearer({ motorBlockId, forceRefresh = false } = {}) {
|
|
212
387
|
// An OAuth grant supersedes minted public tokens entirely — no ak_live_ key
|
|
213
388
|
// and no dashboard JWT needed.
|
package/src/server.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
1
2
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
2
3
|
import { z } from 'zod';
|
|
3
4
|
import { MotoricalClient, loadConfig } from './client.js';
|
|
4
5
|
|
|
5
|
-
const PACKAGE_VERSION =
|
|
6
|
+
const PACKAGE_VERSION = JSON.parse(
|
|
7
|
+
readFileSync(new URL('../package.json', import.meta.url))
|
|
8
|
+
).version;
|
|
6
9
|
|
|
7
10
|
function jsonResult(data, { isError = false } = {}) {
|
|
8
11
|
return {
|
|
@@ -404,6 +407,212 @@ export function createMotoricalMcpServer(options = {}) {
|
|
|
404
407
|
}
|
|
405
408
|
);
|
|
406
409
|
|
|
410
|
+
// ---- Analytics & health tools ----
|
|
411
|
+
// Each wraps one existing Public API route one-to-one. See
|
|
412
|
+
// motorical-docs/plans/2026-09-02-mcp-analytics-health-tools-implementation.md.
|
|
413
|
+
// .optional() on purpose, matching every pre-existing block-scoped tool: a
|
|
414
|
+
// grant covering exactly one Motor Block resolves it automatically, so the
|
|
415
|
+
// caller only has to name one when the grant covers several. Making it
|
|
416
|
+
// required forces single-block users to supply an id they should never need,
|
|
417
|
+
// and turns the multi-block case into a Zod type error instead of
|
|
418
|
+
// resolveBlock's actionable message.
|
|
419
|
+
const blockSelector = z.string().uuid().optional()
|
|
420
|
+
.describe('Required when the authorization covers more than one Motor Block');
|
|
421
|
+
const isoDate = (bound) => z.string().optional().describe(`ISO date or datetime, ${bound}`);
|
|
422
|
+
|
|
423
|
+
registerTool(
|
|
424
|
+
'motorical_get_overview',
|
|
425
|
+
{
|
|
426
|
+
description:
|
|
427
|
+
'Sending overview for one Motor Block over a date range — volume, delivery and bounce '
|
|
428
|
+
+ 'rates, and current usage against plan limits (GET /api/public/v1/motor-blocks/{id}/overview).',
|
|
429
|
+
inputSchema: {
|
|
430
|
+
motorBlockId: blockSelector,
|
|
431
|
+
from: isoDate('inclusive'),
|
|
432
|
+
to: isoDate('inclusive')
|
|
433
|
+
}
|
|
434
|
+
},
|
|
435
|
+
async (args) => {
|
|
436
|
+
try {
|
|
437
|
+
return jsonResult(await client.getOverview(args));
|
|
438
|
+
} catch (err) {
|
|
439
|
+
return errorResult(err);
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
);
|
|
443
|
+
|
|
444
|
+
registerTool('motorical_get_daily_summary', {
|
|
445
|
+
description: 'Per-day send counts and outcomes for one Motor Block (GET /api/public/v1/motor-blocks/{id}/daily-summary).',
|
|
446
|
+
inputSchema: {
|
|
447
|
+
motorBlockId: blockSelector,
|
|
448
|
+
days: z.number().int().positive().optional().describe('How many days back, counting today')
|
|
449
|
+
}
|
|
450
|
+
}, async (args) => { try { return jsonResult(await client.getDailySummary(args)); } catch (err) { return errorResult(err); } });
|
|
451
|
+
|
|
452
|
+
registerTool('motorical_get_metrics', {
|
|
453
|
+
description: 'Time-series send metrics for one Motor Block, bucketed by hour or day (GET /api/public/v1/motor-blocks/{id}/metrics).',
|
|
454
|
+
inputSchema: {
|
|
455
|
+
motorBlockId: blockSelector,
|
|
456
|
+
from: isoDate('inclusive'),
|
|
457
|
+
to: isoDate('inclusive'),
|
|
458
|
+
interval: z.enum(['hour', 'day']).optional().describe('Bucket size; defaults to the API default')
|
|
459
|
+
}
|
|
460
|
+
}, async (args) => { try { return jsonResult(await client.getMetrics(args)); } catch (err) { return errorResult(err); } });
|
|
461
|
+
|
|
462
|
+
registerTool('motorical_get_deliverability', {
|
|
463
|
+
description: 'Deliverability broken down by recipient domain (GET /api/public/v1/motor-blocks/{id}/deliverability).',
|
|
464
|
+
inputSchema: {
|
|
465
|
+
motorBlockId: blockSelector,
|
|
466
|
+
from: isoDate('inclusive'),
|
|
467
|
+
to: isoDate('inclusive'),
|
|
468
|
+
limit: z.number().int().positive().optional().describe('Max recipient domains returned')
|
|
469
|
+
}
|
|
470
|
+
}, async (args) => { try { return jsonResult(await client.getDeliverability(args)); } catch (err) { return errorResult(err); } });
|
|
471
|
+
|
|
472
|
+
registerTool('motorical_get_reputation', {
|
|
473
|
+
description: 'Current sending reputation for one Motor Block (GET /api/public/v1/motor-blocks/{id}/reputation).',
|
|
474
|
+
inputSchema: { motorBlockId: blockSelector }
|
|
475
|
+
}, async (args) => { try { return jsonResult(await client.getReputation(args)); } catch (err) { return errorResult(err); } });
|
|
476
|
+
|
|
477
|
+
registerTool('motorical_get_anomalies', {
|
|
478
|
+
description: 'Detected sending anomalies for one Motor Block — volume spikes, bounce surges, unusual patterns (GET /api/public/v1/motor-blocks/{id}/anomalies).',
|
|
479
|
+
inputSchema: { motorBlockId: blockSelector }
|
|
480
|
+
}, async (args) => { try { return jsonResult(await client.getAnomalies(args)); } catch (err) { return errorResult(err); } });
|
|
481
|
+
|
|
482
|
+
registerTool('motorical_get_providers', {
|
|
483
|
+
description: 'Send outcomes grouped by receiving mailbox provider (GET /api/public/v1/motor-blocks/{id}/providers).',
|
|
484
|
+
inputSchema: {
|
|
485
|
+
motorBlockId: blockSelector,
|
|
486
|
+
from: isoDate('inclusive'),
|
|
487
|
+
to: isoDate('inclusive'),
|
|
488
|
+
limit: z.number().int().positive().optional().describe('Max providers returned')
|
|
489
|
+
}
|
|
490
|
+
}, async (args) => { try { return jsonResult(await client.getProviders(args)); } catch (err) { return errorResult(err); } });
|
|
491
|
+
|
|
492
|
+
registerTool('motorical_get_error_codes', {
|
|
493
|
+
description: 'SMTP error codes seen for one Motor Block, with counts and diagnostics (GET /api/public/v1/motor-blocks/{id}/error-codes).',
|
|
494
|
+
inputSchema: {
|
|
495
|
+
motorBlockId: blockSelector,
|
|
496
|
+
from: isoDate('inclusive'),
|
|
497
|
+
to: isoDate('inclusive'),
|
|
498
|
+
limit: z.number().int().positive().optional().describe('Max distinct error codes returned')
|
|
499
|
+
}
|
|
500
|
+
}, async (args) => { try { return jsonResult(await client.getErrorCodes(args)); } catch (err) { return errorResult(err); } });
|
|
501
|
+
|
|
502
|
+
registerTool('motorical_get_rate_limits', {
|
|
503
|
+
description: "Current hourly and daily send usage against this Motor Block's limits (GET /api/public/v1/motor-blocks/{id}/rate-limits).",
|
|
504
|
+
inputSchema: { motorBlockId: blockSelector }
|
|
505
|
+
}, async (args) => { try { return jsonResult(await client.getRateLimits(args)); } catch (err) { return errorResult(err); } });
|
|
506
|
+
|
|
507
|
+
registerTool('motorical_get_account_rate_limits', {
|
|
508
|
+
description:
|
|
509
|
+
'Account-wide send ceiling and current usage across every Motor Block '
|
|
510
|
+
+ '(GET /api/public/v1/account/rate-limits). Account-scoped: no Motor Block needed.',
|
|
511
|
+
inputSchema: {
|
|
512
|
+
motorBlockId: z.string().uuid().optional()
|
|
513
|
+
.describe('Optional. This operation acts on the whole account, so a Motor Block is never needed; pass one only to record which block the call was made on behalf of.')
|
|
514
|
+
}
|
|
515
|
+
}, async (args) => { try { return jsonResult(await client.getAccountRateLimits(args)); } catch (err) { return errorResult(err); } });
|
|
516
|
+
|
|
517
|
+
registerTool('motorical_get_logs', {
|
|
518
|
+
description:
|
|
519
|
+
'Search send logs for one Motor Block (GET /api/public/v1/motor-blocks/{id}/logs). '
|
|
520
|
+
+ 'Paginate with cursor. Recipient addresses are masked unless the token carries logs.pii, '
|
|
521
|
+
+ 'which OAuth tokens never do.',
|
|
522
|
+
inputSchema: {
|
|
523
|
+
motorBlockId: blockSelector,
|
|
524
|
+
from: isoDate('inclusive'),
|
|
525
|
+
to: isoDate('inclusive'),
|
|
526
|
+
currentOutcome: z.string().optional().describe('Filter to one delivery outcome, e.g. delivered, bounced, deferred'),
|
|
527
|
+
query: z.string().optional().describe('Free-text match against recipient, subject or message id'),
|
|
528
|
+
limit: z.number().int().positive().optional().describe('Page size'),
|
|
529
|
+
cursor: z.string().optional().describe('Opaque cursor from a previous page'),
|
|
530
|
+
includePII: z.boolean().optional().describe('Unmask recipient addresses. Requires the logs.pii scope, which is never granted to OAuth tokens — the API returns 403.')
|
|
531
|
+
}
|
|
532
|
+
}, async (args) => { try { return jsonResult(await client.getLogs(args)); } catch (err) { return errorResult(err); } });
|
|
533
|
+
|
|
534
|
+
registerTool('motorical_get_message_by_smtp_id', {
|
|
535
|
+
description:
|
|
536
|
+
'Look up one message by its SMTP Message-ID header (GET /api/public/v1/messages?smtpMessageId=). '
|
|
537
|
+
+ 'This is a lookup, not a listing: an exact id is required and at most one message is returned. '
|
|
538
|
+
+ 'Use motorical_get_message when you have the internal send UUID instead.',
|
|
539
|
+
inputSchema: {
|
|
540
|
+
smtpMessageId: z.string().describe('The exact SMTP Message-ID, angle brackets included'),
|
|
541
|
+
motorBlockId: blockSelector,
|
|
542
|
+
includePII: z.boolean().optional().describe('Unmask the recipient address. Requires the logs.pii scope, which is never granted to OAuth tokens — the API returns 403.')
|
|
543
|
+
}
|
|
544
|
+
}, async (args) => { try { return jsonResult(await client.getMessageBySmtpId(args)); } catch (err) { return errorResult(err); } });
|
|
545
|
+
|
|
546
|
+
registerTool('motorical_get_config', {
|
|
547
|
+
description: "Configuration of one Motor Block — its sending domain, limits and delivery settings (GET /api/public/v1/motor-blocks/{id}/config).",
|
|
548
|
+
inputSchema: { motorBlockId: blockSelector }
|
|
549
|
+
}, async (args) => { try { return jsonResult(await client.getConfig(args)); } catch (err) { return errorResult(err); } });
|
|
550
|
+
|
|
551
|
+
registerTool('motorical_get_domain_health', {
|
|
552
|
+
description: "DNS and email-authentication health for the Motor Block's sending domain — SPF, DKIM, DMARC, MX (GET /api/public/v1/motor-blocks/{id}/domain-health).",
|
|
553
|
+
inputSchema: {
|
|
554
|
+
motorBlockId: blockSelector,
|
|
555
|
+
refresh: z.boolean().optional().describe('Re-run the live DNS checks now instead of returning the last cached result')
|
|
556
|
+
}
|
|
557
|
+
}, async (args) => { try { return jsonResult(await client.getDomainHealth(args)); } catch (err) { return errorResult(err); } });
|
|
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
|
+
|
|
407
616
|
server.registerResource(
|
|
408
617
|
'motorical-llms',
|
|
409
618
|
'motorical://docs/llms.txt',
|