@ductape/mcp 0.1.43 → 0.1.45

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 ────────────────────────────────
@@ -180,29 +180,33 @@ global interceptors, decorators, and type-safe resource handles.
180
180
 
181
181
  SETUP — register once in AppModule:
182
182
 
183
+ ╔══════════════════════════════════════════════════════════════════════════╗
184
+ ║ redisUrl IS REQUIRED TO USE ANY *.dispatch() ║
185
+ ║ ║
186
+ ║ Every dispatch() call (actions, features, events, databases, storage, ║
187
+ ║ graph, notifications, quotas, fallback) enqueues jobs ║
188
+ ║ via BullMQ over Redis. Without redisUrl the call throws at runtime: ║
189
+ ║ "Queues not configured. dispatch() requires a queue connection." ║
190
+ ║ ║
191
+ ║ *.run(), events.produce(), and @Events.Consumer do NOT need Redis. ║
192
+ ║ Only dispatch() does — and it is non-negotiable. ║
193
+ ╚══════════════════════════════════════════════════════════════════════════╝
194
+
183
195
  import { DuctapeModule } from '@ductape/nestjs';
184
196
 
185
197
  @Module({
186
198
  imports: [
187
199
  DuctapeModule.forIntegration({
188
200
  accessKey: process.env.DUCTAPE_ACCESS_KEY,
189
- product: 'my-product', // optional default — overridable per controller
201
+ product: 'my-product',
190
202
  env: process.env.NODE_ENV === 'production' ? 'prd' : 'snd',
191
- redisUrl: process.env.REDIS_URL, // REQUIRED when using events.dispatch() or @Events.Dispatch
203
+ redisUrl: process.env.DUCTAPE_REDIS_URL, // required no dispatch() works without this
192
204
  }),
193
205
  ],
194
206
  })
195
207
  export class AppModule {}
196
208
 
197
- IMPORTANT redisUrl:
198
- ALL *.dispatch() calls (actions.dispatch, features.dispatch, events.dispatch, databases.dispatch,
199
- storage.dispatch, graph.dispatch, notifications.dispatch, messageBrokers.dispatch, quotas.dispatch,
200
- fallback.dispatch, etc.) enqueue jobs via BullMQ, which requires a Redis connection.
201
- Without redisUrl ANY dispatch() call throws "Queues not configured. dispatch() requires a queue connection."
202
- Set REDIS_URL (e.g. redis://localhost:6379 or a managed Redis connection string) and pass it as redisUrl.
203
- *.run(), events.produce(), and @Events.Consumer do NOT require Redis — only dispatch() does.
204
-
205
- // Async (e.g. pulling key from ConfigService):
209
+ // Async (e.g. pulling from ConfigService):
206
210
  DuctapeModule.forRootAsync({
207
211
  imports: [ConfigModule],
208
212
  inject: [ConfigService],
@@ -210,10 +214,14 @@ SETUP — register once in AppModule:
210
214
  accessKey: cfg.get('DUCTAPE_ACCESS_KEY'),
211
215
  product: cfg.get('DUCTAPE_PRODUCT'),
212
216
  env: cfg.get('DUCTAPE_ENV'),
213
- redisUrl: cfg.get('REDIS_URL'), // required for dispatch
217
+ redisUrl: cfg.get('DUCTAPE_REDIS_URL'), // required no dispatch() works without this
214
218
  }),
215
219
  })
216
220
 
221
+ Environment variable (add to .env and deployment secrets):
222
+ DUCTAPE_REDIS_URL=redis://localhost:6379 # local dev
223
+ DUCTAPE_REDIS_URL=rediss://:<password>@host:6380 # managed Redis (TLS)
224
+
217
225
  INJECTING IN SERVICES AND CONTROLLERS — use @InjectContext():
218
226
 
219
227
  import { InjectContext, DuctapeContext } from '@ductape/nestjs';
@@ -268,7 +276,7 @@ FOR MESSAGING (events.produce / events.consume / events.dispatch):
268
276
  @Events.Produce({ event: 'broker-tag:topic-tag' })
269
277
  emitOrderCreated(payload: { orderId: string }) { return payload; }
270
278
 
271
- Dispatch (scheduled or immediate) — REQUIRES redisUrl in DuctapeModule.forIntegration:
279
+ Dispatch (scheduled or immediate) — dispatch() CANNOT be used without redisUrl in forIntegration:
272
280
  @Events.Dispatch({ broker: 'order-events', event: 'order-events:order-created', schedule: { every: 60000 } })
273
281
  dispatchHeartbeat(payload: { message: { ping: boolean } }) { return payload; }
274
282
 
@@ -343,7 +351,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
343
351
  actions.fetch [app_tag, action_tag]
344
352
  actions.list [app_tag]
