@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 +78 -24
- package/dist/proxy-client.d.ts.map +1 -1
- package/dist/proxy-client.js +8 -2
- package/package.json +1 -1
- package/scripts/check-frontend-analytics-guidance.mjs +13 -0
- package/src/index.ts +78 -24
- package/src/proxy-client.ts +8 -2
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,
|
|
127
|
-
|
|
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,
|
|
494
|
-
notifications.messages.update [product_tag, msg_tag, data: {
|
|
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.
|
|
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
|
|
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
|
-
|
|
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",
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
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,
|
|
2614
|
-
has no command must be configured in Workbench.
|
|
2615
|
-
|
|
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.
|
|
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.
|
|
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,
|
|
3674
|
-
'
|
|
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
|
-
//
|
|
3757
|
-
|
|
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,
|
|
4070
|
-
'
|
|
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,
|
|
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"}
|
package/dist/proxy-client.js
CHANGED
|
@@ -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
|
-
...
|
|
72
|
-
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
|
@@ -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,
|
|
138
|
-
|
|
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,
|
|
505
|
-
notifications.messages.update [product_tag, msg_tag, data: {
|
|
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.
|
|
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
|
|
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
|
-
|
|
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",
|
|
2278
|
-
|
|
2279
|
-
|
|
2280
|
-
|
|
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,
|
|
2700
|
-
has no command must be configured in Workbench.
|
|
2701
|
-
|
|
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.
|
|
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.
|
|
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,
|
|
3768
|
-
'
|
|
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
|
-
//
|
|
3865
|
-
|
|
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,
|
|
4231
|
-
'
|
|
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). ' +
|
package/src/proxy-client.ts
CHANGED
|
@@ -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
|
-
...
|
|
142
|
-
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, {
|