@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 CHANGED
@@ -123,8 +123,9 @@ There are THREE categories of operations. Use the right tool for each:
123
123
  ductape_cli("resources storage list")
124
124
  ductape_cli("resources database create -f db-config.json")
125
125
  This applies to: products, apps, and resources (databases, storage, caches, etc.),
126
- cloud connections, and secrets. Environments, app actions, auths, features, quotas,
127
- fallbacks, jobs, and healthchecks are configured in the Workbench UI.
126
+ cloud connections, and secrets. Environments, app actions, auths, quotas, fallbacks,
127
+ jobs, and healthchecks are configured in the Workbench UI. Features have no CLI create
128
+ command because their definitions are code-first through features.define.
128
129
 
129
130
  ⚠ MULTI-ENV REQUIREMENT — applies to ALL product assets (storage, database, cache,
130
131
  messageBroker, graph, vector, and any other resource with an envs array):
@@ -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.create [product_tag, data: {
726
- tag: string,
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.messaging.produce({ event: "broker:topic", message: {} })
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().default(true).describe('Include a session placeholder inside the generated input object. ' +
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 !== false;
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: 30000,
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, Features, quotas, fallbacks, health checks, and other assets for which the CLI
2654
- has no command must be configured in Workbench. Do not generate an impossible publishable-key
2655
- create/update call. For a missing database action, configure it in Workbench, then verify it exists.
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.messaging.produce) and consume it in your NestJS service — that is the correct pattern.
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.messaging.produce or ductape.events.produce)
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, features, quotas, fallbacks, and jobs are configured ' +
3714
- 'in the Workbench UI there are no CLI commands for them.\n\n' +
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 result = addSessionAwarenessMetadata(await generateExecutablePayload({ ...args, publishable_key: key }), args);
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 generated = addSessionAwarenessMetadata(await generateExecutablePayload({ ...args, publishable_key: key }), args);
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, features, quotas, fallbacks, and jobs are ' +
4115
- 'configured in the Workbench UI the CLI does not have commands for them.\n\n' +
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,CAwBZ"}
1
+ {"version":3,"file":"proxy-client.d.ts","sourceRoot":"","sources":["../src/proxy-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,YAAY,4BAA4B,CAAC;AAEtD,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,KAAK,GACL,WAAW,GACX,OAAO,GACP,UAAU,GACV,eAAe,GACf,gBAAgB,GAChB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,SAAS,GACT,UAAU,GACV,MAAM,GACN,MAAM,GACN,YAAY,GACZ,QAAQ,GACR,UAAU,GACV,SAAS,CAAC;AAUd;;GAEG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,OAAO,EAC/C,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,OAAO,EAAO,GACrB,OAAO,CAAC,CAAC,CAAC,CAuBZ;AAED,MAAM,WAAW,iCAAiC;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACpD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AASD,wBAAsB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYvE;AA4BD,wBAAsB,yBAAyB,CAAC,CAAC,GAAG,kCAAkC,EACpF,OAAO,EAAE,iCAAiC,GACzC,OAAO,CAAC,CAAC,CAAC,CAiCZ"}
@@ -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
- ...request,
72
- targets: request.targets ? normalizeTargets(request.targets) : request.targets,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.1.58",
3
+ "version": "0.1.60",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -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, features, quotas,
138
- fallbacks, jobs, and healthchecks are configured in the Workbench UI.
137
+ cloud connections, and secrets. Environments, app actions, auths, quotas, fallbacks,
138
+ jobs, and healthchecks are configured in the Workbench UI. Features have no CLI create
139
+ command because their definitions are code-first through features.define.
139
140
 
140
141
  ⚠ MULTI-ENV REQUIREMENT — applies to ALL product assets (storage, database, cache,
141
142
  messageBroker, graph, vector, and any other resource with an envs array):
@@ -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.create [product_tag, data: {
737
- tag: string,
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.messaging.produce({ event: "broker:topic", message: {} })
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().default(true).describe(
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 !== false;
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: 30000,
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, Features, quotas, fallbacks, health checks, and other assets for which the CLI
2740
- has no command must be configured in Workbench. Do not generate an impossible publishable-key
2741
- create/update call. For a missing database action, configure it in Workbench, then verify it exists.
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.messaging.produce) and consume it in your NestJS service — that is the correct pattern.
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.messaging.produce or ductape.events.produce)
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, features, quotas, fallbacks, and jobs are configured ' +
3808
- 'in the Workbench UI there are no CLI commands for them.\n\n' +
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, features, quotas, fallbacks, and jobs are ' +
4276
- 'configured in the Workbench UI the CLI does not have commands for them.\n\n' +
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). ' +
@@ -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
- ...request,
142
- targets: request.targets ? normalizeTargets(request.targets as Record<string, unknown>) : request.targets,
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, {