345
353
  actions.run [{ product, env, app, action, input: { "body:fieldName": value, ... } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="run", targets={app, action})
346
- actions.dispatch [{ product, env, app, action, input, retries?, session?, cache?, schedule?: { start_at?, cron?, every?, limit?, tz? } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="dispatch")
354
+ actions.dispatch [{ product, env, app, action, input, retries?, session?, cache?, schedule?: { start_at?, cron?, every?, limit?, tz? } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="dispatch") — requires redisUrl in ductape initialization
347
355
 
348
356
  ━━━ MODULE: auths ━━━
349
357
  auths.create [app_tag, data: { tag: string, name: string, setup_type: "header"|"bearer"|"basic"|"oauth2"|"apikey", expiry: number, period: "seconds"|"minutes"|"hours"|"days", description: string, action_tag?: string }]
@@ -421,7 +429,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
421
429
  quotas.list [product_tag]
422
430
  quotas.delete [product_tag, quota_tag]
423
431
  quotas.run [{ product: string, env: string, tag: string, input: { fieldName: value }, session?: string, cache?: string }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="run", targets={tag})
424
- quotas.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="dispatch")
432
+ quotas.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="dispatch") — requires redisUrl in ductape initialization
425
433
 
426
434
  ━━━ MODULE: fallback ━━━
427
435
  fallback.create [product_tag, data: {
@@ -464,7 +472,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
464
472
  fallback.list [product_tag]
465
473
  fallback.delete [product_tag, fallback_tag]
466
474
  fallback.run [{ product: string, env: string, tag: string, input: { fieldName: value }, session?: string, cache?: string }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="run", targets={tag})
467
- fallback.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="dispatch")
475
+ fallback.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="dispatch") — requires redisUrl in ductape initialization
468
476
 
469
477
  ━━━ MODULE: health ━━━
470
478
  health.create [product_tag, data: { tag: string, name: string, description?: string, app?: string, event?: string, probe?: { type: "app"|"database"|"feature", app?: string, event?: string, input?: object }, interval: number, retries: number, envs: [{ slug: string, input?: object }], onFailure?: { notifications?: [{ notification: string, message: string, channels: { email?: { recipients: string[] }, push?: { recipients?: string[] }, sms?: { recipients: string[] } } }], webhooks?: [{ url: string, method?: "GET"|"POST", headers?: object, body?: object }] } }]
@@ -491,36 +499,36 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
491
499
  notifications.push.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="push.send")
492
500
  notifications.sms.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="sms.send")
493
501
  notifications.callback.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="callback.send")
494
- 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")
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
495
503
  notifications.getMessages [{ product_tag?, env?, notification_tag?, status?, type?, start_date?, end_date?, page?, limit? }]
496
504
 
497
- ━━━ MODULE: messageBrokers ━━━
498
- messageBrokers.create [{ product: string, tag: string, name: string, description?: string, type: "kafka"|"rabbitmq"|"redis"|"sqs", envs: [{ slug: string, connection_url: string }] }]
499
- messageBrokers.update [product_tag, broker_tag, data: { name?: string, description?: string, type?: "kafka"|"rabbitmq"|"redis"|"sqs", envs?: [{ slug: string, connection_url: string }] }]
500
- messageBrokers.fetch [product_tag, broker_tag]
501
- messageBrokers.list [product_tag]
502
- messageBrokers.delete [product_tag, broker_tag]
503
- 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:
504
512
  ductape_cli("events topics create -f topic.json")
505
513
  topic.json: { tag: "broker-tag:topic-tag", name, description?, sample?, idempotent?, queueUrls?: [{ env_slug, url }] }
506
514
  ← Always required before consuming. For SQS: must include queueUrls per env.
507
515
  ← For Pub/Sub, Kafka, RabbitMQ, Redis, NATS: the first produce call auto-registers the topic,
508
516
  but you should still create it explicitly so consumers can subscribe before any produce occurs.
509
- messageBrokers.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
517
+ events.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
510
518
  ductape_cli("events topics update --tag broker:topic -f patch.json")
511
- messageBrokers.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
519
+ events.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
512
520
  ductape_cli("events topics delete --tag broker:topic")
513
- messageBrokers.topics.fetch [product_tag, topic_tag] ← safe via ductape_execute
514
- messageBrokers.topics.list [product_tag, broker_tag] ← safe via ductape_execute
515
- messageBrokers.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }]
516
- messageBrokers.consume [{ product, env, event: "broker_tag:topic_tag", callback: "function_ref" }]
517
- messageBrokers.dispatch [{ product, env, broker, event, input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }]
518
- messageBrokers.messages.query [{ product, env, brokerTag, topicTag?, producerTag?, consumerTag?, status?, startDate?, endDate?, page?, limit? }]
519
- messageBrokers.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
520
- messageBrokers.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
521
- messageBrokers.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, startDate?, endDate?, page?, limit? }]
522
- messageBrokers.messages.getStats [{ product, env, brokerTag }]
523
- 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, broker, event, input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] — 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 }]
524
532
 
525
533
  ━━━ MODULE: storage ━━━
526
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 } }] }]
@@ -540,7 +548,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
540
548
  storage.files.delete [{ product, env, storage, fileName }]
541
549
  storage.files.list [{ product, env, storage, prefix?, limit?, continuationToken? }]
