@ductape/mcp 0.1.56 → 0.1.58

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
@@ -323,6 +323,9 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
323
323
  IMPORTANT: ALL product.* methods require the access key and will return 403 with a publishable key.
324
324
  Use ductape_cli for ALL product operations — never ductape_execute:
325
325
  ductape_cli("products get --tag <tag> --json") ← fetch product + full inventory
326
+ ductape_cli("products components list --tag <tag> --json") ← compact non-secret inventory
327
+ ductape_cli("products components get --tag <tag> --type notifications --json")
328
+ ductape_cli("products components get --tag <tag> --type events --json")
326
329
  ductape_cli("products create --name <name> --tag <tag>")
327
330
  ductape_cli("products environments list <tag> --json")
328
331
  ductape_cli("products environments get <tag> <slug> --json")
@@ -490,8 +493,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
490
493
  notifications.fetch [product_tag, notif_tag]
491
494
  notifications.list [product_tag]
492
495
  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 } }]
496
+ 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 }]
497
+ 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
498
  notifications.messages.fetch [product_tag, msg_tag]
496
499
  notifications.messages.list [product_tag, notification_tag]
497
500
  notifications.send [{ product, env, event, input: { ... } }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="send", targets={notification})
@@ -499,6 +502,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
499
502
  notifications.push.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="push.send")
500
503
  notifications.sms.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="sms.send")
501
504
  notifications.callback.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="callback.send")
