@ductape/mcp 0.2.0 → 0.2.2

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/CHANGELOG.md CHANGED
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ - Broadened Feature guidance from durable/event-driven workflows to synchronous or asynchronous named product capabilities.
6
+ - Added evidence-backed `FEATURE`, `FEATURE_STEP`, `DOMAIN_SERVICE`, `UTILITY`, and `INFRASTRUCTURE_ADAPTER` classification guidance.
7
+ - Added synchronous multi-step examples and repository discovery/grouping rules.
8
+
3
9
  ## 0.2.0 - 2026-07-26
4
10
 
5
11
  - Added exhaustive AI-led migration guidance for TypeScript, Go, Java, and .NET.
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Migration guidance is available from `ductape_docs({ topic: "migration" })`. It starts with an original E2E
4
4
  baseline, defaults to a separate new codebase, requires contextual review and strict parity evidence, and ends
5
- against the unchanged original E2E suite. Administrative work uses `ductape_cli`; `ductape_execute` remains
5
+ against the unchanged original E2E suite. Administrative work and marketplace discovery use `ductape_cli`; `ductape_execute` remains
6
6
  publishable-key runtime-only. MCP never accepts or forwards `DUCTAPE_ACCESS_KEY`.
7
7
 
8
8
  MCP (Model Context Protocol) server that exposes **Ductape SDK** operations as tools. All calls go through the **Ductape backend proxy** at a fixed URL; the SDK never runs in the MCP process. It is completely stateless; you provide your **Publishable Key** per execution.
@@ -82,6 +82,19 @@ The server exposes **three tools**:
82
82
  - ready-to-copy SDK snippet in `typescript` or `python`
83
83
  - Intended for engineers and copilots that need executable examples quickly.
84
84
 
85
+ The `ductape_cli` MCP tool also exposes public app discovery:
86
+ `marketplace search <capability>`, `marketplace categories`, and
87
+ `marketplace get <app_tag>`. Inspect the app before generating or executing an action payload.
88
+
89
+ ## Feature classification
90
+
91
+ `ductape_docs({ topic: "features" })` treats a Feature as a named, reusable product capability
92
+ with a stable input/output contract and a useful managed-execution boundary. Features may be
93
+ synchronous and entirely local; Events, signals, schedules, waits, retries, and rollback are
94
+ optional patterns. Repository analysis distinguishes `FEATURE`, `FEATURE_STEP`, `DOMAIN_SERVICE`,
95
+ `UTILITY`, and `INFRASTRUCTURE_ADAPTER`, explains its evidence, and groups related low-level
96
+ operations instead of turning every exported function into a Feature.
97
+
85
98
  ## Security
86
99
 
87
100
  - Passing the `publishable_key` on a per-request basis guarantees that each execution is isolated. This architecture safely supports deployments that multiplex multiple user connections in a single server thread (e.g., SSE), avoiding cross-tenant leakage.
package/dist/index.js CHANGED
@@ -35,7 +35,24 @@ There are THREE categories of operations. Use the right tool for each:
35
35
  ductape_cli("cloud connections list")
36
36
  ductape_cli("link --product my-product --env dev")
37
37
  If the CLI is not installed, ductape_cli will return install instructions automatically.
38
- NOTE: Environments and app actions are configured in the Workbench UI — there are no CLI commands for them.
38
+ NOTE: App actions are configured in the Workbench UI — there is no CLI command for them.
39
+ Environments DO have CLI commands: ductape_cli("products environments list/get/create/update ...").
40
+
41
+ RESOLVING "No linked project" ERRORS:
42
+ Some commands (declarative sync below, db migrate/schema, products environments *) need a
43
+ linked project — a local .ductape/config.json with a product tag and env slug. The "link"
44
+ command does NOT validate against the server: it just writes that local file. This means you
45
+ can run ductape_cli("link --product <tag> --env <slug>") even before that environment exists on
46
+ the product yet — do not treat "no linked environment exists server-side" as a reason to avoid
47
+ linking first. If the failing command already takes the product tag as an explicit argument
48
+ (e.g. products environments create/update/list/get), linking is not even required for it —
49
+ only commands that need to *infer* the product/env from local project state require a link.
50
+ Also: this MCP server runs the CLI subprocess in the directory named by the DUCTAPE_PROJECT_DIR
51
+ env var (falls back to this server process's own cwd if unset). If a command inexplicably
52
+ reports "no linked project" right after a successful "link" call, the project directory the
53
+ link was written to and the directory this server is running the CLI from may not match — set
54
+ DUCTAPE_PROJECT_DIR explicitly in this server's env (e.g. in the consuming project's .mcp.json)
55
+ to the target project's absolute path.
39
56
 
40
57
  DECLARATIVE SYNC (apply sessions, notifications, events from code; run DB migrations)
41
58
  → Also use ductape_cli. The project must be linked first (ductape init --link).
