@ductape/mcp 0.1.44 → 0.1.46

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/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import { z } from 'zod';
15
15
  import { executeViaProxy, generateExecutablePayload, getAssetSchemas, } from './proxy-client.js';
16
16
  const MODULES = [
17
17
  'product', 'app', 'databases', 'graph', 'webhooks', 'notifications',
18
- 'messageBrokers', 'storage', 'vector', 'caches', 'sessions', 'quotas',
18
+ 'events', 'messageBrokers', 'storage', 'vector', 'caches', 'sessions', 'quotas',
19
19
  'actions', 'features', 'jobs', 'logs', 'resilience', 'health', 'fallback', 'secrets',
20
20
  ];
21
21
  // ─── Exhaustive SDK Method & Params Reference ────────────────────────────────
@@ -184,7 +184,7 @@ SETUP — register once in AppModule:
184
184
  ║ redisUrl IS REQUIRED TO USE ANY *.dispatch() ║
185
185
  ║ ║
186
186
  ║ Every dispatch() call (actions, features, events, databases, storage, ║
187
- ║ graph, notifications, messageBrokers, quotas, fallback) enqueues jobs
187
+ ║ graph, notifications, quotas, fallback) enqueues jobs
188
188
  ║ via BullMQ over Redis. Without redisUrl the call throws at runtime: ║
189
189
  ║ "Queues not configured. dispatch() requires a queue connection." ║
190
190
  ║ ║
@@ -502,33 +502,33 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
502
502
  notifications.dispatch [{ product, env, notification, event, input, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="dispatch") — requires redisUrl in ductape initialization
503
503
  notifications.getMessages [{ product_tag?, env?, notification_tag?, status?, type?, start_date?, end_date?, page?, limit? }]
504
504
 
505
- ━━━ MODULE: messageBrokers ━━━
506
- messageBrokers.create [{ product: string, tag: string, name: string, description?: string, type: "kafka"|"rabbitmq"|"redis"|"sqs", envs: [{ slug: string, connection_url: string }] }]
507
- messageBrokers.update [product_tag, broker_tag, data: { name?: string, description?: string, type?: "kafka"|"rabbitmq"|"redis"|"sqs", envs?: [{ slug: string, connection_url: string }] }]
508
- messageBrokers.fetch [product_tag, broker_tag]
509
- messageBrokers.list [product_tag]
510
- messageBrokers.delete [product_tag, broker_tag]
511
- messageBrokers.topics.create ← FORBIDDEN with publishable key. Use ductape_cli instead:
505
+ ━━━ MODULE: events (alias: messageBrokers — both accepted; events matches the TS SDK naming) ━━━
506
+ events.create [{ product: string, tag: string, name: string, description?: string, type: "kafka"|"rabbitmq"|"redis"|"sqs", envs: [{ slug: string, connection_url: string }] }]
507
+ events.update [product_tag, broker_tag, data: { name?: string, description?: string, type?: "kafka"|"rabbitmq"|"redis"|"sqs", envs?: [{ slug: string, connection_url: string }] }]
508
+ events.fetch [product_tag, broker_tag]
509
+ events.list [product_tag]
510
+ events.delete [product_tag, broker_tag]
511
+ events.topics.create ← FORBIDDEN with publishable key. Use ductape_cli instead:
512
512
  ductape_cli("events topics create -f topic.json")
513
513
  topic.json: { tag: "broker-tag:topic-tag", name, description?, sample?, idempotent?, queueUrls?: [{ env_slug, url }] }
514
514
  ← Always required before consuming. For SQS: must include queueUrls per env.
515
515
  ← For Pub/Sub, Kafka, RabbitMQ, Redis, NATS: the first produce call auto-registers the topic,
516
516
  but you should still create it explicitly so consumers can subscribe before any produce occurs.
517
- messageBrokers.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
517
+ events.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
518
518
  ductape_cli("events topics update --tag broker:topic -f patch.json")
519
- messageBrokers.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
519
+ events.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
520
520
  ductape_cli("events topics delete --tag broker:topic")
521
- messageBrokers.topics.fetch [product_tag, topic_tag] ← safe via ductape_execute
522
- messageBrokers.topics.list [product_tag, broker_tag] ← safe via ductape_execute
523
- messageBrokers.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }]
524
- messageBrokers.consume [{ product, env, event: "broker_tag:topic_tag", callback: "function_ref" }]
525
- messageBrokers.dispatch [{ product, env, broker, event, input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] — requires redisUrl in ductape initialization
526
- messageBrokers.messages.query [{ product, env, brokerTag, topicTag?, producerTag?, consumerTag?, status?, startDate?, endDate?, page?, limit? }]
527
- messageBrokers.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
528
- messageBrokers.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
529
- messageBrokers.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, startDate?, endDate?, page?, limit? }]
530
- messageBrokers.messages.getStats [{ product, env, brokerTag }]
531
- messageBrokers.messages.getDashboard [{ product, env, brokerTag }]
521
+ events.topics.fetch [product_tag, topic_tag] ← safe via ductape_execute
522
+ events.topics.list [product_tag, broker_tag] ← safe via ductape_execute
523
+ events.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }]
524
+ events.consume [{ product, env, event: "broker_tag:topic_tag", callback: "function_ref" }]
525
+ events.dispatch [{ product, env, event: "broker_tag:topic_tag", input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] — event is always the fully-qualified "broker:topic" string; do NOT pass broker separately — requires redisUrl in ductape initialization
526
+ events.messages.query [{ product, env, brokerTag, topicTag?, producerTag?, consumerTag?, status?, startDate?, endDate?, page?, limit? }]
527
+ events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
528
+ events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
529
+ events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, startDate?, endDate?, page?, limit? }]
530
+ events.messages.getStats [{ product, env, brokerTag }]
531
+ events.messages.getDashboard [{ product, env, brokerTag }]
532
532
 