505
+ notifications.slack.send [{ product, env, notification, input: { text?, blocks?, channel? }, session?, cache? }]
506
+ notifications.discord.send [{ product, env, notification, input: { content?, embeds? }, session?, cache? }]
502
507
  notifications.dispatch [{ product, env, notification, event, input, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="dispatch") — requires redisUrl in ductape initialization
503
508
  notifications.getMessages [{ product_tag?, env?, notification_tag?, status?, type?, start_date?, end_date?, page?, limit? }]
504
509
 
@@ -998,7 +1003,7 @@ const ALLOWED_SNIPPET_METHODS = {
998
1003
  graph: ['dispatch', 'find', 'insert', 'update', 'delete', 'query', 'execute'],
999
1004
  vector: ['query', 'find', 'findSimilar', 'upsert', 'insert', 'delete', 'dispatch'],
1000
1005
  storage: ['dispatch', 'upload', 'download', 'remove', 'listFiles', 'getSignedUrl', 'stats'],
1001
- notification: ['dispatch', 'send', 'email.send', 'push.send', 'sms.send', 'callback.send'],
1006
+ notification: ['dispatch', 'send', 'email.send', 'push.send', 'sms.send', 'callback.send', 'slack.send', 'discord.send'],
1002
1007
  messaging: ['dispatch', 'send', 'publish', 'produce', 'consume'],
1003
1008
  broker: ['dispatch', 'send', 'publish', 'produce', 'consume'],
1004
1009
  quota: ['run', 'dispatch', 'check', 'consume'],
@@ -1161,6 +1166,7 @@ const ADMIN_SUBCOMMANDS = [
1161
1166
  'link', 'unlink', 'init',
1162
1167
  'products', 'apps',
1163
1168
  'resources',
1169
+ 'notifications',
1164
1170
  'events',
1165
1171
  'cloud',
1166
1172
  'secrets',
@@ -2168,27 +2174,73 @@ DUCTAPE NOTIFICATIONS
2168
2174
 
2169
2175
  Notifications send messages across multiple channels: email, SMS, push, or HTTP callback.
2170
2176
 
2171
- Create a notification through declarative apply or Workbench (administrative), using:
2177
+ ADMINISTRATION CLI (never ductape_execute)
2178
+ ductape_cli("resources notifications create -f notification.json")
2179
+ ductape_cli("resources notifications get --tag <notification-tag> --json")
2180
+ ductape_cli("resources notifications list --json")
2181
+ ductape_cli("resources notifications update --tag <notification-tag> -f patch.json")
2182
+ ductape_cli("resources notifications delete --tag <notification-tag>")
2183
+ ductape_cli("notifications messages create -f message.json")
2184
+ ductape_cli("notifications messages list --notification <notification-tag> --json")
2185
+ ductape_cli("notifications messages get --tag <notification:message> --json")
2186
+ ductape_cli("notifications messages update --tag <notification:message> -f patch.json")
2187
+ Declarative alternative: ductape/notifications.json then ductape_cli("apply notifications").
2188
+ The file MUST be a top-level JSON array. Each item is a notification definition and may contain
2189
+ a nested "messages" array. {"notifications":[],"messages":[]} is not a valid envelope.
2190
+
2191
+ Notification definition:
2172
2192
  {
2173
- tag: "welcome-email",
2193
+ tag: "welcome-email", // component tags cannot contain ":"
2174
2194
  name: "Welcome Email",
2175
- type: "email", // optional hint; actual channels configured per env
2195
+ description: "Transactional welcome messages",
2196
+ envs: [{
2197
+ slug: "prd",
2198
+ emails: {
2199
+ provider: "smtp",
2200
+ smtp: {
2201
+ host: "smtp.example.com",
2202
+ port: "$Secret{smtp-port}",
2203
+ sender_email: "hello@example.com",
2204
+ auth: { user: "$Secret{smtp-user}", pass: "$Secret{smtp-password}" },
2205
+ secure: true
2206
+ }
2207
+ }
2208
+ }],
2209
+ messages: []
2176
2210
  }
2177
2211
 
2178
2212
  Create a message template through declarative apply or Workbench:
2179
2213
  {
2180
- tag: "welcome-email:default", // format: "notification_tag:message_tag"
2181
- notification: "welcome-email",
2182
- subject: { template: "Welcome, {{name}}!", data: { name: "" } },
2183
- body: { template: "Hi {{name}}, thanks for signing up.", data: { name: "" } },
2214
+ tag: "welcome-email:default", // message tags use component-tag:message-tag
2215
+ name: "Default welcome",
2216
+ description: "Sent after account creation",
2217
+ push_notification: {
2218
+ title: "Welcome, {{name}}!",
2219
+ body: "Thanks for signing up.",
2220
+ data: {}
2221
+ },
2222
+ email: {
2223
+ subject: "Welcome, {{name}}!",
2224
+ template: "Hi {{name}}, thanks for signing up."
2225
+ }
2184
2226
  }
2185
2227
 
2228
+ TAG AND SMTP RULES
2229
+ Component tag: "game-alerts-critical" (no colon).
2230
+ Message tag: "game-alerts-critical:match-launch" (one colon separator).
2231
+ Do not add "notification", "subject", or "body" at message root.
2232
+ SMTP requires emails.smtp.sender_email.
2233
+ emails.smtp.secure is a boolean and cannot be a $Secret{...} string.
2234
+ Credential strings such as auth.user and auth.pass may use $Secret{...}.
2235
+
2186
2236
  Send at runtime (one channel at a time):
2187
2237
  → CALL ductape_generate_payload FIRST (operation_family="notification", method="email.send")
2188
2238
  notifications.email.send [{ product, env, notification, input: { recipients, subject?, template? } }]
2189
2239
  notifications.push.send [{ product, env, notification, input: { device_tokens, title?, body?, data? } }]
2190
2240
  notifications.sms.send [{ product, env, notification, input: { recipients, body? } }]
2191
2241
  notifications.callback.send [{ product, env, notification, input: { query?, headers?, params?, body? } }]
2242
+ notifications.slack.send [{ product, env, notification, input: { text?, blocks?, channel? } }]
2243
+ notifications.discord.send [{ product, env, notification, input: { content?, embeds? } }]
2192
2244
 
2193
2245
  Multi-channel send (all channels in parallel):
2194
2246
  notifications.send [{ product, env, event: "notif_tag:message_tag", input: { ... } }]
@@ -2210,7 +2262,28 @@ Supported providers:
2210
2262
  Push: firebase | expo
2211
2263
  Callback: any HTTP endpoint
2212
2264
 
2213
- Channel configuration (envs per notification) is done in the Workbench UI, not via CLI.
2265
+ Each notification env may configure push_notifications, emails, sms, callbacks, slack, and discord.
2266
+ Email providers: smtp, sendgrid, mailgun, postmark, brevo.
2267
+ SMS providers: twilio, nexmo/vonage, plivo.
2268
+
2269
+ FIREBASE THROUGH A GCP CLOUD CONNECTION
2270
+ Complete and validate a GCP cloud connection with a service account authorized for Firebase
2271
+ Cloud Messaging. Then configure the notification env without copying service-account JSON:
2272
+ {
2273
+ "slug": "snd",
2274
+ "push_notifications": {
2275
+ "type": "firebase",
2276
+ "cloud": "gcp-connection-tag",
2277
+ "authMode": "cloud_connection",
2278
+ "databaseUrl": "https://<project>.firebaseio.com"
2279
+ }
2280
+ }
2281
+ The SDK requests current GCP credentials from the cloud connection at send time. Never put the
2282
+ service-account private key in the notification file. Expo does not use a GCP cloud connection.
2283
+ The recommended cloud connection scope is "notifications". It expresses Ductape capability/UI
2284
+ intent; creating a notification does not prove Google-side FCM permission. Runtime delivery
2285
+ requires fcm.googleapis.com and roles/firebasecloudmessaging.admin, so validate the connection
2286
+ and perform a delivery test.
2214
2287
  Notification tag and message tag are ALWAYS passed together as "notification_tag:message_tag".
2215
2288
  `.trim(),
2216
2289
  resilience: `
@@ -3720,8 +3793,10 @@ async function main() {
3720
3793
  const firstWord = args.command.trim().split(/\s+/)[0];
3721
3794
  const isAuthCommand = firstWord === 'login' || firstWord === 'logout';
3722
3795
  if (!isAuthCommand) {
3723
- // Check login status once per process (cached after first call)
3724
- if (authState === 'unknown') {
3796
+ // Cache successful authentication, but re-check a missing/expired session on every call.
3797
+ // The user may complete `ductape login` in another terminal while this MCP process remains
3798
+ // alive; caching "none" would otherwise make the MCP blind to the newly written session.
3799
+ if (authState === 'unknown' || authState === 'none') {
3725
3800
  checkLoginState();
3726
3801
  }
3727
3802
  if (authState === 'none') {
@@ -4029,7 +4104,10 @@ async function main() {
4029
4104
  ' Message brokers are import-only (no provision-persist). Import flow is the same as storage.\n' +
4030
4105
  ' type field = "messageBrokers" (not "messagebrokers" or "events").\n' +
4031
4106
  ' 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' +
4032
- ' - Listing workspaces, products, secrets\n' +
4107
+ ' - Listing workspaces, products, focused product components, secrets\n' +
4108
+ ' Prefer "products components list --tag <tag> --json" for compact inventory; use\n' +
4109
+ ' "products components get --tag <tag> --type notifications|events --json" for focused detail.\n' +
4110
+ ' - Managing notification components and message templates through "resources notifications" and "notifications messages"\n' +
4033
4111
  ' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
4034
4112
  ' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
4035
4113
  ' - Running database migrations: "db migrate", "db schema generate"\n\n' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.1.56",
3
+ "version": "0.1.58",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -50,6 +50,14 @@ const safetyChecks = [
50
50
  ['CLI 401 triggers login-required state', /authentication expired or was revoked \(HTTP 401\)[\s\S]*ductape login/],
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
+ ['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
+ ['Firebase GCP cloud connection documented', /FIREBASE THROUGH A GCP CLOUD CONNECTION[\s\S]*authMode.*cloud_connection/],
60
+ ['Slack and Discord sends documented', /notifications\.slack\.send[\s\S]*notifications\.discord\.send/],
53
61
  ];
54
62
 
55
63
  for (const [name, pattern] of safetyChecks) {
package/src/index.ts CHANGED
@@ -334,6 +334,9 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
334
334
  IMPORTANT: ALL product.* methods require the access key and will return 403 with a publishable key.
335
335
  Use ductape_cli for ALL product operations — never ductape_execute:
336
336
  ductape_cli("products get --tag <tag> --json") ← fetch product + full inventory
337
+ ductape_cli("products components list --tag <tag> --json") ← compact non-secret inventory
338
+ ductape_cli("products components get --tag <tag> --type notifications --json")
339
+ ductape_cli("products components get --tag <tag> --type events --json")
337
340
  ductape_cli("products create --name <name> --tag <tag>")
338
341
  ductape_cli("products environments list <tag> --json")
339
342
  ductape_cli("products environments get <tag> <slug> --json")
@@ -501,8 +504,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
501
504
  notifications.fetch [product_tag, notif_tag]
502
505
  notifications.list [product_tag]
503
506
  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 } }]
507
+ 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 }]
508
+ 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
509
  notifications.messages.fetch [product_tag, msg_tag]
507
510
  notifications.messages.list [product_tag, notification_tag]
508
511
  notifications.send [{ product, env, event, input: { ... } }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="send", targets={notification})
@@ -510,6 +513,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
510
513
  notifications.push.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="push.send")
511
514
  notifications.sms.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="sms.send")
512
515
  notifications.callback.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="callback.send")
516
+ notifications.slack.send [{ product, env, notification, input: { text?, blocks?, channel? }, session?, cache? }]
517
+ notifications.discord.send [{ product, env, notification, input: { content?, embeds? }, session?, cache? }]
513
518
  notifications.dispatch [{ product, env, notification, event, input, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="dispatch") — requires redisUrl in ductape initialization
514
519
  notifications.getMessages [{ product_tag?, env?, notification_tag?, status?, type?, start_date?, end_date?, page?, limit? }]
515
520
 
@@ -1037,7 +1042,7 @@ const ALLOWED_SNIPPET_METHODS: Record<string, string[]> = {
1037
1042
  graph: ['dispatch', 'find', 'insert', 'update', 'delete', 'query', 'execute'],
1038
1043
  vector: ['query', 'find', 'findSimilar', 'upsert', 'insert', 'delete', 'dispatch'],
1039
1044
  storage: ['dispatch', 'upload', 'download', 'remove', 'listFiles', 'getSignedUrl', 'stats'],
1040
- notification: ['dispatch', 'send', 'email.send', 'push.send', 'sms.send', 'callback.send'],
1045
+ notification: ['dispatch', 'send', 'email.send', 'push.send', 'sms.send', 'callback.send', 'slack.send', 'discord.send'],
1041
1046
  messaging: ['dispatch', 'send', 'publish', 'produce', 'consume'],
1042
1047
  broker: ['dispatch', 'send', 'publish', 'produce', 'consume'],
1043
1048
  quota: ['run', 'dispatch', 'check', 'consume'],
@@ -1222,6 +1227,7 @@ const ADMIN_SUBCOMMANDS = [
1222
1227
  'link', 'unlink', 'init',
1223
1228
  'products', 'apps',
1224
1229
  'resources',
1230
+ 'notifications',
1225
1231
  'events',
1226
1232
  'cloud',
1227
1233
  'secrets',
@@ -2251,27 +2257,73 @@ DUCTAPE NOTIFICATIONS
2251
2257
 
2252
2258
  Notifications send messages across multiple channels: email, SMS, push, or HTTP callback.
2253
2259
 
2254
- Create a notification through declarative apply or Workbench (administrative), using:
2260
+ ADMINISTRATION CLI (never ductape_execute)
2261
+ ductape_cli("resources notifications create -f notification.json")
2262
+ ductape_cli("resources notifications get --tag <notification-tag> --json")
2263
+ ductape_cli("resources notifications list --json")
2264
+ ductape_cli("resources notifications update --tag <notification-tag> -f patch.json")
2265
+ ductape_cli("resources notifications delete --tag <notification-tag>")
2266
+ ductape_cli("notifications messages create -f message.json")
2267
+ ductape_cli("notifications messages list --notification <notification-tag> --json")
2268
+ ductape_cli("notifications messages get --tag <notification:message> --json")
2269
+ ductape_cli("notifications messages update --tag <notification:message> -f patch.json")
2270
+ Declarative alternative: ductape/notifications.json then ductape_cli("apply notifications").
2271
+ The file MUST be a top-level JSON array. Each item is a notification definition and may contain
2272
+ a nested "messages" array. {"notifications":[],"messages":[]} is not a valid envelope.
2273
+
2274
+ Notification definition:
2255
2275
  {
2256
- tag: "welcome-email",
2276
+ tag: "welcome-email", // component tags cannot contain ":"
2257
2277
  name: "Welcome Email",
2258
- type: "email", // optional hint; actual channels configured per env
2278
+ description: "Transactional welcome messages",
2279
+ envs: [{
2280
+ slug: "prd",
2281
+ emails: {
2282
+ provider: "smtp",
2283
+ smtp: {
2284
+ host: "smtp.example.com",
2285
+ port: "$Secret{smtp-port}",
2286
+ sender_email: "hello@example.com",
2287
+ auth: { user: "$Secret{smtp-user}", pass: "$Secret{smtp-password}" },
2288
+ secure: true
2289
+ }
2290
+ }
2291
+ }],
2292
+ messages: []
2259
2293
  }
2260
2294
 
2261
2295
  Create a message template through declarative apply or Workbench:
2262
2296
  {
2263
- tag: "welcome-email:default", // format: "notification_tag:message_tag"
2264
- notification: "welcome-email",
2265
- subject: { template: "Welcome, {{name}}!", data: { name: "" } },
2266
- body: { template: "Hi {{name}}, thanks for signing up.", data: { name: "" } },
2297
+ tag: "welcome-email:default", // message tags use component-tag:message-tag
2298
+ name: "Default welcome",
2299
+ description: "Sent after account creation",
2300
+ push_notification: {
2301
+ title: "Welcome, {{name}}!",
2302
+ body: "Thanks for signing up.",
2303
+ data: {}
2304
+ },
2305
+ email: {
2306
+ subject: "Welcome, {{name}}!",
2307
+ template: "Hi {{name}}, thanks for signing up."
2308
+ }
2267
2309
  }
2268
2310
 
2311
+ TAG AND SMTP RULES
2312
+ Component tag: "game-alerts-critical" (no colon).
2313
+ Message tag: "game-alerts-critical:match-launch" (one colon separator).
2314
+ Do not add "notification", "subject", or "body" at message root.
2315
+ SMTP requires emails.smtp.sender_email.
2316
+ emails.smtp.secure is a boolean and cannot be a $Secret{...} string.
2317
+ Credential strings such as auth.user and auth.pass may use $Secret{...}.
2318
+
2269
2319
  Send at runtime (one channel at a time):
2270
2320
  → CALL ductape_generate_payload FIRST (operation_family="notification", method="email.send")
2271
2321
  notifications.email.send [{ product, env, notification, input: { recipients, subject?, template? } }]
2272
2322
  notifications.push.send [{ product, env, notification, input: { device_tokens, title?, body?, data? } }]
2273
2323
  notifications.sms.send [{ product, env, notification, input: { recipients, body? } }]
2274
2324
  notifications.callback.send [{ product, env, notification, input: { query?, headers?, params?, body? } }]
2325
+ notifications.slack.send [{ product, env, notification, input: { text?, blocks?, channel? } }]
2326
+ notifications.discord.send [{ product, env, notification, input: { content?, embeds? } }]
2275
2327
 
2276
2328
  Multi-channel send (all channels in parallel):
2277
2329
  notifications.send [{ product, env, event: "notif_tag:message_tag", input: { ... } }]
@@ -2293,7 +2345,28 @@ Supported providers:
2293
2345
  Push: firebase | expo
2294
2346
  Callback: any HTTP endpoint
2295
2347
 
2296
- Channel configuration (envs per notification) is done in the Workbench UI, not via CLI.
2348
+ Each notification env may configure push_notifications, emails, sms, callbacks, slack, and discord.
2349
+ Email providers: smtp, sendgrid, mailgun, postmark, brevo.
2350
+ SMS providers: twilio, nexmo/vonage, plivo.
2351
+
2352
+ FIREBASE THROUGH A GCP CLOUD CONNECTION
2353
+ Complete and validate a GCP cloud connection with a service account authorized for Firebase
2354
+ Cloud Messaging. Then configure the notification env without copying service-account JSON:
2355
+ {
2356
+ "slug": "snd",
2357
+ "push_notifications": {
2358
+ "type": "firebase",
2359
+ "cloud": "gcp-connection-tag",
2360
+ "authMode": "cloud_connection",
2361
+ "databaseUrl": "https://<project>.firebaseio.com"
2362
+ }
2363
+ }
2364
+ The SDK requests current GCP credentials from the cloud connection at send time. Never put the
2365
+ service-account private key in the notification file. Expo does not use a GCP cloud connection.
2366
+ The recommended cloud connection scope is "notifications". It expresses Ductape capability/UI
2367
+ intent; creating a notification does not prove Google-side FCM permission. Runtime delivery
2368
+ requires fcm.googleapis.com and roles/firebasecloudmessaging.admin, so validate the connection
2369
+ and perform a delivery test.
2297
2370
  Notification tag and message tag are ALWAYS passed together as "notification_tag:message_tag".
2298
2371
  `.trim(),
2299
2372
 
@@ -3828,8 +3901,10 @@ async function main() {
3828
3901
  const isAuthCommand = firstWord === 'login' || firstWord === 'logout';
3829
3902
 
3830
3903
  if (!isAuthCommand) {
3831
- // Check login status once per process (cached after first call)
3832
- if (authState === 'unknown') {
3904
+ // Cache successful authentication, but re-check a missing/expired session on every call.
3905
+ // The user may complete `ductape login` in another terminal while this MCP process remains
3906
+ // alive; caching "none" would otherwise make the MCP blind to the newly written session.
3907
+ if (authState === 'unknown' || authState === 'none') {
3833
3908
  checkLoginState();
3834
3909
  }
3835
3910
  if (authState === 'none') {
@@ -4190,7 +4265,10 @@ async function main() {
4190
4265
  ' Message brokers are import-only (no provision-persist). Import flow is the same as storage.\n' +
4191
4266
  ' type field = "messageBrokers" (not "messagebrokers" or "events").\n' +
4192
4267
  ' 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' +
4193
- ' - Listing workspaces, products, secrets\n' +
4268
+ ' - Listing workspaces, products, focused product components, secrets\n' +
4269
+ ' Prefer "products components list --tag <tag> --json" for compact inventory; use\n' +
4270
+ ' "products components get --tag <tag> --type notifications|events --json" for focused detail.\n' +
4271
+ ' - Managing notification components and message templates through "resources notifications" and "notifications messages"\n' +
4194
4272
  ' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
4195
4273
  ' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
4196
4274
  ' - Running database migrations: "db migrate", "db schema generate"\n\n' +