@@ -123,9 +140,10 @@ There are THREE categories of operations. Use the right tool for each:
123
140
  ductape_cli("resources storage list")
124
141
  ductape_cli("resources database create -f db-config.json")
125
142
  This applies to: products, apps, and resources (databases, storage, caches, etc.),
126
- cloud connections, and secrets. Environments, app actions, auths, quotas, fallbacks,
127
- jobs, and healthchecks are configured in the Workbench UI. Features have no CLI create
128
- command because their definitions are code-first through features.define.
143
+ cloud connections, and secrets. Environments have their own CLI commands (see below);
144
+ app actions, auths, quotas, fallbacks, jobs, and healthchecks are configured in the
145
+ Workbench UI. Features have no CLI create command because their definitions are code-first
146
+ through features.define.
129
147
 
130
148
  ⚠ MULTI-ENV REQUIREMENT — applies to ALL product assets (storage, database, cache,
131
149
  messageBroker, graph, vector, and any other resource with an envs array):
@@ -324,9 +342,9 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
324
342
  IMPORTANT: ALL product.* methods require the access key and will return 403 with a publishable key.
325
343
  Use ductape_cli for ALL product operations — never ductape_execute:
326
344
  ductape_cli("products get --tag <tag> --json") ← fetch product + full inventory
327
- ductape_cli("products components list --tag <tag> --json") ← compact non-secret inventory
328
- ductape_cli("products components get --tag <tag> --type notifications --json")
329
- ductape_cli("products components get --tag <tag> --type events --json")
345
+ ductape_cli("products components list --product-tag <tag> --json") ← compact non-secret inventory
346
+ ductape_cli("products components get --product-tag <tag> --type notifications --json")
347
+ ductape_cli("products components get --product-tag <tag> --type events --json")
330
348
  ductape_cli("products create --name <name> --tag <tag>")
331
349
  ductape_cli("products environments list <tag> --json")
332
350
  ductape_cli("products environments get <tag> <slug> --json")
@@ -725,6 +743,10 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
725
743
  ━━━ MODULE: features ━━━
726
744
  Feature definitions are code-first. Use features.define in application source; do not call
727
745
  administrative create/update/delete methods through ductape_execute.
746
+ A Feature is a named, reusable product capability with a stable input/output contract that
747
+ benefits from managed execution, composition, observability, retries, versioning, policy
748
+ enforcement, or explicit execution steps. It may execute synchronously and entirely locally.
749
+ Signals, Events, schedules, waits, checkpoints, compensation, and rollback are optional patterns.
728
750
  features.fetch [product_tag, feature_tag]
729
751
  features.fetchAll [product_tag]
730
752
 
@@ -1129,7 +1151,7 @@ const ADMIN_SUBCOMMANDS = [
1129
1151
  'profiles',
1130
1152
  'workspaces',
1131
1153
  'link', 'unlink', 'init',
1132
- 'products', 'apps',
1154
+ 'products', 'apps', 'marketplace',
1133
1155
  'resources',
1134
1156
  'notifications',
1135
1157
  'events',
@@ -1156,6 +1178,7 @@ function checkCli() {
1156
1178
  timeout: 5000,
1157
1179
  stdio: ['pipe', 'pipe', 'pipe'],
1158
1180
  env: cliEnvironment(),
1181
+ cwd: cliCwd(),
1159
1182
  }).trim();
1160
1183
  return { available: true, version: out || 'unknown' };
1161
1184
  }
@@ -1173,6 +1196,7 @@ function checkLoginState() {
1173
1196
  timeout: 10000,
1174
1197
  stdio: ['pipe', 'pipe', 'pipe'],
1175
1198
  env: cliEnvironment(),
1199
+ cwd: cliCwd(),
1176
1200
  });
1177
1201
  authState = 'ok';
1178
1202
  return 'ok';
@@ -1193,6 +1217,7 @@ function syncWorkspace() {
1193
1217
  timeout: 10000,
1194
1218
  stdio: ['pipe', 'pipe', 'pipe'],
1195
1219
  env: cliEnvironment(),
1220
+ cwd: cliCwd(),
1196
1221
  });
1197
1222
  }
1198
1223
  catch {
@@ -1221,6 +1246,7 @@ function runCli(command) {
1221
1246
  timeout: 90000,
1222
1247
  stdio: ['pipe', 'pipe', 'pipe'],
1223
1248
  env: cliEnvironment(),
1249
+ cwd: cliCwd(),
1224
1250
  });
1225
1251
  return { success: true, output: output.trim() };
1226
1252
  }
@@ -1274,6 +1300,19 @@ function cliEnvironment() {
1274
1300
  delete environment.DUCTAPE_ACCESS_KEY;
1275
1301
  return environment;
1276
1302
  }
