@ductape/mcp 0.1.57 → 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})
@@ -1163,6 +1166,7 @@ const ADMIN_SUBCOMMANDS = [
1163
1166
  'link', 'unlink', 'init',
1164
1167
  'products', 'apps',
1165
1168
  'resources',
1169
+ 'notifications',
1166
1170
  'events',
1167
1171
  'cloud',
1168
1172
  'secrets',
@@ -2181,22 +2185,54 @@ ADMINISTRATION — CLI (never ductape_execute)
2181
2185
  ductape_cli("notifications messages get --tag <notification:message> --json")
2182
2186
  ductape_cli("notifications messages update --tag <notification:message> -f patch.json")
2183
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.
2184
2190
 
2185
2191
  Notification definition:
2186
2192
  {
2187
- tag: "welcome-email",
2193
+ tag: "welcome-email", // component tags cannot contain ":"
2188
2194
  name: "Welcome Email",
2189
- 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: []
2190
2210
  }
2191
2211
 
2192
2212
  Create a message template through declarative apply or Workbench:
2193
2213
  {
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: "" } },
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
+ }
2198
2226
  }
2199
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
+
2200
2236
  Send at runtime (one channel at a time):
2201
2237
  → CALL ductape_generate_payload FIRST (operation_family="notification", method="email.send")
2202
2238
  notifications.email.send [{ product, env, notification, input: { recipients, subject?, template? } }]
@@ -2244,6 +2280,10 @@ FIREBASE THROUGH A GCP CLOUD CONNECTION
2244
2280
  }
2245
2281
  The SDK requests current GCP credentials from the cloud connection at send time. Never put the
2246
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.
2247
2287
  Notification tag and message tag are ALWAYS passed together as "notification_tag:message_tag".
2248
2288
  `.trim(),
2249
2289
  resilience: `
@@ -3753,8 +3793,10 @@ async function main() {
3753
3793
  const firstWord = args.command.trim().split(/\s+/)[0];
3754
3794
  const isAuthCommand = firstWord === 'login' || firstWord === 'logout';
3755
3795
  if (!isAuthCommand) {
3756
- // Check login status once per process (cached after first call)
3757
- 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') {
3758
3800
  checkLoginState();
3759
3801
  }
3760
3802
  if (authState === 'none') {
@@ -4062,7 +4104,10 @@ async function main() {
4062
4104
  ' Message brokers are import-only (no provision-persist). Import flow is the same as storage.\n' +
4063
4105
  ' type field = "messageBrokers" (not "messagebrokers" or "events").\n' +
4064
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' +
4065
- ' - 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' +
4066
4111
  ' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
4067
4112
  ' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
4068
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.57",
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",
@@ -51,6 +51,11 @@ 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/],
54
59
  ['Firebase GCP cloud connection documented', /FIREBASE THROUGH A GCP CLOUD CONNECTION[\s\S]*authMode.*cloud_connection/],
55
60
  ['Slack and Discord sends documented', /notifications\.slack\.send[\s\S]*notifications\.discord\.send/],
56
61
  ];
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})
@@ -1224,6 +1227,7 @@ const ADMIN_SUBCOMMANDS = [
1224
1227
  'link', 'unlink', 'init',
1225
1228
  'products', 'apps',
1226
1229
  'resources',
1230
+ 'notifications',
1227
1231
  'events',
1228
1232
  'cloud',
1229
1233
  'secrets',
@@ -2264,22 +2268,54 @@ ADMINISTRATION — CLI (never ductape_execute)
2264
2268
  ductape_cli("notifications messages get --tag <notification:message> --json")
2265
2269
  ductape_cli("notifications messages update --tag <notification:message> -f patch.json")
2266
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.
2267
2273
 
2268
2274
  Notification definition:
2269
2275
  {
2270
- tag: "welcome-email",
2276
+ tag: "welcome-email", // component tags cannot contain ":"
2271
2277
  name: "Welcome Email",
2272
- 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: []
2273
2293
  }
2274
2294
 
2275
2295
  Create a message template through declarative apply or Workbench:
2276
2296
  {
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: "" } },
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
+ }
2281
2309
  }
2282
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
+
2283
2319
  Send at runtime (one channel at a time):
2284
2320
  → CALL ductape_generate_payload FIRST (operation_family="notification", method="email.send")
2285
2321
  notifications.email.send [{ product, env, notification, input: { recipients, subject?, template? } }]
@@ -2327,6 +2363,10 @@ FIREBASE THROUGH A GCP CLOUD CONNECTION
2327
2363
  }
2328
2364
  The SDK requests current GCP credentials from the cloud connection at send time. Never put the
2329
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.
2330
2370
  Notification tag and message tag are ALWAYS passed together as "notification_tag:message_tag".
2331
2371
  `.trim(),
2332
2372
 
@@ -3861,8 +3901,10 @@ async function main() {
3861
3901
  const isAuthCommand = firstWord === 'login' || firstWord === 'logout';
3862
3902
 
3863
3903
  if (!isAuthCommand) {
3864
- // Check login status once per process (cached after first call)
3865
- 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') {
3866
3908
  checkLoginState();
3867
3909
  }
3868
3910
  if (authState === 'none') {
@@ -4223,7 +4265,10 @@ async function main() {
4223
4265
  ' Message brokers are import-only (no provision-persist). Import flow is the same as storage.\n' +
4224
4266
  ' type field = "messageBrokers" (not "messagebrokers" or "events").\n' +
4225
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' +
4226
- ' - 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' +
4227
4272
  ' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
4228
4273
  ' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
4229
4274
  ' - Running database migrations: "db migrate", "db schema generate"\n\n' +