542
550
  storage.files.getSignedUrl [{ product, env, storage, fileName, expiresIn?, action? }]
543
- storage.dispatch [{ product, env, storage, operation, input, retries?, session?, cache?, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="storage", method="dispatch", targets={storage})
551
+ storage.dispatch [{ product, env, storage, operation, input, retries?, session?, cache?, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="storage", method="dispatch", targets={storage}) — requires redisUrl in ductape initialization
544
552
 
545
553
  ━━━ MODULE: databases ━━━
546
554
  databases.create [{ product, tag, name, description?, type: "mongodb"|"postgresql"|"mysql"|"sqlite",
@@ -608,8 +616,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
608
616
  databases.action.fetch [action_tag]
609
617
  databases.action.list [database_tag]
610
618
  databases.action.delete [action_tag]
611
- databases.action.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"})
612
- databases.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"})
619
+ databases.action.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"}) — requires redisUrl in ductape initialization
620
+ databases.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"}) — requires redisUrl in ductape initialization
613
621
  databases.beginTransaction [{ product, env, database, isolationLevel?: "READ_COMMITTED"|"REPEATABLE_READ"|"SERIALIZABLE" }]
614
622
  → returns a transaction object; pass it to insert/update/delete/upsert/query calls as the last argument.
615
623
  Commit with: transaction.commit() Rollback with: transaction.rollback()
@@ -671,7 +679,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
671
679
  graph.beginTransaction [options?]
672
680
  graph.commitTransaction [transaction]
673
681
  graph.rollbackTransaction [transaction]
674
- graph.dispatch [data] ← CALL ductape_generate_payload FIRST (operation_family="graph", method="dispatch", targets={graph})
682
+ graph.dispatch [data] ← CALL ductape_generate_payload FIRST (operation_family="graph", method="dispatch", targets={graph}) — requires redisUrl in ductape initialization
675
683
 
676
684
  ━━━ MODULE: vector ━━━
677
685
  vector.create [{ product, tag, name, description?, provider: "pinecone"|"qdrant"|"weaviate", dimensions: number, metric?: "cosine"|"euclidean"|"dotproduct", envs: [{slug, api_key, environment?}] }]
@@ -791,7 +799,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
791
799
  timeout?: number
792
800
  }]
793
801
 
794
- features.dispatch [{ ← CALL ductape_generate_payload FIRST (operation_family="features", method="dispatch", targets={feature})
802
+ features.dispatch [{ ← CALL ductape_generate_payload FIRST (operation_family="features", method="dispatch", targets={feature}) — requires redisUrl in ductape initialization
795
803
  product: string,
796
804
  env: string,
797
805
  feature: string,
@@ -1019,12 +1027,12 @@ function resolveSdkCallPath(operationFamily, method) {
1019
1027
  return m === 'dispatch' ? 'storage.dispatch' : `storage.${m}`;
1020
1028
  if (family === 'notification')
1021
1029
  return m === 'dispatch' ? 'notifications.dispatch' : `notifications.${m}`;
1022
- if (family === 'messaging' || family === 'broker') {
1030
+ if (family === 'messaging' || family === 'broker' || family === 'events' || family === 'event') {
1023
1031
  if (m === 'dispatch')
1024
- return 'messageBrokers.dispatch';
1032
+ return 'events.dispatch';
1025
1033
  if (m === 'send' || m === 'publish' || m === 'produce')
1026
- return 'messageBrokers.produce';
1027
- return `messageBrokers.${m}`;
1034
+ return 'events.produce';
1035
+ return `events.${m}`;
1028
1036
  }
1029
1037
  if (family === 'quota')
1030
1038
  return `quotas.${m}`;
@@ -2459,7 +2467,7 @@ Import (register an EXISTING cloud resource):
2459
2467
  Producing to a topic also calls ensureTopicRegistered in the background — but DO NOT rely on
2460
2468
  auto-registration for consume paths. Always create topics explicitly.
2461
2469
 
2462
- IMPORTANT: messageBrokers.topics.create requires an access key (admin operation).
2470
+ IMPORTANT: events.topics.create requires an access key (admin operation).
2463
2471
  Use ductape_cli — NOT ductape_execute — to create topics.
2464
2472
 
2465
2473
  Write a topic.json file, then:
@@ -2486,7 +2494,7 @@ Import (register an EXISTING cloud resource):
2486
2494
  ductape_cli("events topics delete --tag order-events:order-created")
2487
2495
 
2488
2496
  Read-only fetches (safe with publishable key via ductape_execute):
2489
- ductape_execute("messageBrokers.fetch", [product_tag, "broker-tag"]) → includes topics[]
2497
+ ductape_execute("events.fetch", [product_tag, "broker-tag"]) → includes topics[]
2490
2498
 
2491
2499
  ━━━ STEP 3: PRODUCE — WRITTEN IN APPLICATION CODE ━━━
2492
2500
 
@@ -2497,7 +2505,12 @@ Import (register an EXISTING cloud resource):
2497
2505
 
2498
2506
  GENERAL BACKEND (TypeScript/Node.js — not NestJS):
2499
2507
  import Ductape from '@ductape/sdk';
2500
- 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
+ });
2501
2514
  await ductape.events.produce({
2502
2515
  product: "my-product",
2503
2516
  env: "prd",
@@ -2512,14 +2525,26 @@ Import (register an EXISTING cloud resource):
2512
2525
  idempotencyTtl?: 86400, // seconds; default 86400 (24h)
2513
2526
  });
2514
2527
 
2515
- 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
+
2516
2541
  import { Events } from '@ductape/nestjs';
2517
2542
  @Injectable() export class OrdersService {
2518
2543
  // Immediate produce — method returns the message payload:
2519
2544
  @Events.Produce({ event: 'order-events:order-created' })
2520
2545
  emitOrderCreated(payload: { orderId: string; total: number }) { return payload; }
2521
2546
 
2522
- // Dispatch with static schedule (known at deploy time):
2547
+ // Dispatch with static schedule requires redisUrl in DuctapeModule initialization:
2523
2548
  @Events.Dispatch({ broker: 'order-events', event: 'order-events:reminder-due',
2524
2549
  schedule: { every: 86400000 } })
2525
2550
  scheduleReminder(payload: { message: { orderId: string } }) { return payload; }
@@ -2552,7 +2577,7 @@ Import (register an EXISTING cloud resource):
2552
2577
  });
2553
2578
 
2554
2579
  SCHEDULED DISPATCH (background job):
2555
- ductape_execute("messageBrokers.dispatch", [{
2580
+ ductape_execute("events.dispatch", [{
2556
2581
  product, env,
2557
2582
  broker: "order-events", // broker tag
2558
2583
  event: "order-events:reminder-due", // "broker:topic"
@@ -2623,24 +2648,24 @@ Import (register an EXISTING cloud resource):
2623
2648
 
2624
2649
  DEAD-LETTER QUEUE (DLQ):
2625
2650
  Messages whose callbacks consistently throw are automatically moved to the DLQ.
2626
- Query: ductape_execute("messageBrokers.messages.getDeadLetters",
2651
+ Query: ductape_execute("events.messages.getDeadLetters",
2627
2652
  [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }])
2628
- Reprocess: ductape_execute("messageBrokers.reprocessDLQ",
2653
+ Reprocess: ductape_execute("events.reprocessDLQ",
2629
2654
  [{ product, env, brokerTag, topicTag?, messageIds?, limit? }])
2630
- Replay: ductape_execute("messageBrokers.replayEvent",
2655
+ Replay: ductape_execute("events.replayEvent",
2631
2656
  [{ product, env, eventId, force? }])
2632
2657
 
2633
2658
  ━━━ OBSERVABILITY ━━━
2634
2659
 
2635
- messageBrokers.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
2636
- messageBrokers.messages.getStats [{ product, env, brokerTag }]
2637
- messageBrokers.messages.getDashboard [{ product, env, brokerTag }]
2638
- messageBrokers.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
2639
- messageBrokers.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2640
- messageBrokers.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2641
- messageBrokers.replayEvent [{ product, env, eventId, force? }]
2642
- messageBrokers.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
2643
- messageBrokers.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
2660
+ events.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
2661
+ events.messages.getStats [{ product, env, brokerTag }]
2662
+ events.messages.getDashboard [{ product, env, brokerTag }]
2663
+ events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
2664
+ events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2665
+ events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2666
+ events.replayEvent [{ product, env, eventId, force? }]
2667
+ events.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
2668
+ events.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
2644
2669
  `.trim(),
2645
2670
  logs: `
2646
2671
  DUCTAPE LOGS
@@ -3268,6 +3293,7 @@ async function loadMcpSdk() {
3268
3293
  console.error('Failed to load MCP SDK. Install: npm install @modelcontextprotocol/sdk zod\n' +
3269
3294
  'Or v2 alpha: npm install @modelcontextprotocol/server zod @cfworker/json-schema');
3270
3295
  process.exit(1);
3296
+ throw new Error('MCP SDK not available');
3271
3297
  }
3272
3298
  function handleCliFlags() {
3273
3299
  const arg = process.argv[2];
@@ -3368,7 +3394,9 @@ async function main() {
3368
3394
  if (!key) {
3369
3395
  throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
3370
3396
  }
3371
- const result = await executeViaProxy(key, args.module, args.method, args.params);
3397
+ // The TS SDK uses ductape.events.* for broker operations; the backend proxy uses messageBrokers.
3398
+ const proxyModule = args.module === 'events' ? 'messageBrokers' : args.module;
3399
+ const result = await executeViaProxy(key, proxyModule, args.method, args.params);
3372
3400
  return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
3373
3401
  }
3374
3402
  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.43",
3
+ "version": "0.1.45",
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
 
@@ -191,29 +191,33 @@ global interceptors, decorators, and type-safe resource handles.
191
191
 
192
192
  SETUP — register once in AppModule:
193
193
 
194
+ ╔══════════════════════════════════════════════════════════════════════════╗
195
+ ║ redisUrl IS REQUIRED TO USE ANY *.dispatch() ║
196
+ ║ ║
197
+ ║ Every dispatch() call (actions, features, events, databases, storage, ║
198
+ ║ graph, notifications, quotas, fallback) enqueues jobs ║
199
+ ║ via BullMQ over Redis. Without redisUrl the call throws at runtime: ║
200
+ ║ "Queues not configured. dispatch() requires a queue connection." ║
201
+ ║ ║
202
+ ║ *.run(), events.produce(), and @Events.Consumer do NOT need Redis. ║
203
+ ║ Only dispatch() does — and it is non-negotiable. ║
204
+ ╚══════════════════════════════════════════════════════════════════════════╝
205
+
194
206
  import { DuctapeModule } from '@ductape/nestjs';
195
207
 
196
208
  @Module({
197
209
  imports: [
198
210
  DuctapeModule.forIntegration({
199
211
  accessKey: process.env.DUCTAPE_ACCESS_KEY,
200
- product: 'my-product', // optional default — overridable per controller
212
+ product: 'my-product',
201
213
  env: process.env.NODE_ENV === 'production' ? 'prd' : 'snd',
202
- redisUrl: process.env.REDIS_URL, // REQUIRED when using events.dispatch() or @Events.Dispatch
214
+ redisUrl: process.env.DUCTAPE_REDIS_URL, // required no dispatch() works without this
203
215
  }),
204
216
  ],
205
217
  })
206
218
  export class AppModule {}
207
219
 
208
- IMPORTANT redisUrl:
209
- ALL *.dispatch() calls (actions.dispatch, features.dispatch, events.dispatch, databases.dispatch,
210
- storage.dispatch, graph.dispatch, notifications.dispatch, messageBrokers.dispatch, quotas.dispatch,
211
- fallback.dispatch, etc.) enqueue jobs via BullMQ, which requires a Redis connection.
212
- Without redisUrl ANY dispatch() call throws "Queues not configured. dispatch() requires a queue connection."
213
- Set REDIS_URL (e.g. redis://localhost:6379 or a managed Redis connection string) and pass it as redisUrl.
214
- *.run(), events.produce(), and @Events.Consumer do NOT require Redis — only dispatch() does.
215
-
216
- // Async (e.g. pulling key from ConfigService):
220
+ // Async (e.g. pulling from ConfigService):
217
221
  DuctapeModule.forRootAsync({
218
222
  imports: [ConfigModule],
219
223
  inject: [ConfigService],
@@ -221,10 +225,14 @@ SETUP — register once in AppModule:
221
225
  accessKey: cfg.get('DUCTAPE_ACCESS_KEY'),
222
226
  product: cfg.get('DUCTAPE_PRODUCT'),
223
227
  env: cfg.get('DUCTAPE_ENV'),
224
- redisUrl: cfg.get('REDIS_URL'), // required for dispatch
228
+ redisUrl: cfg.get('DUCTAPE_REDIS_URL'), // required no dispatch() works without this
225
229
  }),
226
230
  })
227
231
 
232
+ Environment variable (add to .env and deployment secrets):
233
+ DUCTAPE_REDIS_URL=redis://localhost:6379 # local dev
234
+ DUCTAPE_REDIS_URL=rediss://:<password>@host:6380 # managed Redis (TLS)
235
+
228
236
  INJECTING IN SERVICES AND CONTROLLERS — use @InjectContext():
229
237
 
230
238
  import { InjectContext, DuctapeContext } from '@ductape/nestjs';
@@ -279,7 +287,7 @@ FOR MESSAGING (events.produce / events.consume / events.dispatch):
279
287
  @Events.Produce({ event: 'broker-tag:topic-tag' })
280
288
  emitOrderCreated(payload: { orderId: string }) { return payload; }
281
289
 
282
- Dispatch (scheduled or immediate) — REQUIRES redisUrl in DuctapeModule.forIntegration:
290
+ Dispatch (scheduled or immediate) — dispatch() CANNOT be used without redisUrl in forIntegration:
283
291
  @Events.Dispatch({ broker: 'order-events', event: 'order-events:order-created', schedule: { every: 60000 } })
284
292
  dispatchHeartbeat(payload: { message: { ping: boolean } }) { return payload; }
285
293
 
@@ -354,7 +362,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
354
362
  actions.fetch [app_tag, action_tag]
355
363
  actions.list [app_tag]
356
364
  actions.run [{ product, env, app, action, input: { "body:fieldName": value, ... } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="run", targets={app, action})
357
- actions.dispatch [{ product, env, app, action, input, retries?, session?, cache?, schedule?: { start_at?, cron?, every?, limit?, tz? } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="dispatch")
365
+ actions.dispatch [{ product, env, app, action, input, retries?, session?, cache?, schedule?: { start_at?, cron?, every?, limit?, tz? } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="dispatch") — requires redisUrl in ductape initialization
358
366
 
359
367
  ━━━ MODULE: auths ━━━
360
368
  auths.create [app_tag, data: { tag: string, name: string, setup_type: "header"|"bearer"|"basic"|"oauth2"|"apikey", expiry: number, period: "seconds"|"minutes"|"hours"|"days", description: string, action_tag?: string }]
@@ -432,7 +440,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
432
440
  quotas.list [product_tag]
433
441
  quotas.delete [product_tag, quota_tag]
434
442
  quotas.run [{ product: string, env: string, tag: string, input: { fieldName: value }, session?: string, cache?: string }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="run", targets={tag})
435
- quotas.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="dispatch")
443
+ quotas.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="dispatch") — requires redisUrl in ductape initialization
436
444
 
437
445
  ━━━ MODULE: fallback ━━━
438
446
  fallback.create [product_tag, data: {
@@ -475,7 +483,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
475
483
  fallback.list [product_tag]
476
484
  fallback.delete [product_tag, fallback_tag]
477
485
  fallback.run [{ product: string, env: string, tag: string, input: { fieldName: value }, session?: string, cache?: string }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="run", targets={tag})
478
- fallback.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="dispatch")
486
+ fallback.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="dispatch") — requires redisUrl in ductape initialization
479
487
 
480
488
  ━━━ MODULE: health ━━━
481
489
  health.create [product_tag, data: { tag: string, name: string, description?: string, app?: string, event?: string, probe?: { type: "app"|"database"|"feature", app?: string, event?: string, input?: object }, interval: number, retries: number, envs: [{ slug: string, input?: object }], onFailure?: { notifications?: [{ notification: string, message: string, channels: { email?: { recipients: string[] }, push?: { recipients?: string[] }, sms?: { recipients: string[] } } }], webhooks?: [{ url: string, method?: "GET"|"POST", headers?: object, body?: object }] } }]
@@ -502,36 +510,36 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
502
510
  notifications.push.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="push.send")
503
511
  notifications.sms.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="sms.send")
504
512
  notifications.callback.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="callback.send")
505
- 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")
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
506
514
  notifications.getMessages [{ product_tag?, env?, notification_tag?, status?, type?, start_date?, end_date?, page?, limit? }]
507
515
 
508
- ━━━ MODULE: messageBrokers ━━━
509
- messageBrokers.create [{ product: string, tag: string, name: string, description?: string, type: "kafka"|"rabbitmq"|"redis"|"sqs", envs: [{ slug: string, connection_url: string }] }]
510
- messageBrokers.update [product_tag, broker_tag, data: { name?: string, description?: string, type?: "kafka"|"rabbitmq"|"redis"|"sqs", envs?: [{ slug: string, connection_url: string }] }]
511
- messageBrokers.fetch [product_tag, broker_tag]
512
- messageBrokers.list [product_tag]
513
- messageBrokers.delete [product_tag, broker_tag]
514
- 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:
515
523
  ductape_cli("events topics create -f topic.json")
516
524
  topic.json: { tag: "broker-tag:topic-tag", name, description?, sample?, idempotent?, queueUrls?: [{ env_slug, url }] }
517
525
  ← Always required before consuming. For SQS: must include queueUrls per env.
518
526
  ← For Pub/Sub, Kafka, RabbitMQ, Redis, NATS: the first produce call auto-registers the topic,
519
527
  but you should still create it explicitly so consumers can subscribe before any produce occurs.
520
- messageBrokers.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
528
+ events.topics.update ← FORBIDDEN with publishable key. Use ductape_cli:
521
529
  ductape_cli("events topics update --tag broker:topic -f patch.json")
522
- messageBrokers.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
530
+ events.topics.delete ← FORBIDDEN with publishable key. Use ductape_cli:
523
531
  ductape_cli("events topics delete --tag broker:topic")
524
- messageBrokers.topics.fetch [product_tag, topic_tag] ← safe via ductape_execute
525
- messageBrokers.topics.list [product_tag, broker_tag] ← safe via ductape_execute
526
- messageBrokers.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }]
527
- messageBrokers.consume [{ product, env, event: "broker_tag:topic_tag", callback: "function_ref" }]
528
- messageBrokers.dispatch [{ product, env, broker, event, input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }]
529
- messageBrokers.messages.query [{ product, env, brokerTag, topicTag?, producerTag?, consumerTag?, status?, startDate?, endDate?, page?, limit? }]
530
- messageBrokers.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
531
- messageBrokers.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
532
- messageBrokers.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, startDate?, endDate?, page?, limit? }]
533
- messageBrokers.messages.getStats [{ product, env, brokerTag }]
534
- 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, broker, event, input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] — 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 }]
535
543
 
536
544
  ━━━ MODULE: storage ━━━
537
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 } }] }]
@@ -551,7 +559,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
551
559
  storage.files.delete [{ product, env, storage, fileName }]