1303
+ /**
1304
+ * Directory the `ductape` CLI subprocess runs in. Commands that resolve a linked project
1305
+ * (findProjectConfig walking up from cwd — e.g. `products environments *`, `apply`, `db migrate`)
1306
+ * depend on this being the user's actual project directory, not wherever this MCP server process
1307
+ * itself happened to be spawned from. execSync inherits process.cwd() when no cwd is given, which
1308
+ * is only correct if this server was started from inside the target project — that doesn't hold
1309
+ * for every launch path (e.g. a host attaching this server to an already-running session whose
1310
+ * cwd is unrelated to the project). Set DUCTAPE_PROJECT_DIR explicitly in the server's env
1311
+ * (e.g. in .mcp.json) to pin it; falls back to this process's own cwd otherwise.
1312
+ */
1313
+ function cliCwd() {
1314
+ return process.env.DUCTAPE_PROJECT_DIR || process.cwd();
1315
+ }
1277
1316
  function shellArgument(value) {
1278
1317
  return `'${value.replace(/'/g, `'\\''`)}'`;
1279
1318
  }
@@ -1372,10 +1411,12 @@ ENVIRONMENTS
1372
1411
  (production→prd, sandbox→snd, staging→stg) but present uncertain mappings for confirmation.
1373
1412
  Before creating any asset, list product environments and require complete per-environment coverage.
1374
1413
  Missing environments can be created idempotently through the authenticated standalone CLI:
1375
- ductape_cli("products environments create <product-tag> -f <environment.json> --json")
1414
+ ductape_cli("products environments create <product-tag> --env-file <environment.json> --json")
1376
1415
  The JSON requires env_name, description, and a three-character slug. The CLI fetches first, creates only
1377
- when absent, then fetches again to verify persistence. Update and verify with:
1378
- ductape_cli("products environments update <product-tag> <slug> -f <patch.json> --json")
1416
+ when absent, then fetches again to verify persistence (with a short retry on the verification read to
1417
+ absorb replication lag). No linked project is required — the product tag is always the explicit argument.
1418
+ Update and verify with:
1419
+ ductape_cli("products environments update <product-tag> <slug> --env-file <patch.json> --json")
1379
1420
  Export authenticated inventory with ductape_cli("products environments list <product-tag> --json"),
1380
1421
  save it as evidence, then reconcile locally:
1381
1422
  ductape_cli("migration-environments --analysis <analysis.json> --inventory <inventory.json> --strict --json")
@@ -1436,9 +1477,12 @@ COMPONENT DECISIONS
1436
1477
  Databases/transactions → Ductape Database; brokers/queues → Events; object stores → Storage;
1437
1478
  SMTP/SMS/push/callbacks → Notifications; Redis/cache → Cache; Neo4j/Neptune/Arango/Memgraph → Graph;
1438
1479
  Pinecone/Qdrant/Weaviate/OpenSearch → Vector; JWT/session middleware → Sessions.
1439
- Multi-step durable/scheduled/signal-driven work → code-first Features.
1480
+ Named reusable product capabilities with a stable managed-execution boundary → code-first Features,
1481
+ including synchronous multi-step capabilities and durable/scheduled/signal-driven orchestration.
1440
1482
  Retries, health checks, fallbacks, quotas, circuit breakers → Resilience.
1441
- Keep deterministic calculations, validation, domain rules, and ordinary synchronous services as code.
1483
+ Keep low-level deterministic rules as ordinary domain functions when they do not form a useful
1484
+ independent capability boundary. Wrap or compose them into Features when the combined operation
1485
+ represents a reusable product capability.
1442
1486
 
1443
1487
  LANGUAGE RUNTIME SHAPES
1444
1488
  TypeScript: @ductape/sdk; NestJS uses @ductape/nestjs, @Events.Consumer, and request-scoped context.
@@ -1474,7 +1518,8 @@ AI EDITING STANDARD
1474
1518
  Compilation and happy-path tests alone do not demonstrate parity. Use existing tests, characterization
1475
1519
  tests, contract tests, integration tests, failure injection, and snd smoke tests appropriate to the slice.
1476
1520
  If parity cannot be demonstrated, stop short of cutover and report the exact unverified behavior.
1477
- Use Events for internal async boundaries and Apps/Actions for external APIs. Keep deterministic rules in code.
1521
+ Use Events for internal async boundaries and Apps/Actions for external APIs. Keep low-level deterministic
1522
+ rules in code, while allowing a coherent synchronous capability composed from those rules to be a Feature.
1478
1523
  Immediate work propagates the full session; durable work uses approved actor metadata or system context.
1479
1524
  Consumers are idempotent, retry ownership is singular and bounded, and external effects are not duplicated.
1480
1525
  After each slice: format, build, test, rescan, reconcile assets through ductape_cli, smoke-test in snd,
@@ -1550,6 +1595,10 @@ AUDITABLE REVIEW LEDGER
1550
1595
  ductape_cli("migration-review validate --ledger <path>/review-ledger.json --json")
1551
1596
  Before proposing cutover, require strict validation:
1552
1597
  ductape_cli("migration-review validate --ledger <path>/review-ledger.json --strict --json")
1598
+
1599
+ Before writing SDK integration code, query the exact supported language/version catalog:
1600
+ ductape_cli("migration-capabilities --language <typescript|go|java|dotnet> --version <exact-version> --json")
1601
+ The catalog is source-evidenced but does not replace inspection of the package actually installed in the target.
1553
1602
  Strict readiness requires no pending or stale files, reasoned exclusions, complete follow-ups,
1554
1603
  covered internal references, recorded file purpose, and verified or reasoned-not-applicable
1555
1604
  interface/functional/operational parity.
@@ -1627,6 +1676,18 @@ DEPENDENCY-AWARE LARGE-REPOSITORY PROPOSAL
1627
1676
  Verification-matrix records may declare details.depends_on using category.requirement identifiers. A stale
1628
1677
  cited dependency transitively invalidates dependent service/package summaries and readiness.
1629
1678
 
1679
+ MIGRATION ARTIFACT SAFETY
1680
+ Migration JSON is size-limited, secret-material scanned, version checked, and written through exclusive
1681
+ locks plus atomic rename. Mutating manifests retain a recoverable .bak copy. Validate or recover locally:
1682
+ ductape_cli("migration-artifact validate --file <artifact.json> --json")
1683
+ ductape_cli("migration-artifact recover --file <artifact.json> --json")
1684
+ Controlled legacy upgrades never rewrite the source artifact:
1685
+ ductape_cli("migration-artifact migrate --file <legacy.json> --output <v1.json> --json")
1686
+ Inspect the machine-readable schema catalog:
1687
+ ductape_cli("migration-artifact schemas --json")
1688
+ Errors include stable codes such as ARTIFACT_TOO_LARGE, ARTIFACT_SECRET_MATERIAL, ARTIFACT_LOCKED,
1689
+ ARTIFACT_VERSION_UNSUPPORTED, and ARTIFACT_RECOVERY_FAILED. Never bypass these checks with direct JSON edits.
1690
+
1630
1691
  PARITY-GATED MIGRATION SLICES
1631
1692
  Create a JSON array containing the reviewed repository-relative files for one vertical slice, then:
1632
1693
  ductape_cli("migration-slice init --ledger <ledger> --tag <tag> --name <name> --files <files.json> --json")
@@ -2277,6 +2338,18 @@ An App must be fully set up in Ductape before any code can use it:
2277
2338
  4. Action endpoints must be defined (each action = one HTTP endpoint spec: method, path, body/query/header shape, response shape)
2278
2339
  5. The App must be connected to the product (product.apps.add) and its envs mapped
2279
2340
 
2341
+ DISCOVER BEFORE CREATING:
2342
+ ductape_cli("marketplace search payments --json")
2343
+ ductape_cli("marketplace search paystack --json")
2344
+ ductape_cli("marketplace categories --json")
2345
+ ductape_cli("marketplace get <app_tag> --json")
2346
+
2347
+ marketplace search matches capability terms against public app names, tags, descriptions,
2348
+ categories, actions, and webhooks. marketplace get returns the complete public app definition,
2349
+ including the exact current-version action tags and body/query/header/param schemas. Never infer
2350
+ Paystack action names such as "initialize" or "verify": inspect the marketplace record first.
2351
+ If no suitable app exists, create one or import Paystack's OpenAPI/Postman definition.
2352
+
2280
2353
  ONLY after all five steps can any code call:
2281
2354
  ctx.api.run({ app: '<app_tag>', event: '<action_tag>', input: { ... } }) ← in a feature handler
2282
2355
  actions.run([{ product, env, app: '<app_tag>', action: '<action_tag>', input }]) ← at runtime
@@ -2339,6 +2412,22 @@ Run an action at runtime:
2339
2412
  ductape_execute("actions.run", [{ product, env, app, action, input: { "body:field": value } }])
2340
2413
  ductape_execute("actions.dispatch", [{ product, env, app, action, input, schedule? }])
2341
2414
 
2415
+ In a Ductape feature handler, call the registered action through:
2416
+ await ctx.api.run({ app: "<app_tag>", event: "<action_tag>", input: { ... } })
2417
+ ctx.api is the supported feature-context surface (also described as ctx.action in older code).
2418
+ There is no ctx.apps, ctx.integrations, or generic external-HTTP feature surface.
2419
+
2420
+ PAYSTACK CONFIGURATION:
2421
+ - Store the secret key as a workspace secret such as PAYSTACK_SECRET_KEY.
2422
+ - Reference it from app auth as: Authorization = "Bearer $Secret{PAYSTACK_SECRET_KEY}".
2423
+ - Never place the key in feature input, source code, logs, or marketplace app metadata.
2424
+ - Initialization and verification are raw app actions unless the inspected app explicitly
2425
+ publishes a higher-level contract. Ductape has no universal application payment abstraction.
2426
+ - Model inbound events as app webhook events. Verify x-paystack-signature against the raw
2427
+ request body using HMAC-SHA512 and PAYSTACK_SECRET_KEY before processing.
2428
+ - Acknowledge quickly, process asynchronously, deduplicate by event/reference, and verify the
2429
+ transaction through the inspected verification action before granting value.
2430
+
2342
2431
  Auth schemes (how the app authenticates outbound requests):
2343
2432
  Setup types: header | bearer | basic | oauth2 | apikey
2344
2433
  Configure auth in Workbench (administrative).
@@ -2995,9 +3084,90 @@ PAYLOAD RECIPES
2995
3084
  features: `
2996
3085
  DUCTAPE FEATURES
2997
3086
 
2998
- A feature is an orchestrated workflow of durable steps. Steps can call app actions, database
2999
- operations, graph queries, storage uploads, notifications, broker publishes, child features,
3000
- quotas, fallbacks, and more. Features support rollback, signals, checkpoints, and sleep.
3087
+ A Feature is a named, reusable product capability with a stable input/output contract that
3088
+ benefits from managed execution, composition, observability, retries, versioning, policy
3089
+ enforcement, or explicit execution steps.
3090
+
3091
+ A Feature may be synchronous or asynchronous and may be entirely local. Signals, Events, waits,
3092
+ schedules, checkpoints, retries, compensation, and rollback are optional capabilities—not
3093
+ prerequisites. Asynchronous behavior is not the primary definition of a Feature.
3094
+
3095
+ Common Feature patterns:
3096
+ - synchronous capability
3097
+ - multi-step computation
3098
+ - event-driven orchestration
3099
+ - signal-driven human workflow
3100
+ - scheduled capability
3101
+ - parent/child Feature composition
3102
+
3103
+ ━━━ CAPABILITY CLASSIFICATION — explain the evidence for every classification ━━━
3104
+
3105
+ Use exactly these categories while reviewing application code:
3106
+ FEATURE
3107
+ An independently meaningful product/domain capability with a useful managed-execution boundary.
3108
+ Recommendation: "Make this a standalone Ductape Feature."
3109
+ FEATURE_STEP
3110
+ A meaningful stage inside a larger capability, but not a useful independent execution boundary.
3111
+ Recommendation: "Expose this as a named ctx.step(...) inside another Feature."
3112
+ DOMAIN_SERVICE
3113
+ Reusable domain logic without a useful independent managed-execution boundary.
3114
+ Recommendation: "Keep this as ordinary domain logic called by a Feature."
3115
+ UTILITY
3116
+ A low-level helper such as hashing, formatting, redaction, normalization, conversion, or a type guard.
3117
+ Recommendation: "Keep this as a utility."
3118
+ INFRASTRUCTURE_ADAPTER
3119
+ Database, Event, HTTP, cache, storage, provider, transport, or framework integration code.
3120
+ Recommendation: "Keep this as an infrastructure adapter."
3121
+
3122
+ TypeScript export is only evidence of reuse. It is neither sufficient nor necessary for Feature
3123
+ classification. Never classify every exported function as a Feature.
3124
+
3125
+ Evaluate a candidate by asking:
3126
+ - Does it represent a recognizable product or domain capability?
3127
+ - Does it have a coherent responsibility?
3128
+ - Can it have a stable, typed input/output contract?
3129
+ - Is it reused across entry points, services, or other Features?
3130
+ - Would independent execution or composition be useful?
3131
+ - Would execution history or step-level observability be valuable?
3132
+ - Does it need explicit versioning, authorization, quotas, retries, or policy?
3133
+ - Does it contain several meaningful stages?
3134
+ - Would users or developers naturally name it as a product feature?
3135
+ A positive answer to several questions makes it a Feature candidate even when it is synchronous
3136
+ and local. No single answer is sufficient, and signals, Events, or long runtime are never required.
3137
+
3138
+ Keep low-level deterministic rules as ordinary domain functions when they do not form a useful
3139
+ independent capability boundary. Wrap or compose them into Features when the combined operation
3140
+ represents a reusable product capability.
3141
+
3142
+ When inspecting a TypeScript repository, examine:
3143
+ exported functions; public service methods; controller entry points; Event consumers; scheduled
3144
+ jobs; repeated orchestration sequences; domain operations reused in several locations; functions
3145
+ with substantial typed inputs/outputs; functions composing several stages; and product terminology
3146
+ in documentation and API routes. Do not restrict discovery to *.feature.ts or features.define calls.
3147
+
3148
+ Group related low-level operations into one coherent capability candidate. For example,
3149
+ resolveNationOrders, applyProvinceStockpileProduction, applyProvinceStockpileTransfers, and
3150
+ resolveFormationCommands may collectively suggest resolve-match-boundary or resolve-nation-turn.
3151
+ They must not automatically become four separate Features.
3152
+
3153
+ For repository analysis, return a structured inventory for every recommendation:
3154
+ {
3155
+ "candidate": "resolve-nation-turn",
3156
+ "classification": "FEATURE",
3157
+ "executionStyle": "synchronous-multistep",
3158
+ "evidence": [
3159
+ "Represents a recognizable game capability",
3160
+ "Has a stable input/output boundary",
3161
+ "Composes validation, production, resolution, and reporting",
3162
+ "Useful as an independently observable execution"
3163
+ ],
3164
+ "suggestedSteps": ["validate-orders", "apply-production", "resolve-orders", "build-reports"],
3165
+ "signalsRequired": false,
3166
+ "eventsRequired": false,
3167
+ "recommendation": "Make this a standalone Ductape Feature."
3168
+ }
3169
+ Every classification needs concrete code, caller, contract, or product-language evidence. If the
3170
+ boundary remains ambiguous, report both plausible categories and the missing evidence; do not guess.
3001
3171
 
3002
3172
  ━━━ AI DESIGN WORKFLOW — follow this process every time a user asks you to build or plan a feature ━━━
3003
3173
 
@@ -3033,12 +3203,14 @@ STEP 2 — INVENTORY existing Ductape components
3033
3203
 
3034
3204
  STEP 3 — PLAN each step
3035
3205
  For every logical step:
3036
- a. Identify which existing component handles it, or flag it as needing creation.
3206
+ a. Identify whether it is local domain logic, a child Feature, or an existing Ductape component.
3207
+ Local typed domain logic may run inside ctx.step; it does not require an Event or App.
3037
3208
  If a step calls an external service, it MUST go through a registered Ductape App.
3038
3209
  If no App for that service exists in the product → mark it "App to create: <service name>".
3039
3210
  DO NOT plan a raw HTTP call, a direct dispatch to a URL, or any workaround in place of a missing App.
3040
3211
  b. Note how inputs flow: ctx.input fields, or return values from earlier steps (plain JS variables — no special notation needed)
3041
- c. Decide if a rollback handler is needed (e.g. charge → refund on later failure)
3212
+ c. Decide if a rollback handler is needed (e.g. charge → refund on later failure).
3213
+ Rollback is optional and is not a Feature qualification requirement.
3042
3214
  d. Decide allow_fail: true for non-critical steps (email, analytics, audit logs)
3043
3215
 
3044
3216
  STEP 4 — PRESENT the plan and get approval BEFORE writing any code or creating anything
@@ -3092,8 +3264,40 @@ STEP 8 — SET rollbacks for reversible steps
3092
3264
  async (result) => ctx.api.run({ app: 'stripe', event: 'refund', input: { chargeId: result.id } })
3093
3265
  );
