@ductape/mcp 0.2.15 → 0.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +207 -25
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -445,7 +445,9 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
445
445
  ductape_cli("products create --name <name> --tag <tag>")
446
446
  ductape_cli("products environments list <tag> --json")
447
447
  ductape_cli("products environments get <tag> <slug> --json")
448
- ductape_cli("products apps list --product <id> --json")
448
+ ductape_cli("products apps list --product <id_or_tag> --json") ← compact linked apps only
449
+ ductape_cli("products apps actions list --product <id_or_tag> --app <app_tag> --json")
450
+ ductape_cli("products apps actions get --product <id_or_tag> --app <app_tag> --action <action_tag> --json")
449
451
 
450
452
  SDK method signatures (for reference, admin key only):
451
453
  product.create [data: { name, description, tag?, envs?: [{slug, name}] }]
@@ -805,7 +807,7 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
805
807
  graph.dropConstraint [name: string]
806
808
  graph.listLabels []
807
809
  graph.listRelationshipTypes []
808
- graph.createAction [{ graphTag, tag, name, query, params? }, productTag?]
810
+ graph.createAction [{ graphTag, name, description?, operation, query: { operation, options }, parameters: [{ name, path, defaultValue, type, description?, required? }] }, productTag?]
809
811
  graph.listActions [graphTag?, productTag?]
810
812
  graph.getAction [actionTag, graphTag?, productTag?]
811
813
  graph.updateAction [actionTag, updates, graphTag?, productTag?]
