@ductape/mcp 0.2.22 → 0.2.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -90,12 +90,15 @@ There are THREE categories of operations. Use the right tool for each:
90
90
  These require an access key and CANNOT be done via ductape_execute (publishable key only).
91
91
  → Use ductape_cli instead. Examples:
92
92
  ductape_cli("products list")
93
- ductape_cli("products create --name \\"My Product\\" --tag my-product")
93
+ ductape_cli("products create -f product.json") # body: { name, tag?, envs?: [...] } — no --name/--tag flags exist, only -f <file.json> or interactive TTY
94
94
  ductape_cli("cloud connections list")
95
95
  ductape_cli("link --product my-product --env dev")
96
96
  If the CLI is not installed, ductape_cli will return install instructions automatically.
97
- NOTE: App actions are configured in the Workbench UI — there is no CLI command for them.
98
97
  Environments DO have CLI commands: ductape_cli("products environments list/get/create/update ...").
98
+ App actions (endpoints) also have CLI commands: ductape_cli("apps actions list/get/create/update/delete
99
+ --app <app_tag> ..."). When the user wants to integrate a new endpoint, call
100
+ ductape_integrate_endpoint first — it returns the full ordered flow (choose/create the app,
101
+ define the action, connect the app to the product) before any code should be written.
99
102
 
100
103
  RESOLVING "No linked project" ERRORS:
101
104
  Some commands (declarative sync below, db migrate/schema, products environments *) need a
@@ -194,13 +197,22 @@ There are THREE categories of operations. Use the right tool for each:
194
197
  1. ASSET CREATION / UPDATE (create, update, add, register… for ANY asset type)
195
198
  ALL creation and update operations require an access key and CANNOT go through ductape_execute.
196
199
  → Use ductape_cli for every create/update operation. Examples:
197
- ductape_cli("apps create --name \\"Email Service\\" --description \\"Transactional email\\"")
200
+ ductape_cli("apps create -f app.json") # no --name/--description flags exist, only -f <file.json> or interactive TTY. body: { app_name, description, tag, envs?: [{ env_name, slug, description, base_url, active }] } — tag is REQUIRED.
198
201
  ductape_cli("apps list")
199
202
  ductape_cli("resources storage list")
200
203
  ductape_cli("resources database create -f db-config.json")
201
204
  This applies to: products, apps, and resources (databases, storage, caches, etc.),
202
205
  cloud connections, and secrets. Environments have their own CLI commands (see below);
203
- App actions and auths are configured in the Workbench UI. Quotas, fallbacks, jobs, and
206
+ app actions have their own CLI commands too ductape_cli("apps actions create --app <tag>
207
+ --action-file action.json") (NOT -f — that flag is claimed by the parent "apps" command, so
208
+ Commander resolves it against the ancestor and this subcommand never sees it; same fix as
209
+ "products environments create/update" uses --env-file for the identical reason). Body: {
210
+ tag, name, resource, method, request_type, description?, params?, query?, headers?, body?,
211
+ response? }, where resource is a path RELATIVE to each of the app's environment base_urls
212
+ (e.g. "/v1/users/{id}"), not a full URL, and request_type is REQUIRED — one of
213
+ "application/json" | "application/x-www-form-urlencoded" | "multipart/form-data" | "SOAP" |
214
+ "html" (not the short names "json"/"xml"/"form"). Auths are still configured
215
+ in the Workbench UI. Quotas, fallbacks, jobs, and
204
216
  healthchecks are administrative resources managed with ductape_cli("resources <type> ...").
205
217
  Features have no CLI create command because their definitions are code-first through
206
218
  features.define. They DO have a CLI persistence command — ductape_cli("features sync") —
@@ -470,8 +482,26 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
470
482
  app.init [app_tag]
471
483
 
472
484
  ━━━ MODULE: actions (app actions) ━━━
473
- 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" } }]
474
- 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 } }]
485
+ These are admin ops go through ductape_cli, not ductape_execute:
486
+ ductape_cli("apps actions create --app <app_tag> --action-file action.json")
487
+ ductape_cli("apps actions update --app <app_tag> --action <action_tag> --action-file action.json")
488
+ ductape_cli("apps actions list --app <app_tag> --json")
489
+ ductape_cli("apps actions get --app <app_tag> --action <action_tag> --json")
490
+ ductape_cli("apps actions delete --app <app_tag> --action <action_tag>")
491
+ NOTE: the flag is --action-file, NOT -f/--file — that flag belongs to the parent "apps" command
492
+ (used by "apps create"/"apps update"), and Commander resolves a flag shared by an ancestor and a
493
+ descendant against the ancestor, so -f here would silently never reach this subcommand. Verified
494
+ live: passing -f prints "required option '-f, --file <path>' not specified" even though it was
495
+ supplied; --action-file is the fix (same pattern already used by "products environments
496
+ create/update" with --env-file for the identical collision).
497
+ resource is a path RELATIVE to each of the app's environment base_urls (e.g. "/v1/users/{id}"),
498
+ never a full URL — Ductape joins base_url + resource per environment at call time.
499
+ request_type is REQUIRED (verified live) — one of "application/json" |
500
+ "application/x-www-form-urlencoded" | "multipart/form-data" | "SOAP" | "html". response.status_code
501
+ is a STRING enum of standard HTTP status codes (e.g. "200", not the number 200) and
502
+ response.response_format uses the SAME enum as request_type above (not "json"/"xml"/"form").
503
+ actions.create [app_tag, data: { tag: string, name: string, resource: string, method: "GET"|"POST"|"PUT"|"PATCH"|"DELETE", request_type: "application/json"|"application/x-www-form-urlencoded"|"multipart/form-data"|"SOAP"|"html", description?: string, body?: object, params?: object, query?: object, headers?: object, response?: { name?: string, status_code: string, success: boolean, body: object, response_format: "application/json"|"application/x-www-form-urlencoded"|"multipart/form-data"|"SOAP"|"html" } }]
504
+ actions.update [app_tag, action_tag, data: { resource?: string, method?: "GET"|"POST"|"PUT"|"PATCH"|"DELETE", description?: string, request_type?: "application/json"|"application/x-www-form-urlencoded"|"multipart/form-data"|"SOAP"|"html", body?: object, query?: object, params?: object, headers?: object, response?: { name?: string, success: boolean, body: object, response_format: "application/json"|"application/x-www-form-urlencoded"|"multipart/form-data"|"SOAP"|"html", status_code: string } }]
475
505
  actions.fetch [app_tag, action_tag]
476
506
  actions.list [app_tag]
477
507
  actions.run [{ product, env, app, action, input: { "body:fieldName": value, ... } }] ← CALL ductape_generate_payload FIRST (operation_family="action", method="run", targets={app, action})
@@ -1112,6 +1142,11 @@ const marketplaceConnectInputSchema = z.object({
1112
1142
  const marketplaceInspectInputSchema = z.object({
1113
1143
  app_tag: z.string().min(1).describe('Exact public app tag returned by ductape_marketplace_discover.'),
1114
1144
  });
1145
+ const integrateEndpointInputSchema = z.object({
1146
+ product_tag: z.string().min(1).optional().describe('Target product tag, if already known. Omit if the user has not said which product this is for yet.'),
1147
+ app_tag: z.string().min(1).optional().describe('Existing app tag, if the user already said which app this endpoint belongs to. Omit when creating ' +
1148
+ 'a new app, or when the app has not been chosen yet — the returned flow starts by asking.'),
1149
+ });
1115
1150
  function toPrettyJson(value) {
1116
1151
  return JSON.stringify(value ?? {}, null, 2);
1117
1152
  }
@@ -2750,8 +2785,12 @@ If a feature step needs to call an external service and no App is registered for
2750
2785
  An app is a versioned API integration definition. It contains environments (base URLs), actions
2751
2786
  (individual endpoint specs), auth schemes, webhooks, variables, and constants.
2752
2787
 
2753
- Create an app (admin ductape_cli):
2754
- ductape_cli("apps create --name \\"Email Service\\" --description \\"Transactional email\\"")
2788
+ Call ductape_integrate_endpoint for the full ordered flow (choose/create the app, define the
2789
+ action, connect it to the product) before writing the ctx.api.run call below.
2790
+
2791
+ Create an app (admin — ductape_cli; no --name/--description flags exist, only -f <file.json> or
2792
+ interactive TTY):
2793
+ ductape_cli("apps create -f app.json") # body: { app_name, description, tag, envs?: [...] } — tag is REQUIRED
2755
2794
  ductape_cli("app.init", ["app_tag"]) → loads app into builder state
2756
2795
 
2757
2796
  Import from a file:
@@ -2832,15 +2871,27 @@ Connecting an app to a product (after creation):
2832
2871
  NOTE: All product.* module methods require the access key and CANNOT use ductape_execute.
2833
2872
  Use ductape_cli for all product-level operations.
2834
2873
 
2835
- FULL FLOW to make an app callable from a product:
2836
- 1. Create the app: ductape_cli("apps create --name \\"Service Name\\" --description \\"...\\"")
2837
- 2. Add environments: Workbench
2838
- 3. Configure auth: Workbench
2839
- 4. Define actions: Workbench
2874
+ FULL FLOW to make an app callable from a product — call ductape_integrate_endpoint for the
2875
+ ordered version of this with exact ask-the-user points; summary (all verified live):
2876
+ 1. Create the app: ductape_cli("apps create -f app.json")
2877
+ body: { app_name, description, tag, envs: [{ env_name, slug, description,
2878
+ base_url, active }, ...] } — tag is REQUIRED; every active env needs a base_url.
2879
+ No --name/--description flags exist, only -f <file.json> or interactive TTY.
2880
+ 2. Add environments: Part of step 1's envs array, or later via ductape_cli("apps update --tag
2881
+ <app_tag> --proxy -f app.json") with an updated envs array.
2882
+ 3. Configure auth: Workbench (no CLI command).
2883
+ 4. Define actions: ductape_cli("apps actions create --app <app_tag> --action-file action.json")
2884
+ body: { tag, name, resource, method, request_type, description?, params?,
2885
+ query?, headers?, body?, response? } — request_type is REQUIRED, one of
2886
+ "application/json" | "application/x-www-form-urlencoded" |
2887
+ "multipart/form-data" | "SOAP" | "html" (not "json"/"xml"/"form"); resource
2888
+ is relative to each env's base_url, never a full URL.
2840
2889
  OR import: ductape_cli("apps import <file.json> -t postman|openapi")
2841
- 5. Connect to product: Requires the product_id.
2842
- There is no CLI command for this step — the SDK product.apps.add method requires
2843
- an access key which only the backend can provide. Connect via Workbench.
2890
+ 5. Connect to product: ductape_cli("products apps connect --product <product_tag> --app <app_tag>
2891
+ --env-map <app_env>:<product_env> [repeat --env-map per environment] --json")
2892
+ Requires the app to be public in the marketplace OR owned by the same
2893
+ workspace as the product. Does the full connect + environment-mapping
2894
+ sequence in one call.
2844
2895
  6. Verify: ductape_cli("products apps list --product <product_tag> --json")
2845
2896
  ductape_cli("products apps actions list --product <product_tag> --app <app_tag> --json")
2846
2897
  `.trim(),
@@ -5002,7 +5053,14 @@ EMBEDDING EXECUTION — CURRENT CODE-FIRST CONTRACT:
5002
5053
  generated.embeddings.some(v => v.length !== EXPECTED_VECTOR_DIMENSIONS)) {
5003
5054
  throw new Error('EMBEDDING_DIMENSION_MISMATCH');
5004
5055
  }
5005
- Register a real local handler at application bootstrap or configure a signed HTTPS/events transport.
5056
+ Register a real local handler at application bootstrap or configure gRPC-mTLS, Events, or signed HTTPS.
5057
+ Remote resolution order is gRPC-mTLS, Events, then signed HTTPS. A gRPC transport is
5058
+ { type:'grpc', endpoint:'host:443', service:'ductape.functions.v1.PortableFunctions',
5059
+ method:'Invoke', authentication:'mtls', tls:{ ca_env, cert_env, key_env } }.
5060
+ Never write PEM, certificates, private keys, tokens, or access keys into function JSON. Set
5061
+ DUCTAPE_FUNCTION_GRPC_ENDPOINT and referenced TLS variables in the application runtime. The gRPC
5062
+ adapter must pool channels. Only UNAVAILABLE/deadline failures may fall back; auth, correlation,
5063
+ schema, and application failures must not execute again over another transport.
5006
5064
  Input and output JSON schemas are validated at runtime. Missing implementation fails with
5007
5065
  FUNCTION_UNAVAILABLE; schema mismatch fails with FUNCTION_SCHEMA_VALIDATION_FAILED; timeout fails
5008
5066
  with FUNCTION_TIMEOUT. Never substitute sample/random/recording-time embeddings. The Feature's
@@ -5156,6 +5214,113 @@ const portableFunctionSetupHandler = async (args) => {
5156
5214
  ],
5157
5215
  }, null, 2) }] };
5158
5216
  };
5217
+ // This tool only derives a step-by-step plan from its arguments and read-only lookups the calling
5218
+ // agent performs itself via other tools. It runs no CLI command and writes nothing.
5219
+ const integrateEndpointHandler = async (args) => {
5220
+ const hasApp = Boolean(args.app_tag);
5221
+ const hasProduct = Boolean(args.product_tag);
5222
+ const stages = [];
5223
+ if (!hasApp) {
5224
+ stages.push({
5225
+ stage: 0,
5226
+ title: 'Choose the app',
5227
+ ask_user: 'Should this endpoint be added to an existing app, or do you want to create a new app for it?',
5228
+ if_existing_app: [
5229
+ 'Get the exact app tag. If unsure, call ductape_cli("apps list --json"), or ' +
5230
+ 'ductape_marketplace_discover/ductape_marketplace_inspect if it might be a public marketplace app.',
5231
+ 'Re-call this tool with that app_tag once known — the returned flow will skip straight to stage 2.',
5232
+ ],
5233
+ if_new_app: 'Continue to stage 1.',
5234
+ });
5235
+ }
5236
+ if (!hasApp) {
5237
+ stages.push({
5238
+ stage: 1,
5239
+ title: 'Create the app (new app path only — skip if using an existing app)',
5240
+ ask_user: [
5241
+ 'App name and a short description.',
5242
+ 'What environments does this app need (e.g. dev, staging, production), and what is the base ' +
5243
+ 'URL for each? At least one environment is required. Every ACTIVE environment must have a ' +
5244
+ 'base_url before the action in stage 2 can be called against it.',
5245
+ ],
5246
+ then: [
5247
+ 'Write a JSON body: { app_name, description, tag, envs: [{ env_name, slug, description, ' +
5248
+ 'base_url, active: true }, ...] }. tag is REQUIRED — derive a slug from app_name if the ' +
5249
+ 'user has not given one explicitly.',
5250
+ 'Run: ductape_cli("apps create -f <path-to-body.json> --json")',
5251
+ 'The app tag is what you supplied, not something the response generates — use it as app_tag ' +
5252
+ 'in every later stage.',
5253
+ ],
5254
+ });
5255
+ }
5256
+ stages.push({
5257
+ stage: 2,
5258
+ title: 'Define the action (the endpoint itself)',
5259
+ app_tag: args.app_tag ?? '<app tag from stage 1, or the existing app tag>',
5260
+ ask_user: [
5261
+ 'The HTTP method (GET/POST/PUT/PATCH/DELETE).',
5262
+ 'The endpoint PATH relative to the app\'s base_url — e.g. "/v1/users/{id}", never a full URL. ' +
5263
+ 'Ductape joins base_url + resource per environment at call time, so the same action works ' +
5264
+ 'across every environment automatically.',
5265
+ 'Only what is relevant to this specific endpoint: query params, headers, request body shape ' +
5266
+ '(for POST/PUT/PATCH), and the expected response shape (status_code, success flag, a body ' +
5267
+ 'sample). Do not invent fields the user has not described or that are not in the API docs.',
5268
+ ],
5269
+ then: [
5270
+ 'If the exact body shape is uncertain, call ductape_schema first.',
5271
+ 'Write a JSON body: { tag, name, resource, method, request_type, description?, params?, ' +
5272
+ 'query?, headers?, body?, response? }. request_type is REQUIRED — one of "application/json" ' +
5273
+ '| "application/x-www-form-urlencoded" | "multipart/form-data" | "SOAP" | "html" (not ' +
5274
+ '"json"/"xml"/"form"). If response is included, response.status_code and ' +
5275
+ 'response.response_format use that same set of values, and status_code is a STRING (e.g. ' +
5276
+ '"200", not the number 200).',
5277
+ 'Run: ductape_cli("apps actions create --app <app_tag> --action-file <path-to-action.json> ' +
5278
+ '--json") — the flag is --action-file, not -f (that flag belongs to the parent "apps" ' +
5279
+ 'command and Commander resolves it there, so -f would silently be ignored here).',
5280
+ ],
5281
+ });
5282
+ stages.push({
5283
+ stage: 3,
5284
+ title: 'Connect the app to the product',
5285
+ skip_if: 'The app is already connected to the target product — check with ' +
5286
+ 'ductape_cli("products apps list --product <product_tag> --json") before asking the user anything here.',
5287
+ product_tag: args.product_tag ?? '<ask the user, if not already established from context>',
5288
+ then: [
5289
+ hasProduct
5290
+ ? `Product already known: ${args.product_tag}.`
5291
+ : 'Ask the user which product this belongs to, unless already obvious from context.',
5292
+ 'Get the product\'s environments: ductape_cli("products environments list <product_tag> --json")',
5293
+ 'Map each product environment to one of the app\'s environments (usually 1:1 by name) — ask the ' +
5294
+ 'user rather than guessing if it is not obvious.',
5295
+ 'Run: ductape_cli("products apps connect --product <product_tag> --app <app_tag> --env-map ' +
5296
+ '<app_env>:<product_env> [repeat --env-map per environment] --json"). If the app is a public ' +
5297
+ 'marketplace app, ductape_marketplace_connect does the same thing in one call.',
5298
+ ],
5299
+ });
5300
+ stages.push({
5301
+ stage: 4,
5302
+ title: 'Only now write integration code',
5303
+ instructions: [
5304
+ 'Do not write any code that calls this endpoint before stages 0-3 are complete — the action and ' +
5305
+ 'the product connection must exist first, or the generated code will reference a tag that ' +
5306
+ 'does not exist yet.',
5307
+ 'Call ductape_generate_payload (operation_family="action", method="run" or "dispatch", ' +
5308
+ 'targets={app, action}) first, then ductape_generate_snippet or ductape_execute to call it.',
5309
+ ],
5310
+ });
5311
+ return {
5312
+ content: [{
5313
+ type: 'text',
5314
+ text: JSON.stringify({
5315
+ ok: true,
5316
+ flow: 'integrate_endpoint',
5317
+ note: 'Follow these stages in order. Each stage tells you what to ask the user and which ' +
5318
+ 'ductape_cli command to run. Stages already satisfied by the arguments you passed are omitted.',
5319
+ stages,
5320
+ }, null, 2),
5321
+ }],
5322
+ };
5323
+ };
5159
5324
  const eventsTopicSetupHandler = async (args) => {
5160
5325
  const tag = `${args.broker_tag}:${args.topic_tag}`;
5161
5326
  const relativePath = `ductape/events/${args.topic_tag}.topic.json`;
@@ -5840,6 +6005,15 @@ async function main() {
5840
6005
  command: `products apps connect --product ${shellArgument(args.product_tag)} --app ${shellArgument(args.app_tag)}${mappings} --json`,
5841
6006
  });
5842
6007
  });
6008
+ server.registerTool('ductape_integrate_endpoint', {
6009
+ title: 'Integrate a New Endpoint',
6010
+ description: 'Call this FIRST whenever the user wants to integrate a new API endpoint — before writing any ' +
6011
+ 'code. Returns the ordered flow: choose an existing app or create a new one, define the ' +
6012
+ 'action (endpoint) on it, connect the app to the target product, and only then write code. ' +
6013
+ 'Read-only — it runs no CLI command itself and performs the steps only when you follow up with ' +
6014
+ 'the ductape_cli calls it names.',
6015
+ inputSchema: integrateEndpointInputSchema,
6016
+ }, integrateEndpointHandler);
5843
6017
  server.registerTool('ductape_cli', {
5844
6018
  title: 'Ductape CLI',
5845
6019
  description: 'Run a Ductape CLI command for administrative operations.\n\n' +
@@ -5958,6 +6132,7 @@ async function main() {
5958
6132
  server.tool('ductape_function_setup', portableFunctionSetupInputSchema.shape, portableFunctionSetupAnnotations, portableFunctionSetupHandler);
5959
6133
  server.tool('ductape_redis_setup', redisSetupInputSchema.shape, redisSetupHandler);
5960
6134
  server.tool('ductape_migration_plan', migrationInputSchema.shape, migrationHandler);
6135
+ server.tool('ductape_integrate_endpoint', integrateEndpointInputSchema.shape, integrateEndpointHandler);
5961
6136
  server.tool('ductape_cli', cliInputSchema.shape, cliHandler);
5962
6137
  }
5963
6138
  else {
package/docs/TOOLS.md CHANGED
@@ -2,6 +2,42 @@
2
2
 
3
3
  The Ductape MCP server exposes proxy, CLI, discovery, documentation, migration, and portable-function setup tools.
4
4
 
5
+ ## Tool: `ductape_integrate_endpoint`
6
+
7
+ Call this first whenever the user wants to integrate a new API endpoint, before writing any code.
8
+ It is read-only guidance — it runs no CLI command itself and returns the ordered flow to follow:
9
+
10
+ 1. **Choose the app** — ask first: existing app, or create a new one? Do not assume "new" just
11
+ because the API seems unfamiliar — check `ductape_cli("apps list --json")` and let the user
12
+ decide. If new: `ductape_cli("apps create -f app.json")`, body `{ app_name, description, tag,
13
+ envs: [...] }` (`tag` is REQUIRED — no `--name`/`--description` flags exist, only `-f
14
+ <file.json>` or interactive TTY), collecting an environment + base URL for each environment the
15
+ app needs; every active environment needs a `base_url` before its action can be called.
16
+ 2. **Define the action** — the HTTP method and the endpoint's path *relative to the app's base_url*
17
+ (e.g. `/v1/users/{id}`, never a full URL — Ductape joins `base_url + resource` per environment at
18
+ call time), plus only the params/headers/body/response fields relevant to that endpoint. Then
19
+ `ductape_cli("apps actions create --app <tag> --action-file action.json")` — the flag is
20
+ `--action-file`, **not** `-f`/`--file` (that flag belongs to the parent `apps` command; Commander
21
+ resolves a flag shared by an ancestor and a descendant against the ancestor, so `-f` here is
22
+ silently ignored — same fix `products environments create/update` uses `--env-file` for).
23
+ `request_type` is REQUIRED on the body — one of `"application/json"` |
24
+ `"application/x-www-form-urlencoded"` | `"multipart/form-data"` | `"SOAP"` | `"html"` (not the
25
+ short names `"json"`/`"xml"`/`"form"`); if `response` is included, `response.status_code` is a
26
+ **string** (e.g. `"200"`) and `response.response_format` uses that same value set.
27
+ 3. **Connect the app to the product** — skip if already connected. Otherwise map each product
28
+ environment to an app environment and run
29
+ `ductape_cli("products apps connect --product <tag> --app <tag> --env-map <app_env>:<product_env> ...")`.
30
+ 4. **Only then write integration code** — `ductape_generate_payload` → `ductape_generate_snippet` /
31
+ `ductape_execute`, calling `actions.run` or `actions.dispatch`.
32
+
33
+ Optional arguments `product_tag` and `app_tag` let the tool skip stages already answered (e.g. pass
34
+ `app_tag` once the user has said which existing app this belongs to, and the returned flow starts at
35
+ stage 2 instead of asking again) — but never infer `app_tag` on the tool's behalf without the user
36
+ having actually said so; when in doubt, omit it and let stage 1 ask.
37
+
38
+ All of the above was verified against a live workspace: created a `jsonplaceholder` app with `prd`/
39
+ `snd` environments, added a `GET /todos/1` action, and connected it to a real product end to end.
40
+
5
41
  ## Tools: `ductape_events_topic_setup` and `ductape_events_validate_project`
6
42
 
7
43
  `ductape_events_topic_setup` is a read-only generator for one canonical topic asset. It returns the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.2.22",
3
+ "version": "0.2.23",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",