3094
3266
 
3095
- Step types: action | database | graph | notification | storage | produce | quota | fallback |
3096
- vector | child_feature | sleep | wait_for_signal | checkpoint
3267
+ Step types: local_domain | action | database | graph | notification | storage | produce | quota |
3268
+ fallback | vector | child_feature | sleep | wait_for_signal | checkpoint
3269
+
3270
+ Valid synchronous Feature candidates include generate-world, resolve-nation-turn,
3271
+ calculate-route-capacity, price-subscription, evaluate-entitlement, and build-replay.
3272
+
3273
+ Synchronous multi-step Feature (no Event, schedule, sleep, signal, or external system):
3274
+ await ductape.features.define({
3275
+ product: 'example-product',
3276
+ tag: 'resolve-nation-turn',
3277
+ name: 'Resolve Nation Turn',
3278
+ input: {
3279
+ nationId: { type: 'string', required: true },
3280
+ tick: { type: 'number', required: true },
3281
+ },
3282
+ output: {
3283
+ acceptedOrders: { type: 'number' },
3284
+ rejectedOrders: { type: 'number' },
3285
+ },
3286
+ handler: async (ctx) => {
3287
+ const validated = await ctx.step('validate-orders', async () => {
3288
+ return validateOrders(ctx.input);
3289
+ });
3290
+ const resolved = await ctx.step('resolve-orders', async () => {
3291
+ return resolveOrders(validated);
3292
+ });
3293
+ return ctx.step('build-result', async () => {
3294
+ return buildResult(resolved);
3295
+ });
3296
+ },
3297
+ });
3298
+ This is a valid Feature despite requiring no signal and producing no Event. Its qualification comes
3299
+ from the named capability, stable contract, meaningful stages, reuse/composition value, and useful
3300
+ step-level execution history.
3097
3301
 
