@ductape/mcp 0.1.57 → 0.1.59

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
@@ -123,8 +123,9 @@ There are THREE categories of operations. Use the right tool for each:
123
123
  ductape_cli("resources storage list")
124
124
  ductape_cli("resources database create -f db-config.json")
125
125
  This applies to: products, apps, and resources (databases, storage, caches, etc.),
126
- cloud connections, and secrets. Environments, app actions, auths, features, quotas,
127
- fallbacks, jobs, and healthchecks are configured in the Workbench UI.
126
+ cloud connections, and secrets. Environments, app actions, auths, quotas, fallbacks,
127
+ jobs, and healthchecks are configured in the Workbench UI. Features have no CLI create
128
+ command because their definitions are code-first through features.define.
128
129
 
129
130
  ⚠ MULTI-ENV REQUIREMENT — applies to ALL product assets (storage, database, cache,
130
131
  messageBroker, graph, vector, and any other resource with an envs array):
@@ -323,6 +324,9 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
323
324
  IMPORTANT: ALL product.* methods require the access key and will return 403 with a publishable key.
324
325
  Use ductape_cli for ALL product operations — never ductape_execute:
325
326
  ductape_cli("products get --tag <tag> --json") ← fetch product + full inventory
327
+ ductape_cli("products components list --tag <tag> --json") ← compact non-secret inventory
328
+ ductape_cli("products components get --tag <tag> --type notifications --json")
329
+ ductape_cli("products components get --tag <tag> --type events --json")
326
330
  ductape_cli("products create --name <name> --tag <tag>")
327
331
  ductape_cli("products environments list <tag> --json")
328
332
  ductape_cli("products environments get <tag> <slug> --json")
@@ -490,8 +494,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
490
494
  notifications.fetch [product_tag, notif_tag]
491
495
  notifications.list [product_tag]
492
496
  notifications.delete [product_tag, notif_tag]
493
- notifications.messages.create [product_tag, data: { tag: string, notification: string, subject?: { template: string, data: object }, body?: { template: string, data: object } }]
494
- notifications.messages.update [product_tag, msg_tag, data: { subject?: { template: string, data: object }, body?: { template: string, data: object } }]
497
+ notifications.messages.create [product_tag, data: { tag: string, name: string, description?: string, push_notification?: { title: string, body: string, data?: object }, email?: { subject: string, template: string }, callback?: object, sms?: string }]
498
+ notifications.messages.update [product_tag, msg_tag, data: { name?: string, description?: string, push_notification?: { title: string, body: string, data?: object }, email?: { subject: string, template: string }, callback?: object, sms?: string }]
495
499
  notifications.messages.fetch [product_tag, msg_tag]
496
500
  notifications.messages.list [product_tag, notification_tag]
497
501
  notifications.send [{ product, env, event, input: { ... } }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="send", targets={notification})
@@ -783,7 +787,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
783
787
  // ctx.graph.execute({ graph, action, input })
784
788
  // ctx.notification.send/email/push/sms({ notification, event, ... })
785
789
  // ctx.storage.upload/download({ storage, event, input })
786
- // ctx.messaging.produce({ event: "broker:topic", message: {} })
790
+ // ctx.events.produce({ event: "broker:topic", message: {} })
787
791
  // ctx.quota.execute({ quota, input })
788
792
  // ctx.fallback.execute({ fallback, input })
789
793
  // ctx.healthcheck.getStatus(tag)
