@ductape/mcp 0.1.34 → 0.1.36

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.
Files changed (3) hide show
  1. package/dist/index.js +99 -37
  2. package/package.json +1 -1
  3. package/src/index.ts +98 -36
package/dist/index.js CHANGED
@@ -722,7 +722,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
722
722
  handler: async (ctx) => {
723
723
  // ctx.input – typed feature input
724
724
  // ctx.step(tag, fn, rollback?, opts?) – define a durable step
725
- // ctx.action.run({ app, event, input }) – call an app action
725
+ // ctx.api.run({ app, event, input }) – call an app action
726
726
  // ctx.database.query/insert/update/delete({ database, event, ... })
727
727
  // ctx.graph.execute({ graph, action, input })
728
728
  // ctx.notification.send/email/push/sms({ notification, event, ... })
@@ -2069,34 +2069,109 @@ A feature is an orchestrated workflow of durable steps. Steps can call app actio
2069
2069
  operations, graph queries, storage uploads, notifications, broker publishes, child features,
2070
2070
  quotas, fallbacks, and more. Features support rollback, signals, checkpoints, and sleep.
2071
2071
 
2072
+ ━━━ AI DESIGN WORKFLOW — follow this process every time a user asks you to build or plan a feature ━━━
2073
+
2074
+ ALWAYS use features.define (code-first). NEVER use features.create.
2075
+ The feature handler is real code written into the project's source files — find the project's
2076
+ language and framework, write the feature into an appropriate file in the codebase, and call
2077
+ features.define from there. Do not generate inline snippets and stop — write the actual file.
2078
+
2079
+ STEP 1 — UNDERSTAND the goal
2080
+ Ask clarifying questions if the user's intent is unclear. Do not start designing until you
2081
+ understand: what the feature does, what it returns, what can fail and how failures should behave.
2082
+
2083
+ STEP 2 — INVENTORY existing Ductape components
2084
+ Call ductape_execute("products.fetch", [product_tag]) to read the product.
2085
+ Note what already exists:
2086
+ - databases[] → available for ctx.database.insert/query/update/delete steps
2087
+ - apps[] → available for ctx.api.run steps (check app.events[] for event tags)
2088
+ - notifications[] → available for ctx.notification.email/sms/push steps
2089
+ - storage[] → available for ctx.storage.upload/download steps
2090
+ - messageBrokers[] → available for ctx.messaging.produce steps
2091
+ - graphs[] → available for ctx.graph steps
2092
+ - features[] → can be called as child features via ctx.feature()
2093
+ - caches[], sessions[]
2094
+ Do NOT assume a component or event tag exists — verify from the product before using it.
2095
+
2096
+ STEP 3 — PLAN each step
2097
+ For every logical step:
2098
+ a. Identify which existing component handles it, or flag it as needing creation
2099
+ b. Note how inputs flow: ctx.input fields, or return values from earlier steps (plain JS variables — no special notation needed)
2100
+ c. Decide if a rollback handler is needed (e.g. charge → refund on later failure)
2101
+ d. Decide allow_fail: true for non-critical steps (email, analytics, audit logs)
2102
+
2103
+ STEP 4 — PRESENT the plan and get approval BEFORE writing any code or creating anything
2104
+ Show the user:
2105
+ Feature: <name> (<tag>)
2106
+ Input fields: list them with types
2107
+ Steps (in order):
2108
+ 1. <step-tag> — <what it does> — <component> [rollback: yes/no] [allow_fail: yes/no]
2109
+ 2. ...
2110
+ Return value: describe what the handler returns
2111
+ Components to create: list anything missing with a short description
2112
+ Ask: "Should I proceed? Shall I create the missing components first?"
2113
+ Wait for confirmation before writing code or calling any create tool.
2114
+
2115
+ STEP 5 — CREATE missing components (only with user approval)
2116
+ Use ductape_execute for any missing databases, apps, database actions, notification events, etc.
2117
+ For a missing database action:
2118
+ ductape_execute("databases.action.create", [product_tag, db_tag, { tag, name, type, query }])
2119
+ For a missing child feature, recursively apply this same workflow.
2120
+ Tell the user what you are about to create before each tool call.
2121
+
2122
+ STEP 6 — WRITE the feature into the project codebase
2123
+ - Locate or create an appropriate file in the project (e.g. src/features/feature-name.ts, features/feature_name.py)
2124
+ - Use features.define with an async handler
2125
+ - Step results are plain variables — just await ctx.step(...) and use the return value in the next step
2126
+ - No special notation needed: const user = await ctx.step('create-user', async () => { ... }); then use user.id directly in the next step
2127
+ - Write rollback handlers inline as the third argument to ctx.step()
2128
+ - Return a plain object as the feature's output
2129
+
2130
+ STEP 7 — HANDLE conditionals, loops, and branching (when applicable)
2131
+ Branch on step result (early return in handler):
2132
+ → Add branchOverrides: { stepTag: { field: value } } so all branches are recorded
2133
+ → At runtime the executor evaluates the real result and skips or runs later steps accordingly
2134
+ Loop over input array:
2135
+ → Add recordInput: { items: [{ id: "1" }, { id: "2" }] } so the loop runs during recording
2136
+ → Use unique step tags per iteration (e.g. "process-" + item.id)
2137
+ Switch/if-else on feature input values:
2138
+ → Add recordScenarios: [{ type: "a" }, { type: "b" }] — handler runs once per scenario
2139
+ → Only the scenario whose input matches runs at execution time
2140
+
2141
+ STEP 8 — SET rollbacks for reversible steps
2142
+ Any step that allocates a resource should undo it if a later step fails:
2143
+ const charge = await ctx.step(
2144
+ 'charge',
2145
+ async () => ctx.api.run({ app: 'stripe', event: 'create-charge', input: { amount: ctx.input.amount } }),
2146
+ async (result) => ctx.api.run({ app: 'stripe', event: 'refund', input: { chargeId: result.id } })
2147
+ );
2148
+
2072
2149
  Step types: action | database | graph | notification | storage | produce | quota | fallback |
2073
2150
  vector | child_feature | sleep | wait_for_signal | checkpoint
2074
2151
 
2075
- Create a feature (adminductape_execute):
2076
- ductape_execute("features.create", [product_tag, {
2152
+ Define a feature (write this into the project's source files do NOT use features.create):
2153
+ // src/features/onboard-user.ts (or the equivalent path/language for the project)
2154
+ await ductape.features.define({
2077
2155
  tag: "onboard-user",
2078
2156
  name: "Onboard User",
2079
- input: { userId: { type: "string", required: true }, email: { type: "string" } },
2080
- steps: [
2081
- {
2082
- tag: "create-account",
2083
- type: "database",
2084
- database: "core-db",
2085
- event: "insert-user",
2086
- input: { "body:userId": "$Input{userId}", "body:email": "$Input{email}" },
2087
- options: { retries: 2, timeout: 5000 },
2088
- },
2089
- {
2090
- tag: "send-welcome",
2091
- type: "notification",
2092
- notification: "welcome-email",
2093
- event: "welcome-email:default",
2094
- input: { "body:email": "$Input{email}" },
2095
- dependsOn: ["create-account"],
2096
- options: { allow_fail: true }, // welcome email failure won't abort the feature
2097
- },
2098
- ],
2099
- }])
2157
+ handler: async (ctx) => {
2158
+ const account = await ctx.step("create-account", async () =>
2159
+ ctx.database.insert({ database: "core-db", event: "insert-user",
2160
+ data: { userId: ctx.input.userId, email: ctx.input.email } }),
2161
+ async (result) => ctx.database.delete({ database: "core-db", event: "delete-user",
2162
+ where: { id: result.id } }) // rollback
2163
+ );
2164
+
2165
+ await ctx.step("send-welcome", async () =>
2166
+ ctx.notification.email({ notification: "welcome-email", event: "welcome",
2167
+ recipients: [ctx.input.email], subject: {}, template: {} }),
2168
+ null,
2169
+ { allow_fail: true }
2170
+ );
2171
+
2172
+ return { userId: account.id };
2173
+ },
2174
+ });
2100
2175
 
2101
2176
  Execute at runtime:
2102
2177
  → CALL ductape_generate_payload FIRST (operation_family="features", method="execute", targets={tag})
@@ -2122,21 +2197,8 @@ Signals and queries (for long-running features):
2122
2197
  features.signal [{ product, env, feature_id, signal: "payment-confirmed", payload? }]
2123
2198
  features.query [{ product, env, feature_id, query: "current-status", params? }]
2124
2199
 
2125
- Step input references:
2126
- $Input{field} → map from feature's declared input
2127
- $Step{stepTag}{field} → output field from a prior step
2128
- $StepOutput{field} → current step's own return value
2129
- $Concat([...parts], delim) → string interpolation
2130
-
2131
2200
  Rollback strategies: reverse_all | reverse_critical | compensate | none
2132
2201
  Feature statuses: pending | running | completed | failed | rolled_back | rolling_back | paused
2133
-
2134
- Code-first (define API — compiles async handler to JSON step schema):
2135
- features.define({ tag, name, input, handler: async (ctx) => { ... } })
2136
- ctx provides: ctx.step(), ctx.action.run(), ctx.database.query/insert(),
2137
- ctx.graph.execute(), ctx.notification.send(), ctx.storage.upload(),
2138
- ctx.messaging.produce(), ctx.quota.execute(), ctx.fallback.execute(),
2139
- ctx.sleep(), ctx.waitForSignal(), ctx.setState/getState(), ctx.feature()
2140
2202
  `.trim(),
2141
2203
  events: `
2142
2204
  DUCTAPE EVENTS (MESSAGE BROKERS)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.1.34",
3
+ "version": "0.1.36",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
package/src/index.ts CHANGED
@@ -2141,34 +2141,109 @@ A feature is an orchestrated workflow of durable steps. Steps can call app actio
2141
2141
  operations, graph queries, storage uploads, notifications, broker publishes, child features,
2142
2142
  quotas, fallbacks, and more. Features support rollback, signals, checkpoints, and sleep.
2143
2143
 
2144
+ ━━━ AI DESIGN WORKFLOW — follow this process every time a user asks you to build or plan a feature ━━━
2145
+
2146
+ ALWAYS use features.define (code-first). NEVER use features.create.
2147
+ The feature handler is real code written into the project's source files — find the project's
2148
+ language and framework, write the feature into an appropriate file in the codebase, and call
2149
+ features.define from there. Do not generate inline snippets and stop — write the actual file.
2150
+
2151
+ STEP 1 — UNDERSTAND the goal
2152
+ Ask clarifying questions if the user's intent is unclear. Do not start designing until you
2153
+ understand: what the feature does, what it returns, what can fail and how failures should behave.
2154
+
2155
+ STEP 2 — INVENTORY existing Ductape components
2156
+ Call ductape_execute("products.fetch", [product_tag]) to read the product.
2157
+ Note what already exists:
2158
+ - databases[] → available for ctx.database.insert/query/update/delete steps
2159
+ - apps[] → available for ctx.action.run steps (check app.events[] for event tags)
2160
+ - notifications[] → available for ctx.notification.email/sms/push steps
2161
+ - storage[] → available for ctx.storage.upload/download steps
2162
+ - messageBrokers[] → available for ctx.messaging.produce steps
2163
+ - graphs[] → available for ctx.graph steps
2164
+ - features[] → can be called as child features via ctx.feature()
2165
+ - caches[], sessions[]
2166
+ Do NOT assume a component or event tag exists — verify from the product before using it.
2167
+
2168
+ STEP 3 — PLAN each step
2169
+ For every logical step:
2170
+ a. Identify which existing component handles it, or flag it as needing creation
2171
+ b. Note how inputs flow: ctx.input fields, or return values from earlier steps (plain JS variables — no special notation needed)
2172
+ c. Decide if a rollback handler is needed (e.g. charge → refund on later failure)
2173
+ d. Decide allow_fail: true for non-critical steps (email, analytics, audit logs)
2174
+
2175
+ STEP 4 — PRESENT the plan and get approval BEFORE writing any code or creating anything
2176
+ Show the user:
2177
+ Feature: <name> (<tag>)
2178
+ Input fields: list them with types
2179
+ Steps (in order):
2180
+ 1. <step-tag> — <what it does> — <component> [rollback: yes/no] [allow_fail: yes/no]
2181
+ 2. ...
2182
+ Return value: describe what the handler returns
2183
+ Components to create: list anything missing with a short description
2184
+ Ask: "Should I proceed? Shall I create the missing components first?"
2185
+ Wait for confirmation before writing code or calling any create tool.
2186
+
2187
+ STEP 5 — CREATE missing components (only with user approval)
2188
+ Use ductape_execute for any missing databases, apps, database actions, notification events, etc.
2189
+ For a missing database action:
2190
+ ductape_execute("databases.action.create", [product_tag, db_tag, { tag, name, type, query }])
2191
+ For a missing child feature, recursively apply this same workflow.
2192
+ Tell the user what you are about to create before each tool call.
2193
+
2194
+ STEP 6 — WRITE the feature into the project codebase
2195
+ - Locate or create an appropriate file in the project (e.g. src/features/feature-name.ts, features/feature_name.py)
2196
+ - Use features.define with an async handler
2197
+ - Step results are plain variables — just await ctx.step(...) and use the return value in the next step
2198
+ - No special notation needed: const user = await ctx.step('create-user', async () => { ... }); then use user.id directly in the next step
2199
+ - Write rollback handlers inline as the third argument to ctx.step()
2200
+ - Return a plain object as the feature's output
2201
+
2202
+ STEP 7 — HANDLE conditionals, loops, and branching (when applicable)
2203
+ Branch on step result (early return in handler):
2204
+ → Add branchOverrides: { stepTag: { field: value } } so all branches are recorded
2205
+ → At runtime the executor evaluates the real result and skips or runs later steps accordingly
2206
+ Loop over input array:
2207
+ → Add recordInput: { items: [{ id: "1" }, { id: "2" }] } so the loop runs during recording
2208
+ → Use unique step tags per iteration (e.g. "process-" + item.id)
2209
+ Switch/if-else on feature input values:
2210
+ → Add recordScenarios: [{ type: "a" }, { type: "b" }] — handler runs once per scenario
2211
+ → Only the scenario whose input matches runs at execution time
2212
+
2213
+ STEP 8 — SET rollbacks for reversible steps
2214
+ Any step that allocates a resource should undo it if a later step fails:
2215
+ const charge = await ctx.step(
2216
+ 'charge',
2217
+ async () => ctx.action.run({ app: 'stripe', event: 'create-charge', input: { amount: ctx.input.amount } }),
2218
+ async (result) => ctx.action.run({ app: 'stripe', event: 'refund', input: { chargeId: result.id } })
2219
+ );
2220
+
2144
2221
  Step types: action | database | graph | notification | storage | produce | quota | fallback |
2145
2222
  vector | child_feature | sleep | wait_for_signal | checkpoint
2146
2223
 
2147
- Create a feature (adminductape_execute):
2148
- ductape_execute("features.create", [product_tag, {
2224
+ Define a feature (write this into the project's source files do NOT use features.create):
2225
+ // src/features/onboard-user.ts (or the equivalent path/language for the project)
2226
+ await ductape.features.define({
2149
2227
  tag: "onboard-user",
2150
2228
  name: "Onboard User",
2151
- input: { userId: { type: "string", required: true }, email: { type: "string" } },
2152
- steps: [
2153
- {
2154
- tag: "create-account",
2155
- type: "database",
2156
- database: "core-db",
2157
- event: "insert-user",
2158
- input: { "body:userId": "$Input{userId}", "body:email": "$Input{email}" },
2159
- options: { retries: 2, timeout: 5000 },
2160
- },
2161
- {
2162
- tag: "send-welcome",
2163
- type: "notification",
2164
- notification: "welcome-email",
2165
- event: "welcome-email:default",
2166
- input: { "body:email": "$Input{email}" },
2167
- dependsOn: ["create-account"],
2168
- options: { allow_fail: true }, // welcome email failure won't abort the feature
2169
- },
2170
- ],
2171
- }])
2229
+ handler: async (ctx) => {
2230
+ const account = await ctx.step("create-account", async () =>
2231
+ ctx.database.insert({ database: "core-db", event: "insert-user",
2232
+ data: { userId: ctx.input.userId, email: ctx.input.email } }),
2233
+ async (result) => ctx.database.delete({ database: "core-db", event: "delete-user",
2234
+ where: { id: result.id } }) // rollback
2235
+ );
2236
+
2237
+ await ctx.step("send-welcome", async () =>
2238
+ ctx.notification.email({ notification: "welcome-email", event: "welcome",
2239
+ recipients: [ctx.input.email], subject: {}, template: {} }),
2240
+ null,
2241
+ { allow_fail: true }
2242
+ );
2243
+
2244
+ return { userId: account.id };
2245
+ },
2246
+ });
2172
2247
 
2173
2248
  Execute at runtime:
2174
2249
  → CALL ductape_generate_payload FIRST (operation_family="features", method="execute", targets={tag})
@@ -2194,21 +2269,8 @@ Signals and queries (for long-running features):
2194
2269
  features.signal [{ product, env, feature_id, signal: "payment-confirmed", payload? }]
2195
2270
  features.query [{ product, env, feature_id, query: "current-status", params? }]
2196
2271
 
2197
- Step input references:
2198
- $Input{field} → map from feature's declared input
2199
- $Step{stepTag}{field} → output field from a prior step
2200
- $StepOutput{field} → current step's own return value
2201
- $Concat([...parts], delim) → string interpolation
2202
-
2203
2272
  Rollback strategies: reverse_all | reverse_critical | compensate | none
2204
2273
  Feature statuses: pending | running | completed | failed | rolled_back | rolling_back | paused
2205
-
2206
- Code-first (define API — compiles async handler to JSON step schema):
2207
- features.define({ tag, name, input, handler: async (ctx) => { ... } })
2208
- ctx provides: ctx.step(), ctx.action.run(), ctx.database.query/insert(),
2209
- ctx.graph.execute(), ctx.notification.send(), ctx.storage.upload(),
2210
- ctx.messaging.produce(), ctx.quota.execute(), ctx.fallback.execute(),
2211
- ctx.sleep(), ctx.waitForSignal(), ctx.setState/getState(), ctx.feature()
2212
2274
  `.trim(),
2213
2275
 
2214
2276
  events: `