3098
3302
  Define a feature (write this into the project's source files — do NOT use features.create):
3099
3303
  // src/features/onboard-user.ts (or the equivalent path/language for the project)
@@ -3166,12 +3370,16 @@ When you call features.define({ handler }), the handler runs TWICE:
3166
3370
  outer handler body. Code in the outer body runs during recording with proxy values and
3167
3371
  may behave unexpectedly (e.g. typeof proxy === 'object' is true but .someField is a proxy).
3168
3372
 
3169
- Features do NOT execute arbitrary NestJS or server code directly. A feature handler can only
3170
- call Ductape component primitives (ctx.api, ctx.database, ctx.notification, etc.) as steps.
3171
- To invoke internal application business logic, produce a broker event from a feature step
3172
- (ctx.events.produce in the currently published SDK) and consume it in your NestJS service.
3173
- ctx.publish is deprecated; do not use it. Do not assume a ctx.messaging alias exists unless the
3174
- installed SDK types explicitly expose it.
3373
+ A ctx.step callback may call ordinary local domain functions and injected/application services
3374
+ available to the registration scope. This is the normal shape for a synchronous capability such
3375
+ as pricing, entitlement evaluation, route-capacity calculation, or turn resolution. Keep the
3376
+ meaningful work inside ctx.step callbacks so recording does not execute it.
3377
+
3378
+ Use an Event when the operation genuinely crosses an asynchronous process/service boundary,
3379
+ needs broker delivery semantics, or must be consumed independently. Do not produce an Event merely
3380
+ to reach local domain logic. When an Event is appropriate, use ctx.events.produce in the currently
3381
+ published SDK and consume it in the NestJS service. ctx.publish is deprecated; do not use it.
3382
+ Do not assume a ctx.messaging alias exists unless installed SDK types explicitly expose it.
3175
3383
 
3176
3384
  ━━━ ORCHESTRATION DECISION RULE ━━━
3177
3385
 
@@ -3181,7 +3389,10 @@ When you call features.define({ handler }), the handler runs TWICE:
3181
3389
  e.g. ductape.api.dispatch({ ..., schedule: { start_at: ... } })
3182
3390
  e.g. ductape.database.dispatch({ ..., schedule: { start_at: ... } })
3183
3391
 
3184
- Several durable Ductape component operations in sequence (with rollback / retry / state):
3392
+ A named synchronous or asynchronous product capability with meaningful managed steps:
3393
+ → define a Feature; execute it directly when immediate, or dispatch it when scheduled/background
3394
+
3395
+ Several durable Ductape component operations in sequence (with optional rollback / retry / state):
3185
3396
  → define a Feature, then features.dispatch to schedule it
3186
3397
 
3187
3398
  Invoke internal application business logic (your own NestJS/backend service code):
@@ -4120,9 +4331,11 @@ const cliInputSchema = z.object({
4120
4331
  'Use this tool for administrative operations: creating or updating products, apps, ' +
4121
4332
  'resources (databases, storage, caches…), event broker topics, cloud connections, secrets, ' +
4122
4333
  'and for apply/migrate workflows.\n\n' +
4123
- 'Note: environments, app actions, quotas, fallbacks, and jobs are configured in the ' +
4124
- 'Workbench UI. Features also have no CLI creation command: define them in application code ' +
4125
- 'with features.define so application boot/runtime registration makes them available.\n\n' +
4334
+ 'Note: environments have their own CLI commands (products environments list/get/create/update, ' +
4335
+ 'no linked project required the product tag is always an explicit argument). App actions, ' +
4336
+ 'quotas, fallbacks, and jobs are configured in the Workbench UI. Features also have no CLI ' +
4337
+ 'creation command: define them in application code with features.define so application ' +
4338
+ 'boot/runtime registration makes them available.\n\n' +
4126
4339
  'The CLI uses the user\'s local logged-in session (ductape login) — no key is required.'),
4127
4340
  });