533
533
  ━━━ MODULE: storage ━━━
534
534
  storage.create [{ product: string, tag: string, name: string, description?: string, envs: [{ slug: string, type: "aws"|"azure"|"gcp", config: { bucket?: string, region?: string, accessKeyId?: string, secretAccessKey?: string, containerName?: string, connectionString?: string, projectId?: string, keyFilename?: string } }] }]
@@ -1027,12 +1027,12 @@ function resolveSdkCallPath(operationFamily, method) {
1027
1027
  return m === 'dispatch' ? 'storage.dispatch' : `storage.${m}`;
1028
1028
  if (family === 'notification')
1029
1029
  return m === 'dispatch' ? 'notifications.dispatch' : `notifications.${m}`;
1030
- if (family === 'messaging' || family === 'broker') {
1030
+ if (family === 'messaging' || family === 'broker' || family === 'events' || family === 'event') {
1031
1031
  if (m === 'dispatch')
1032
- return 'messageBrokers.dispatch';
1032
+ return 'events.dispatch';
1033
1033
  if (m === 'send' || m === 'publish' || m === 'produce')
1034
- return 'messageBrokers.produce';
1035
- return `messageBrokers.${m}`;
1034
+ return 'events.produce';
1035
+ return `events.${m}`;
1036
1036
  }
1037
1037
  if (family === 'quota')
1038
1038
  return `quotas.${m}`;
@@ -2467,7 +2467,7 @@ Import (register an EXISTING cloud resource):
2467
2467
  Producing to a topic also calls ensureTopicRegistered in the background — but DO NOT rely on
2468
2468
  auto-registration for consume paths. Always create topics explicitly.
2469
2469
 
2470
- IMPORTANT: messageBrokers.topics.create requires an access key (admin operation).
2470
+ IMPORTANT: events.topics.create requires an access key (admin operation).
2471
2471
  Use ductape_cli — NOT ductape_execute — to create topics.
2472
2472
 
2473
2473
  Write a topic.json file, then:
@@ -2494,7 +2494,7 @@ Import (register an EXISTING cloud resource):
2494
2494
  ductape_cli("events topics delete --tag order-events:order-created")
2495
2495
 
2496
2496
  Read-only fetches (safe with publishable key via ductape_execute):
2497
- ductape_execute("messageBrokers.fetch", [product_tag, "broker-tag"]) → includes topics[]
2497
+ ductape_execute("events.fetch", [product_tag, "broker-tag"]) → includes topics[]
2498
2498
 
2499
2499
  ━━━ STEP 3: PRODUCE — WRITTEN IN APPLICATION CODE ━━━
2500
2500
 
@@ -2505,7 +2505,12 @@ Import (register an EXISTING cloud resource):
2505
2505
 
2506
2506
  GENERAL BACKEND (TypeScript/Node.js — not NestJS):
2507
2507
  import Ductape from '@ductape/sdk';
2508
- const ductape = new Ductape({ accessKey: 'your-access-key' });
2508
+ // produce() does not need redis_url.
2509
+ // dispatch() requires redis_url in the Ductape initialization options — it throws at runtime without it.
2510
+ const ductape = new Ductape({
2511
+ accessKey: process.env.DUCTAPE_ACCESS_KEY,
2512
+ redis_url: process.env.DUCTAPE_REDIS_URL, // required for any dispatch(); omit only if never dispatching
2513
+ });
2509
2514
  await ductape.events.produce({
2510
2515
  product: "my-product",
2511
2516
  env: "prd",
@@ -2520,14 +2525,26 @@ Import (register an EXISTING cloud resource):
2520
2525
  idempotencyTtl?: 86400, // seconds; default 86400 (24h)
2521
2526
  });
2522
2527
 
2523
- NESTJS — method decorators:
2528
+ NESTJS — initialization + method decorators:
2529
+ // AppModule — redisUrl is required whenever any *.dispatch() is used:
2530
+ DuctapeModule.forRootAsync({
2531
+ useFactory: () => ({
2532
+ accessKey: process.env.DUCTAPE_ACCESS_KEY,
2533
+ product: 'my-product',
2534
+ env: process.env.NODE_ENV === 'production' ? 'prd' : 'snd',
2535
+ redisUrl: process.env.DUCTAPE_REDIS_URL, // required — dispatch() throws without this
2536
+ }),
2537
+ });
2538
+ // Environment: DUCTAPE_REDIS_URL=redis://localhost:6379 (local) or rediss://:<pw>@host:6380 (managed)
2539
+ // produce() and @Events.Consumer do NOT need DUCTAPE_REDIS_URL — only dispatch() does.
2540
+
2524
2541
  import { Events } from '@ductape/nestjs';
2525
2542
  @Injectable() export class OrdersService {
2526
2543
  // Immediate produce — method returns the message payload:
2527
2544
  @Events.Produce({ event: 'order-events:order-created' })
2528
2545
  emitOrderCreated(payload: { orderId: string; total: number }) { return payload; }
2529
2546
 
2530
- // Dispatch with static schedule (known at deploy time):
2547
+ // Dispatch with static schedule requires redisUrl in DuctapeModule initialization:
2531
2548
  @Events.Dispatch({ broker: 'order-events', event: 'order-events:reminder-due',
2532
2549
  schedule: { every: 86400000 } })
2533
2550
  scheduleReminder(payload: { message: { orderId: string } }) { return payload; }
@@ -2560,10 +2577,9 @@ Import (register an EXISTING cloud resource):
2560
2577
  });
2561
2578
 
2562
2579
  SCHEDULED DISPATCH (background job):
2563
- ductape_execute("messageBrokers.dispatch", [{
2580
+ ductape_execute("events.dispatch", [{
2564
2581
  product, env,
2565
- broker: "order-events", // broker tag
2566
- event: "order-events:reminder-due", // "broker:topic"
2582
+ event: "order-events:reminder-due", // fully-qualified "broker-tag:topic-tag" — no separate broker field
2567
2583
  input: { message: { orderId: "123" } },
2568
2584
  retries?: 3,
2569
2585
  session?: "session-tag:jwt",
@@ -2631,24 +2647,24 @@ Import (register an EXISTING cloud resource):
2631
2647
 
2632
2648
  DEAD-LETTER QUEUE (DLQ):
2633
2649
  Messages whose callbacks consistently throw are automatically moved to the DLQ.
2634
- Query: ductape_execute("messageBrokers.messages.getDeadLetters",
2650
+ Query: ductape_execute("events.messages.getDeadLetters",
2635
2651
  [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }])
2636
- Reprocess: ductape_execute("messageBrokers.reprocessDLQ",
2652
+ Reprocess: ductape_execute("events.reprocessDLQ",
2637
2653
  [{ product, env, brokerTag, topicTag?, messageIds?, limit? }])
2638
- Replay: ductape_execute("messageBrokers.replayEvent",
2654
+ Replay: ductape_execute("events.replayEvent",
2639
2655
  [{ product, env, eventId, force? }])
2640
2656
 
2641
2657
  ━━━ OBSERVABILITY ━━━
2642
2658
 
2643
- messageBrokers.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
2644
- messageBrokers.messages.getStats [{ product, env, brokerTag }]
2645
- messageBrokers.messages.getDashboard [{ product, env, brokerTag }]
2646
- messageBrokers.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
2647
- messageBrokers.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2648
- messageBrokers.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2649
- messageBrokers.replayEvent [{ product, env, eventId, force? }]
2650
- messageBrokers.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
2651
- messageBrokers.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
2659
+ events.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
2660
+ events.messages.getStats [{ product, env, brokerTag }]
2661
+ events.messages.getDashboard [{ product, env, brokerTag }]
2662
+ events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
2663
+ events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2664
+ events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2665
+ events.replayEvent [{ product, env, eventId, force? }]
2666
+ events.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
2667
+ events.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
2652
2668
  `.trim(),
2653
2669
  logs: `
2654
2670
  DUCTAPE LOGS
@@ -3377,7 +3393,28 @@ async function main() {
3377
3393
  if (!key) {
3378
3394
  throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
3379
3395
  }
3380
- const result = await executeViaProxy(key, args.module, args.method, args.params);
3396
+ // The TS SDK uses ductape.events.* for broker operations; the backend proxy uses messageBrokers.
3397
+ const proxyModule = args.module === 'events' ? 'messageBrokers' : args.module;
3398
+ // Normalize events.dispatch params: event must be fully-qualified "broker:topic".
3399
+ // If an agent passes both broker and event="broker:topic", strip broker and de-duplicate.
3400
+ let params = args.params;
3401
+ if ((args.module === 'events' || args.module === 'messageBrokers') && args.method === 'dispatch') {
3402
+ params = params.map((p) => {
3403
+ if (p && typeof p === 'object' && !Array.isArray(p)) {
3404
+ const obj = p;
3405
+ const broker = typeof obj['broker'] === 'string' ? obj['broker'] : '';
3406
+ const event = typeof obj['event'] === 'string' ? obj['event'] : '';
3407
+ if (broker && event) {
3408
+ // Strip broker prefix if event is already "broker:..." to avoid "broker:broker:topic"
3409
+ const normalized = event.startsWith(broker + ':') ? event : `${broker}:${event}`;
3410
+ const { broker: _removed, ...rest } = obj;
3411
+ return { ...rest, event: normalized };
3412
+ }
3413
+ }
3414
+ return p;
3415
+ });
3416
+ }
3417
+ const result = await executeViaProxy(key, proxyModule, args.method, params);
3381
3418
  return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
3382
3419
  }
3383
3420
  catch (err) {
@@ -2,7 +2,7 @@
2
2
  * Client for the Ductape backend SDK proxy using Publishable Key.
3
3
  */
4
4
  export declare const API_BASE_URL = "https://api.ductape.app";
5
- export type SDKModule = 'product' | 'app' | 'databases' | 'graph' | 'webhooks' | 'notifications' | 'messageBrokers' | 'storage' | 'vector' | 'caches' | 'sessions' | 'quotas' | 'actions' | 'features' | 'jobs' | 'logs' | 'resilience' | 'health' | 'fallback' | 'secrets';
5
+ export type SDKModule = 'product' | 'app' | 'databases' | 'graph' | 'webhooks' | 'notifications' | 'messageBrokers' | 'events' | 'storage' | 'vector' | 'caches' | 'sessions' | 'quotas' | 'actions' | 'features' | 'jobs' | 'logs' | 'resilience' | 'health' | 'fallback' | 'secrets';
6
6
  /**
7
7
  * Execute an SDK operation via the backend proxy using a Publishable Key.
8
8
  */
@@ -1 +1 @@
1
- {"version":3,"file":"proxy-client.d.ts","sourceRoot":"","sources":["../src/proxy-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,YAAY,4BAA4B,CAAC;AAEtD,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,KAAK,GACL,WAAW,GACX,OAAO,GACP,UAAU,GACV,eAAe,GACf,gBAAgB,GAChB,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,SAAS,GACT,UAAU,GACV,MAAM,GACN,MAAM,GACN,YAAY,GACZ,QAAQ,GACR,UAAU,GACV,SAAS,CAAC;AAUd;;GAEG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,OAAO,EAC/C,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,OAAO,EAAO,GACrB,OAAO,CAAC,CAAC,CAAC,CAuBZ;AAED,MAAM,WAAW,iCAAiC;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AASD,wBAAsB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYvE;AA4BD,wBAAsB,yBAAyB,CAAC,CAAC,GAAG,kCAAkC,EACpF,OAAO,EAAE,iCAAiC,GACzC,OAAO,CAAC,CAAC,CAAC,CAwBZ"}
1
+ {"version":3,"file":"proxy-client.d.ts","sourceRoot":"","sources":["../src/proxy-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,YAAY,4BAA4B,CAAC;AAEtD,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,KAAK,GACL,WAAW,GACX,OAAO,GACP,UAAU,GACV,eAAe,GACf,gBAAgB,GAChB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,SAAS,GACT,UAAU,GACV,MAAM,GACN,MAAM,GACN,YAAY,GACZ,QAAQ,GACR,UAAU,GACV,SAAS,CAAC;AAUd;;GAEG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,OAAO,EAC/C,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,OAAO,EAAO,GACrB,OAAO,CAAC,CAAC,CAAC,CAuBZ;AAED,MAAM,WAAW,iCAAiC;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AASD,wBAAsB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYvE;AA4BD,wBAAsB,yBAAyB,CAAC,CAAC,GAAG,kCAAkC,EACpF,OAAO,EAAE,iCAAiC,GACzC,OAAO,CAAC,CAAC,CAAC,CAwBZ"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.1.44",
3
+ "version": "0.1.46",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -22,7 +22,7 @@ import {
22
22
 
23
23
  const MODULES: SDKModule[] = [
24
24
  'product', 'app', 'databases', 'graph', 'webhooks', 'notifications',
25
- 'messageBrokers', 'storage', 'vector', 'caches', 'sessions', 'quotas',
25
+ 'events', 'messageBrokers', 'storage', 'vector', 'caches', 'sessions', 'quotas',
26
26
  'actions', 'features', 'jobs', 'logs', 'resilience', 'health', 'fallback', 'secrets',
27
27
  ];
28
28
 
@@ -195,7 +195,7 @@ SETUP — register once in AppModule:
195
195
  ║ redisUrl IS REQUIRED TO USE ANY *.dispatch() ║
196
196
  ║ ║
197
197
  ║ Every dispatch() call (actions, features, events, databases, storage, ║
198
- ║ graph, notifications, messageBrokers, quotas, fallback) enqueues jobs
198
+ ║ graph, notifications, quotas, fallback) enqueues jobs
199
199
  ║ via BullMQ over Redis. Without redisUrl the call throws at runtime: ║
200
200
  ║ "Queues not configured. dispatch() requires a queue connection." ║
201
201
  ║ ║
@@ -513,33 +513,33 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
513
513
  notifications.dispatch [{ product, env, notification, event, input, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="dispatch") — requires redisUrl in ductape initialization
514
514
  notifications.getMessages [{ product_tag?, env?, notification_tag?, status?, type?, start_date?, end_date?, page?, limit? }]
515
515
 
516
- ━━━ MODULE: messageBrokers ━━━
517
- messageBrokers.create [{ product: string, tag: string, name: string, description?: string, type: "kafka"|"rabbitmq"|"redis"|"sqs", envs: [{ slug: string, connection_url: string }] }]
518
- messageBrokers.update [product_tag, broker_tag, data: { name?: string, description?: string, type?: "kafka"|"rabbitmq"|"redis"|"sqs", envs?: [{ slug: string, connection_url: string }] }]
519
- messageBrokers.fetch [product_tag, broker_tag]
520
- messageBrokers.list [product_tag]
521
- messageBrokers.delete [product_tag, broker_tag]
522
- messageBrokers.topics.create ← FORBIDDEN with publishable key. Use ductape_cli instead:
516
+ ━━━ MODULE: events (alias: messageBrokers — both accepted; events matches the TS SDK naming) ━━━
517
+ events.create [{ product: string, tag: string, name: string, description?: string, type: "kafka"|"rabbitmq"|"redis"|"sqs", envs: [{ slug: string, connection_url: string }] }]
518
+ events.update [product_tag, broker_tag, data: { name?: string, description?: string, type?: "kafka"|"rabbitmq"|"redis"|"sqs", envs?: [{ slug: string, connection_url: string }] }]
519
+ events.fetch [product_tag, broker_tag]
520
+ events.list [product_tag]
521
+ events.delete [product_tag, broker_tag]
522
+ events.topics.create ← FORBIDDEN with publishable key. Use ductape_cli instead:
523
523
  ductape_cli("events topics create -f topic.json")
524
524
  topic.json: { tag: "broker-tag:topic-tag", name, description?, sample?, idempotent?, queueUrls?: [{ env_slug, url }] }
525
525
  ← Always required before consuming. For SQS: must include queueUrls per env.
526
526
  ← For Pub/Sub, Kafka, RabbitMQ, Redis, NATS: the first produce call auto-registers the topic,
527
527
  but you should still create it explicitly so consumers can subscribe before any produce occurs.
528
- messageBrokers.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
528
+ events.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
529
529
  ductape_cli("events topics update --tag broker:topic -f patch.json")
530
- messageBrokers.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
530
+ events.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
531
531
  ductape_cli("events topics delete --tag broker:topic")
532
- messageBrokers.topics.fetch [product_tag, topic_tag] ← safe via ductape_execute
533
- messageBrokers.topics.list [product_tag, broker_tag] ← safe via ductape_execute
534
- messageBrokers.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }]
535
- messageBrokers.consume [{ product, env, event: "broker_tag:topic_tag", callback: "function_ref" }]
536
- messageBrokers.dispatch [{ product, env, broker, event, input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] — requires redisUrl in ductape initialization
537
- messageBrokers.messages.query [{ product, env, brokerTag, topicTag?, producerTag?, consumerTag?, status?, startDate?, endDate?, page?, limit? }]
538
- messageBrokers.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
539
- messageBrokers.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
540
- messageBrokers.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, startDate?, endDate?, page?, limit? }]
541
- messageBrokers.messages.getStats [{ product, env, brokerTag }]
542
- messageBrokers.messages.getDashboard [{ product, env, brokerTag }]
532
+ events.topics.fetch [product_tag, topic_tag] ← safe via ductape_execute
533
+ events.topics.list [product_tag, broker_tag] ← safe via ductape_execute
534
+ events.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }]
535
+ events.consume [{ product, env, event: "broker_tag:topic_tag", callback: "function_ref" }]
536
+ events.dispatch [{ product, env, event: "broker_tag:topic_tag", input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] — event is always the fully-qualified "broker:topic" string; do NOT pass broker separately — requires redisUrl in ductape initialization
537
+ events.messages.query [{ product, env, brokerTag, topicTag?, producerTag?, consumerTag?, status?, startDate?, endDate?, page?, limit? }]
538
+ events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
539
+ events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
540
+ events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, startDate?, endDate?, page?, limit? }]
541
+ events.messages.getStats [{ product, env, brokerTag }]
542
+ events.messages.getDashboard [{ product, env, brokerTag }]
543
543
 
544
544
  ━━━ MODULE: storage ━━━
545
545
  storage.create [{ product: string, tag: string, name: string, description?: string, envs: [{ slug: string, type: "aws"|"azure"|"gcp", config: { bucket?: string, region?: string, accessKeyId?: string, secretAccessKey?: string, containerName?: string, connectionString?: string, projectId?: string, keyFilename?: string } }] }]
@@ -1062,10 +1062,10 @@ function resolveSdkCallPath(operationFamily: string, method: string): string {
1062
1062
  if (family === 'vector') return `vector.${m}`;
1063
1063
  if (family === 'storage') return m === 'dispatch' ? 'storage.dispatch' : `storage.${m}`;
1064
1064
  if (family === 'notification') return m === 'dispatch' ? 'notifications.dispatch' : `notifications.${m}`;
1065
- if (family === 'messaging' || family === 'broker') {
1066
- if (m === 'dispatch') return 'messageBrokers.dispatch';
1067
- if (m === 'send' || m === 'publish' || m === 'produce') return 'messageBrokers.produce';
1068
- return `messageBrokers.${m}`;
1065
+ if (family === 'messaging' || family === 'broker' || family === 'events' || family === 'event') {
1066
+ if (m === 'dispatch') return 'events.dispatch';
1067
+ if (m === 'send' || m === 'publish' || m === 'produce') return 'events.produce';
1068
+ return `events.${m}`;
1069
1069
  }
1070
1070
  if (family === 'quota') return `quotas.${m}`;
1071
1071
  if (family === 'fallback') return `fallback.${m}`;
@@ -2540,7 +2540,7 @@ Import (register an EXISTING cloud resource):
2540
2540
  Producing to a topic also calls ensureTopicRegistered in the background — but DO NOT rely on
2541
2541
  auto-registration for consume paths. Always create topics explicitly.
2542
2542
 
2543
- IMPORTANT: messageBrokers.topics.create requires an access key (admin operation).
2543
+ IMPORTANT: events.topics.create requires an access key (admin operation).
2544
2544
  Use ductape_cli — NOT ductape_execute — to create topics.
2545
2545
 
2546
2546
  Write a topic.json file, then:
@@ -2567,7 +2567,7 @@ Import (register an EXISTING cloud resource):
2567
2567
  ductape_cli("events topics delete --tag order-events:order-created")
2568
2568
 
2569
2569
  Read-only fetches (safe with publishable key via ductape_execute):
2570
- ductape_execute("messageBrokers.fetch", [product_tag, "broker-tag"]) → includes topics[]
2570
+ ductape_execute("events.fetch", [product_tag, "broker-tag"]) → includes topics[]
2571
2571
 
2572
2572
  ━━━ STEP 3: PRODUCE — WRITTEN IN APPLICATION CODE ━━━
2573
2573
 
@@ -2578,7 +2578,12 @@ Import (register an EXISTING cloud resource):
2578
2578
 
2579
2579
  GENERAL BACKEND (TypeScript/Node.js — not NestJS):
2580
2580
  import Ductape from '@ductape/sdk';
2581
- const ductape = new Ductape({ accessKey: 'your-access-key' });
2581
+ // produce() does not need redis_url.
2582
+ // dispatch() requires redis_url in the Ductape initialization options — it throws at runtime without it.
2583
+ const ductape = new Ductape({
2584
+ accessKey: process.env.DUCTAPE_ACCESS_KEY,
2585
+ redis_url: process.env.DUCTAPE_REDIS_URL, // required for any dispatch(); omit only if never dispatching
2586
+ });
2582
2587
  await ductape.events.produce({
2583
2588
  product: "my-product",
2584
2589
  env: "prd",
@@ -2593,14 +2598,26 @@ Import (register an EXISTING cloud resource):
2593
2598
  idempotencyTtl?: 86400, // seconds; default 86400 (24h)
2594
2599
  });
2595
2600
 
2596
- NESTJS — method decorators:
2601
+ NESTJS — initialization + method decorators:
2602
+ // AppModule — redisUrl is required whenever any *.dispatch() is used:
2603
+ DuctapeModule.forRootAsync({
2604
+ useFactory: () => ({
2605
+ accessKey: process.env.DUCTAPE_ACCESS_KEY,
2606
+ product: 'my-product',
2607
+ env: process.env.NODE_ENV === 'production' ? 'prd' : 'snd',
2608
+ redisUrl: process.env.DUCTAPE_REDIS_URL, // required — dispatch() throws without this
2609
+ }),
2610
+ });
2611
+ // Environment: DUCTAPE_REDIS_URL=redis://localhost:6379 (local) or rediss://:<pw>@host:6380 (managed)
2612
+ // produce() and @Events.Consumer do NOT need DUCTAPE_REDIS_URL — only dispatch() does.
2613
+
2597
2614
  import { Events } from '@ductape/nestjs';
2598
2615
  @Injectable() export class OrdersService {
2599
2616
  // Immediate produce — method returns the message payload:
2600
2617
  @Events.Produce({ event: 'order-events:order-created' })
2601
2618
  emitOrderCreated(payload: { orderId: string; total: number }) { return payload; }
2602
2619
 
2603
- // Dispatch with static schedule (known at deploy time):
2620
+ // Dispatch with static schedule requires redisUrl in DuctapeModule initialization:
2604
2621
  @Events.Dispatch({ broker: 'order-events', event: 'order-events:reminder-due',
2605
2622
  schedule: { every: 86400000 } })
2606
2623
  scheduleReminder(payload: { message: { orderId: string } }) { return payload; }
@@ -2633,10 +2650,9 @@ Import (register an EXISTING cloud resource):
2633
2650
  });
2634
2651
 
2635
2652
  SCHEDULED DISPATCH (background job):
2636
- ductape_execute("messageBrokers.dispatch", [{
2653
+ ductape_execute("events.dispatch", [{
2637
2654
  product, env,
2638
- broker: "order-events", // broker tag
2639
- event: "order-events:reminder-due", // "broker:topic"
2655
+ event: "order-events:reminder-due", // fully-qualified "broker-tag:topic-tag" — no separate broker field
2640
2656
  input: { message: { orderId: "123" } },
2641
2657
  retries?: 3,
2642
2658
  session?: "session-tag:jwt",
@@ -2704,24 +2720,24 @@ Import (register an EXISTING cloud resource):
2704
2720
 
2705
2721
  DEAD-LETTER QUEUE (DLQ):
2706
2722
  Messages whose callbacks consistently throw are automatically moved to the DLQ.
2707
- Query: ductape_execute("messageBrokers.messages.getDeadLetters",
2723
+ Query: ductape_execute("events.messages.getDeadLetters",
2708
2724
  [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }])
2709
- Reprocess: ductape_execute("messageBrokers.reprocessDLQ",
2725
+ Reprocess: ductape_execute("events.reprocessDLQ",
2710
2726
  [{ product, env, brokerTag, topicTag?, messageIds?, limit? }])
2711
- Replay: ductape_execute("messageBrokers.replayEvent",
2727
+ Replay: ductape_execute("events.replayEvent",
2712
2728
  [{ product, env, eventId, force? }])
2713
2729
 
2714
2730
  ━━━ OBSERVABILITY ━━━
2715
2731
 
2716
- messageBrokers.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
2717
- messageBrokers.messages.getStats [{ product, env, brokerTag }]
2718
- messageBrokers.messages.getDashboard [{ product, env, brokerTag }]
2719
- messageBrokers.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
2720
- messageBrokers.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2721
- messageBrokers.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2722
- messageBrokers.replayEvent [{ product, env, eventId, force? }]
2723
- messageBrokers.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
2724
- messageBrokers.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
2732
+ events.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
2733
+ events.messages.getStats [{ product, env, brokerTag }]
2734
+ events.messages.getDashboard [{ product, env, brokerTag }]
2735
+ events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
2736
+ events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2737
+ events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2738
+ events.replayEvent [{ product, env, eventId, force? }]
2739
+ events.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
2740
+ events.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
2725
2741
  `.trim(),
2726
2742
 
2727
2743
  logs: `
@@ -3475,7 +3491,30 @@ async function main() {
3475
3491
  if (!key) {
3476
3492
  throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
3477
3493
  }
3478
- const result = await executeViaProxy(key, args.module, args.method, args.params);
3494
+ // The TS SDK uses ductape.events.* for broker operations; the backend proxy uses messageBrokers.
3495
+ const proxyModule: SDKModule = args.module === 'events' ? 'messageBrokers' : args.module;
3496
+
3497
+ // Normalize events.dispatch params: event must be fully-qualified "broker:topic".
3498
+ // If an agent passes both broker and event="broker:topic", strip broker and de-duplicate.
3499
+ let params = args.params;
3500
+ if ((args.module === 'events' || args.module === 'messageBrokers') && args.method === 'dispatch') {
3501
+ params = params.map((p) => {
3502
+ if (p && typeof p === 'object' && !Array.isArray(p)) {
3503
+ const obj = p as Record<string, unknown>;
3504
+ const broker = typeof obj['broker'] === 'string' ? obj['broker'] : '';
3505
+ const event = typeof obj['event'] === 'string' ? obj['event'] : '';
3506
+ if (broker && event) {
3507
+ // Strip broker prefix if event is already "broker:..." to avoid "broker:broker:topic"
3508
+ const normalized = event.startsWith(broker + ':') ? event : `${broker}:${event}`;
3509
+ const { broker: _removed, ...rest } = obj;
3510
+ return { ...rest, event: normalized };
3511
+ }
3512
+ }
3513
+ return p;
3514
+ });
3515
+ }
3516
+
3517
+ const result = await executeViaProxy(key, proxyModule, args.method, params);
3479
3518
  return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
3480
3519
  } catch (err) {
3481
3520
  const message = err instanceof Error ? err.message : String(err);
@@ -12,6 +12,7 @@ export type SDKModule =
12
12
  | 'webhooks'
13
13
  | 'notifications'
14
14
  | 'messageBrokers'
15
+ | 'events'
15
16
  | 'storage'
16
17
  | 'vector'
17
18
  | 'caches'