@ductape/mcp 0.1.0

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 ADDED
@@ -0,0 +1,757 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Ductape MCP Server
4
+ *
5
+ * Exposes Ductape SDK operations as MCP tools by calling the backend proxy.
6
+ *
7
+ * Authentication:
8
+ * Requires passing a `publishable_key` with every `ductape_execute` payload.
9
+ */
10
+ import { z } from 'zod';
11
+ import { executeViaProxy, generateExecutablePayload, } from './proxy-client.js';
12
+ const MODULES = [
13
+ 'product', 'app', 'databases', 'graph', 'webhooks', 'notifications',
14
+ 'messageBrokers', 'storage', 'vector', 'caches', 'sessions', 'quotas',
15
+ 'actions', 'features', 'jobs', 'logs', 'resilience', 'health', 'fallback', 'secrets',
16
+ ];
17
+ // ─── Exhaustive SDK Method & Params Reference ────────────────────────────────
18
+ // Built from a complete read of sdk/ts/src/index.ts (Ductape class public API).
19
+ // Each entry follows: [module].[method] → params array signature.
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+ const METHOD_DOCS = `
22
+ ALL params are passed as a JSON array in positional order matching the SDK signature.
23
+
24
+ ━━━ MODULE: product ━━━
25
+ product.create [data: { name, description, tag?, envs?: [{slug, name}] }]
26
+ product.fetch [product_tag: string]
27
+ product.update [product_tag: string, data: { name?: string, description?: string }]
28
+ product.init [product_tag: string]
29
+ product.environments.create [product_tag, data: { slug: string, env_name: string, description: string, active?: boolean }]
30
+ product.environments.update [product_tag, slug: string, data: { env_name?: string, description?: string, active?: boolean }]
31
+ product.environments.list [product_tag]
32
+ product.environments.fetch [product_tag, slug]
33
+ product.apps.connect [product_tag, app_tag]
34
+ product.apps.add [product_tag, app: { access_tag: string, envs: [{ app_env_slug: string, product_env_slug: string, variables?: [{key: string, value: string}], auth?: { auth_tag: string, data: string|object, expiry?: number } }] }]
35
+ product.apps.list [product_tag]
36
+ product.apps.fetch [product_tag, access_tag]
37
+ product.apps.update [product_tag, access_tag: string, data: { version?: string, envs?: [{ app_env_slug: string, product_env_slug: string, variables?: [{key: string, value: string}], auth?: { auth_tag: string, data: string|object, expiry?: number } }] }]
38
+
39
+ ━━━ MODULE: app ━━━
40
+ app.create [data: { app_name: string, description: string, unique?: boolean }]
41
+ app.fetch [app_tag]
42
+ app.update [app_tag, data: { app_name?: string, description?: string, logo?: string, colors?: { primary?: string, secondary?: string, accent?: string, background?: string }, require_whitelist?: boolean, aboutText?: string, aboutHTML?: string, request_type?: string }]
43
+ app.init [app_tag]
44
+
45
+ ━━━ MODULE: actions (app actions) ━━━
46
+ actions.create [app_tag, data: { tag: string, name: string, resource: string, method: "GET"|"POST"|"PUT"|"PATCH"|"DELETE", description?: string, body?: object, params?: object, query?: object, headers?: object, response?: { name?: string, status_code: number, success: boolean, body: object, response_format: "json"|"xml"|"form" } }]
47
+ 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
+ actions.fetch [app_tag, action_tag]
49
+ 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? } }]
52
+
53
+ ━━━ MODULE: auths ━━━
54
+ 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 }]
55
+ auths.update [app_tag, auth_tag, data: { name?: string, setup_type?: "header"|"bearer"|"basic"|"oauth2"|"apikey", expiry?: number, period?: "seconds"|"minutes"|"hours"|"days", description?: string, action_tag?: string }]
56
+ auths.fetch [app_tag, auth_tag]
57
+ auths.list [app_tag]
58
+
59
+ ━━━ MODULE: webhooks ━━━
60
+ webhooks.create [app_tag, data: { tag: string, name: string, description: string, envs?: [{ slug: string, registration_url?: string, method?: "GET"|"POST"|"PUT"|"PATCH", sample?: object }] }]
61
+ webhooks.update [app_tag, webhook_tag, data: { name?: string, description?: string, envs?: [{ slug: string, registration_url?: string, method?: "GET"|"POST"|"PUT"|"PATCH", sample?: object }] }]
62
+ webhooks.fetch [app_tag, webhook_tag]
63
+ webhooks.list [app_tag]
64
+ webhooks.events.create [app_tag, data: { tag: string, name: string, selector: string, description: string, sample: object }]
65
+ webhooks.events.update [app_tag, event_tag, data: { name?: string, selector?: string, selectorValue?: string, description?: string, sample?: object }]
66
+ webhooks.events.fetch [app_tag, event_tag]
67
+ webhooks.events.list [app_tag, webhook_tag]
68
+
69
+ ━━━ MODULE: sessions ━━━
70
+ sessions.create [product_tag, data: { tag: string, name: string, description?: string, expiry: number, period: "seconds"|"minutes"|"hours"|"days", selector: string, schema: { fieldName: "string"|"number"|"boolean" } }]
71
+ sessions.update [product_tag, session_tag, data: { name?: string, description?: string, expiry?: number, period?: "seconds"|"minutes"|"hours"|"days", selector?: string, schema?: object }]
72
+ sessions.fetch [product_tag, session_tag]
73
+ sessions.list [product_tag]
74
+ sessions.delete [product_tag, session_tag]
75
+ sessions.start [{ product, env, tag, data: { key: value } }]
76
+ sessions.verify [{ product, env, tag, token }]
77
+ sessions.refresh [{ product, env, tag, refreshToken }]
78
+ sessions.revoke [{ product, env, tag, sessionId?, identifier? }]
79
+ sessions.listActive [{ product, env, tag, identifier?, page?, limit? }]
80
+ sessions.fetchUsers [{ product, session, env?, page?, limit? }]
81
+ sessions.fetchUserDetails [{ product, session, identifier, env? }]
82
+ sessions.fetchDashboard [{ product, session, env? }]
83
+
84
+ ━━━ MODULE: quotas ━━━
85
+ quotas.create [product_tag, data: {
86
+ tag: string, // unique identifier
87
+ name?: string, // display name
88
+ description?: string,
89
+ input: { // declare the input fields this quota expects
90
+ fieldName: {
91
+ type: "string"|"number"|"boolean"|"object"|"array",
92
+ required?: boolean,
93
+ description?: string
94
+ }
95
+ },
96
+ options: [ // list of providers tried in order
97
+ {
98
+ provider?: string, // friendly name for this provider slot
99
+ app: string, // app tag whose action to call
100
+ type: "action",
101
+ event: string, // action tag on that app
102
+ quota: number, // max allowed uses for this provider
103
+ uses?: number, // current use count (usually 0 at creation)
104
+ input: { "body:field": "$Input{fieldName}" }, // maps quota input → action input
105
+ output: {}, // expected output shape (can be {})
106
+ retries: number,
107
+ healthcheck?: string, // optional healthcheck tag to gate this provider
108
+ check_interval?: number // ms between health polls
109
+ }
110
+ ]
111
+ }]
112
+ quotas.update [product_tag, quota_tag, data: { name?: string, description?: string, options?: array }]
113
+ quotas.fetch [product_tag, quota_tag]
114
+ quotas.list [product_tag]
115
+ 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 } }]
118
+
119
+ ━━━ MODULE: fallback ━━━
120
+ fallback.create [product_tag, data: {
121
+ tag: string,
122
+ name?: string,
123
+ description?: string,
124
+ input: { // input fields this fallback expects
125
+ fieldName: {
126
+ type: "string"|"number"|"boolean"|"object"|"array",
127
+ required?: boolean,
128
+ description?: string
129
+ }
130
+ },
131
+ options: [ // ordered list: primary first, then fallback(s)
132
+ {
133
+ provider?: string, // friendly name e.g. "primary", "backup"
134
+ app: string, // app tag
135
+ type: "action",
136
+ event: string, // action tag
137
+ input: { "body:field": "$Input{fieldName}" },
138
+ output: {},
139
+ retries: number,
140
+ healthcheck?: string,
141
+ check_interval?: number
142
+ }
143
+ ]
144
+ }]
145
+ fallback.update [product_tag, fallback_tag, data: { name?: string, description?: string, options?: array }]
146
+ fallback.fetch [product_tag, fallback_tag]
147
+ fallback.list [product_tag]
148
+ 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 } }]
151
+
152
+ ━━━ 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 }]
155
+ health.fetch [product_tag, health_tag]
156
+ health.list [product_tag]
157
+ health.delete [product_tag, health_tag]
158
+ health.status [{ product, env, tag }]
159
+ health.run [{ product, env, tag }]
160
+ health.check [{ product, env, tag }]
161
+
162
+ ━━━ MODULE: notifications ━━━
163
+ notifications.create [product_tag, data: { tag: string, name: string, type?: "email"|"push"|"sms"|"callback" }]
164
+ notifications.update [product_tag, notif_tag, data: { name?: string, type?: "email"|"push"|"sms"|"callback" }]
165
+ notifications.fetch [product_tag, notif_tag]
166
+ notifications.list [product_tag]
167
+ notifications.delete [product_tag, notif_tag]
168
+ notifications.messages.create [product_tag, data: { tag: string, notification: string, subject?: { template: string, data: object }, body?: { template: string, data: object } }]
169
+ notifications.messages.update [product_tag, msg_tag, data: { subject?: { template: string, data: object }, body?: { template: string, data: object } }]
170
+ notifications.messages.fetch [product_tag, msg_tag]
171
+ 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? } }]
178
+ notifications.getMessages [{ product_tag?, env?, notification_tag?, status?, type?, start_date?, end_date?, page?, limit? }]
179
+
180
+ ━━━ MODULE: messageBrokers ━━━
181
+ messageBrokers.create [{ product: string, tag: string, name: string, description?: string, type: "kafka"|"rabbitmq"|"redis"|"sqs", envs: [{ slug: string, connection_url: string }] }]
182
+ messageBrokers.update [product_tag, broker_tag, data: { name?: string, description?: string, type?: "kafka"|"rabbitmq"|"redis"|"sqs", envs?: [{ slug: string, connection_url: string }] }]
183
+ messageBrokers.fetch [product_tag, broker_tag]
184
+ messageBrokers.list [product_tag]
185
+ messageBrokers.delete [product_tag, broker_tag]
186
+ messageBrokers.topics.create [product_tag, data: { tag: string, name: string, broker: string, type: "producer"|"consumer"|"both" }]
187
+ messageBrokers.topics.update [product_tag, topic_tag, data: { name?: string, type?: "producer"|"consumer"|"both" }]
188
+ messageBrokers.topics.fetch [product_tag, topic_tag]
189
+ messageBrokers.topics.list [product_tag, broker_tag]
190
+ messageBrokers.produce [{ product, env, event: "broker_tag:topic_tag", message: { key: value }, session?, cache? }]
191
+ 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? } }]
193
+ messageBrokers.messages.query [{ product, env, brokerTag, topicTag?, producerTag?, consumerTag?, status?, startDate?, endDate?, page?, limit? }]
194
+ messageBrokers.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
195
+ messageBrokers.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
196
+ messageBrokers.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, startDate?, endDate?, page?, limit? }]
197
+ messageBrokers.messages.getStats [{ product, env, brokerTag }]
198
+ messageBrokers.messages.getDashboard [{ product, env, brokerTag }]
199
+
200
+ ━━━ MODULE: storage ━━━
201
+ storage.create [{ product: string, tag: string, name: string, description?: string, envs: [{ slug: string, type: "aws"|"azure"|"gcp", config: { bucket?: string, region?: string, accessKeyId?: string, secretAccessKey?: string, containerName?: string, connectionString?: string, projectId?: string, keyFilename?: string } }] }]
202
+ storage.update [product_tag, storage_tag, data: { name?: string, description?: string, envs?: [{ slug: string, type: "aws"|"azure"|"gcp", config: { bucket?: string, region?: string, accessKeyId?: string, secretAccessKey?: string } }] }]
203
+ storage.fetch [product_tag, storage_tag]
204
+ storage.list [product_tag]
205
+ storage.delete [product_tag, storage_tag]
206
+ storage.upload [{ product, env, storage, fileName, buffer: string|Buffer, mimeType? }]
207
+ storage.download [{ product, env, storage, fileName }]
208
+ storage.remove [{ product, env, storage, fileName }]
209
+ storage.listFiles [{ product, env, storage, prefix?, limit?, continuationToken? }]
210
+ storage.getSignedUrl [{ product, env, storage, fileName, expiresIn?: number, action?: "read"|"write" }]
211
+ storage.stats [{ product, env, storage, prefix?, session?, cache? }]
212
+ storage.testConnection [{ product, env, storage }]
213
+ storage.files.upload [{ product, env, storage, fileName, buffer, mimeType? }]
214
+ storage.files.download [{ product, env, storage, fileName }]
215
+ storage.files.delete [{ product, env, storage, fileName }]
216
+ storage.files.list [{ product, env, storage, prefix?, limit?, continuationToken? }]
217
+ storage.files.getSignedUrl [{ product, env, storage, fileName, expiresIn?, action? }]
218
+ storage.dispatch [{ product, env, storage, operation, input, retries?, session?, cache?, schedule? }]
219
+
220
+ ━━━ MODULE: databases ━━━
221
+ databases.create [{ product, tag, name, description?, type: "mongodb"|"postgresql"|"mysql"|"sqlite", envs: [{slug, connection_url}] }]
222
+ databases.register [product_tag, data: IProductDatabase]
223
+ databases.list [product_tag]
224
+ databases.fetch [product_tag, database_tag]
225
+ databases.updateDatabase [product_tag, database_tag, data: { name?: string, description?: string, type?: "postgresql"|"mongodb"|"mysql", envs?: [{ slug: string, connection_url: string, description?: string }] }]
226
+ databases.connect [{ product, env, database }]
227
+ databases.testConnection [{ product, env, database }]
228
+ databases.disconnect []
229
+ databases.query [{ product, env, database, entity, where?: {field: {$eq|$gt|$lt|$in: value}}, select?: string[], orderBy?: [{field, order:"ASC"|"DESC"}], limit?, offset? }]
230
+ databases.insert [{ product, env, database, entity, data: {key: value}|{key:value}[], returning? }]
231
+ databases.update [{ product, env, database, entity, data: {key:value}, where: {key: {$eq: value}}, returning? }]
232
+ databases.delete [{ product, env, database, entity, where: {key: {$eq: value}}, returning? }]
233
+ databases.upsert [{ product, env, database, entity, data: {key:value}, conflictKeys: string[], returning? }]
234
+ databases.count [{ product, env, database, entity, where? }]
235
+ databases.sum [{ product, env, database, entity, field, where? }]
236
+ databases.avg [{ product, env, database, entity, field, where? }]
237
+ databases.min [{ product, env, database, entity, field, where? }]
238
+ databases.max [{ product, env, database, entity, field, where? }]
239
+ databases.aggregate [{ product, env, database, entity, aggregations: [{type:"count"|"sum"|"avg"|"min"|"max", field?, alias}], where?, groupBy? }]
240
+ databases.schema.create [collection_name, definition: { fieldName: "string"|"number"|"boolean"|"date"|{type, required?, unique?, default?} }, options?]
241
+ databases.schema.drop [collection_name, options?]
242
+ databases.schema.addField [collection, fieldName, definition]
243
+ databases.schema.dropField [collection, fieldName]
244
+ databases.schema.renameField [collection, oldName, newName]
245
+ databases.schema.modifyField [collection, fieldName, changes: Partial<{type, required, unique, default}>]
246
+ databases.schema.createIndex [collection, fields: string[]|[{field, order}], options?: {unique?, name?}]
247
+ databases.schema.dropIndex [collection, indexName]
248
+ databases.schema.list [schemaName?]
249
+ databases.schema.describe [collection_name]
250
+ databases.schema.indexes [collection_name]
251
+ databases.migration.create [{ product, database, data: { name, tag, description?, value: { up: string[], down: string[] } } }]
252
+ databases.migration.update [{ product, tag, data: { name?, description?, value? } }]
253
+ databases.migration.fetch [{ product, tag }]
254
+ databases.migration.list [{ product, database }]
255
+ databases.migration.delete [{ product, tag }]
256
+ databases.migration.run [migrations, options?]
257
+ databases.migration.rollback [migrations, count?]
258
+ databases.migration.history []
259
+ databases.migration.status [migrations]
260
+ databases.action.create [{ product, database, data: { tag, name, description?, type:"sql"|"nosql", query } }]
261
+ databases.action.update [{ product: string, tag: string, data: { name?: string, description?: string, query?: string } }]
262
+ databases.action.fetch [action_tag]
263
+ databases.action.list [database_tag]
264
+ databases.action.delete [action_tag]
265
+ databases.action.dispatch [{ product, env, database, action, input, schedule? }]
266
+ databases.dispatch [{ product, env, database, action, input, schedule? }]
267
+
268
+ ━━━ MODULE: graph ━━━
269
+ graph.create [{ product, tag, name, description?, type: "neo4j"|"nebula"|"arangodb", envs: [{slug, connection_url, username?, password?}] }]
270
+ graph.fetch [product_tag, graph_tag]
271
+ graph.list [product_tag?]
272
+ graph.update [product_tag, graph_tag, data: { name?: string, description?: string, type?: "neo4j"|"neptune"|"arangodb"|"memgraph", envs?: [{ slug: string, connection_url: string, username?: string, password?: string, database?: string, graphName?: string, region?: string }] }]
273
+ graph.delete [graph_tag, product_tag?]
274
+ graph.connect [{ product, env, graph }]
275
+ graph.testConnection [config]
276
+ graph.createNode [{ labels: string[], properties: { key: value } }, transaction?]
277
+ graph.findNodes [{ labels?: string[], where?: { key: value }, limit?, skip? }, transaction?]
278
+ graph.findNodeById [id: string|number, transaction?]
279
+ graph.updateNode [{ id, properties: { key: value } }, transaction?]
280
+ graph.deleteNode [{ id, detach?: boolean }, transaction?]
281
+ graph.mergeNode [{ labels: string[], matchProps: { key: value }, setProps?: { key: value } }, transaction?]
282
+ graph.addLabels [{ id, labels: string[] }, transaction?]
283
+ graph.removeLabels [{ id, labels: string[] }, transaction?]
284
+ graph.setLabels [{ id, labels: string[] }, transaction?]
285
+ graph.createRelationship [{ fromId, toId, type: string, properties?: { key: value } }, transaction?]
286
+ graph.findRelationships [{ type?: string, where?, limit? }, transaction?]
287
+ graph.findRelationshipById [id, transaction?]
288
+ graph.updateRelationship [{ id, properties: { key: value } }, transaction?]
289
+ graph.deleteRelationship [{ id }, transaction?]
290
+ graph.mergeRelationship [{ fromId, toId, type, matchProps?, setProps? }, transaction?]
291
+ graph.traverse [{ startId, direction?: "in"|"out"|"both", relationshipTypes?: string[], maxDepth?, where? }, transaction?]
292
+ graph.shortestPath [{ fromId, toId, relationshipType?, maxDepth? }, transaction?]
293
+ graph.allPaths [{ fromId, toId, relationshipType?, maxDepth? }, transaction?]
294
+ graph.getNeighborhood [{ id, depth?, relationshipTypes? }, transaction?]
295
+ graph.findConnectedComponents [{ labels? }, transaction?]
296
+ graph.query [cypher_query: string, params?: { key: value }, transaction?]
297
+ graph.getStatistics [transaction?]
298
+ graph.countNodes [labels?: string[], where?, transaction?]
299
+ graph.countRelationships [types?: string[], where?, transaction?]
300
+ graph.fullTextSearch [{ index, query, limit? }, transaction?]
301
+ graph.vectorSearch [{ index, vector: number[], topK? }, transaction?]
302
+ graph.createNodeIndex [{ label, field, type?: "btree"|"fulltext"|"vector" }]
303
+ graph.createNodeConstraint [{ label, field, type: "unique"|"exists" }]
304
+ graph.createRelationshipIndex [{ type, field }]
305
+ graph.listIndexes []
306
+ graph.listConstraints []
307
+ graph.dropIndex [name: string]
308
+ graph.dropConstraint [name: string]
309
+ graph.listLabels []
310
+ graph.listRelationshipTypes []
311
+ graph.createAction [{ graphTag, tag, name, query, params? }, productTag?]
312
+ graph.listActions [graphTag?, productTag?]
313
+ graph.getAction [actionTag, graphTag?, productTag?]
314
+ graph.updateAction [actionTag, updates, graphTag?, productTag?]
315
+ graph.deleteAction [actionTag, graphTag?, productTag?]
316
+ graph.beginTransaction [options?]
317
+ graph.commitTransaction [transaction]
318
+ graph.rollbackTransaction [transaction]
319
+ graph.dispatch [data]
320
+
321
+ ━━━ MODULE: vector ━━━
322
+ vector.create [{ product, tag, name, description?, provider: "pinecone"|"qdrant"|"weaviate", dimensions: number, metric?: "cosine"|"euclidean"|"dotproduct", envs: [{slug, api_key, environment?}] }]
323
+ vector.update [{ product: string, tag: string, data: { name?: string, description?: string, type?: "pinecone"|"qdrant"|"weaviate"|"chroma"|"milvus"|"pgvector"|"memory", dimensions?: number, metric?: "cosine"|"euclidean"|"dotproduct", envs?: [{ slug: string, endpoint?: string, apiKey?: string, region?: string, index?: string, namespace?: string }] } }]
324
+ vector.fetch [{ product, tag }]
325
+ vector.list [{ product }]
326
+ vector.delete [{ product, tag }]
327
+ vector.connect [{ product, env, vector }]
328
+ vector.disconnect [{ product, env, vector }]
329
+ vector.query [{ product, env, tag, vector: number[], topK?: number, filter?, namespace?, includeValues?, includeMetadata? }]
330
+ vector.upsert [{ product, env, tag, vectors: [{id, values: number[], metadata?}], namespace? }]
331
+ vector.fetchVectors [{ product, env, vector, ids: string[], namespace? }]
332
+ vector.deleteVectors [{ product, env, tag, ids: string[], namespace?, deleteAll? }]
333
+ vector.findSimilar [{ product, env, vector, values: number[], topK?, filter?, namespace?, includeValues?, includeMetadata? }]
334
+ vector.upsertOne [{ product, env, tag, id, values: number[], metadata?, namespace?, session? }]
335
+ vector.fetchOne [{ product, env, vector, id, namespace? }]
336
+ vector.updateVector [{ product, env, vector, id, values?, setMetadata?, mergeMetadata?, namespace? }]
337
+ vector.updateMetadata [{ product, env, vector, id, metadata: { key: value }, merge?, namespace? }]
338
+ vector.deleteByIds [{ product, env, vector, ids: string[], namespace? }]
339
+ vector.deleteAll [{ product, env, vector, namespace? }]
340
+ vector.listVectors [{ product, env, vector, namespace?, prefix?, limit?, cursor? }]
341
+ vector.listAllVectors [{ product, env, vector, namespace?, prefix? }]
342
+ vector.listNamespaces [{ product, env, vector }]
343
+ vector.deleteNamespace [{ product, env, vector, namespace }]
344
+ vector.describeIndex [{ product, env, vector }]
345
+ vector.getStats [{ product, env, vector }]
346
+ vector.createIndex [{ product, env, vector, name, dimensions, metric?, replicas?, shards? }]
347
+ vector.deleteIndex [{ product, env, vector, name }]
348
+ vector.listIndexes [{ product, env, vector }]
349
+ vector.count [{ product, env, vector, namespace? }]
350
+
351
+ ━━━ MODULE: workflow ━━━
352
+ workflow.create [product_tag, data: {
353
+ tag: string,
354
+ name: string,
355
+ description?: string,
356
+ input?: { fieldName: { type: string, required?: boolean } },
357
+ output?: object,
358
+ envs?: [{ slug: string, active?: boolean }],
359
+ steps: [
360
+ {
361
+ tag: string, // unique step id
362
+ name?: string,
363
+ type: "action"|"database"|"graph"|"notification"|"storage"|"publish"|"workflow"|"sleep"|"wait_signal",
364
+ app?: string, // for type=action
365
+ event?: string, // action/event tag
366
+ database?: string, // for type=database
367
+ graph?: string, // for type=graph
368
+ notification?: string, // for type=notification
369
+ storage?: string, // for type=storage
370
+ broker?: string, // for type=publish
371
+ workflow?: string, // for type=workflow (child)
372
+ input?: { "body:field": "$Input{fieldName}" | "$Step{stepTag}{field}" | literal },
373
+ condition?: string, // e.g. "$Step{validate}{valid} == true"
374
+ dependsOn?: string[],
375
+ options?: { retries?: number, timeout?: number, allow_fail?: boolean, critical?: boolean }
376
+ }
377
+ ]
378
+ }]
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]
383
+
384
+ workflow.define [{
385
+ product?: string,
386
+ tag: string,
387
+ name: string,
388
+ description?: string,
389
+ input?: { fieldName: { type: string, required?: boolean } },
390
+ output?: object,
391
+ signals?: { signalName: { input?: object } },
392
+ queries?: { queryName: { handler?: function } },
393
+ options?: { timeout?: number, retries?: number },
394
+ envs?: [{ slug: string, active?: boolean }],
395
+ recordInput?: object, // sample input for step-recording (run once during define)
396
+ recordScenarios?: object[], // multiple recording scenarios for branching
397
+ branchOverrides?: object, // force step results during recording to reach later branches
398
+ handler: async (ctx) => {
399
+ // ctx.input – typed workflow input
400
+ // ctx.step(tag, fn, rollback?, opts?) – define a durable step
401
+ // ctx.action.run({ app, event, input }) – call an app action
402
+ // ctx.database.query/insert/update/delete({ database, event, ... })
403
+ // ctx.graph.execute({ graph, action, input })
404
+ // ctx.notification.send/email/push/sms({ notification, event, ... })
405
+ // ctx.storage.upload/download({ storage, event, input })
406
+ // ctx.messaging.produce({ event: "broker:topic", message: {} })
407
+ // ctx.quota.execute({ quota, input })
408
+ // ctx.fallback.execute({ fallback, input })
409
+ // ctx.healthcheck.getStatus(tag)
410
+ // ctx.sleep(ms|"1h30m")
411
+ // ctx.waitForSignal("signal-name", { timeout? })
412
+ // ctx.setState(key, value) / ctx.getState(key)
413
+ // ctx.workflow(childId, childTag, childInput) – child workflow
414
+ }
415
+ }]
416
+
417
+ workflow.execute [{
418
+ product: string,
419
+ env: string,
420
+ tag: string,
421
+ input: { fieldName: value },
422
+ session?: string, // "session_tag:jwt_token"
423
+ idempotency_key?: string,
424
+ cache?: string,
425
+ retries?: number,
426
+ timeout?: number
427
+ }]
428
+
429
+ workflow.dispatch [{
430
+ product: string,
431
+ env: string,
432
+ workflow: string,
433
+ input: { fieldName: value },
434
+ schedule?: { start_at?: number|string, cron?: string, every?: number, limit?: number, endDate?: number|string, tz?: string },
435
+ session?: string,
436
+ cache?: string,
437
+ retries?: number
438
+ }]
439
+
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]
452
+
453
+ ━━━ MODULE: caches ━━━
454
+ caches.create [{ product, tag, name, description?, type: "redis"|"memcached"|"in-memory", envs: [{slug, connection_url}] }]
455
+ caches.update [product_tag, cache_tag, data: { name?: string, description?: string, expiry?: number }]
456
+ caches.fetch [product_tag, cache_tag]
457
+ caches.list [product_tag]
458
+ caches.delete [product_tag, cache_tag]
459
+ caches.get [{ key: string }]
460
+ caches.set [{ product, cache, key, value: string, componentTag?, componentType?, expiry?: Date }]
461
+ caches.clear [{ key: string }]
462
+ caches.clearAll [{ product, cache, env? }]
463
+ caches.fetchValues [{ product, cache, env?, page?, limit?, expiryFilter?: "all"|"expiring"|"permanent"|"expired" }]
464
+ caches.fetchDashboard [{ product, cache, env? }]
465
+
466
+ ━━━ MODULE: jobs ━━━
467
+ jobs.create [product_tag, data: { tag, name, description?, type: "action"|"notification"|"storage"|"database"|"publish", app?, event?, executions?, intervals?, start_at? }]
468
+ jobs.update [product_tag, job_tag, data: { name?: string, description?: string, type?: "action"|"notification"|"storage"|"database"|"publish", app?: string, event?: string, executions?: number, intervals?: number, start_at?: number }]
469
+ jobs.fetch [product_tag, job_tag]
470
+ jobs.list [product_tag]
471
+ jobs.delete [product_tag, job_tag]
472
+ jobs.get [jobId: string]
473
+ jobs.listJobs [{ product?, status?, limit?, page? }]
474
+ jobs.cancel [jobId, { reason? }?]
475
+ jobs.cancelMany [{ product?, status? }]
476
+ jobs.pause [jobId]
477
+ jobs.pauseMany [{ product?, recurring? }]
478
+ jobs.resume [jobId]
479
+ jobs.resumeMany [{ product?, status? }]
480
+ jobs.retry [jobId, { delay? }?]
481
+ jobs.retryMany [{ status?, from? }]
482
+ jobs.reschedule [jobId, { start_at?, cron?, every? }]
483
+ jobs.getHistory [jobId, { limit? }?]
484
+ jobs.getStats [{ product?, env?, from?, to? }?]
485
+
486
+ ━━━ MODULE: secrets ━━━
487
+ secrets.create [{ key, value, description?, token_type?: "api"|"password"|"certificate", scope?: string[], envs?: string[], expires_at?: number }]
488
+ secrets.update [key, { value?, description?, scope?, envs?, expires_at? }]
489
+ secrets.fetch [key]
490
+ secrets.list []
491
+ secrets.delete [key]
492
+ secrets.revoke [key]
493
+ secrets.exists [key]
494
+ secrets.resolve [value, { env?, app? }?]
495
+ secrets.validate [value]
496
+
497
+ ━━━ MODULE: logs ━━━
498
+ logs.init [product_tag?, app_tag?] (at least one required)
499
+ logs.fetch [{ product_id?, app_id?, env?, level?: "info"|"warn"|"error"|"debug", start_date?, end_date?, page?, limit? }]
500
+ `;
501
+ const executeInputSchema = z.object({
502
+ publishable_key: z.string().describe('The publishable key for your workspace. Required for authentication.'),
503
+ module: z.enum(MODULES).describe('SDK module to target. Options: ' + MODULES.join(', ') + '.'),
504
+ 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
+ 'Full reference: see the METHOD_DOCS embedded in the params description.'),
506
+ params: z.array(z.any()).default([]).describe(METHOD_DOCS),
507
+ });
508
+ 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(),
521
+ });
522
+ const snippetGenerateInputSchema = payloadGenerateInputSchema.extend({
523
+ language: z.enum(['typescript', 'python']).default('typescript'),
524
+ });
525
+ function toPrettyJson(value) {
526
+ return JSON.stringify(value ?? {}, null, 2);
527
+ }
528
+ const ALLOWED_SNIPPET_METHODS = {
529
+ action: ['run', 'dispatch', 'execute'],
530
+ workflow: ['run', 'dispatch', 'execute'],
531
+ database: ['dispatch', 'find', 'insert', 'update', 'delete', 'query', 'upsert', 'count', 'aggregate'],
532
+ graph: ['dispatch', 'find', 'insert', 'update', 'delete', 'query', 'execute'],
533
+ vector: ['query', 'find', 'findSimilar', 'upsert', 'insert', 'delete', 'dispatch'],
534
+ storage: ['dispatch', 'upload', 'download', 'remove', 'listFiles', 'getSignedUrl', 'stats'],
535
+ notification: ['dispatch', 'send', 'email.send', 'push.send', 'sms.send', 'callback.send'],
536
+ messaging: ['dispatch', 'send', 'publish', 'produce', 'consume'],
537
+ broker: ['dispatch', 'send', 'publish', 'produce', 'consume'],
538
+ quota: ['run', 'dispatch', 'check', 'consume'],
539
+ fallback: ['run', 'dispatch'],
540
+ healthcheck: ['run', 'check', 'status'],
541
+ health: ['run', 'check', 'status'],
542
+ session: ['start', 'verify', 'refresh', 'revoke', 'listActive'],
543
+ cache: ['get', 'set', 'clear', 'clearAll', 'dispatch', 'fetchValues'],
544
+ };
545
+ function ensureSupportedSnippetOperation(operationFamily, method) {
546
+ const family = String(operationFamily || '').toLowerCase();
547
+ const m = String(method || '');
548
+ const allowed = ALLOWED_SNIPPET_METHODS[family] || [];
549
+ if (!allowed.includes(m)) {
550
+ const supportedFamilies = Object.keys(ALLOWED_SNIPPET_METHODS).sort().join(', ');
551
+ throw new Error(`Unsupported snippet operation "${family}.${m}". Allowed methods for "${family}": ${allowed.join(', ') || '(none)'}.\n` +
552
+ `Supported families: ${supportedFamilies}`);
553
+ }
554
+ }
555
+ function resolveSdkCallPath(operationFamily, method) {
556
+ const family = String(operationFamily || '').toLowerCase();
557
+ const m = String(method || '').toLowerCase();
558
+ if (family === 'action')
559
+ return `actions.${m}`;
560
+ if (family === 'workflow')
561
+ return `workflow.${m}`;
562
+ if (family === 'database')
563
+ return m === 'dispatch' ? 'databases.dispatch' : `databases.${m}`;
564
+ if (family === 'graph')
565
+ return m === 'dispatch' ? 'graph.dispatch' : `graph.${m}`;
566
+ if (family === 'vector')
567
+ return `vector.${m}`;
568
+ if (family === 'storage')
569
+ return m === 'dispatch' ? 'storage.dispatch' : `storage.${m}`;
570
+ if (family === 'notification')
571
+ return m === 'dispatch' ? 'notifications.dispatch' : `notifications.${m}`;
572
+ if (family === 'messaging' || family === 'broker') {
573
+ if (m === 'dispatch')
574
+ return 'messageBrokers.dispatch';
575
+ if (m === 'send' || m === 'publish' || m === 'produce')
576
+ return 'messageBrokers.produce';
577
+ return `messageBrokers.${m}`;
578
+ }
579
+ if (family === 'quota')
580
+ return `quotas.${m}`;
581
+ if (family === 'fallback')
582
+ return `fallback.${m}`;
583
+ if (family === 'healthcheck' || family === 'health')
584
+ return `health.${m}`;
585
+ if (family === 'session')
586
+ return `sessions.${m}`;
587
+ if (family === 'cache')
588
+ return m === 'dispatch' ? 'caches.dispatch' : `caches.${m}`;
589
+ return `${family}.${m}`;
590
+ }
591
+ function buildSdkInvocationArgs(payload) {
592
+ const input = payload?.input || {};
593
+ return {
594
+ product: payload?.product_tag,
595
+ env: payload?.env,
596
+ ...input,
597
+ ...(payload?.session ? { session: payload.session } : {}),
598
+ ...(payload?.cache ? { cache: payload.cache } : {}),
599
+ };
600
+ }
601
+ function buildTypeScriptSnippet(payload, operationFamily, method) {
602
+ const callPath = resolveSdkCallPath(operationFamily, method);
603
+ const invocationArgs = buildSdkInvocationArgs(payload);
604
+ return `import Ductape from "@ductape/sdk";
605
+
606
+ const ductape = new Ductape({
607
+ workspace_id: process.env.DUCTAPE_WORKSPACE_ID!,
608
+ user_id: process.env.DUCTAPE_USER_ID!,
609
+ public_key: process.env.DUCTAPE_PUBLIC_KEY!,
610
+ });
611
+
612
+ async function run() {
613
+ const payload = ${toPrettyJson(payload)};
614
+ const args = ${toPrettyJson(invocationArgs)};
615
+ const result = await ductape.${callPath}(args);
616
+ return { payload, result };
617
+ }
618
+
619
+ run().catch(console.error);
620
+ `;
621
+ }
622
+ function buildPythonSnippet(payload, operationFamily, method) {
623
+ const callPath = resolveSdkCallPath(operationFamily, method);
624
+ const invocationArgs = buildSdkInvocationArgs(payload);
625
+ return `from ductape import Ductape
626
+ import os
627
+ import json
628
+
629
+ ductape = Ductape(
630
+ workspace_id=os.environ.get("DUCTAPE_WORKSPACE_ID"),
631
+ user_id=os.environ.get("DUCTAPE_USER_ID"),
632
+ public_key=os.environ.get("DUCTAPE_PUBLIC_KEY"),
633
+ )
634
+
635
+ def run():
636
+ payload = ${toPrettyJson(payload)}
637
+ args = ${toPrettyJson(invocationArgs)}
638
+ result = ductape.${callPath}(args)
639
+ return {"payload": payload, "result": result}
640
+
641
+ if __name__ == "__main__":
642
+ print(json.dumps(run(), indent=2))
643
+ `;
644
+ }
645
+ function buildSnippet(language, payload, operationFamily, method) {
646
+ return language === 'python'
647
+ ? buildPythonSnippet(payload, operationFamily, method)
648
+ : buildTypeScriptSnippet(payload, operationFamily, method);
649
+ }
650
+ async function loadMcpSdk() {
651
+ try {
652
+ const [{ McpServer }, { StdioServerTransport }] = await Promise.all([
653
+ import('@modelcontextprotocol/sdk/server/mcp.js'),
654
+ import('@modelcontextprotocol/sdk/server/stdio.js'),
655
+ ]);
656
+ if (McpServer && StdioServerTransport) {
657
+ return { McpServer, StdioServerTransport };
658
+ }
659
+ }
660
+ catch {
661
+ // fall through to v2 alpha package
662
+ }
663
+ try {
664
+ const sdk = await import('@modelcontextprotocol/server');
665
+ if (sdk.McpServer && sdk.StdioServerTransport) {
666
+ return { McpServer: sdk.McpServer, StdioServerTransport: sdk.StdioServerTransport };
667
+ }
668
+ }
669
+ catch {
670
+ // fall through
671
+ }
672
+ console.error('Failed to load MCP SDK. Install: npm install @modelcontextprotocol/sdk zod\n' +
673
+ 'Or v2 alpha: npm install @modelcontextprotocol/server zod @cfworker/json-schema');
674
+ process.exit(1);
675
+ }
676
+ async function main() {
677
+ const { McpServer, StdioServerTransport } = await loadMcpSdk();
678
+ const server = new McpServer({ name: 'ductape-mcp', version: '0.1.0' });
679
+ const transport = new StdioServerTransport();
680
+ const executeHandler = async (args) => {
681
+ try {
682
+ if (!args.publishable_key) {
683
+ throw new Error('Not authenticated. Please provide the `publishable_key` in your tool invocation arguments.');
684
+ }
685
+ const result = await executeViaProxy(args.publishable_key, args.module, args.method, args.params);
686
+ return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
687
+ }
688
+ catch (err) {
689
+ const message = err instanceof Error ? err.message : String(err);
690
+ return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
691
+ }
692
+ };
693
+ const payloadGenerateHandler = async (args) => {
694
+ try {
695
+ const result = await generateExecutablePayload(args);
696
+ return { content: [{ type: 'text', text: JSON.stringify(result ?? null, null, 2) }] };
697
+ }
698
+ catch (err) {
699
+ const message = err instanceof Error ? err.message : String(err);
700
+ return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
701
+ }
702
+ };
703
+ const snippetGenerateHandler = async (args) => {
704
+ try {
705
+ ensureSupportedSnippetOperation(args.operation_family, args.method);
706
+ const generated = await generateExecutablePayload(args);
707
+ const payload = generated?.payload ?? {};
708
+ const snippet = buildSnippet(args.language, payload, args.operation_family, args.method);
709
+ return {
710
+ content: [
711
+ {
712
+ type: 'text',
713
+ text: JSON.stringify({
714
+ payload: generated,
715
+ snippet,
716
+ }, null, 2),
717
+ },
718
+ ],
719
+ };
720
+ }
721
+ catch (err) {
722
+ const message = err instanceof Error ? err.message : String(err);
723
+ return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
724
+ }
725
+ };
726
+ if (typeof server.registerTool === 'function') {
727
+ server.registerTool('ductape_execute', {
728
+ title: 'Ductape SDK Execute',
729
+ description: 'Execute a Ductape SDK operation via the backend proxy (databases, graph, storage, etc.)',
730
+ inputSchema: executeInputSchema,
731
+ }, executeHandler);
732
+ server.registerTool('ductape_generate_payload', {
733
+ title: 'Ductape Payload Generator',
734
+ description: 'Generate canonical executable payload templates and schema metadata for SDK code snippet generation',
735
+ inputSchema: payloadGenerateInputSchema,
736
+ }, payloadGenerateHandler);
737
+ server.registerTool('ductape_generate_snippet', {
738
+ title: 'Ductape Snippet Generator',
739
+ description: 'Generate canonical payload and ready TypeScript/Python snippet for engineers',
740
+ inputSchema: snippetGenerateInputSchema,
741
+ }, snippetGenerateHandler);
742
+ }
743
+ else if (typeof server.tool === 'function') {
744
+ server.tool('ductape_execute', executeInputSchema.shape, executeHandler);
745
+ server.tool('ductape_generate_payload', payloadGenerateInputSchema.shape, payloadGenerateHandler);
746
+ server.tool('ductape_generate_snippet', snippetGenerateInputSchema.shape, snippetGenerateHandler);
747
+ }
748
+ else {
749
+ console.error('MCP server does not expose .registerTool() or .tool()');
750
+ process.exit(1);
751
+ }
752
+ await server.connect(transport);
753
+ }
754
+ main().catch((err) => {
755
+ console.error(err);
756
+ process.exit(1);
757
+ });