@ductape/mcp 0.1.0 → 0.1.1

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.d.ts CHANGED
@@ -5,7 +5,9 @@
5
5
  * Exposes Ductape SDK operations as MCP tools by calling the backend proxy.
6
6
  *
7
7
  * Authentication:
8
- * Requires passing a `publishable_key` with every `ductape_execute` payload.
8
+ * Set DUCTAPE_PUBLISHABLE_KEY in the MCP server's env config to avoid passing
9
+ * publishable_key on every tool call. Per-call publishable_key still overrides
10
+ * the env var when provided.
9
11
  */
10
12
  export {};
11
13
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;GAOG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;GASG"}
package/dist/index.js CHANGED
@@ -5,10 +5,12 @@
5
5
  * Exposes Ductape SDK operations as MCP tools by calling the backend proxy.
6
6
  *
7
7
  * Authentication:
8
- * Requires passing a `publishable_key` with every `ductape_execute` payload.
8
+ * Set DUCTAPE_PUBLISHABLE_KEY in the MCP server's env config to avoid passing
9
+ * publishable_key on every tool call. Per-call publishable_key still overrides
10
+ * the env var when provided.
9
11
  */
10
12
  import { z } from 'zod';
11
- import { executeViaProxy, generateExecutablePayload, } from './proxy-client.js';
13
+ import { executeViaProxy, generateExecutablePayload, getAssetSchemas, } from './proxy-client.js';
12
14
  const MODULES = [
13
15
  'product', 'app', 'databases', 'graph', 'webhooks', 'notifications',
14
16
  'messageBrokers', 'storage', 'vector', 'caches', 'sessions', 'quotas',
@@ -19,6 +21,32 @@ const MODULES = [
19
21
  // Each entry follows: [module].[method] → params array signature.
20
22
  // ─────────────────────────────────────────────────────────────────────────────
21
23
  const METHOD_DOCS = `
24
+ ━━━ TOOL SELECTION GUIDE ━━━
25
+
26
+ There are two categories of SDK operations. Use the right tool for each:
27
+
28
+ 1. ASSET CREATION / UPDATE (create, update, add, register…)
29
+ Input shape is fixed by the SDK's Joi validators.
30
+ → Call ductape_schema first to discover required fields and enum values.
31
+ → Then call ductape_execute with the filled params.
32
+ EXCEPTION — features.create / quotas.create / fallback.create have TWO distinct input fields:
33
+ a) Top-level "input": a SCHEMA DECLARATION — defines what fields the asset accepts at runtime.
34
+ Construct this yourself based on what the caller will provide. Do NOT use ductape_generate_payload for this.
35
+ b) options[].input (quotas/fallbacks) or steps[].input (features): maps declared inputs → the underlying action's fields.
36
+ ← CALL ductape_generate_payload for the target action to discover what fields it accepts,
37
+ then wire them using "$Input{fieldName}" or "$Step{stepTag}{field}" references.
38
+
39
+ 2. RUNTIME OPERATIONS (run, dispatch, execute, start, send, produce, query, insert, update, delete…)
40
+ The "input" field shape is product- and operation-specific — it is NOT derivable from Joi validators.
41
+ It is defined by how the product's action/feature/session/quota/etc. was configured in Ductape.
42
+ → ALWAYS call ductape_generate_payload first to get the canonical payload template.
43
+ → The template shows you exactly which input keys are expected and their types/defaults.
44
+ → Then fill in the values and pass the completed payload to ductape_execute.
45
+
46
+ Skipping ductape_generate_payload for runtime operations will produce incorrect or empty input payloads.
47
+
48
+ ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
49
+
22
50
  ALL params are passed as a JSON array in positional order matching the SDK signature.
23
51
 
24
52
  ━━━ MODULE: product ━━━
@@ -47,8 +75,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
47
75
  actions.update [app_tag, action_tag, data: { resource?: string, method?: "GET"|"POST"|"PUT"|"PATCH"|"DELETE", description?: string, request_type?: "json"|"xml"|"form", body?: object, query?: object, params?: object, headers?: object, response?: { name?: string, success: boolean, body: object, response_format: "json"|"xml"|"form", status_code: number } }]
48
76
  actions.fetch [app_tag, action_tag]
49
77
  actions.list [app_tag]
50
- actions.run [{ product, env, app, action, input: { "body:fieldName": value, "headers:Authorization": "Bearer token", "params:id": "123", "query:limit": 10 } }]
51
- actions.dispatch [{ product, env, app, action, input, retries?, session?, cache?, schedule?: { start_at?, cron?, every?, limit?, tz? } }]
78
+ actions.run [{ product, env, app, action, input: { "body:fieldName": value, ... } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="run", targets={app, action})
79
+ actions.dispatch [{ product, env, app, action, input, retries?, session?, cache?, schedule?: { start_at?, cron?, every?, limit?, tz? } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="dispatch")
52
80
 
53
81
  ━━━ MODULE: auths ━━━
54
82
  auths.create [app_tag, data: { tag: string, name: string, setup_type: "header"|"bearer"|"basic"|"oauth2"|"apikey", expiry: number, period: "seconds"|"minutes"|"hours"|"days", description: string, action_tag?: string }]
@@ -72,7 +100,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
72
100
  sessions.fetch [product_tag, session_tag]
73
101
  sessions.list [product_tag]
74
102
  sessions.delete [product_tag, session_tag]
75
- sessions.start [{ product, env, tag, data: { key: value } }]
103
+ sessions.start [{ product, env, tag, data: { key: value } }] ← CALL ductape_generate_payload FIRST (operation_family="session", method="start", targets={tag}) to discover the data field shape
76
104
  sessions.verify [{ product, env, tag, token }]
77
105
  sessions.refresh [{ product, env, tag, refreshToken }]
78
106
  sessions.revoke [{ product, env, tag, sessionId?, identifier? }]
@@ -86,13 +114,18 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
86
114
  tag: string, // unique identifier
87
115
  name?: string, // display name
88
116
  description?: string,
89
- input: { // declare the input fields this quota expects
117
+
118
+ // INPUT SCHEMA (top-level): declares what fields the quota accepts when called at runtime.
119
+ // These are the fields callers will pass to quotas.run / quotas.dispatch.
120
+ // Construct this yourself — it is a schema declaration, not a runtime value.
121
+ input: {
90
122
  fieldName: {
91
123
  type: "string"|"number"|"boolean"|"object"|"array",
92
124
  required?: boolean,
93
125
  description?: string
94
126
  }
95
127
  },
128
+
96
129
  options: [ // list of providers tried in order
97
130
  {
98
131
  provider?: string, // friendly name for this provider slot
@@ -101,7 +134,12 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
101
134
  event: string, // action tag on that app
102
135
  quota: number, // max allowed uses for this provider
103
136
  uses?: number, // current use count (usually 0 at creation)
104
- input: { "body:field": "$Input{fieldName}" }, // maps quota input → action input
137
+
138
+ // OPTIONS INPUT (per provider): maps the quota's declared input fields → the underlying action's fields.
139
+ // ← CALL ductape_generate_payload (operation_family="action", method="run", targets={app, event})
140
+ // to discover what fields the action accepts, then wire them with "$Input{fieldName}" references.
141
+ input: { "body:field": "$Input{fieldName}" },
142
+
105
143
  output: {}, // expected output shape (can be {})
106
144
  retries: number,
107
145
  healthcheck?: string, // optional healthcheck tag to gate this provider
@@ -113,28 +151,38 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
113
151
  quotas.fetch [product_tag, quota_tag]
114
152
  quotas.list [product_tag]
115
153
  quotas.delete [product_tag, quota_tag]
116
- quotas.run [{ product: string, env: string, tag: string, input: { fieldName: value }, session?: string, cache?: string }]
117
- quotas.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }]
154
+ quotas.run [{ product: string, env: string, tag: string, input: { fieldName: value }, session?: string, cache?: string }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="run", targets={tag})
155
+ quotas.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="quota", method="dispatch")
118
156
 
119
157
  ━━━ MODULE: fallback ━━━
120
158
  fallback.create [product_tag, data: {
121
159
  tag: string,
122
160
  name?: string,
123
161
  description?: string,
124
- input: { // input fields this fallback expects
162
+
163
+ // INPUT SCHEMA (top-level): declares what fields the fallback accepts when called at runtime.
164
+ // These are the fields callers will pass to fallback.run / fallback.dispatch.
165
+ // Construct this yourself — it is a schema declaration, not a runtime value.
166
+ input: {
125
167
  fieldName: {
126
168
  type: "string"|"number"|"boolean"|"object"|"array",
127
169
  required?: boolean,
128
170
  description?: string
129
171
  }
130
172
  },
173
+
131
174
  options: [ // ordered list: primary first, then fallback(s)
132
175
  {
133
176
  provider?: string, // friendly name e.g. "primary", "backup"
134
177
  app: string, // app tag
135
178
  type: "action",
136
179
  event: string, // action tag
180
+
181
+ // OPTIONS INPUT (per provider): maps the fallback's declared input fields → the underlying action's fields.
182
+ // ← CALL ductape_generate_payload (operation_family="action", method="run", targets={app, event})
183
+ // to discover what fields the action accepts, then wire them with "$Input{fieldName}" references.
137
184
  input: { "body:field": "$Input{fieldName}" },
185
+
138
186
  output: {},
139
187
  retries: number,
140
188
  healthcheck?: string,
@@ -146,12 +194,12 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
146
194
  fallback.fetch [product_tag, fallback_tag]
147
195
  fallback.list [product_tag]
148
196
  fallback.delete [product_tag, fallback_tag]
149
- fallback.run [{ product: string, env: string, tag: string, input: { fieldName: value }, session?: string, cache?: string }]
150
- fallback.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }]
197
+ fallback.run [{ product: string, env: string, tag: string, input: { fieldName: value }, session?: string, cache?: string }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="run", targets={tag})
198
+ fallback.dispatch [{ product: string, env: string, tag: string, input: object, session?: string, cache?: string, schedule?: { cron?: string, delay?: number, at?: string } }] ← CALL ductape_generate_payload FIRST (operation_family="fallback", method="dispatch")
151
199
 
152
200
  ━━━ MODULE: health ━━━
153
- health.create [product_tag, data: { tag: string, name: string, description?: string, app?: string, event?: string, probe?: { type: "app"|"database"|"workflow"|"feature", app?: string, event?: string, input?: object }, interval: number, retries: number, envs: [{ slug: string, input?: object }], onFailure?: { notifications?: [{ notification: string, message: string, channels: { email?: { recipients: string[] }, push?: { recipients?: string[] }, sms?: { recipients: string[] } } }], webhooks?: [{ url: string, method?: "GET"|"POST", headers?: object, body?: object }] } }]
154
- health.update [product_tag, health_tag, data: { name?: string, description?: string, app?: string, event?: string, probe?: { type: "app"|"database"|"workflow"|"feature", app?: string, event?: string, input?: object }, interval?: number, retries?: number, envs?: [{ slug: string, input?: object }], onFailure?: object }]
201
+ health.create [product_tag, data: { tag: string, name: string, description?: string, app?: string, event?: string, probe?: { type: "app"|"database"|"feature", app?: string, event?: string, input?: object }, interval: number, retries: number, envs: [{ slug: string, input?: object }], onFailure?: { notifications?: [{ notification: string, message: string, channels: { email?: { recipients: string[] }, push?: { recipients?: string[] }, sms?: { recipients: string[] } } }], webhooks?: [{ url: string, method?: "GET"|"POST", headers?: object, body?: object }] } }]
202
+ health.update [product_tag, health_tag, data: { name?: string, description?: string, app?: string, event?: string, probe?: { type: "app"|"database"|"feature", app?: string, event?: string, input?: object }, interval?: number, retries?: number, envs?: [{ slug: string, input?: object }], onFailure?: object }]
155
203
  health.fetch [product_tag, health_tag]
156
204
  health.list [product_tag]
157
205
  health.delete [product_tag, health_tag]
@@ -169,12 +217,12 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
169
217
  notifications.messages.update [product_tag, msg_tag, data: { subject?: { template: string, data: object }, body?: { template: string, data: object } }]
170
218
  notifications.messages.fetch [product_tag, msg_tag]
171
219
  notifications.messages.list [product_tag, notification_tag]
172
- notifications.send [{ product, env, event, input: { email?: { recipients, subject, template }, push_notification?: { device_tokens, title, body, data }, sms?: { recipients, body }, callback?: { query, params, body, headers } } }]
173
- notifications.email.send [{ product, env, notification, input: { recipients: string[], subject: object, template: object }, session?, cache? }]
174
- notifications.push.send [{ product, env, notification, input: { device_tokens: string[], title: object, body: object, data: object }, session?, cache? }]
175
- notifications.sms.send [{ product, env, notification, input: { recipients: string[], body: object }, session?, cache? }]
176
- notifications.callback.send [{ product, env, notification, input: { query, params, body, headers }, session?, cache? }]
177
- notifications.dispatch [{ product, env, notification, event, input, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }]
220
+ notifications.send [{ product, env, event, input: { ... } }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="send", targets={notification})
221
+ notifications.email.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="email.send")
222
+ notifications.push.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="push.send")
223
+ notifications.sms.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="sms.send")
224
+ notifications.callback.send [{ product, env, notification, input: { ... }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="callback.send")
225
+ notifications.dispatch [{ product, env, notification, event, input, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] ← CALL ductape_generate_payload FIRST (operation_family="notification", method="dispatch")
178
226
  notifications.getMessages [{ product_tag?, env?, notification_tag?, status?, type?, start_date?, end_date?, page?, limit? }]
179
227
 
180
228
  ━━━ MODULE: messageBrokers ━━━
@@ -187,9 +235,9 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
187
235
  messageBrokers.topics.update [product_tag, topic_tag, data: { name?: string, type?: "producer"|"consumer"|"both" }]
188
236
  messageBrokers.topics.fetch [product_tag, topic_tag]
189
237
  messageBrokers.topics.list [product_tag, broker_tag]
190
- messageBrokers.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }]
238
+ messageBrokers.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }] ← CALL ductape_generate_payload FIRST (operation_family="messaging", method="produce", targets={broker, event})
191
239
  messageBrokers.consume [{ product, env, event: "broker_tag:topic_tag", callback: "function_ref" }]
192
- messageBrokers.dispatch [{ product, env, broker, event, input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }]
240
+ messageBrokers.dispatch [{ product, env, broker, event, input: { message }, retries?, session?, cache?, schedule?: { cron?, every?, start_at? } }] ← CALL ductape_generate_payload FIRST (operation_family="messaging", method="dispatch")
193
241
  messageBrokers.messages.query [{ product, env, brokerTag, topicTag?, producerTag?, consumerTag?, status?, startDate?, endDate?, page?, limit? }]
194
242
  messageBrokers.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
195
243
  messageBrokers.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
@@ -215,7 +263,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
215
263
  storage.files.delete [{ product, env, storage, fileName }]
216
264
  storage.files.list [{ product, env, storage, prefix?, limit?, continuationToken? }]
217
265
  storage.files.getSignedUrl [{ product, env, storage, fileName, expiresIn?, action? }]
218
- storage.dispatch [{ product, env, storage, operation, input, retries?, session?, cache?, schedule? }]
266
+ storage.dispatch [{ product, env, storage, operation, input, retries?, session?, cache?, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="storage", method="dispatch", targets={storage})
219
267
 
220
268
  ━━━ MODULE: databases ━━━
221
269
  databases.create [{ product, tag, name, description?, type: "mongodb"|"postgresql"|"mysql"|"sqlite", envs: [{slug, connection_url}] }]
@@ -262,8 +310,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
262
310
  databases.action.fetch [action_tag]
263
311
  databases.action.list [database_tag]
264
312
  databases.action.delete [action_tag]
265
- databases.action.dispatch [{ product, env, database, action, input, schedule? }]
266
- databases.dispatch [{ product, env, database, action, input, schedule? }]
313
+ databases.action.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch", targets={database, action})
314
+ databases.dispatch [{ product, env, database, action, input, schedule? }] ← CALL ductape_generate_payload FIRST (operation_family="database", method="dispatch")
267
315
 
268
316
  ━━━ MODULE: graph ━━━
269
317
  graph.create [{ product, tag, name, description?, type: "neo4j"|"nebula"|"arangodb", envs: [{slug, connection_url, username?, password?}] }]
@@ -316,7 +364,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
316
364
  graph.beginTransaction [options?]
317
365
  graph.commitTransaction [transaction]
318
366
  graph.rollbackTransaction [transaction]
319
- graph.dispatch [data]
367
+ graph.dispatch [data] ← CALL ductape_generate_payload FIRST (operation_family="graph", method="dispatch", targets={graph})
320
368
 
321
369
  ━━━ MODULE: vector ━━━
322
370
  vector.create [{ product, tag, name, description?, provider: "pinecone"|"qdrant"|"weaviate", dimensions: number, metric?: "cosine"|"euclidean"|"dotproduct", envs: [{slug, api_key, environment?}] }]
@@ -348,19 +396,24 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
348
396
  vector.listIndexes [{ product, env, vector }]
349
397
  vector.count [{ product, env, vector, namespace? }]
350
398
 
351
- ━━━ MODULE: workflow ━━━
352
- workflow.create [product_tag, data: {
399
+ ━━━ MODULE: features ━━━
400
+ features.create [product_tag, data: {
353
401
  tag: string,
354
402
  name: string,
355
403
  description?: string,
404
+
405
+ // INPUT SCHEMA (top-level): declares what fields this feature accepts when executed at runtime.
406
+ // These are the fields callers will pass to features.execute / features.dispatch.
407
+ // Construct this yourself — it is a schema declaration, not a runtime value.
356
408
  input?: { fieldName: { type: string, required?: boolean } },
409
+
357
410
  output?: object,
358
411
  envs?: [{ slug: string, active?: boolean }],
359
412
  steps: [
360
413
  {
361
414
  tag: string, // unique step id
362
415
  name?: string,
363
- type: "action"|"database"|"graph"|"notification"|"storage"|"publish"|"workflow"|"sleep"|"wait_signal",
416
+ type: "action"|"database"|"graph"|"notification"|"storage"|"publish"|"feature"|"sleep"|"wait_signal",
364
417
  app?: string, // for type=action
365
418
  event?: string, // action/event tag
366
419
  database?: string, // for type=database
@@ -368,20 +421,25 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
368
421
  notification?: string, // for type=notification
369
422
  storage?: string, // for type=storage
370
423
  broker?: string, // for type=publish
371
- workflow?: string, // for type=workflow (child)
424
+ feature?: string, // for type=feature (child feature)
425
+
426
+ // STEP INPUT: maps this feature's declared input fields (or prior step outputs) → the step's underlying action/event fields.
427
+ // ← CALL ductape_generate_payload (operation_family matching step type, method="run", targets={app/event/database/etc.})
428
+ // to discover what fields the target accepts, then wire them with "$Input{fieldName}" or "$Step{stepTag}{field}" references.
372
429
  input?: { "body:field": "$Input{fieldName}" | "$Step{stepTag}{field}" | literal },
430
+
373
431
  condition?: string, // e.g. "$Step{validate}{valid} == true"
374
432
  dependsOn?: string[],
375
433
  options?: { retries?: number, timeout?: number, allow_fail?: boolean, critical?: boolean }
376
434
  }
377
435
  ]
378
436
  }]
379
- workflow.update [product_tag, workflow_tag, data: { name?: string, description?: string, steps?: array, envs?: array }]
380
- workflow.fetch [product_tag, workflow_tag]
381
- workflow.fetchAll [product_tag]
382
- workflow.delete [product_tag, workflow_tag]
437
+ features.update [product_tag, feature_tag, data: { name?: string, description?: string, steps?: array, envs?: array }]
438
+ features.fetch [product_tag, feature_tag]
439
+ features.fetchAll [product_tag]
440
+ features.delete [product_tag, feature_tag]
383
441
 
384
- workflow.define [{
442
+ features.define [{
385
443
  product?: string,
386
444
  tag: string,
387
445
  name: string,
@@ -396,7 +454,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
396
454
  recordScenarios?: object[], // multiple recording scenarios for branching
397
455
  branchOverrides?: object, // force step results during recording to reach later branches
398
456
  handler: async (ctx) => {
399
- // ctx.input – typed workflow input
457
+ // ctx.input – typed feature input
400
458
  // ctx.step(tag, fn, rollback?, opts?) – define a durable step
401
459
  // ctx.action.run({ app, event, input }) – call an app action
402
460
  // ctx.database.query/insert/update/delete({ database, event, ... })
@@ -410,11 +468,11 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
410
468
  // ctx.sleep(ms|"1h30m")
411
469
  // ctx.waitForSignal("signal-name", { timeout? })
412
470
  // ctx.setState(key, value) / ctx.getState(key)
413
- // ctx.workflow(childId, childTag, childInput) – child workflow
471
+ // ctx.feature(childId, childTag, childInput) – child feature
414
472
  }
415
473
  }]
416
474
 
417
- workflow.execute [{
475
+ features.execute [{ ← CALL ductape_generate_payload FIRST (operation_family="features", method="execute", targets={tag}) to discover the input field shape
418
476
  product: string,
419
477
  env: string,
420
478
  tag: string,
@@ -426,10 +484,10 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
426
484
  timeout?: number
427
485
  }]
428
486
 
429
- workflow.dispatch [{
487
+ features.dispatch [{ ← CALL ductape_generate_payload FIRST (operation_family="features", method="dispatch", targets={feature})
430
488
  product: string,
431
489
  env: string,
432
- workflow: string,
490
+ feature: string,
433
491
  input: { fieldName: value },
434
492
  schedule?: { start_at?: number|string, cron?: string, every?: number, limit?: number, endDate?: number|string, tz?: string },
435
493
  session?: string,
@@ -437,18 +495,18 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
437
495
  retries?: number
438
496
  }]
439
497
 
440
- workflow.signal [{ product, env, workflow_id, signal: string, payload?: object }]
441
- workflow.query [{ product, env, workflow_id, query: string, params?: object }]
442
- workflow.status [executionId: string]
443
- workflow.cancel [executionId: string, reason?: string]
444
- workflow.replay [executionId: string, options?: object]
445
- workflow.restart [executionId: string]
446
- workflow.resume [executionId: string]
447
- workflow.replayFromStep [executionId: string, stepTag: string]
448
- workflow.history [executionId: string]
449
- workflow.stepDetail [executionId: string, stepTag: string]
450
- workflow.relatedExecutions [executionId: string]
451
- workflow.compare [executionId1: string, executionId2: string]
498
+ features.signal [{ product, env, feature_id, signal: string, payload?: object }]
499
+ features.query [{ product, env, feature_id, query: string, params?: object }]
500
+ features.status [executionId: string]
501
+ features.cancel [executionId: string, reason?: string]
502
+ features.replay [executionId: string, options?: object]
503
+ features.restart [executionId: string]
504
+ features.resume [executionId: string]
505
+ features.replayFromStep [executionId: string, stepTag: string]
506
+ features.history [executionId: string]
507
+ features.stepDetail [executionId: string, stepTag: string]
508
+ features.relatedExecutions [executionId: string]
509
+ features.compare [executionId1: string, executionId2: string]
452
510
 
453
511
  ━━━ MODULE: caches ━━━
454
512
  caches.create [{ product, tag, name, description?, type: "redis"|"memcached"|"in-memory", envs: [{slug, connection_url}] }]
@@ -497,37 +555,108 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
497
555
  ━━━ MODULE: logs ━━━
498
556
  logs.init [product_tag?, app_tag?] (at least one required)
499
557
  logs.fetch [{ product_id?, app_id?, env?, level?: "info"|"warn"|"error"|"debug", start_date?, end_date?, page?, limit? }]
558
+
559
+ ━━━ TOOL: ductape_schema ━━━
560
+ Use this tool to dynamically discover the full field manifest for Ductape asset creation and update operations,
561
+ derived live from the SDK's Joi validators. No arguments are required to get everything.
562
+
563
+ Arguments:
564
+ module?: "app" | "product" (optional — omit to get both modules + all enums)
565
+
566
+ Returns:
567
+ When module is omitted:
568
+ {
569
+ modules: {
570
+ app: { [method: string]: { fields: { [field]: FieldDef } } },
571
+ product: { [method: string]: { fields: { [field]: FieldDef } } }
572
+ },
573
+ enums: {
574
+ HttpMethods, AuthTypes, DataFormats, DataTypes, StatusCodes, TokenPeriods,
575
+ DatabaseTypes, AppComponents, ProductComponents, Categories, DefaultTypes,
576
+ EventTypes, InputsTypes, PublicStates
577
+ }
578
+ }
579
+
580
+ When module="app" or module="product":
581
+ { module: string, methods: { [method]: { fields: { [field]: FieldDef } } }, enums: { ... } }
582
+
583
+ FieldDef shape:
584
+ {
585
+ type: string, // "string" | "number" | "boolean" | "object" | "array" | "alternatives" | "any"
586
+ required: boolean,
587
+ enum?: (string | number | boolean)[], // present when field is restricted to specific values
588
+ fields?: { [key]: FieldDef }, // present for nested objects
589
+ items?: FieldDef, // present for arrays (describes each element)
590
+ oneOf?: FieldDef[], // present for alternatives (.try(...))
591
+ minLength?: number, maxLength?: number, // string length constraints
592
+ min?: number, max?: number, // number range constraints
593
+ pattern?: string // string pattern regex
594
+ }
595
+
596
+ Example method keys for app module:
597
+ "create", "update", "environments.create", "environments.update",
598
+ "actions.create", "actions.update", "auths.create", "auths.update",
599
+ "variables.create", "variables.update", "constants.create", "constants.update",
600
+ "webhooks.create", "webhooks.update", "webhooks.events.create", "webhooks.events.update"
601
+
602
+ Example method keys for product module:
603
+ "create", "environments.create", "environments.update", "apps.add", "apps.update",
604
+ "databases.create", "databases.update", "notifications.create", "notifications.update",
605
+ "caches.create", "caches.update", "jobs.create", "jobs.update",
606
+ "messageBrokers.create", "messageBrokers.update", "fallbacks.create", "fallbacks.update",
607
+ "graphs.create", "graphs.update", "vectors.create", "vectors.update",
608
+ "sessions.create", "sessions.update", "healthchecks.create",
609
+ "quotas.create", "quotas.update", "functions.create", "functions.update",
610
+ "agents.create", "agents.update", "models.create", "models.update"
611
+
612
+ When to use this tool:
613
+ - Before calling ductape_execute to create or update any asset — use this to discover required fields
614
+ - To enumerate valid enum values (e.g. which DatabaseTypes or AuthTypes are accepted)
615
+ - To understand nested object shapes without reading SDK docs
500
616
  `;
501
617
  const executeInputSchema = z.object({
502
- publishable_key: z.string().describe('The publishable key for your workspace. Required for authentication.'),
618
+ publishable_key: z.string().optional().describe('Your workspace publishable key. Omit if DUCTAPE_PUBLISHABLE_KEY is set in the MCP server env config.'),
503
619
  module: z.enum(MODULES).describe('SDK module to target. Options: ' + MODULES.join(', ') + '.'),
504
620
  method: z.string().describe('The SDK method to call. Use dot-notation for nested methods (e.g. "environments.create", "schema.addField", "messages.query").\n' +
505
621
  'Full reference: see the METHOD_DOCS embedded in the params description.'),
506
622
  params: z.array(z.any()).default([]).describe(METHOD_DOCS),
507
623
  });
508
624
  const payloadGenerateInputSchema = z.object({
509
- workspace_id: z.string(),
510
- user_id: z.string(),
511
- public_key: z.string(),
512
- product_tag: z.string(),
513
- env_slug: z.string(),
514
- operation_family: z.string(),
515
- method: z.string(),
516
- targets: z.record(z.any()).optional(),
517
- include_session: z.boolean().optional().default(true),
518
- include_cache: z.boolean().optional().default(true),
519
- schema_mode: z.enum(['strict', 'best_effort']).optional().default('best_effort'),
520
- input_hint: z.record(z.any()).optional(),
625
+ publishable_key: z.string().optional().describe('Your workspace publishable key. Omit if DUCTAPE_PUBLISHABLE_KEY is set in the MCP server env config.'),
626
+ product_tag: z.string().describe('The product tag (e.g. "my-product"). Identifies which product configuration to read.'),
627
+ env_slug: z.string().describe('The environment slug (e.g. "dev", "prod"). Determines which env-specific values are used.'),
628
+ operation_family: z.string().describe('The operation category. One of: "action", "features", "database", "graph", "vector", "storage", ' +
629
+ '"notification", "messaging", "quota", "fallback", "healthcheck", "session", "cache".'),
630
+ method: z.string().describe('The method within the operation family. E.g. "run", "dispatch", "execute", "start", "send", "produce", ' +
631
+ '"query", "insert", "update", "delete". Must match an allowed method for the given operation_family.'),
632
+ targets: z.record(z.any()).optional().describe('Identifies the specific operation to generate a payload for. ' +
633
+ 'For actions: { app: "app_tag", action: "action_tag" }. ' +
634
+ 'For features: { feature: "feature_tag" }. ' +
635
+ 'For databases: { database: "db_tag", action?: "action_tag" }. ' +
636
+ 'For sessions: { session: "session_tag" }. ' +
637
+ 'For notifications: { notification: "notif_tag" }. ' +
638
+ 'For quotas/fallbacks: { tag: "resource_tag" }. ' +
639
+ 'For storage: { storage: "storage_tag" }. ' +
640
+ 'For messaging: { broker: "broker_tag", topic?: "topic_tag" }.'),
641
+ include_session: z.boolean().optional().default(true).describe('Include session field in the generated payload template.'),
642
+ include_cache: z.boolean().optional().default(true).describe('Include cache field in the generated payload template.'),
643
+ schema_mode: z.enum(['strict', 'best_effort']).optional().default('best_effort').describe('"strict" — fail if any required field cannot be resolved. ' +
644
+ '"best_effort" — fill what is known, leave unknowns as null/placeholder. Use best_effort when exploring.'),
645
+ input_hint: z.record(z.any()).optional().describe('Optional. Partial input values you already know. These are merged into the generated payload template ' +
646
+ 'so the result is pre-filled. Use this to get a payload with known values already substituted.'),
521
647
  });
522
648
  const snippetGenerateInputSchema = payloadGenerateInputSchema.extend({
523
649
  language: z.enum(['typescript', 'python']).default('typescript'),
524
650
  });
651
+ const schemaInputSchema = z.object({
652
+ module: z.enum(['app', 'product']).optional().describe('Optional. Scope the result to a single module ("app" or "product"). Omit to get the full manifest including enums.'),
653
+ });
525
654
  function toPrettyJson(value) {
526
655
  return JSON.stringify(value ?? {}, null, 2);
527
656
  }
528
657
  const ALLOWED_SNIPPET_METHODS = {
529
658
  action: ['run', 'dispatch', 'execute'],
530
- workflow: ['run', 'dispatch', 'execute'],
659
+ features: ['run', 'dispatch', 'execute'],
531
660
  database: ['dispatch', 'find', 'insert', 'update', 'delete', 'query', 'upsert', 'count', 'aggregate'],
532
661
  graph: ['dispatch', 'find', 'insert', 'update', 'delete', 'query', 'execute'],
533
662
  vector: ['query', 'find', 'findSimilar', 'upsert', 'insert', 'delete', 'dispatch'],
@@ -557,8 +686,8 @@ function resolveSdkCallPath(operationFamily, method) {
557
686
  const m = String(method || '').toLowerCase();
558
687
  if (family === 'action')
559
688
  return `actions.${m}`;
560
- if (family === 'workflow')
561
- return `workflow.${m}`;
689
+ if (family === 'features' || family === 'feature')
690
+ return `features.${m}`;
562
691
  if (family === 'database')
563
692
  return m === 'dispatch' ? 'databases.dispatch' : `databases.${m}`;
564
693
  if (family === 'graph')
@@ -679,10 +808,11 @@ async function main() {
679
808
  const transport = new StdioServerTransport();
680
809
  const executeHandler = async (args) => {
681
810
  try {
682
- if (!args.publishable_key) {
683
- throw new Error('Not authenticated. Please provide the `publishable_key` in your tool invocation arguments.');
811
+ const key = args.publishable_key || process.env.DUCTAPE_PUBLISHABLE_KEY;
812
+ if (!key) {
813
+ throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
684
814
  }
685
- const result = await executeViaProxy(args.publishable_key, args.module, args.method, args.params);
815
+ const result = await executeViaProxy(key, args.module, args.method, args.params);
686
816
  return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
687
817
  }
688
818
  catch (err) {
@@ -692,7 +822,11 @@ async function main() {
692
822
  };
693
823
  const payloadGenerateHandler = async (args) => {
694
824
  try {
695
- const result = await generateExecutablePayload(args);
825
+ const key = args.publishable_key || process.env.DUCTAPE_PUBLISHABLE_KEY;
826
+ if (!key) {
827
+ throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
828
+ }
829
+ const result = await generateExecutablePayload({ ...args, publishable_key: key });
696
830
  return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
697
831
  }
698
832
  catch (err) {
@@ -702,8 +836,12 @@ async function main() {
702
836
  };
703
837
  const snippetGenerateHandler = async (args) => {
704
838
  try {
839
+ const key = args.publishable_key || process.env.DUCTAPE_PUBLISHABLE_KEY;
840
+ if (!key) {
841
+ throw new Error('Not authenticated. Set DUCTAPE_PUBLISHABLE_KEY in your MCP server env config, or pass publishable_key on every tool call.');
842
+ }
705
843
  ensureSupportedSnippetOperation(args.operation_family, args.method);
706
- const generated = await generateExecutablePayload(args);
844
+ const generated = await generateExecutablePayload({ ...args, publishable_key: key });
707
845
  const payload = generated?.payload ?? {};
708
846
  const snippet = buildSnippet(args.language, payload, args.operation_family, args.method);
709
847
  return {
@@ -723,15 +861,36 @@ async function main() {
723
861
  return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
724
862
  }
725
863
  };
864
+ const schemaHandler = async (args) => {
865
+ try {
866
+ const data = await getAssetSchemas(args.module);
867
+ return { content: [{ type: 'text', text: JSON.stringify(data ?? null, null, 2) }] };
868
+ }
869
+ catch (err) {
870
+ const message = err instanceof Error ? err.message : String(err);
871
+ return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
872
+ }
873
+ };
726
874
  if (typeof server.registerTool === 'function') {
727
875
  server.registerTool('ductape_execute', {
728
876
  title: 'Ductape SDK Execute',
729
- description: 'Execute a Ductape SDK operation via the backend proxy (databases, graph, storage, etc.)',
877
+ description: 'Execute a Ductape SDK operation via the backend proxy.\n\n' +
878
+ 'IMPORTANT — two-step rule for runtime operations (run, dispatch, execute, start, send, produce, query, insert, update, delete):\n' +
879
+ ' 1. Call ductape_generate_payload first to get the canonical payload template for the specific operation.\n' +
880
+ ' This reveals the exact "input" field keys and their types — they are product/env/operation-specific.\n' +
881
+ ' 2. Fill in the values from the template, then call ductape_execute.\n\n' +
882
+ 'For asset creation/update operations (create, update, add), use ductape_schema to discover required fields instead.',
730
883
  inputSchema: executeInputSchema,
731
884
  }, executeHandler);
732
885
  server.registerTool('ductape_generate_payload', {
733
886
  title: 'Ductape Payload Generator',
734
- description: 'Generate canonical executable payload templates and schema metadata for SDK code snippet generation',
887
+ description: 'Generate the canonical input payload template for a runtime SDK operation.\n\n' +
888
+ 'CALL THIS BEFORE ductape_execute when you need to run, dispatch, execute, start, send, produce, ' +
889
+ 'or otherwise trigger any Ductape operation that takes an "input" field.\n\n' +
890
+ 'The "input" field shape is defined by how each action/feature/session/quota/etc. was configured ' +
891
+ 'in the product — it cannot be inferred from the SDK schema alone. This tool returns the exact ' +
892
+ 'field names, types, and placeholder values for that specific operation in that specific environment.\n\n' +
893
+ 'Returns: { payload: { product, env, input: { fieldName: placeholder, ... }, session?, cache? }, meta: { ... } }',
735
894
  inputSchema: payloadGenerateInputSchema,
736
895
  }, payloadGenerateHandler);
737
896
  server.registerTool('ductape_generate_snippet', {
@@ -739,11 +898,20 @@ async function main() {
739
898
  description: 'Generate canonical payload and ready TypeScript/Python snippet for engineers',
740
899
  inputSchema: snippetGenerateInputSchema,
741
900
  }, snippetGenerateHandler);
901
+ server.registerTool('ductape_schema', {
902
+ title: 'Ductape Asset Schema',
903
+ description: 'Returns the full field manifest for Ductape asset creation/update methods, ' +
904
+ 'derived live from the SDK Joi validators. Includes field types, required flags, ' +
905
+ 'enum values, nested structures, and all enum constants. ' +
906
+ 'Pass module="app" or module="product" to scope the result.',
907
+ inputSchema: schemaInputSchema,
908
+ }, schemaHandler);
742
909
  }
743
910
  else if (typeof server.tool === 'function') {
744
911
  server.tool('ductape_execute', executeInputSchema.shape, executeHandler);
745
912
  server.tool('ductape_generate_payload', payloadGenerateInputSchema.shape, payloadGenerateHandler);
746
913
  server.tool('ductape_generate_snippet', snippetGenerateInputSchema.shape, snippetGenerateHandler);
914
+ server.tool('ductape_schema', schemaInputSchema.shape, schemaHandler);
747
915
  }
748
916
  else {
749
917
  console.error('MCP server does not expose .registerTool() or .tool()');
@@ -8,9 +8,7 @@ export type SDKModule = 'product' | 'app' | 'databases' | 'graph' | 'webhooks' |
8
8
  */
9
9
  export declare function executeViaProxy<T = unknown>(publishable_key: string, module: SDKModule, method: string, params?: unknown[]): Promise<T>;
10
10
  export interface IGenerateExecutablePayloadRequest {
11
- workspace_id: string;
12
- user_id: string;
13
- public_key: string;
11
+ publishable_key: string;
14
12
  product_tag: string;
15
13
  env_slug: string;
16
14
  operation_family: string;
@@ -25,5 +23,6 @@ export interface IGenerateExecutablePayloadResponse {
25
23
  payload: Record<string, unknown>;
26
24
  meta: Record<string, unknown>;
27
25
  }
26
+ export declare function getAssetSchemas(module?: string): Promise<unknown>;
28
27
  export declare function generateExecutablePayload<T = IGenerateExecutablePayloadResponse>(request: IGenerateExecutablePayloadRequest): Promise<T>;
29
28
  //# sourceMappingURL=proxy-client.d.ts.map
@@ -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,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,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,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,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;AAQD,wBAAsB,yBAAyB,CAAC,CAAC,GAAG,kCAAkC,EACpF,OAAO,EAAE,iCAAiC,GACzC,OAAO,CAAC,CAAC,CAAC,CAmBZ"}
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,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,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;AAQD,wBAAsB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAYvE;AAED,wBAAsB,yBAAyB,CAAC,CAAC,GAAG,kCAAkC,EACpF,OAAO,EAAE,iCAAiC,GACzC,OAAO,CAAC,CAAC,CAAC,CAmBZ"}