@@ -1163,6 +1167,7 @@ const ADMIN_SUBCOMMANDS = [
1163
1167
  'link', 'unlink', 'init',
1164
1168
  'products', 'apps',
1165
1169
  'resources',
1170
+ 'notifications',
1166
1171
  'events',
1167
1172
  'cloud',
1168
1173
  'secrets',
@@ -1227,7 +1232,9 @@ function runCli(command) {
1227
1232
  try {
1228
1233
  const output = execSync(`ductape ${finalCommand}`, {
1229
1234
  encoding: 'utf8',
1230
- timeout: 30000,
1235
+ // Must exceed the proxy's operation timeout so stderr can preserve the structured timeout
1236
+ // instead of this wrapper killing the CLI first and reducing it to "(no data)".
1237
+ timeout: 90000,
1231
1238
  stdio: ['pipe', 'pipe', 'pipe'],
1232
1239
  });
1233
1240
  return { success: true, output: output.trim() };
@@ -2181,22 +2188,54 @@ ADMINISTRATION — CLI (never ductape_execute)
2181
2188
  ductape_cli("notifications messages get --tag <notification:message> --json")
2182
2189
  ductape_cli("notifications messages update --tag <notification:message> -f patch.json")
2183
2190
  Declarative alternative: ductape/notifications.json then ductape_cli("apply notifications").
2191
+ The file MUST be a top-level JSON array. Each item is a notification definition and may contain
2192
+ a nested "messages" array. {"notifications":[],"messages":[]} is not a valid envelope.
2184
2193
 
2185
2194
  Notification definition:
2186
2195
  {
2187
- tag: "welcome-email",
2196
+ tag: "welcome-email", // component tags cannot contain ":"
2188
2197
  name: "Welcome Email",
2189
- type: "email", // optional hint; actual channels configured per env
2198
+ description: "Transactional welcome messages",
2199
+ envs: [{
2200
+ slug: "prd",
2201
+ emails: {
2202
+ provider: "smtp",
2203
+ smtp: {
2204
+ host: "smtp.example.com",
2205
+ port: "$Secret{smtp-port}",
2206
+ sender_email: "hello@example.com",
2207
+ auth: { user: "$Secret{smtp-user}", pass: "$Secret{smtp-password}" },
2208
+ secure: true
2209
+ }
2210
+ }
2211
+ }],
2212
+ messages: []
2190
2213
  }
2191
2214
 
2192
2215
  Create a message template through declarative apply or Workbench:
2193
2216
  {
2194
- tag: "welcome-email:default", // format: "notification_tag:message_tag"
2195
- notification: "welcome-email",
2196
- subject: { template: "Welcome, {{name}}!", data: { name: "" } },
2197
- body: { template: "Hi {{name}}, thanks for signing up.", data: { name: "" } },
2217
+ tag: "welcome-email:default", // message tags use component-tag:message-tag
2218
+ name: "Default welcome",
2219
+ description: "Sent after account creation",
2220
+ push_notification: {
2221
+ title: "Welcome, {{name}}!",
2222
+ body: "Thanks for signing up.",
2223
+ data: {}
2224
+ },
2225
+ email: {
2226
+ subject: "Welcome, {{name}}!",
2227
+ template: "Hi {{name}}, thanks for signing up."
2228
+ }
2198
2229
  }
2199
2230
 
2231
+ TAG AND SMTP RULES
2232
+ Component tag: "game-alerts-critical" (no colon).
2233
+ Message tag: "game-alerts-critical:match-launch" (one colon separator).
2234
+ Do not add "notification", "subject", or "body" at message root.
2235
+ SMTP requires emails.smtp.sender_email.
2236
+ emails.smtp.secure is a boolean and cannot be a $Secret{...} string.
2237
+ Credential strings such as auth.user and auth.pass may use $Secret{...}.
2238
+
2200
2239
  Send at runtime (one channel at a time):
2201
2240
  → CALL ductape_generate_payload FIRST (operation_family="notification", method="email.send")
2202
2241
  notifications.email.send [{ product, env, notification, input: { recipients, subject?, template? } }]
@@ -2244,6 +2283,10 @@ FIREBASE THROUGH A GCP CLOUD CONNECTION
2244
2283
  }
2245
2284
  The SDK requests current GCP credentials from the cloud connection at send time. Never put the
2246
2285
  service-account private key in the notification file. Expo does not use a GCP cloud connection.
2286
+ The recommended cloud connection scope is "notifications". It expresses Ductape capability/UI
2287
+ intent; creating a notification does not prove Google-side FCM permission. Runtime delivery
2288
+ requires fcm.googleapis.com and roles/firebasecloudmessaging.admin, so validate the connection
2289
+ and perform a delivery test.
2247
2290
  Notification tag and message tag are ALWAYS passed together as "notification_tag:message_tag".
2248
2291
  `.trim(),
2249
2292
  resilience: `
@@ -2610,9 +2653,11 @@ STEP 4 — PRESENT the plan and get approval BEFORE writing any code or creating
2610
2653
  STEP 5 — CREATE missing components (only with user approval)
2611
2654
  Administrative assets must never be created through ductape_execute. Use ductape_cli for products,
2612
2655
  apps, supported resources, broker topics, cloud connections, secrets, and declarative apply flows.
2613
- App actions, auths, Features, quotas, fallbacks, health checks, and other assets for which the CLI
2614
- has no command must be configured in Workbench. Do not generate an impossible publishable-key
2615
- create/update call. For a missing database action, configure it in Workbench, then verify it exists.
2656
+ App actions, auths, quotas, fallbacks, health checks, and other administrative assets for which
2657
+ the CLI has no command must be configured in Workbench. Feature definitions are the exception:
2658
+ they are code-first via features.define, are registered by application boot/runtime initialization,
2659
+ and cannot be created with the CLI. Do not generate a Workbench-only or publishable-key
2660
+ create/update call for a Feature. For a missing database action, configure it in Workbench, then verify it exists.
2616
2661
  For a missing child feature, recursively apply this same workflow.
2617
2662
  Tell the user what you are about to create before each tool call.
2618
2663
 
@@ -2721,7 +2766,9 @@ When you call features.define({ handler }), the handler runs TWICE:
2721
2766
  Features do NOT execute arbitrary NestJS or server code directly. A feature handler can only
2722
2767
  call Ductape component primitives (ctx.api, ctx.database, ctx.notification, etc.) as steps.
2723
2768
  To invoke internal application business logic, produce a broker event from a feature step
2724
- (ctx.messaging.produce) and consume it in your NestJS service — that is the correct pattern.
2769
+ (ctx.events.produce in the currently published SDK) and consume it in your NestJS service.
2770
+ ctx.publish is deprecated; do not use it. Do not assume a ctx.messaging alias exists unless the
2771
+ installed SDK types explicitly expose it.
2725
2772
 
2726
2773
  ━━━ ORCHESTRATION DECISION RULE ━━━
2727
2774
 
@@ -2735,7 +2782,7 @@ When you call features.define({ handler }), the handler runs TWICE:
2735
2782
  → define a Feature, then features.dispatch to schedule it
2736
2783
 
2737
2784
  Invoke internal application business logic (your own NestJS/backend service code):
2738
- → produce a broker event (ctx.messaging.produce or ductape.events.produce)
2785
+ → produce a broker event (ctx.events.produce inside a Feature, or ductape.events.produce outside it)
2739
2786
  → follow ductape_docs({ topic: "events" }) and use the canonical NestJS decorator:
2740
2787
  @Events.Consumer({ event: "broker-tag:topic-tag" })
2741
2788
  async handle(message: MessageShape) { /* injected-service business logic; throw to nack */ }
@@ -3670,8 +3717,9 @@ const cliInputSchema = z.object({
3670
3717
  'Use this tool for administrative operations: creating or updating products, apps, ' +
3671
3718
  'resources (databases, storage, caches…), event broker topics, cloud connections, secrets, ' +
3672
3719
  'and for apply/migrate workflows.\n\n' +
3673
- 'Note: environments, app actions, features, quotas, fallbacks, and jobs are configured ' +
3674
- 'in the Workbench UI there are no CLI commands for them.\n\n' +
3720
+ 'Note: environments, app actions, quotas, fallbacks, and jobs are configured in the ' +
3721
+ 'Workbench UI. Features also have no CLI creation command: define them in application code ' +
3722
+ 'with features.define so application boot/runtime registration makes them available.\n\n' +
3675
3723
  'The CLI uses the user\'s local logged-in session (ductape login) — no key is required.'),
3676
3724
  });
3677
3725
  async function loadMcpSdk() {
@@ -3753,8 +3801,10 @@ async function main() {
3753
3801
  const firstWord = args.command.trim().split(/\s+/)[0];
3754
3802
  const isAuthCommand = firstWord === 'login' || firstWord === 'logout';
3755
3803
  if (!isAuthCommand) {
3756
- // Check login status once per process (cached after first call)
3757
- if (authState === 'unknown') {
3804
+ // Cache successful authentication, but re-check a missing/expired session on every call.
3805
+ // The user may complete `ductape login` in another terminal while this MCP process remains
3806
+ // alive; caching "none" would otherwise make the MCP blind to the newly written session.
3807
+ if (authState === 'unknown' || authState === 'none') {
3758
3808
  checkLoginState();
3759
3809
  }
3760
3810
  if (authState === 'none') {
@@ -4062,12 +4112,16 @@ async function main() {
4062
4112
  ' Message brokers are import-only (no provision-persist). Import flow is the same as storage.\n' +
4063
4113
  ' type field = "messageBrokers" (not "messagebrokers" or "events").\n' +
4064
4114
  ' After importing, create topics first with ductape_cli("events topics create -f topic.json") — SQS requires explicit topic creation with queueUrls. For other providers, topics auto-register on first produce but should still be created explicitly before any consumer subscribes.\n' +
4065
- ' - Listing workspaces, products, secrets\n' +
4115
+ ' - Listing workspaces, products, focused product components, secrets\n' +
4116
+ ' Prefer "products components list --tag <tag> --json" for compact inventory; use\n' +
4117
+ ' "products components get --tag <tag> --type notifications|events --json" for focused detail.\n' +
4118
+ ' - Managing notification components and message templates through "resources notifications" and "notifications messages"\n' +
4066
4119
  ' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
4067
4120
  ' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
4068
4121
  ' - Running database migrations: "db migrate", "db schema generate"\n\n' +
4069
- 'NOTE: Environments, app actions, auths, features, quotas, fallbacks, and jobs are ' +
4070
- 'configured in the Workbench UI the CLI does not have commands for them.\n\n' +
4122
+ 'NOTE: Environments, app actions, auths, quotas, fallbacks, and jobs are configured ' +
4123
+ 'in the Workbench UI. Features have no CLI creation command because definitions are ' +
4124
+ 'code-first through features.define and registered by the application runtime.\n\n' +
4071
4125
  'DO NOT use ductape_execute for admin operations — it uses a publishable key which only ' +
4072
4126
  'covers runtime operations. Administrative operations will fail with "Authentication failed".\n\n' +
4073
4127
  'The CLI uses the user\'s local logged-in session (ductape login). ' +
@@ -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,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,iBAAiB,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACpD,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,iBAAiB,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACpD,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,CA8BZ"}
@@ -67,9 +67,15 @@ function normalizeTargets(targets) {
67
67
  return result;
68
68
  }
69
69
  export async function generateExecutablePayload(request) {
70
+ // execution_context is MCP guidance metadata, not part of the integrations
71
+ // payload-generator API contract. Keep it for local session-awareness output
72
+ // but never forward it to the backend validator.
73
+ const { execution_context: _executionContext, ...backendRequest } = request;
70
74
  const normalizedRequest = {
71
- ...request,
72
- targets: request.targets ? normalizeTargets(request.targets) : request.targets,
75
+ ...backendRequest,
76
+ targets: backendRequest.targets
77
+ ? normalizeTargets(backendRequest.targets)
78
+ : backendRequest.targets,
73
79
  };
74
80
  const url = `${API_BASE_URL.replace(/\/$/, '')}/integrations/v1/payloads/generate`;
75
81
  const res = await fetch(url, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.1.57",
3
+ "version": "0.1.59",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -51,6 +51,13 @@ const safetyChecks = [
51
51
  ['CLI distinguishes endpoint 401 from expired login', /session and active workspace are authenticated[\s\S]*Do not ask the user to log in again/],
52
52
  ['CLI auth guidance does not request secrets', /Never ask the user to paste their password/],
53
53
  ['notification CLI administration documented', /resources notifications create[\s\S]*notifications messages create/],
54
+ ['notification command allowed by MCP', /ADMIN_SUBCOMMANDS[\s\S]*'notifications'/],
55
+ ['notification template uses SDK shape', /push_notification\?: \{ title: string, body: string[\s\S]*email\?: \{ subject: string, template: string/],
56
+ ['notification declarations require array shape', /file MUST be a top-level JSON array/],
57
+ ['external CLI login is re-read', /authState === 'unknown' \|\| authState === 'none'/],
58
+ ['compact product inventory documented', /products components list --tag <tag> --json/],
59
+ ['features use installed events context', /ctx\.events\.produce inside a Feature/],
60
+ ['features are code-first rather than Workbench-created', /Features have no CLI (?:create|creation)\s+command because (?:their )?definitions are code-first/],
54
61
  ['Firebase GCP cloud connection documented', /FIREBASE THROUGH A GCP CLOUD CONNECTION[\s\S]*authMode.*cloud_connection/],
55
62
  ['Slack and Discord sends documented', /notifications\.slack\.send[\s\S]*notifications\.discord\.send/],
56
63
  ];
@@ -72,4 +79,10 @@ assert.doesNotMatch(
72
79
  'Browser Events guidance must not import the server SDK',
73
80
  );
74
81
 
82
+ assert.doesNotMatch(
83
+ source,
84
+ /ctx\.messaging\.produce/,
85
+ 'Feature guidance must use the installed ctx.events API, not a nonexistent ctx.messaging alias',
86
+ );
87
+
75
88
  console.log(`MCP guidance: ${checks.length + 1 + safetyChecks.length} acceptance checks passed`);
package/src/index.ts CHANGED
@@ -134,8 +134,9 @@ There are THREE categories of operations. Use the right tool for each:
134
134
  ductape_cli("resources storage list")
135
135
  ductape_cli("resources database create -f db-config.json")
136
136
  This applies to: products, apps, and resources (databases, storage, caches, etc.),
137
- cloud connections, and secrets. Environments, app actions, auths, features, quotas,
138
- fallbacks, jobs, and healthchecks are configured in the Workbench UI.
137
+ cloud connections, and secrets. Environments, app actions, auths, quotas, fallbacks,
138
+ jobs, and healthchecks are configured in the Workbench UI. Features have no CLI create
139
+ command because their definitions are code-first through features.define.
139
140
 
140
141
  ⚠ MULTI-ENV REQUIREMENT — applies to ALL product assets (storage, database, cache,
141
142
  messageBroker, graph, vector, and any other resource with an envs array):
@@ -334,6 +335,9 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
334
335
  IMPORTANT: ALL product.* methods require the access key and will return 403 with a publishable key.
335
336
  Use ductape_cli for ALL product operations — never ductape_execute:
336
337
  ductape_cli("products get --tag <tag> --json") ← fetch product + full inventory
338
+ ductape_cli("products components list --tag <tag> --json") ← compact non-secret inventory
339
+ ductape_cli("products components get --tag <tag> --type notifications --json")
340
+ ductape_cli("products components get --tag <tag> --type events --json")
337
341
  ductape_cli("products create --name <name> --tag <tag>")
338
342
  ductape_cli("products environments list <tag> --json")
339
343
  ductape_cli("products environments get <tag> <slug> --json")
@@ -501,8 +505,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
501
505
  notifications.fetch [product_tag, notif_tag]
502
506
  notifications.list [product_tag]
503
507
  notifications.delete [product_tag, notif_tag]
504
- notifications.messages.create [product_tag, data: { tag: string, notification: string, subject?: { template: string, data: object }, body?: { template: string, data: object } }]
505
- notifications.messages.update [product_tag, msg_tag, data: { subject?: { template: string, data: object }, body?: { template: string, data: object } }]
508
+ notifications.messages.create [product_tag, data: { tag: string, name: string, description?: string, push_notification?: { title: string, body: string, data?: object }, email?: { subject: string, template: string }, callback?: object, sms?: string }]
509
+ notifications.messages.update [product_tag, msg_tag, data: { name?: string, description?: string, push_notification?: { title: string, body: string, data?: object }, email?: { subject: string, template: string }, callback?: object, sms?: string }]
506
510
  notifications.messages.fetch [product_tag, msg_tag]
507
511
  notifications.messages.list [product_tag, notification_tag]
508
512
  notifications.send [{ product, env, event, input: { ... } }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="send", targets={notification})
@@ -794,7 +798,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
794
798
  // ctx.graph.execute({ graph, action, input })
795
799
  // ctx.notification.send/email/push/sms({ notification, event, ... })
796
800
  // ctx.storage.upload/download({ storage, event, input })
797
- // ctx.messaging.produce({ event: "broker:topic", message: {} })
801
+ // ctx.events.produce({ event: "broker:topic", message: {} })
798
802
  // ctx.quota.execute({ quota, input })
799
803
  // ctx.fallback.execute({ fallback, input })
800
804
  // ctx.healthcheck.getStatus(tag)
@@ -1224,6 +1228,7 @@ const ADMIN_SUBCOMMANDS = [
1224
1228
  'link', 'unlink', 'init',
1225
1229
  'products', 'apps',
1226
1230
  'resources',
1231
+ 'notifications',
1227
1232
  'events',
1228
1233
  'cloud',
1229
1234
  'secrets',
@@ -1288,7 +1293,9 @@ function runCli(command: string): { success: boolean; output: string } {
1288
1293
  try {
1289
1294
  const output = execSync(`ductape ${finalCommand}`, {
1290
1295
  encoding: 'utf8',
1291
- timeout: 30000,
1296
+ // Must exceed the proxy's operation timeout so stderr can preserve the structured timeout
1297
+ // instead of this wrapper killing the CLI first and reducing it to "(no data)".
1298
+ timeout: 90000,
1292
1299
  stdio: ['pipe', 'pipe', 'pipe'],
1293
1300
  });
1294
1301
  return { success: true, output: output.trim() };
@@ -2264,22 +2271,54 @@ ADMINISTRATION — CLI (never ductape_execute)
2264
2271
  ductape_cli("notifications messages get --tag <notification:message> --json")
2265
2272
  ductape_cli("notifications messages update --tag <notification:message> -f patch.json")
2266
2273
  Declarative alternative: ductape/notifications.json then ductape_cli("apply notifications").
2274
+ The file MUST be a top-level JSON array. Each item is a notification definition and may contain
2275
+ a nested "messages" array. {"notifications":[],"messages":[]} is not a valid envelope.
2267
2276
 
2268
2277
  Notification definition:
2269
2278
  {
2270
- tag: "welcome-email",
2279
+ tag: "welcome-email", // component tags cannot contain ":"
2271
2280
  name: "Welcome Email",
2272
- type: "email", // optional hint; actual channels configured per env
2281
+ description: "Transactional welcome messages",
2282
+ envs: [{
2283
+ slug: "prd",
2284
+ emails: {
2285
+ provider: "smtp",
2286
+ smtp: {
2287
+ host: "smtp.example.com",
2288
+ port: "$Secret{smtp-port}",
2289
+ sender_email: "hello@example.com",
2290
+ auth: { user: "$Secret{smtp-user}", pass: "$Secret{smtp-password}" },
2291
+ secure: true
2292
+ }
2293
+ }
2294
+ }],
2295
+ messages: []
2273
2296
  }
2274
2297
 
2275
2298
  Create a message template through declarative apply or Workbench:
2276
2299
  {
2277
- tag: "welcome-email:default", // format: "notification_tag:message_tag"
2278
- notification: "welcome-email",
2279
- subject: { template: "Welcome, {{name}}!", data: { name: "" } },
2280
- body: { template: "Hi {{name}}, thanks for signing up.", data: { name: "" } },
2300
+ tag: "welcome-email:default", // message tags use component-tag:message-tag
2301
+ name: "Default welcome",
2302
+ description: "Sent after account creation",
2303
+ push_notification: {
2304
+ title: "Welcome, {{name}}!",
2305
+ body: "Thanks for signing up.",
2306
+ data: {}
2307
+ },
2308
+ email: {
2309
+ subject: "Welcome, {{name}}!",
2310
+ template: "Hi {{name}}, thanks for signing up."
2311
+ }
2281
2312
  }
2282
2313
 
2314
+ TAG AND SMTP RULES
2315
+ Component tag: "game-alerts-critical" (no colon).
2316
+ Message tag: "game-alerts-critical:match-launch" (one colon separator).
2317
+ Do not add "notification", "subject", or "body" at message root.
2318
+ SMTP requires emails.smtp.sender_email.
2319
+ emails.smtp.secure is a boolean and cannot be a $Secret{...} string.
2320
+ Credential strings such as auth.user and auth.pass may use $Secret{...}.
2321
+
2283
2322
  Send at runtime (one channel at a time):
2284
2323
  → CALL ductape_generate_payload FIRST (operation_family="notification", method="email.send")
2285
2324
  notifications.email.send [{ product, env, notification, input: { recipients, subject?, template? } }]
@@ -2327,6 +2366,10 @@ FIREBASE THROUGH A GCP CLOUD CONNECTION
2327
2366
  }
2328
2367
  The SDK requests current GCP credentials from the cloud connection at send time. Never put the
2329
2368
  service-account private key in the notification file. Expo does not use a GCP cloud connection.
2369
+ The recommended cloud connection scope is "notifications". It expresses Ductape capability/UI
2370
+ intent; creating a notification does not prove Google-side FCM permission. Runtime delivery
2371
+ requires fcm.googleapis.com and roles/firebasecloudmessaging.admin, so validate the connection
2372
+ and perform a delivery test.
2330
2373
  Notification tag and message tag are ALWAYS passed together as "notification_tag:message_tag".
2331
2374
  `.trim(),
2332
2375
 
@@ -2696,9 +2739,11 @@ STEP 4 — PRESENT the plan and get approval BEFORE writing any code or creating
2696
2739
  STEP 5 — CREATE missing components (only with user approval)
2697
2740
  Administrative assets must never be created through ductape_execute. Use ductape_cli for products,
2698
2741
  apps, supported resources, broker topics, cloud connections, secrets, and declarative apply flows.
2699
- App actions, auths, Features, quotas, fallbacks, health checks, and other assets for which the CLI
2700
- has no command must be configured in Workbench. Do not generate an impossible publishable-key
2701
- create/update call. For a missing database action, configure it in Workbench, then verify it exists.
2742
+ App actions, auths, quotas, fallbacks, health checks, and other administrative assets for which
2743
+ the CLI has no command must be configured in Workbench. Feature definitions are the exception:
2744
+ they are code-first via features.define, are registered by application boot/runtime initialization,
2745
+ and cannot be created with the CLI. Do not generate a Workbench-only or publishable-key
2746
+ create/update call for a Feature. For a missing database action, configure it in Workbench, then verify it exists.
2702
2747
  For a missing child feature, recursively apply this same workflow.
2703
2748
  Tell the user what you are about to create before each tool call.
2704
2749
 
@@ -2807,7 +2852,9 @@ When you call features.define({ handler }), the handler runs TWICE:
2807
2852
  Features do NOT execute arbitrary NestJS or server code directly. A feature handler can only
2808
2853
  call Ductape component primitives (ctx.api, ctx.database, ctx.notification, etc.) as steps.
2809
2854
  To invoke internal application business logic, produce a broker event from a feature step
2810
- (ctx.messaging.produce) and consume it in your NestJS service — that is the correct pattern.
2855
+ (ctx.events.produce in the currently published SDK) and consume it in your NestJS service.
2856
+ ctx.publish is deprecated; do not use it. Do not assume a ctx.messaging alias exists unless the
2857
+ installed SDK types explicitly expose it.
2811
2858
 
2812
2859
  ━━━ ORCHESTRATION DECISION RULE ━━━
2813
2860
 
@@ -2821,7 +2868,7 @@ When you call features.define({ handler }), the handler runs TWICE:
2821
2868
  → define a Feature, then features.dispatch to schedule it
2822
2869
 
2823
2870
  Invoke internal application business logic (your own NestJS/backend service code):
2824
- → produce a broker event (ctx.messaging.produce or ductape.events.produce)
2871
+ → produce a broker event (ctx.events.produce inside a Feature, or ductape.events.produce outside it)
2825
2872
  → follow ductape_docs({ topic: "events" }) and use the canonical NestJS decorator:
2826
2873
  @Events.Consumer({ event: "broker-tag:topic-tag" })
2827
2874
  async handle(message: MessageShape) { /* injected-service business logic; throw to nack */ }
@@ -3764,8 +3811,9 @@ const cliInputSchema = z.object({
3764
3811
  'Use this tool for administrative operations: creating or updating products, apps, ' +
3765
3812
  'resources (databases, storage, caches…), event broker topics, cloud connections, secrets, ' +
3766
3813
  'and for apply/migrate workflows.\n\n' +
3767
- 'Note: environments, app actions, features, quotas, fallbacks, and jobs are configured ' +
3768
- 'in the Workbench UI there are no CLI commands for them.\n\n' +
3814
+ 'Note: environments, app actions, quotas, fallbacks, and jobs are configured in the ' +
3815
+ 'Workbench UI. Features also have no CLI creation command: define them in application code ' +
3816
+ 'with features.define so application boot/runtime registration makes them available.\n\n' +
3769
3817
  'The CLI uses the user\'s local logged-in session (ductape login) — no key is required.',
3770
3818
  ),
3771
3819
  });
@@ -3861,8 +3909,10 @@ async function main() {
3861
3909
  const isAuthCommand = firstWord === 'login' || firstWord === 'logout';
3862
3910
 
3863
3911
  if (!isAuthCommand) {
3864
- // Check login status once per process (cached after first call)
3865
- if (authState === 'unknown') {
3912
+ // Cache successful authentication, but re-check a missing/expired session on every call.
3913
+ // The user may complete `ductape login` in another terminal while this MCP process remains
3914
+ // alive; caching "none" would otherwise make the MCP blind to the newly written session.
3915
+ if (authState === 'unknown' || authState === 'none') {
3866
3916
  checkLoginState();
3867
3917
  }
3868
3918
  if (authState === 'none') {
@@ -4223,12 +4273,16 @@ async function main() {
4223
4273
  ' Message brokers are import-only (no provision-persist). Import flow is the same as storage.\n' +
4224
4274
  ' type field = "messageBrokers" (not "messagebrokers" or "events").\n' +
4225
4275
  ' After importing, create topics first with ductape_cli("events topics create -f topic.json") — SQS requires explicit topic creation with queueUrls. For other providers, topics auto-register on first produce but should still be created explicitly before any consumer subscribes.\n' +
4226
- ' - Listing workspaces, products, secrets\n' +
4276
+ ' - Listing workspaces, products, focused product components, secrets\n' +
4277
+ ' Prefer "products components list --tag <tag> --json" for compact inventory; use\n' +
4278
+ ' "products components get --tag <tag> --type notifications|events --json" for focused detail.\n' +
4279
+ ' - Managing notification components and message templates through "resources notifications" and "notifications messages"\n' +
4227
4280
  ' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
4228
4281
  ' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
4229
4282
  ' - Running database migrations: "db migrate", "db schema generate"\n\n' +
4230
- 'NOTE: Environments, app actions, auths, features, quotas, fallbacks, and jobs are ' +
4231
- 'configured in the Workbench UI the CLI does not have commands for them.\n\n' +
4283
+ 'NOTE: Environments, app actions, auths, quotas, fallbacks, and jobs are configured ' +
4284
+ 'in the Workbench UI. Features have no CLI creation command because definitions are ' +
4285
+ 'code-first through features.define and registered by the application runtime.\n\n' +
4232
4286
  'DO NOT use ductape_execute for admin operations — it uses a publishable key which only ' +
4233
4287
  'covers runtime operations. Administrative operations will fail with "Authentication failed".\n\n' +
4234
4288
  'The CLI uses the user\'s local logged-in session (ductape login). ' +
@@ -137,9 +137,15 @@ function normalizeTargets(targets: Record<string, unknown>): Record<string, unkn
137
137
  export async function generateExecutablePayload<T = IGenerateExecutablePayloadResponse>(
138
138
  request: IGenerateExecutablePayloadRequest,
139
139
  ): Promise<T> {
140
+ // execution_context is MCP guidance metadata, not part of the integrations
141
+ // payload-generator API contract. Keep it for local session-awareness output
142
+ // but never forward it to the backend validator.
143
+ const { execution_context: _executionContext, ...backendRequest } = request;
140
144
  const normalizedRequest = {
141
- ...request,
142
- targets: request.targets ? normalizeTargets(request.targets as Record<string, unknown>) : request.targets,
145
+ ...backendRequest,
146
+ targets: backendRequest.targets
147
+ ? normalizeTargets(backendRequest.targets as Record<string, unknown>)
148
+ : backendRequest.targets,
143
149
  };
144
150
  const url = `${API_BASE_URL.replace(/\/$/, '')}/integrations/v1/payloads/generate`;
145
151
  const res = await fetch(url, {