552
560
  storage.files.list [{ product, env, storage, prefix?, limit?, continuationToken? }]
553
561
  storage.files.getSignedUrl [{ product, env, storage, fileName, expiresIn?, action? }]
554
- storage.dispatch [{ product, env, storage, operation, input, retries?, session?, cache?, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="storage", method="dispatch", targets={storage})
562
+ storage.dispatch [{ product, env, storage, operation, input, retries?, session?, cache?, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="storage", method="dispatch", targets={storage}) — requires redisUrl in ductape initialization
555
563
 
556
564
  ━━━ MODULE: databases ━━━
557
565
  databases.create [{ product, tag, name, description?, type: "mongodb"|"postgresql"|"mysql"|"sqlite",
@@ -619,8 +627,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
619
627
  databases.action.fetch [action_tag]
620
628
  databases.action.list [database_tag]
621
629
  databases.action.delete [action_tag]
622
- databases.action.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"})
623
- databases.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"})
630
+ databases.action.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"}) — requires redisUrl in ductape initialization
631
+ databases.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database: "db_tag", table: "table_name"}) — requires redisUrl in ductape initialization
624
632
  databases.beginTransaction [{ product, env, database, isolationLevel?: "READ_COMMITTED"|"REPEATABLE_READ"|"SERIALIZABLE" }]
625
633
  → returns a transaction object; pass it to insert/update/delete/upsert/query calls as the last argument.
626
634
  Commit with: transaction.commit() Rollback with: transaction.rollback()
@@ -682,7 +690,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
682
690
  graph.beginTransaction [options?]
683
691
  graph.commitTransaction [transaction]
684
692
  graph.rollbackTransaction [transaction]
685
- graph.dispatch [data] ← CALL ductape_generate_payload FIRST (operation_family="graph", method="dispatch", targets={graph})
693
+ graph.dispatch [data] ← CALL ductape_generate_payload FIRST (operation_family="graph", method="dispatch", targets={graph}) — requires redisUrl in ductape initialization
686
694
 
687
695
  ━━━ MODULE: vector ━━━
688
696
  vector.create [{ product, tag, name, description?, provider: "pinecone"|"qdrant"|"weaviate", dimensions: number, metric?: "cosine"|"euclidean"|"dotproduct", envs: [{slug, api_key, environment?}] }]
@@ -802,7 +810,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
802
810
  timeout?: number
803
811
  }]