@@ -1086,6 +1088,25 @@ const snippetGenerateInputSchema = payloadGenerateInputSchema.extend({
1086
1088
  const schemaInputSchema = z.object({
1087
1089
  module: z.enum(['app', 'product']).optional().describe('Optional. Scope the result to one module. With no method, returns only the compact list of available methods.'),
1088
1090
  method: z.string().optional().describe('Optional method key such as "databases.create" or "notifications.update". Use with module to return only that method schema.'),
1091
+ product_tag: z.string().optional().describe('For live app-action discovery, the product the app is linked to. Must be supplied with app_tag and action_tag.'),
1092
+ app_tag: z.string().optional().describe('For live app-action discovery, the linked app tag. Must be supplied with product_tag and action_tag.'),
1093
+ action_tag: z.string().optional().describe('For live app-action discovery, the exact action tag whose complete contract should be returned.'),
1094
+ });
1095
+ const marketplaceDiscoverInputSchema = z.object({
1096
+ query: z.string().min(1).describe('Capability or provider search, for example "payments" or "paystack".'),
1097
+ category: z.string().optional().describe('Optional marketplace category/domain filter.'),
1098
+ limit: z.number().int().min(1).max(50).optional().default(10),
1099
+ });
1100
+ const marketplaceConnectInputSchema = z.object({
1101
+ product_tag: z.string().min(1).describe('Product that should receive the marketplace app connection.'),
1102
+ app_tag: z.string().min(1).describe('Exact public app tag returned by ductape_marketplace_discover.'),
1103
+ environments: z.array(z.object({
1104
+ app_env_slug: z.string().min(1),
1105
+ product_env_slug: z.string().min(1),
1106
+ })).min(1).describe('Explicit app-to-product environment mappings. Include every product environment that will use the app.'),
1107
+ });
1108
+ const marketplaceInspectInputSchema = z.object({
1109
+ app_tag: z.string().min(1).describe('Exact public app tag returned by ductape_marketplace_discover.'),
1089
1110
  });
1090
1111
  function toPrettyJson(value) {
1091
1112
  return JSON.stringify(value ?? {}, null, 2);
@@ -2636,17 +2657,27 @@ An App must be fully set up in Ductape before any code can use it:
2636
2657
  5. The App must be connected to the product (product.apps.add) and its envs mapped
2637
2658
 
2638
2659
  DISCOVER BEFORE CREATING:
2639
- ductape_cli("marketplace search payments --json")
2640
- ductape_cli("marketplace search paystack --json")
2660
+ ductape_marketplace_discover({ query: "payments" })
2661
+ ductape_marketplace_discover({ query: "paystack" })
2641
2662
  ductape_cli("marketplace categories --json")
2642
- ductape_cli("marketplace get <app_tag> --json")
2663
+ ductape_marketplace_inspect({ app_tag: "<exact-returned-tag>" })
2643
2664
 
2644
2665
  marketplace search matches capability terms against public app names, tags, descriptions,
2645
- categories, actions, and webhooks. marketplace get returns the complete public app definition,
2646
- including the exact current-version action tags and body/query/header/param schemas. Never infer
2666
+ categories, actions, and webhooks. Marketplace inspect returns a compact current-version definition,
2667
+ including the exact action tags and body/query/header/param schemas. Never infer
2647
2668
  Paystack action names such as "initialize" or "verify": inspect the marketplace record first.
2648
2669
  If no suitable app exists, create one or import Paystack's OpenAPI/Postman definition.
2649
2670
 
2671
+ CONNECT A DISCOVERED APP THROUGH MCP:
2672
+ 1. Run ductape_marketplace_discover and select an exact returned app tag.
2673
+ 2. Run ductape_marketplace_inspect to read its environments and compact action contracts.
2674
+ 3. Read product environments with ductape_cli("products environments list <product_tag> --json").
2675
+ 4. Match each product environment explicitly to one environment exposed by the app.
2676
+ 5. Call ductape_marketplace_connect({ product_tag, app_tag, environments }).
2677
+ 6. Verify with ductape_cli("products apps list --product <product_tag> --json").
2678
+ Never guess environment mappings or action tags. Connecting mutates product configuration;
2679
+ obtain user approval when the user has not already requested the connection.
2680
+
2650
2681
  ONLY after all five steps can any code call:
2651
2682
  ctx.api.run({ app: '<app_tag>', event: '<action_tag>', input: { ... } }) ← in a feature handler
2652
2683
  actions.run([{ product, env, app: '<app_tag>', action: '<action_tag>', input }]) ← at runtime
@@ -2676,18 +2707,21 @@ Manage environments (base URLs per stage) in Workbench; this currently has no CL
2676
2707
 
2677
2708
  Discover apps in a product and their actions (ALWAYS do this before writing any ctx.api.run call):
2678
2709
  Step 1 — list apps connected to the product:
2679
- ductape_cli("products get --tag <product_tag> --json") → full product document; check apps[]
2680
- ductape_cli("products apps list --product <product_id> --json") → apps[] with access_tag, envs
2681
- Step 2 — list actions in an app:
2682
- ductape_execute("actions.list", [app_tag]) → returns all action tags + names
2683
- Step 3 — fetch the input schema for an action:
2684
- ductape_execute("actions.fetch", [app_tag, action_tag])
2710
+ ductape_cli("products apps list --product <product_id_or_tag> --json")
2711
+ compact list containing ONLY apps linked to that product; accepts an id or tag
2712
+ Step 2 — list compact action summaries for one linked app:
2713
+ ductape_cli("products apps actions list --product <product_id_or_tag> --app <app_tag> --json")
2714
+ Step 3 — fetch one complete action contract:
2715
+ ductape_cli("products apps actions get --product <product_id_or_tag> --app <app_tag> --action <action_tag> --json")
2716
+ OR ductape_schema({ product_tag: "<product_tag>", app_tag: "<app_tag>", action_tag: "<action_tag>" })
2685
2717
  → returns { body: {fieldName: {type, required}}, params: {}, query: {}, headers: {} }
2686
2718
  OR: call ductape_generate_payload (operation_family="action", method="run",
2687
2719
  targets={app: "app_tag", action: "action_tag"}) to get the exact resolved payload shape
2688
2720
  Step 4 — call ductape_schema({ module: "app" }) if you need the JSON schema for creating/updating
2689
2721
  app resources (not for runtime input — use actions.fetch or ductape_generate_payload for that)
2690
2722
  NEVER assume action input field names. Always fetch the action definition first.
2723
+ NEVER fetch products get, apps get, marketplace get, or products apps list --full merely to inspect
2724
+ one action. Those expanded catalogues can be very large and may exceed MCP/client token limits.
2691
2725
 
2692
2726
  Manage actions (individual API endpoints) in Workbench or import an OpenAPI/Postman file.
2693
2727
  Creation/update are administrative and must never use ductape_execute.
@@ -2746,11 +2780,11 @@ Connecting an app to a product (after creation):
2746
2780
  3. Configure auth: Workbench
2747
2781
  4. Define actions: Workbench
2748
2782
  OR import: ductape_cli("apps import <file.json> -t postman|openapi")
2749
- 5. Connect to product: Requires the product_id (from ductape_cli("products get --tag <tag> --json")).
2783
+ 5. Connect to product: Requires the product_id.
2750
2784
  There is no CLI command for this step — the SDK product.apps.add method requires
2751
2785
  an access key which only the backend can provide. Connect via Workbench.
2752
- 6. Verify: ductape_cli("products get --tag <product_tag> --json") → check apps[] contains the app
2753
- ductape_execute("actions.list", [app_tag]) verify actions are registered
2786
+ 6. Verify: ductape_cli("products apps list --product <product_tag> --json")
2787
+ ductape_cli("products apps actions list --product <product_tag> --app <app_tag> --json")
2754
2788
  `.trim(),
2755
2789
  products: `
2756
2790
  DUCTAPE PRODUCTS
@@ -3524,7 +3558,8 @@ STEP 1 — UNDERSTAND the goal
3524
3558
  understand: what the feature does, what it returns, what can fail and how failures should behave.
3525
3559
 
3526
3560
  STEP 2 — INVENTORY existing Ductape components
3527
- Call ductape_cli("products get --tag <product_tag> --json") to read the full product document.
3561
+ Call ductape_cli("products components list --product-tag <product_tag> --json") for the compact inventory.
3562
+ Call ductape_cli("products apps list --product <product_tag> --json") for linked apps only.
3528
3563
  (product.* requires the access key — never use ductape_execute for product reads, it will return 403)
3529
3564
  Note what already exists:
3530
3565
  - databases[] → available for ctx.database.insert/query/update/delete steps
@@ -3616,10 +3651,25 @@ STEP 8 — SET rollbacks for reversible steps
3616
3651
  async (result) => ctx.api.run({ app: 'stripe', event: 'refund', input: { chargeId: result.id } })
3617
3652
  );
3618
3653
 
3619
- Code-first ctx step types currently record: function | action | database | graph | vector |
3654
+ Code-first ctx step types currently record: function | action | database | graph | vector | session |
3620
3655
  notification | storage | produce | quota | fallback | child_feature | sleep | wait_for_signal |
3621
3656
  checkpoint.
3622
3657
 
3658
+ CODE-FIRST SESSION LIFECYCLE:
3659
+ ctx.session is the optional session token inherited by the Feature and all nested Ductape
3660
+ operations. It is read-only execution context and is not a session service.
3661
+ ctx.sessions records explicit product-session lifecycle steps:
3662
+ ctx.sessions.start<T>({ session: "player-session", data, cache? })
3663
+ ctx.sessions.verify<T>({ session: "player-session", token, cache? })
3664
+ ctx.sessions.refresh<T>({ session: "player-session", refreshToken, cache? })
3665
+ ctx.sessions.revoke({ session: "player-session", sessionId?, identifier? })
3666
+ ctx.sessions.list<T>({ session: "player-session", identifier?, page?, limit?, cache? })
3667
+ Put each call inside ctx.step(). The session field identifies the configured product session; the
3668
+ executor supplies product and env. start creates a new token pair but does not replace ctx.session
3669
+ for subsequent steps in the same Feature. Pass the returned token explicitly where application
3670
+ logic needs the newly created session. Never serialize the inherited ctx.session into the Feature
3671
+ definition, logs, metadata, or another step unless the target operation explicitly requires a token.
3672
+
3623
3673
  Valid synchronous Feature candidates include generate-world, resolve-nation-turn,
3624
3674
  calculate-route-capacity, price-subscription, evaluate-entitlement, and build-replay.
3625
3675
 
@@ -4716,9 +4766,51 @@ EXACT CODE-FIRST GRAPH CONTEXT:
4716
4766
  ctx.graph.deleteRelationship({ graph, id })
4717
4767
  ctx.graph.query<T>({ graph, action, params? })
4718
4768
  ctx.graph.execute<T>({ graph, action, input })
4719
- Every call must be inside ctx.step(). createNode/createRelationship are not projection upserts.
4720
- For replay-safe Neo4j projections, register a saved graph action whose Cypher uses MERGE and call
4721
- that action through ctx.graph.execute. Do not describe CREATE as idempotent.
4769
+ Every call must be inside ctx.step(). Prefer the direct ctx.graph primitive that expresses the
4770
+ operation. Saved actions are not generally preferred over createNode/updateNode/deleteNode,
4771
+ relationship operations, or query; use a saved action only for a reusable parameterized operation
4772
+ that the typed primitives cannot express. createNode/createRelationship are not projection upserts.
4773
+
4774
+ SAVED GRAPH ACTION ASSET AND CLI CONTRACT:
4775
+ Canonical project path:
4776
+ ductape/graphs/<graph-tag>/actions/<action-tag>.action.json
4777
+ action-tag is generated by lowercasing name, replacing non-alphanumerics with hyphens, and trimming
4778
+ hyphens. Exactly one action object belongs in each file. Allowed top-level fields are graphTag,
4779
+ name, description, operation, query, and parameters. Required fields are graphTag, name, operation,
4780
+ query, and parameters. There is no persisted version field.
4781
+
4782
+ Example ductape/graphs/discovery-graph/actions/upsert-product-v1.action.json:
4783
+ {
4784
+ "graphTag": "discovery-graph",
4785
+ "name": "Upsert Product V1",
4786
+ "description": "Idempotently project one product by stable domain ID",
4787
+ "operation": "executeRaw",
4788
+ "query": {
4789
+ "operation": "executeRaw",
4790
+ "options": {
4791
+ "query": "MERGE (p:Product {id: $productId}) SET p.name = $name, p.sourceVersion = $sourceVersion RETURN p",
4792
+ "params": { "productId": null, "name": null, "sourceVersion": null }
4793
+ }
4794
+ },
4795
+ "parameters": [
4796
+ { "name": "productId", "path": "options.params.productId", "defaultValue": null, "type": "string", "required": true },
4797
+ { "name": "name", "path": "options.params.name", "defaultValue": null, "type": "string", "required": true },
4798
+ { "name": "sourceVersion", "path": "options.params.sourceVersion", "defaultValue": 0, "type": "number", "required": true }
4799
+ ]
4800
+ }
4801
+ Validate without mutation:
4802
+ ductape graph validateAction -f ductape/graphs/discovery-graph/actions/upsert-product-v1.action.json --json
4803
+ Create and persist against the linked product:
4804
+ ductape graph createAction -f ductape/graphs/discovery-graph/actions/upsert-product-v1.action.json --json
4805
+ Read back with graph.getAction/listActions before relying on it. In-place updates use:
4806
+ ductape graph updateAction -f <patch.json> --json
4807
+ where patch.json is { "actionTag":"upsert-product-v1", "graphTag":"discovery-graph", "updates":{...} }.
4808
+ Updating name does not change the stored tag. For breaking query/input/result changes, create a new
4809
+ name/tag such as V2 and migrate callers; use updateAction for compatible corrections only.
4810
+ ctx.graph.execute returns IExecuteGraphActionResult<T>: { success, executionTime, data: T[], count,
4811
+ error? }. It resolves parameter values by each parameter.path. The SDK currently returns
4812
+ success:false for action execution errors, so Feature code must inspect success and throw to fail
4813
+ the step; do not treat a resolved unsuccessful result as success.
4722
4814
 
4723
4815
  EXACT CODE-FIRST VECTOR CONTEXT:
4724
4816
  ctx.vector.upsert<T>({
@@ -4738,9 +4830,40 @@ EXACT CODE-FIRST VECTOR CONTEXT:
4738
4830
  them into the Feature definition. Note the deliberate naming: ctx.vector.query uses values for
4739
4831
  the embedding, while the lower-level ductape.vector.query runtime facade uses vector.
4740
4832
  Vertex AI Vector Search stores and searches supplied vectors. It does NOT generate embeddings.
4741
- Generate embeddings through a separately registered/called model or App/function contract and
4742
- verify that the returned number[] dimension exactly matches the vector component. Do not claim
4743
- that a Vertex Vector Search index embeds text.
4833
+ Do not claim that a Vertex Vector Search index embeds text.
4834
+
4835
+ EMBEDDING EXECUTION CURRENT CODE-FIRST CONTRACT:
4836
+ Registered product models are configuration CRUD only in the current SDK. There is no ctx.model,
4837
+ ctx.models, ctx.ai, or registered-model execute method, so never generate those calls. To execute
4838
+ discovery-embeddings portably inside a Feature, define it as a portable function contract (preferred
4839
+ for application-owned embedding code) or call an already registered App action via ctx.api.run.
4840
+
4841
+ import { defineFunctions } from '@ductape/sdk';
4842
+ type EmbedInput = { texts: string[] };
4843
+ type EmbedOutput = { embeddings: number[][]; dimensions: number; model: string };
4844
+ export const DiscoveryEmbeddings = defineFunctions({
4845
+ namespace: 'discovery-embeddings', version: '1', operations: { embed: {
4846
+ input: { type:'object', required:['texts'], additionalProperties:false,
4847
+ properties:{ texts:{ type:'array', items:{ type:'string', minLength:1 } } } },
4848
+ output: { type:'object', required:['embeddings','dimensions','model'], additionalProperties:false,
4849
+ properties:{ embeddings:{ type:'array', items:{ type:'array', items:{ type:'number' } } },
4850
+ dimensions:{ type:'integer', minimum:1 }, model:{ type:'string', minLength:1 } } },
4851
+ timeout_ms: 15000, idempotent: true, transports:[{ type:'local' }]
4852
+ } } });
4853
+
4854
+ In the Feature handler:
4855
+ const embeddings = ctx.functions.use(DiscoveryEmbeddings);
4856
+ const generated = await ctx.step('embed-products', () =>
4857
+ embeddings.embed({ texts: ctx.input.texts }));
4858
+ if (generated.embeddings.length !== ctx.input.texts.length ||
4859
+ generated.embeddings.some(v => v.length !== EXPECTED_VECTOR_DIMENSIONS)) {
4860
+ throw new Error('EMBEDDING_DIMENSION_MISMATCH');
4861
+ }
4862
+ Register a real local handler at application bootstrap or configure a signed HTTPS/events transport.
4863
+ Input and output JSON schemas are validated at runtime. Missing implementation fails with
4864
+ FUNCTION_UNAVAILABLE; schema mismatch fails with FUNCTION_SCHEMA_VALIDATION_FAILED; timeout fails
4865
+ with FUNCTION_TIMEOUT. Never substitute sample/random/recording-time embeddings. The Feature's
4866
+ session is inherited in the portable function context, outside its business input.
4744
4867
 
4745
4868
  NESTJS CONSUMER ROUTING AND FEATURE INPUT:
4746
4869
  @Events.Consumer receives the published message object. Ductape may add
@@ -5303,6 +5426,35 @@ async function main() {
5303
5426
  };
5304
5427
  const schemaHandler = async (args) => {
5305
5428
  try {
5429
+ const liveActionScope = [args.product_tag, args.app_tag, args.action_tag];
5430
+ if (liveActionScope.some(Boolean)) {
5431
+ if (!liveActionScope.every(Boolean)) {
5432
+ throw new Error('product_tag, app_tag, and action_tag are all required to fetch a live app action contract');
5433
+ }
5434
+ const response = await cliHandler({
5435
+ command: `products apps actions get --product ${shellArgument(args.product_tag)} --app ${shellArgument(args.app_tag)} --action ${shellArgument(args.action_tag)} --json`,
5436
+ });
5437
+ if (response.isError)
5438
+ return response;
5439
+ const raw = response.content[0]?.type === 'text' ? response.content[0].text : '';
5440
+ let action = raw;
5441
+ try {
5442
+ action = JSON.parse(raw);
5443
+ }
5444
+ catch { /* Preserve the CLI diagnostic verbatim. */ }
5445
+ return {
5446
+ content: [{
5447
+ type: 'text',
5448
+ text: JSON.stringify({
5449
+ product_tag: args.product_tag,
5450
+ app_tag: args.app_tag,
5451
+ action_tag: args.action_tag,
5452
+ action,
5453
+ hint: 'This is the complete contract for one action linked to this product; use products apps actions list for compact discovery.',
5454
+ }, null, 2),
5455
+ }],
5456
+ };
5457
+ }
5306
5458
  if (args.method && !args.module) {
5307
5459
  throw new Error('module is required when method is provided');
5308
5460
  }
@@ -5392,9 +5544,10 @@ async function main() {
5392
5544
  }, snippetGenerateHandler);
5393
5545
  server.registerTool('ductape_schema', {
5394
5546
  title: 'Ductape Asset Schema',
5395
- description: 'Returns a compact method index or one targeted asset schema derived from the SDK Joi validators. ' +
5547
+ description: 'Returns a compact method index, one targeted asset schema, or one complete live app-action contract. ' +
5396
5548
  'Call with module="app" or module="product" first to list method keys, then call again with ' +
5397
5549
  'module and method (for example method="databases.create") for the complete field schema. ' +
5550
+ 'For one linked app action, call with product_tag, app_tag, and action_tag; do not fetch a full app catalogue. ' +
5398
5551
  'Avoid calling without module unless you explicitly need the entire manifest.\n\n' +
5399
5552
  'ALWAYS call this before constructing a file for "resources <type> create" or any cloud ' +
5400
5553
  'import/provision operation — field shapes are not guessable from context.\n\n' +
@@ -5469,6 +5622,35 @@ async function main() {
5469
5622
  'Supports in-place and new-codebase guidance destinations. Read-only unless write or ensure_product is explicitly enabled.',
5470
5623
  inputSchema: migrationInputSchema,
5471
5624
  }, migrationHandler);
5625
+ server.registerTool('ductape_marketplace_discover', {
5626
+ title: 'Discover Ductape Marketplace Apps',
5627
+ description: 'Search public marketplace apps by capability and return compact app, environment, and action summaries. ' +
5628
+ 'Use the returned exact app tag; never infer provider or action tags from a brand name.',
5629
+ inputSchema: marketplaceDiscoverInputSchema,
5630
+ }, async (args) => cliHandler({
5631
+ command: `marketplace search ${shellArgument(args.query)}${args.category ? ` --category ${shellArgument(args.category)}` : ''} --limit ${args.limit ?? 10} --json`,
5632
+ }));
5633
+ server.registerTool('ductape_marketplace_inspect', {
5634
+ title: 'Inspect a Ductape Marketplace App',
5635
+ description: 'Return the selected public app version, supported environment slugs, and compact action contracts. ' +
5636
+ 'Call this after discovery and before choosing environment mappings or writing action calls.',
5637
+ inputSchema: marketplaceInspectInputSchema,
5638
+ }, async (args) => cliHandler({
5639
+ command: `marketplace get ${shellArgument(args.app_tag)} --json`,
5640
+ }));
5641
+ server.registerTool('ductape_marketplace_connect', {
5642
+ title: 'Connect a Marketplace App to a Product',
5643
+ description: 'Create/reuse secure app access, connect an exact discovered marketplace app to a product, and map its environments. ' +
5644
+ 'Discover first, inspect the returned environments/actions, obtain any required user configuration, and never guess mappings.',
5645
+ inputSchema: marketplaceConnectInputSchema,
5646
+ }, async (args) => {
5647
+ const mappings = args.environments
5648
+ .map((env) => ` --env-map ${shellArgument(`${env.app_env_slug}:${env.product_env_slug}`)}`)
5649
+ .join('');
5650
+ return cliHandler({
5651
+ command: `products apps connect --product ${shellArgument(args.product_tag)} --app ${shellArgument(args.app_tag)}${mappings} --json`,
5652
+ });
5653
+ });
5472
5654
  server.registerTool('ductape_cli', {
5473
5655
  title: 'Ductape CLI',
5474
5656
  description: 'Run a Ductape CLI command for administrative operations.\n\n' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.2.15",
3
+ "version": "0.2.17",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",