4128
4341
  async function loadMcpSdk() {
@@ -4204,13 +4417,7 @@ async function main() {
4204
4417
  const firstWord = args.command.trim().split(/\s+/)[0];
4205
4418
  const isAuthCommand = firstWord === 'login' || firstWord === 'logout';
4206
4419
  const isLocalMigrationGuidance = (firstWord === 'migrate-codebase' && !args.command.includes('--ensure-product')) ||
4207
- firstWord === 'migration-review' ||
4208
- firstWord === 'migration-slice' ||
4209
- firstWord === 'migration-portfolio' ||
4210
- firstWord === 'migration-database' ||
4211
- firstWord === 'migration-environments' ||
4212
- firstWord === 'migration-products' ||
4213
- firstWord === 'migration-secrets';
4420
+ firstWord.startsWith('migration-');
4214
4421
  if (!isAuthCommand && !isLocalMigrationGuidance) {
4215
4422
  // Cache successful authentication, but re-check a missing/expired session on every call.
4216
4423
  // The user may complete `ductape login` in another terminal while this MCP process remains
@@ -4268,6 +4475,7 @@ async function main() {
4268
4475
  isError: true,
4269
4476
  };
4270
4477
  }
4478
+ const client = server.server?.getClientVersion?.();
4271
4479
  const command = [
4272
4480
  'migrate-codebase',
4273
4481
  '--source', shellArgument(args.source),
@@ -4282,6 +4490,9 @@ async function main() {
4282
4490
  ...(args.exclude.length ? ['--exclude', shellArgument(args.exclude.join(','))] : []),
4283
4491
  ...(args.ensure_product ? ['--ensure-product'] : []),
4284
4492
  ...(args.write ? ['--write'] : []),
4493
+ ...(client?.name && client?.version
4494
+ ? ['--mcp-client-name', shellArgument(client.name), '--mcp-client-version', shellArgument(client.version)]
4495
+ : []),
4285
4496
  '--json',
4286
4497
  ].join(' ');
4287
4498
  return cliHandler({ command });
@@ -4480,6 +4691,10 @@ async function main() {
4480
4691
  title: 'Ductape AI Migration Guidance',
4481
4692
  description: 'Inspect a TypeScript, Go, Java, or .NET repository without exposing secret values. ' +
4482
4693
  'Builds a relevant-file review queue, secret-name inventory, checksummed migration evidence, and low-confidence navigation hints. ' +
4694
+ 'The review standards classify capability candidates as FEATURE, FEATURE_STEP, DOMAIN_SERVICE, UTILITY, or INFRASTRUCTURE_ADAPTER; ' +
4695
+ 'they inspect exports, public methods, entry points, consumers, jobs, repeated orchestration, typed operations, routes, and product terminology. ' +
4696
+ 'Synchronous local multi-step capabilities may be Features, while related low-level functions must be grouped rather than promoted one-by-one. ' +
4697
+ 'Every recommendation must return classification, execution style, evidence, suggested steps, and whether signals or Events are actually required. ' +
4483
4698
  'The AI must review files contextually and maintain an evidence ledger before proposing components or schemas. ' +
4484
4699
  'It never generates or rewrites application code or executable assets. ' +
4485
4700
  'Supports in-place and new-codebase guidance destinations. Read-only unless write or ensure_product is explicitly enabled.',
@@ -4572,15 +4787,16 @@ async function main() {
4572
4787
  ' type field = "messageBrokers" (not "messagebrokers" or "events").\n' +
4573
4788
  ' After importing, create topics first with ductape_cli("events topics create -f topic.json") — SQS requires explicit topic creation with queueUrls. For other providers, topics auto-register on first produce but should still be created explicitly before any consumer subscribes.\n' +
4574
4789
  ' - Listing workspaces, products, focused product components, secrets\n' +
4575
- ' Prefer "products components list --tag <tag> --json" for compact inventory; use\n' +
4576
- ' "products components get --tag <tag> --type notifications|events --json" for focused detail.\n' +
4790
+ ' Prefer "products components list --product-tag <tag> --json" for compact inventory; use\n' +
4791
+ ' "products components get --product-tag <tag> --type notifications|events --json" for focused detail.\n' +
4577
4792
  ' - Managing notification components and message templates through "resources notifications" and "notifications messages"\n' +
4578
4793
  ' - Linking a project folder: "link --product <tag> --env <slug>"\n' +
4579
4794
  ' - Syncing sessions/notifications/events: "apply" or "apply sessions" etc.\n' +
4580
4795
  ' - Running database migrations: "db migrate", "db schema generate"\n\n' +
4581
- 'NOTE: Environments, app actions, auths, quotas, fallbacks, and jobs are configured ' +
4582
- 'in the Workbench UI. Features have no CLI creation command because definitions are ' +
4583
- 'code-first through features.define and registered by the application runtime.\n\n' +
4796
+ 'NOTE: Environments have their own CLI commands (products environments *). App actions, ' +
4797
+ 'auths, quotas, fallbacks, and jobs are configured in the Workbench UI. Features have no ' +
4798
+ 'CLI creation command because definitions are code-first through features.define and ' +
4799
+ 'registered by the application runtime.\n\n' +
4584
4800
  'DO NOT use ductape_execute for admin operations — it uses a publishable key which only ' +
4585
4801
  'covers runtime operations. Administrative operations will fail with "Authentication failed".\n\n' +
4586
4802
  'The CLI uses the user\'s local logged-in session (ductape login). ' +
package/docs/TOOLS.md CHANGED
@@ -4,6 +4,20 @@ The Ductape MCP server exposes **one tool**. All operations go through the backe
4
4
 
5
5
  ---
6
6
 
7
+ ## Marketplace discovery
8
+
9
+ Use the `ductape_cli` tool before creating an integration or guessing action names:
10
+
11
+ ```text
12
+ ductape_cli("marketplace search payments --json")
13
+ ductape_cli("marketplace search paystack --json")
14
+ ductape_cli("marketplace get <app_tag> --json")
15
+ ```
16
+
17
+ The detail response is the authority for action tags and their body, query, header, and path
18
+ inputs. After an app is connected to a product, call `ductape_generate_payload` for the selected
19
+ action and then execute it through `actions.run`.
20
+
7
21
  ## Tool: `ductape_execute`
8
22
 
9
23
  Executes a Ductape SDK operation via the backend proxy. Use this for databases, graph, storage, vector, caches, webhooks, jobs, and all other supported modules.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",