@ductape/mcp 0.1.58 → 0.1.60
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 +31 -56
- package/dist/proxy-client.d.ts.map +1 -1
- package/dist/proxy-client.js +12 -3
- package/package.json +1 -1
- package/scripts/check-frontend-analytics-guidance.mjs +8 -0
- package/src/index.ts +31 -56
- package/src/proxy-client.ts +12 -3
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):
|
|
@@ -722,47 +723,10 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
722
723
|
vector.count [{ product, env, vector, namespace? }]
|
|
723
724
|
|
|
724
725
|
━━━ MODULE: features ━━━
|
|
725
|
-
features.
|
|
726
|
-
|
|
727
|
-
name: string,
|
|
728
|
-
description?: string,
|
|
729
|
-
|
|
730
|
-
// INPUT SCHEMA (top-level): declares what fields this feature accepts when executed at runtime.
|
|
731
|
-
// These are the fields callers will pass to features.execute / features.dispatch.
|
|
732
|
-
// Construct this yourself — it is a schema declaration, not a runtime value.
|
|
733
|
-
input?: { fieldName: { type: string, required?: boolean } },
|
|
734
|
-
|
|
735
|
-
output?: object,
|
|
736
|
-
envs?: [{ slug: string, active?: boolean }],
|
|
737
|
-
steps: [
|
|
738
|
-
{
|
|
739
|
-
tag: string, // unique step id
|
|
740
|
-
name?: string,
|
|
741
|
-
type: "action"|"database"|"graph"|"notification"|"storage"|"publish"|"feature"|"sleep"|"wait_signal",
|
|
742
|
-
app?: string, // for type=action
|
|
743
|
-
event?: string, // action/event tag
|
|
744
|
-
database?: string, // for type=database
|
|
745
|
-
graph?: string, // for type=graph
|
|
746
|
-
notification?: string, // for type=notification
|
|
747
|
-
storage?: string, // for type=storage
|
|
748
|
-
broker?: string, // for type=publish
|
|
749
|
-
feature?: string, // for type=feature (child feature)
|
|
750
|
-
|
|
751
|
-
// STEP INPUT: maps this feature's declared input fields (or prior step outputs) → the step's underlying action/event fields.
|
|
752
|
-
// ← CALL ductape_generate_payload (operation_family matching step type, method="run", targets={app/event/database/etc.})
|
|
753
|
-
// to discover what fields the target accepts, then wire them with "$Input{fieldName}" or "$Step{stepTag}{field}" references.
|
|
754
|
-
input?: { "body:field": "$Input{fieldName}" | "$Step{stepTag}{field}" | literal },
|
|
755
|
-
|
|
756
|
-
condition?: string, // e.g. "$Step{validate}{valid} == true"
|
|
757
|
-
dependsOn?: string[],
|
|
758
|
-
options?: { retries?: number, timeout?: number, allow_fail?: boolean, critical?: boolean }
|
|
759
|
-
}
|
|
760
|
-
]
|
|
761
|
-
}]
|
|
762
|
-
features.update [product_tag, feature_tag, data: { name?: string, description?: string, steps?: array, envs?: array }]
|
|
726
|
+
Feature definitions are code-first. Use features.define in application source; do not call
|
|
727
|
+
administrative create/update/delete methods through ductape_execute.
|
|
763
728
|
features.fetch [product_tag, feature_tag]
|
|
764
729
|
features.fetchAll [product_tag]
|
|
765
|
-
features.delete [product_tag, feature_tag]
|
|
766
730
|
|
|
767
731
|
features.define [{
|
|
768
732
|
product?: string,
|
|
@@ -786,7 +750,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
786
750
|
// ctx.graph.execute({ graph, action, input })
|
|
787
751
|
// ctx.notification.send/email/push/sms({ notification, event, ... })
|
|
788
752
|
// ctx.storage.upload/download({ storage, event, input })
|
|
789
|
-
// ctx.
|
|
753
|
+
// ctx.events.produce({ event: "broker:topic", message: {} })
|
|
790
754
|
// ctx.quota.execute({ quota, input })
|
|
791
755
|
// ctx.fallback.execute({ fallback, input })
|
|
792
756
|
// ctx.healthcheck.getStatus(tag)
|
|
@@ -976,7 +940,8 @@ const payloadGenerateInputSchema = z.object({
|
|
|
976
940
|
'For quotas/fallbacks: { tag: "resource_tag" }. ' +
|
|
977
941
|
'For storage: { storage: "storage_tag" }. ' +
|
|
978
942
|
'For messaging: { broker: "broker_tag", topic?: "topic_tag" }.'),
|
|
979
|
-
include_session: z.boolean().optional().
|
|
943
|
+
include_session: z.boolean().optional().describe('Include a session placeholder inside the generated input object. ' +
|
|
944
|
+
'Defaults to false for execution_context="system" and true otherwise. ' +
|
|
980
945
|
'The placeholder is named "<session_tag_token>" to indicate it expects the runtime JWT, not the tag name.'),
|
|
981
946
|
execution_context: z.enum(['user', 'delegated', 'system']).optional().default('user').describe('Actor intent for this runtime operation. "user" means an active request initiated by the authenticated user; ' +
|
|
982
947
|
'"delegated" means work acting on behalf of a user with a short-lived delegated identity or application-owned ' +
|
|
@@ -1083,7 +1048,7 @@ function addSessionAwarenessMetadata(generated, args) {
|
|
|
1083
1048
|
const acceptsSession = operationAcceptsSession(args.operation_family, args.method);
|
|
1084
1049
|
const executionContext = args.execution_context ?? 'user';
|
|
1085
1050
|
const payloadHasSession = Boolean(generated?.payload?.session || generated?.payload?.input?.session);
|
|
1086
|
-
const sessionRequested = args.include_session !==
|
|
1051
|
+
const sessionRequested = args.include_session ?? executionContext !== 'system';
|
|
1087
1052
|
const warnings = [];
|
|
1088
1053
|
if (acceptsSession && executionContext !== 'system' && (!sessionRequested || !payloadHasSession)) {
|
|
1089
1054
|
warnings.push(`Session attribution is missing for a ${executionContext}-context operation. ` +
|
|
@@ -1231,7 +1196,9 @@ function runCli(command) {
|
|
|
1231
1196
|
try {
|
|
1232
1197
|
const output = execSync(`ductape ${finalCommand}`, {
|
|
1233
1198
|
encoding: 'utf8',
|
|
1234
|
-
timeout
|
|
1199
|
+
// Must exceed the proxy's operation timeout so stderr can preserve the structured timeout
|
|
1200
|
+
// instead of this wrapper killing the CLI first and reducing it to "(no data)".
|
|
1201
|
+
timeout: 90000,
|
|
1235
1202
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1236
1203
|
});
|
|
1237
1204
|
return { success: true, output: output.trim() };
|
|
@@ -2650,9 +2617,11 @@ STEP 4 — PRESENT the plan and get approval BEFORE writing any code or creating
|
|
|
2650
2617
|
STEP 5 — CREATE missing components (only with user approval)
|
|
2651
2618
|
Administrative assets must never be created through ductape_execute. Use ductape_cli for products,
|
|
2652
2619
|
apps, supported resources, broker topics, cloud connections, secrets, and declarative apply flows.
|
|
2653
|
-
App actions, auths,
|
|
2654
|
-
has no command must be configured in Workbench.
|
|
2655
|
-
|
|
2620
|
+
App actions, auths, quotas, fallbacks, health checks, and other administrative assets for which
|
|
2621
|
+
the CLI has no command must be configured in Workbench. Feature definitions are the exception:
|
|
2622
|
+
they are code-first via features.define, are registered by application boot/runtime initialization,
|
|
2623
|
+
and cannot be created with the CLI. Do not generate a Workbench-only or publishable-key
|
|
2624
|
+
create/update call for a Feature. For a missing database action, configure it in Workbench, then verify it exists.
|
|
2656
2625
|
For a missing child feature, recursively apply this same workflow.
|
|
2657
2626
|
Tell the user what you are about to create before each tool call.
|
|
2658
2627
|
|
|
@@ -2761,7 +2730,9 @@ When you call features.define({ handler }), the handler runs TWICE:
|
|
|
2761
2730
|
Features do NOT execute arbitrary NestJS or server code directly. A feature handler can only
|
|
2762
2731
|
call Ductape component primitives (ctx.api, ctx.database, ctx.notification, etc.) as steps.
|
|
2763
2732
|
To invoke internal application business logic, produce a broker event from a feature step
|
|
2764
|
-
(ctx.
|
|
2733
|
+
(ctx.events.produce in the currently published SDK) and consume it in your NestJS service.
|
|
2734
|
+
ctx.publish is deprecated; do not use it. Do not assume a ctx.messaging alias exists unless the
|
|
2735
|
+
installed SDK types explicitly expose it.
|
|
2765
2736
|
|
|
2766
2737
|
━━━ ORCHESTRATION DECISION RULE ━━━
|
|
2767
2738
|
|
|
@@ -2775,7 +2746,7 @@ When you call features.define({ handler }), the handler runs TWICE:
|
|
|
2775
2746
|
→ define a Feature, then features.dispatch to schedule it
|
|
2776
2747
|
|
|
2777
2748
|
Invoke internal application business logic (your own NestJS/backend service code):
|
|
2778
|
-
→ produce a broker event (ctx.
|
|
2749
|
+
→ produce a broker event (ctx.events.produce inside a Feature, or ductape.events.produce outside it)
|
|
2779
2750
|
→ follow ductape_docs({ topic: "events" }) and use the canonical NestJS decorator:
|
|
2780
2751
|
@Events.Consumer({ event: "broker-tag:topic-tag" })
|
|
2781
2752
|
async handle(message: MessageShape) { /* injected-service business logic; throw to nack */ }
|
|
@@ -3710,8 +3681,9 @@ const cliInputSchema = z.object({
|
|
|
3710
3681
|
'Use this tool for administrative operations: creating or updating products, apps, ' +
|
|
3711
3682
|
'resources (databases, storage, caches…), event broker topics, cloud connections, secrets, ' +
|
|
3712
3683
|
'and for apply/migrate workflows.\n\n' +
|
|
3713
|
-
'Note: environments, app actions,
|
|
3714
|
-
'
|
|
3684
|
+
'Note: environments, app actions, quotas, fallbacks, and jobs are configured in the ' +
|
|
3685
|
+
'Workbench UI. Features also have no CLI creation command: define them in application code ' +
|
|
3686
|
+
'with features.define so application boot/runtime registration makes them available.\n\n' +
|
|
3715
3687
|
'The CLI uses the user\'s local logged-in session (ductape login) — no key is required.'),
|
|
3716
3688
|
});
|
|
3717
3689
|
async function loadMcpSdk() {
|
|
@@ -3906,7 +3878,8 @@ async function main() {
|
|
|
3906
3878
|
if (!key) {
|
|
3907
3879
|
throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
|
|
3908
3880
|
}
|
|
3909
|
-
const
|
|
3881
|
+
const includeSession = args.include_session ?? args.execution_context !== 'system';
|
|
3882
|
+
const result = addSessionAwarenessMetadata(await generateExecutablePayload({ ...args, include_session: includeSession, publishable_key: key }), args);
|
|
3910
3883
|
let text = JSON.stringify(result ?? null, null, 2);
|
|
3911
3884
|
if (args.operation_family === 'database') {
|
|
3912
3885
|
const meta = result?.meta ?? {};
|
|
@@ -3933,7 +3906,8 @@ async function main() {
|
|
|
3933
3906
|
throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
|
|
3934
3907
|
}
|
|
3935
3908
|
ensureSupportedSnippetOperation(args.operation_family, args.method);
|
|
3936
|
-
const
|
|
3909
|
+
const includeSession = args.include_session ?? args.execution_context !== 'system';
|
|
3910
|
+
const generated = addSessionAwarenessMetadata(await generateExecutablePayload({ ...args, include_session: includeSession, publishable_key: key }), args);
|
|
3937
3911
|
const payload = generated?.payload ?? {};
|
|
3938
3912
|
const snippet = buildSnippet(args.language, payload, args.operation_family, args.method);
|
|
3939
3913
|
return {
|
|
@@ -4111,8 +4085,9 @@ async function main() {
|
|
|
4111
4085
|
' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
|
|
4112
4086
|
' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
|
|
4113
4087
|
' - Running database migrations: "db migrate", "db schema generate"\n\n' +
|
|
4114
|
-
'NOTE: Environments, app actions, auths,
|
|
4115
|
-
'
|
|
4088
|
+
'NOTE: Environments, app actions, auths, quotas, fallbacks, and jobs are configured ' +
|
|
4089
|
+
'in the Workbench UI. Features have no CLI creation command because definitions are ' +
|
|
4090
|
+
'code-first through features.define and registered by the application runtime.\n\n' +
|
|
4116
4091
|
'DO NOT use ductape_execute for admin operations — it uses a publishable key which only ' +
|
|
4117
4092
|
'covers runtime operations. Administrative operations will fail with "Authentication failed".\n\n' +
|
|
4118
4093
|
'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,CAiCZ"}
|
package/dist/proxy-client.js
CHANGED
|
@@ -14,7 +14,7 @@ export async function executeViaProxy(publishable_key, module, method, params =
|
|
|
14
14
|
},
|
|
15
15
|
body: JSON.stringify({
|
|
16
16
|
publishable_key,
|
|
17
|
-
module,
|
|
17
|
+
module: module === 'features' ? 'feature' : module,
|
|
18
18
|
method,
|
|
19
19
|
params,
|
|
20
20
|
}),
|
|
@@ -67,9 +67,18 @@ 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
|
-
|
|
75
|
+
...backendRequest,
|
|
76
|
+
operation_family: backendRequest.operation_family.toLowerCase() === 'features'
|
|
77
|
+
? 'feature'
|
|
78
|
+
: backendRequest.operation_family,
|
|
79
|
+
targets: backendRequest.targets
|
|
80
|
+
? normalizeTargets(backendRequest.targets)
|
|
81
|
+
: backendRequest.targets,
|
|
73
82
|
};
|
|
74
83
|
const url = `${API_BASE_URL.replace(/\/$/, '')}/integrations/v1/payloads/generate`;
|
|
75
84
|
const res = await fetch(url, {
|
package/package.json
CHANGED
|
@@ -56,6 +56,8 @@ const safetyChecks = [
|
|
|
56
56
|
['notification declarations require array shape', /file MUST be a top-level JSON array/],
|
|
57
57
|
['external CLI login is re-read', /authState === 'unknown' \|\| authState === 'none'/],
|
|
58
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/],
|
|
59
61
|
['Firebase GCP cloud connection documented', /FIREBASE THROUGH A GCP CLOUD CONNECTION[\s\S]*authMode.*cloud_connection/],
|
|
60
62
|
['Slack and Discord sends documented', /notifications\.slack\.send[\s\S]*notifications\.discord\.send/],
|
|
61
63
|
];
|
|
@@ -77,4 +79,10 @@ assert.doesNotMatch(
|
|
|
77
79
|
'Browser Events guidance must not import the server SDK',
|
|
78
80
|
);
|
|
79
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
|
+
|
|
80
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):
|
|
@@ -733,47 +734,10 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
733
734
|
vector.count [{ product, env, vector, namespace? }]
|
|
734
735
|
|
|
735
736
|
━━━ MODULE: features ━━━
|
|
736
|
-
features.
|
|
737
|
-
|
|
738
|
-
name: string,
|
|
739
|
-
description?: string,
|
|
740
|
-
|
|
741
|
-
// INPUT SCHEMA (top-level): declares what fields this feature accepts when executed at runtime.
|
|
742
|
-
// These are the fields callers will pass to features.execute / features.dispatch.
|
|
743
|
-
// Construct this yourself — it is a schema declaration, not a runtime value.
|
|
744
|
-
input?: { fieldName: { type: string, required?: boolean } },
|
|
745
|
-
|
|
746
|
-
output?: object,
|
|
747
|
-
envs?: [{ slug: string, active?: boolean }],
|
|
748
|
-
steps: [
|
|
749
|
-
{
|
|
750
|
-
tag: string, // unique step id
|
|
751
|
-
name?: string,
|
|
752
|
-
type: "action"|"database"|"graph"|"notification"|"storage"|"publish"|"feature"|"sleep"|"wait_signal",
|
|
753
|
-
app?: string, // for type=action
|
|
754
|
-
event?: string, // action/event tag
|
|
755
|
-
database?: string, // for type=database
|
|
756
|
-
graph?: string, // for type=graph
|
|
757
|
-
notification?: string, // for type=notification
|
|
758
|
-
storage?: string, // for type=storage
|
|
759
|
-
broker?: string, // for type=publish
|
|
760
|
-
feature?: string, // for type=feature (child feature)
|
|
761
|
-
|
|
762
|
-
// STEP INPUT: maps this feature's declared input fields (or prior step outputs) → the step's underlying action/event fields.
|
|
763
|
-
// ← CALL ductape_generate_payload (operation_family matching step type, method="run", targets={app/event/database/etc.})
|
|
764
|
-
// to discover what fields the target accepts, then wire them with "$Input{fieldName}" or "$Step{stepTag}{field}" references.
|
|
765
|
-
input?: { "body:field": "$Input{fieldName}" | "$Step{stepTag}{field}" | literal },
|
|
766
|
-
|
|
767
|
-
condition?: string, // e.g. "$Step{validate}{valid} == true"
|
|
768
|
-
dependsOn?: string[],
|
|
769
|
-
options?: { retries?: number, timeout?: number, allow_fail?: boolean, critical?: boolean }
|
|
770
|
-
}
|
|
771
|
-
]
|
|
772
|
-
}]
|
|
773
|
-
features.update [product_tag, feature_tag, data: { name?: string, description?: string, steps?: array, envs?: array }]
|
|
737
|
+
Feature definitions are code-first. Use features.define in application source; do not call
|
|
738
|
+
administrative create/update/delete methods through ductape_execute.
|
|
774
739
|
features.fetch [product_tag, feature_tag]
|
|
775
740
|
features.fetchAll [product_tag]
|
|
776
|
-
features.delete [product_tag, feature_tag]
|
|
777
741
|
|
|
778
742
|
features.define [{
|
|
779
743
|
product?: string,
|
|
@@ -797,7 +761,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
797
761
|
// ctx.graph.execute({ graph, action, input })
|
|
798
762
|
// ctx.notification.send/email/push/sms({ notification, event, ... })
|
|
799
763
|
// ctx.storage.upload/download({ storage, event, input })
|
|
800
|
-
// ctx.
|
|
764
|
+
// ctx.events.produce({ event: "broker:topic", message: {} })
|
|
801
765
|
// ctx.quota.execute({ quota, input })
|
|
802
766
|
// ctx.fallback.execute({ fallback, input })
|
|
803
767
|
// ctx.healthcheck.getStatus(tag)
|
|
@@ -999,8 +963,9 @@ const payloadGenerateInputSchema = z.object({
|
|
|
999
963
|
'For storage: { storage: "storage_tag" }. ' +
|
|
1000
964
|
'For messaging: { broker: "broker_tag", topic?: "topic_tag" }.'
|
|
1001
965
|
),
|
|
1002
|
-
include_session: z.boolean().optional().
|
|
966
|
+
include_session: z.boolean().optional().describe(
|
|
1003
967
|
'Include a session placeholder inside the generated input object. ' +
|
|
968
|
+
'Defaults to false for execution_context="system" and true otherwise. ' +
|
|
1004
969
|
'The placeholder is named "<session_tag_token>" to indicate it expects the runtime JWT, not the tag name.'
|
|
1005
970
|
),
|
|
1006
971
|
execution_context: z.enum(['user', 'delegated', 'system']).optional().default('user').describe(
|
|
@@ -1118,7 +1083,7 @@ function addSessionAwarenessMetadata(
|
|
|
1118
1083
|
const acceptsSession = operationAcceptsSession(args.operation_family, args.method);
|
|
1119
1084
|
const executionContext = args.execution_context ?? 'user';
|
|
1120
1085
|
const payloadHasSession = Boolean(generated?.payload?.session || generated?.payload?.input?.session);
|
|
1121
|
-
const sessionRequested = args.include_session !==
|
|
1086
|
+
const sessionRequested = args.include_session ?? executionContext !== 'system';
|
|
1122
1087
|
const warnings: string[] = [];
|
|
1123
1088
|
|
|
1124
1089
|
if (acceptsSession && executionContext !== 'system' && (!sessionRequested || !payloadHasSession)) {
|
|
@@ -1292,7 +1257,9 @@ function runCli(command: string): { success: boolean; output: string } {
|
|
|
1292
1257
|
try {
|
|
1293
1258
|
const output = execSync(`ductape ${finalCommand}`, {
|
|
1294
1259
|
encoding: 'utf8',
|
|
1295
|
-
timeout
|
|
1260
|
+
// Must exceed the proxy's operation timeout so stderr can preserve the structured timeout
|
|
1261
|
+
// instead of this wrapper killing the CLI first and reducing it to "(no data)".
|
|
1262
|
+
timeout: 90000,
|
|
1296
1263
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1297
1264
|
});
|
|
1298
1265
|
return { success: true, output: output.trim() };
|
|
@@ -2736,9 +2703,11 @@ STEP 4 — PRESENT the plan and get approval BEFORE writing any code or creating
|
|
|
2736
2703
|
STEP 5 — CREATE missing components (only with user approval)
|
|
2737
2704
|
Administrative assets must never be created through ductape_execute. Use ductape_cli for products,
|
|
2738
2705
|
apps, supported resources, broker topics, cloud connections, secrets, and declarative apply flows.
|
|
2739
|
-
App actions, auths,
|
|
2740
|
-
has no command must be configured in Workbench.
|
|
2741
|
-
|
|
2706
|
+
App actions, auths, quotas, fallbacks, health checks, and other administrative assets for which
|
|
2707
|
+
the CLI has no command must be configured in Workbench. Feature definitions are the exception:
|
|
2708
|
+
they are code-first via features.define, are registered by application boot/runtime initialization,
|
|
2709
|
+
and cannot be created with the CLI. Do not generate a Workbench-only or publishable-key
|
|
2710
|
+
create/update call for a Feature. For a missing database action, configure it in Workbench, then verify it exists.
|
|
2742
2711
|
For a missing child feature, recursively apply this same workflow.
|
|
2743
2712
|
Tell the user what you are about to create before each tool call.
|
|
2744
2713
|
|
|
@@ -2847,7 +2816,9 @@ When you call features.define({ handler }), the handler runs TWICE:
|
|
|
2847
2816
|
Features do NOT execute arbitrary NestJS or server code directly. A feature handler can only
|
|
2848
2817
|
call Ductape component primitives (ctx.api, ctx.database, ctx.notification, etc.) as steps.
|
|
2849
2818
|
To invoke internal application business logic, produce a broker event from a feature step
|
|
2850
|
-
(ctx.
|
|
2819
|
+
(ctx.events.produce in the currently published SDK) and consume it in your NestJS service.
|
|
2820
|
+
ctx.publish is deprecated; do not use it. Do not assume a ctx.messaging alias exists unless the
|
|
2821
|
+
installed SDK types explicitly expose it.
|
|
2851
2822
|
|
|
2852
2823
|
━━━ ORCHESTRATION DECISION RULE ━━━
|
|
2853
2824
|
|
|
@@ -2861,7 +2832,7 @@ When you call features.define({ handler }), the handler runs TWICE:
|
|
|
2861
2832
|
→ define a Feature, then features.dispatch to schedule it
|
|
2862
2833
|
|
|
2863
2834
|
Invoke internal application business logic (your own NestJS/backend service code):
|
|
2864
|
-
→ produce a broker event (ctx.
|
|
2835
|
+
→ produce a broker event (ctx.events.produce inside a Feature, or ductape.events.produce outside it)
|
|
2865
2836
|
→ follow ductape_docs({ topic: "events" }) and use the canonical NestJS decorator:
|
|
2866
2837
|
@Events.Consumer({ event: "broker-tag:topic-tag" })
|
|
2867
2838
|
async handle(message: MessageShape) { /* injected-service business logic; throw to nack */ }
|
|
@@ -3804,8 +3775,9 @@ const cliInputSchema = z.object({
|
|
|
3804
3775
|
'Use this tool for administrative operations: creating or updating products, apps, ' +
|
|
3805
3776
|
'resources (databases, storage, caches…), event broker topics, cloud connections, secrets, ' +
|
|
3806
3777
|
'and for apply/migrate workflows.\n\n' +
|
|
3807
|
-
'Note: environments, app actions,
|
|
3808
|
-
'
|
|
3778
|
+
'Note: environments, app actions, quotas, fallbacks, and jobs are configured in the ' +
|
|
3779
|
+
'Workbench UI. Features also have no CLI creation command: define them in application code ' +
|
|
3780
|
+
'with features.define so application boot/runtime registration makes them available.\n\n' +
|
|
3809
3781
|
'The CLI uses the user\'s local logged-in session (ductape login) — no key is required.',
|
|
3810
3782
|
),
|
|
3811
3783
|
});
|
|
@@ -4025,8 +3997,9 @@ async function main() {
|
|
|
4025
3997
|
if (!key) {
|
|
4026
3998
|
throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
|
|
4027
3999
|
}
|
|
4000
|
+
const includeSession = args.include_session ?? args.execution_context !== 'system';
|
|
4028
4001
|
const result = addSessionAwarenessMetadata(
|
|
4029
|
-
await generateExecutablePayload({ ...args, publishable_key: key }),
|
|
4002
|
+
await generateExecutablePayload({ ...args, include_session: includeSession, publishable_key: key }),
|
|
4030
4003
|
args,
|
|
4031
4004
|
);
|
|
4032
4005
|
|
|
@@ -4060,8 +4033,9 @@ async function main() {
|
|
|
4060
4033
|
throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
|
|
4061
4034
|
}
|
|
4062
4035
|
ensureSupportedSnippetOperation(args.operation_family, args.method);
|
|
4036
|
+
const includeSession = args.include_session ?? args.execution_context !== 'system';
|
|
4063
4037
|
const generated = addSessionAwarenessMetadata(
|
|
4064
|
-
await generateExecutablePayload({ ...args, publishable_key: key }),
|
|
4038
|
+
await generateExecutablePayload({ ...args, include_session: includeSession, publishable_key: key }),
|
|
4065
4039
|
args,
|
|
4066
4040
|
);
|
|
4067
4041
|
const payload = (generated as any)?.payload ?? {};
|
|
@@ -4272,8 +4246,9 @@ async function main() {
|
|
|
4272
4246
|
' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
|
|
4273
4247
|
' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
|
|
4274
4248
|
' - Running database migrations: "db migrate", "db schema generate"\n\n' +
|
|
4275
|
-
'NOTE: Environments, app actions, auths,
|
|
4276
|
-
'
|
|
4249
|
+
'NOTE: Environments, app actions, auths, quotas, fallbacks, and jobs are configured ' +
|
|
4250
|
+
'in the Workbench UI. Features have no CLI creation command because definitions are ' +
|
|
4251
|
+
'code-first through features.define and registered by the application runtime.\n\n' +
|
|
4277
4252
|
'DO NOT use ductape_execute for admin operations — it uses a publishable key which only ' +
|
|
4278
4253
|
'covers runtime operations. Administrative operations will fail with "Authentication failed".\n\n' +
|
|
4279
4254
|
'The CLI uses the user\'s local logged-in session (ductape login). ' +
|
package/src/proxy-client.ts
CHANGED
|
@@ -52,7 +52,7 @@ export async function executeViaProxy<T = unknown>(
|
|
|
52
52
|
},
|
|
53
53
|
body: JSON.stringify({
|
|
54
54
|
publishable_key,
|
|
55
|
-
module,
|
|
55
|
+
module: module === 'features' ? 'feature' : module,
|
|
56
56
|
method,
|
|
57
57
|
params,
|
|
58
58
|
}),
|
|
@@ -137,9 +137,18 @@ 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
|
-
|
|
145
|
+
...backendRequest,
|
|
146
|
+
operation_family: backendRequest.operation_family.toLowerCase() === 'features'
|
|
147
|
+
? 'feature'
|
|
148
|
+
: backendRequest.operation_family,
|
|
149
|
+
targets: backendRequest.targets
|
|
150
|
+
? normalizeTargets(backendRequest.targets as Record<string, unknown>)
|
|
151
|
+
: backendRequest.targets,
|
|
143
152
|
};
|
|
144
153
|
const url = `${API_BASE_URL.replace(/\/$/, '')}/integrations/v1/payloads/generate`;
|
|
145
154
|
const res = await fetch(url, {
|