804
812
 
805
- features.dispatch [{ ← CALL ductape_generate_payload FIRST (operation_family="features", method="dispatch", targets={feature})
813
+ features.dispatch [{ ← CALL ductape_generate_payload FIRST (operation_family="features", method="dispatch", targets={feature}) — requires redisUrl in ductape initialization
806
814
  product: string,
807
815
  env: string,
808
816
  feature: string,
@@ -1054,10 +1062,10 @@ function resolveSdkCallPath(operationFamily: string, method: string): string {
1054
1062
  if (family === 'vector') return `vector.${m}`;
1055
1063
  if (family === 'storage') return m === 'dispatch' ? 'storage.dispatch' : `storage.${m}`;
1056
1064
  if (family === 'notification') return m === 'dispatch' ? 'notifications.dispatch' : `notifications.${m}`;
1057
- if (family === 'messaging' || family === 'broker') {
1058
- if (m === 'dispatch') return 'messageBrokers.dispatch';
1059
- if (m === 'send' || m === 'publish' || m === 'produce') return 'messageBrokers.produce';
1060
- 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}`;
1061
1069
  }
1062
1070
  if (family === 'quota') return `quotas.${m}`;
1063
1071
  if (family === 'fallback') return `fallback.${m}`;
@@ -2532,7 +2540,7 @@ Import (register an EXISTING cloud resource):
2532
2540
  Producing to a topic also calls ensureTopicRegistered in the background — but DO NOT rely on
2533
2541
  auto-registration for consume paths. Always create topics explicitly.
2534
2542
 
2535
- IMPORTANT: messageBrokers.topics.create requires an access key (admin operation).
2543
+ IMPORTANT: events.topics.create requires an access key (admin operation).
2536
2544
  Use ductape_cli — NOT ductape_execute — to create topics.
2537
2545
 
2538
2546
  Write a topic.json file, then:
@@ -2559,7 +2567,7 @@ Import (register an EXISTING cloud resource):
2559
2567
  ductape_cli("events topics delete --tag order-events:order-created")
2560
2568
 
2561
2569
  Read-only fetches (safe with publishable key via ductape_execute):
2562
- ductape_execute("messageBrokers.fetch", [product_tag, "broker-tag"]) → includes topics[]
2570
+ ductape_execute("events.fetch", [product_tag, "broker-tag"]) → includes topics[]
2563
2571
 
2564
2572
  ━━━ STEP 3: PRODUCE — WRITTEN IN APPLICATION CODE ━━━
2565
2573
 
@@ -2570,7 +2578,12 @@ Import (register an EXISTING cloud resource):
2570
2578
 
2571
2579
  GENERAL BACKEND (TypeScript/Node.js — not NestJS):
2572
2580
  import Ductape from '@ductape/sdk';
2573
- 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
+ });
2574
2587
  await ductape.events.produce({
2575
2588
  product: "my-product",
2576
2589
  env: "prd",
@@ -2585,14 +2598,26 @@ Import (register an EXISTING cloud resource):
2585
2598
  idempotencyTtl?: 86400, // seconds; default 86400 (24h)
2586
2599
  });
2587
2600
 
2588
- 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
+
2589
2614
  import { Events } from '@ductape/nestjs';
2590
2615
  @Injectable() export class OrdersService {
2591
2616
  // Immediate produce — method returns the message payload:
2592
2617
  @Events.Produce({ event: 'order-events:order-created' })
2593
2618
  emitOrderCreated(payload: { orderId: string; total: number }) { return payload; }
2594
2619
 
2595
- // Dispatch with static schedule (known at deploy time):
2620
+ // Dispatch with static schedule requires redisUrl in DuctapeModule initialization:
2596
2621
  @Events.Dispatch({ broker: 'order-events', event: 'order-events:reminder-due',
2597
2622
  schedule: { every: 86400000 } })
2598
2623
  scheduleReminder(payload: { message: { orderId: string } }) { return payload; }
@@ -2625,7 +2650,7 @@ Import (register an EXISTING cloud resource):
2625
2650
  });
2626
2651
 
2627
2652
  SCHEDULED DISPATCH (background job):
2628
- ductape_execute("messageBrokers.dispatch", [{
2653
+ ductape_execute("events.dispatch", [{
2629
2654
  product, env,
2630
2655
  broker: "order-events", // broker tag
2631
2656
  event: "order-events:reminder-due", // "broker:topic"
@@ -2696,24 +2721,24 @@ Import (register an EXISTING cloud resource):
2696
2721
 
2697
2722
  DEAD-LETTER QUEUE (DLQ):
2698
2723
  Messages whose callbacks consistently throw are automatically moved to the DLQ.
2699
- Query: ductape_execute("messageBrokers.messages.getDeadLetters",
2724
+ Query: ductape_execute("events.messages.getDeadLetters",
2700
2725
  [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }])
2701
- Reprocess: ductape_execute("messageBrokers.reprocessDLQ",
2726
+ Reprocess: ductape_execute("events.reprocessDLQ",
2702
2727
  [{ product, env, brokerTag, topicTag?, messageIds?, limit? }])
2703
- Replay: ductape_execute("messageBrokers.replayEvent",
2728
+ Replay: ductape_execute("events.replayEvent",
2704
2729
  [{ product, env, eventId, force? }])
2705
2730
 
2706
2731
  ━━━ OBSERVABILITY ━━━
2707
2732
 
2708
- messageBrokers.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
2709
- messageBrokers.messages.getStats [{ product, env, brokerTag }]
2710
- messageBrokers.messages.getDashboard [{ product, env, brokerTag }]
2711
- messageBrokers.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
2712
- messageBrokers.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2713
- messageBrokers.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2714
- messageBrokers.replayEvent [{ product, env, eventId, force? }]
2715
- messageBrokers.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
2716
- messageBrokers.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
2733
+ events.messages.query [{ product, env, brokerTag, topicTag?, status?, page?, limit? }]
2734
+ events.messages.getStats [{ product, env, brokerTag }]
2735
+ events.messages.getDashboard [{ product, env, brokerTag }]
2736
+ events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
2737
+ events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2738
+ events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
2739
+ events.replayEvent [{ product, env, eventId, force? }]
2740
+ events.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
2741
+ events.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
2717
2742
  `.trim(),
2718
2743
 
2719
2744
  logs: `
@@ -3355,6 +3380,7 @@ async function loadMcpSdk(): Promise<{
3355
3380
  'Or v2 alpha: npm install @modelcontextprotocol/server zod @cfworker/json-schema',
3356
3381
  );
3357
3382
  process.exit(1);
3383
+ throw new Error('MCP SDK not available');
3358
3384
  }
3359
3385
 
3360
3386
  function handleCliFlags(): boolean {
@@ -3466,7 +3492,9 @@ async function main() {
3466
3492
  if (!key) {
3467
3493
  throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
3468
3494
  }
3469
- const result = await executeViaProxy(key, args.module, args.method, args.params);
3495
+ // The TS SDK uses ductape.events.* for broker operations; the backend proxy uses messageBrokers.
3496
+ const proxyModule: SDKModule = args.module === 'events' ? 'messageBrokers' : args.module;
3497
+ const result = await executeViaProxy(key, proxyModule, args.method, args.params);
3470
3498
  return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
3471
3499
  } catch (err) {
3472
3500
  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'
package/tsconfig.json CHANGED
@@ -9,7 +9,8 @@
9
9
  "esModuleInterop": true,
10
10
  "skipLibCheck": true,
11
11
  "declaration": true,
12
- "declarationMap": true
12
+ "declarationMap": true,
13
+ "types": ["node"]
13
14
  },
14
15
  "include": ["src/**/*"],
15
16
  "exclude": ["node_modules"]