@ductape/mcp 0.2.32 → 0.2.33

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 +295 -4
  2. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1128,7 +1128,8 @@ const payloadGenerateInputSchema = z.object({
1128
1128
  targets: z.record(z.any()).optional().describe('Identifies the specific operation to generate a payload for. ' +
1129
1129
  'For actions: { app: "app_tag", action: "action_tag" }. ' +
1130
1130
  'For features: { feature: "feature_tag" }. ' +
1131
- 'For databases: { database: "db_tag", table: "table_or_collection_name" }. ' +
1131
+ 'For databases: { database: "db_tag", table?: "table_or_collection_name", action?: "action_tag" }. ' +
1132
+ ' When generating code for a saved action, action is required and its static outputContract must be inspected. ' +
1132
1133
  ' Providing table is strongly recommended — the generator scans all actions configured for that table, ' +
1133
1134
  ' aggregates field definitions (name, type, required, sample value), and returns them in ' +
1134
1135
  ' meta.schema_context.database.fields. The where/data placeholders in the payload are also ' +
@@ -1161,11 +1162,16 @@ const snippetGenerateInputSchema = payloadGenerateInputSchema.extend({
1161
1162
  language: z.enum(['typescript', 'python']).default('typescript'),
1162
1163
  });
1163
1164
  const schemaInputSchema = z.object({
1165
+ publishable_key: z.string().optional().describe('Optional publishable key for live database schema lookup. Omit when DUCTAPE_PUBLISHABLE_KEY is configured.'),
1164
1166
  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.'),
1165
1167
  method: z.string().optional().describe('Optional method key such as "databases.create" or "notifications.update". Use with module to return only that method schema.'),
1166
1168
  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.'),
1167
1169
  app_tag: z.string().optional().describe('For live app-action discovery, the linked app tag. Must be supplied with product_tag and action_tag.'),
1168
- action_tag: z.string().optional().describe('For live app-action discovery, the exact action tag whose complete contract should be returned.'),
1170
+ action_tag: z.string().optional().describe('For live app-action or database-action discovery, the exact action tag whose complete contract should be returned.'),
1171
+ database_tag: z.string().optional().describe('For live database discovery, the database component tag. Requires product_tag and env_slug.'),
1172
+ env_slug: z.string().optional().describe('For live database discovery, the environment whose synchronized table schema and action contract should be used.'),
1173
+ table: z.string().optional().describe('For live database discovery, the exact table/collection. Required for direct query row contracts; inferred from a saved action when action_tag is supplied.'),
1174
+ database_operation: z.enum(['query', 'insert', 'update', 'delete', 'upsert', 'aggregate', 'raw']).optional().default('query').describe('Direct database primitive whose envelope should be combined with the table schema. Ignored when action_tag identifies a saved action.'),
1169
1175
  });
1170
1176
  const marketplaceDiscoverInputSchema = z.object({
1171
1177
  query: z.string().min(1).describe('Capability or provider search, for example "payments" or "paystack".'),
@@ -1351,6 +1357,44 @@ function buildSnippet(language, payload, operationFamily, method) {
1351
1357
  ? buildPythonSnippet(payload, operationFamily, method)
1352
1358
  : buildTypeScriptSnippet(payload, operationFamily, method);
1353
1359
  }
1360
+ function buildDatabaseActionFeatureExample(targets, contract) {
1361
+ const database = targets.database ?? targets.database_tag ?? '<database-tag>';
1362
+ const action = targets.action ?? targets.action_tag ?? '<action-tag>';
1363
+ if (!contract?.normalizedOutput) {
1364
+ return `// Output contract unknown: do not infer a row, array, or envelope.\n` +
1365
+ `// Sync the table schema and fetch the Database Action contract before generating this Feature.`;
1366
+ }
1367
+ const operation = String(contract.operation ?? '').toUpperCase();
1368
+ if (operation === 'QUERY' || operation === 'READ') {
1369
+ return `type DatabaseActionResult = { data: Record<string, unknown>[]; count: number; fields?: string[] };\n\n` +
1370
+ `const queryResult = await ctx.step("${action}", () =>\n` +
1371
+ ` ctx.database.execute<DatabaseActionResult>({\n` +
1372
+ ` database: ${JSON.stringify(database)},\n` +
1373
+ ` action: ${JSON.stringify(action)},\n` +
1374
+ ` input: { /* values from ctx.input */ },\n` +
1375
+ ` }),\n` +
1376
+ `);\n\n` +
1377
+ `await ctx.branch(ctx.when.eq(ctx.transform.length(queryResult.data), 0), {\n` +
1378
+ ` then: async () => { /* handle successful empty query */ },\n` +
1379
+ ` else: async () => { const row = queryResult.data[0]; /* consume validated row */ },\n` +
1380
+ `});`;
1381
+ }
1382
+ if ((operation === 'INSERT' || operation === 'CREATE') && contract.createdRecordPath === 'data[0]') {
1383
+ return `const insertResult = await ctx.step("${action}", () =>\n` +
1384
+ ` ctx.database.execute<{ data: Record<string, unknown>[]; count: number; insertedIds: unknown[] }>({\n` +
1385
+ ` database: ${JSON.stringify(database)}, action: ${JSON.stringify(action)}, input: { /* values */ },\n` +
1386
+ ` }),\n` +
1387
+ `);\n` +
1388
+ `const createdRow = insertResult.data[0]; // No redundant follow-up query: contract returns rows.`;
1389
+ }
1390
+ return `// ${operation} returns ${contract.returnedRows ?? 'unknown'} rows.\n` +
1391
+ `const result = await ctx.step("${action}", () =>\n` +
1392
+ ` ctx.database.execute({ database: ${JSON.stringify(database)}, action: ${JSON.stringify(action)}, input: { /* values */ } }),\n` +
1393
+ `);\n` +
1394
+ (contract.followUpQueryRequired
1395
+ ? `// This contract returns mutation metadata only; add an explicit saved QUERY action before consuming a row.`
1396
+ : `// Consume only fields declared by normalizedOutput.`);
1397
+ }
1354
1398
  // ─── CLI helpers ─────────────────────────────────────────────────────────────
1355
1399
  // Per-process cache: avoids re-running whoami / workspaces use on every call.
1356
1400
  let authState = 'unknown';
@@ -2475,12 +2519,124 @@ ctx.database.execute inside a Feature step invokes a saved action through its \`
2475
2519
  Direct ctx.database.query/insert/update/delete calls retain their normal {table, where, data, ...}
2476
2520
  shape and do not resolve a saved action.
2477
2521
 
2522
+ HARDENED DIRECT DATABASE PRIMITIVE CONTRACTS:
2523
+ Direct calls also return typed adapter envelopes; they do not return a raw row, raw array,
2524
+ boolean, or arbitrary T. Derive row fields from the selected table schema and use these exact
2525
+ normalized contracts:
2526
+ ctx.database.query<Row>(...) → { data: Row[], count: number, fields?: string[] }
2527
+ ctx.database.insert<Row>(...) → { data: Row[], count: number, insertedIds: unknown[] }
2528
+ ctx.database.update<Row>(...) → { data: Row[], count: number }
2529
+ ctx.database.delete(...) → { count: number, data?: Row[] }
2530
+ ctx.database.upsert<Row>(...) → { data: Row[], count: number, operation: "inserted"|"updated" }
2531
+ ctx.database.raw<Row>(...) → { data: Row[], count: number, fields?: string[], rowsAffected?: number }
2532
+ ctx.database.aggregate(...) → an alias-keyed aggregate object declared by operations; it is
2533
+ NOT a row envelope unless the selected adapter contract says so.
2534
+
2535
+ Never generate queryResult[0], queryResult.length, a boolean check on deleteResult, or direct
2536
+ field access such as insertResult.id. Query rows live at queryResult.data; delete success/count
2537
+ is deleteResult.count; inserted/updated rows exist in mutationResult.data only when returning is
2538
+ true. INSERT always includes insertedIds, but an inserted ID is not a complete row.
2539
+
2540
+ Direct primitive contracts are derived from the explicit method, adapter/version, table schema,
2541
+ and call options such as returning, returningColumns, select, limit, and aggregation aliases.
2542
+ Never infer row fields from domain expectations. If table schema is missing, call static schema
2543
+ discovery/sync and mark row fields unknown; do not execute the operation merely to learn shape.
2544
+
2545
+ DIRECT FEATURE-SAFE EXAMPLES:
2546
+ const queryResult = await ctx.step("find-customer", () =>
2547
+ ctx.database.query<Customer>({
2548
+ database: "payment-processing", table: "customers",
2549
+ where: { email: ctx.input.customerEmail }, limit: 1,
2550
+ }),
2551
+ );
2552
+ await ctx.branch(ctx.when.eq(ctx.transform.length(queryResult.data), 0), {
2553
+ then: async () => { /* create */ },
2554
+ else: async () => { const customer = queryResult.data[0]; /* consume */ },
2555
+ });
2556
+
2557
+ const insertResult = await ctx.step("create-customer", () =>
2558
+ ctx.database.insert<Customer>({
2559
+ database: "payment-processing", table: "customers", data: { /* fields */ }, returning: true,
2560
+ }),
2561
+ );
2562
+ // returning:true declares created rows at insertResult.data; do not re-query.
2563
+ const createdCustomer = insertResult.data[0];
2564
+
2565
+ Empty { data: [], count: 0 } is successful. Thrown/explicit failure is a failed step. A successful
2566
+ value that violates the primitive's declared envelope is a platform contract violation and must
2567
+ fail at the producing step boundary. Never let a missing required row field flow to an app action.
2568
+
2478
2569
  REUSE BEFORE CREATE:
2479
2570
  1. ductape_cli("db actions list --database product_tag:database_tag --json")
2480
2571
  2. Inspect likely matches with ductape_cli("db actions get product_tag:database_tag:action_tag --json").
2481
2572
  3. Reuse the matching tag with database.execute/ctx.database.execute when operation, table,
2482
2573
  template, parameters, and result semantics match. Create only when no exact functional match exists.
2483
2574
 
2575
+ OUTPUT CONTRACTS ARE MANDATORY DISCOVERY METADATA:
2576
+ Every action returned by databases.action.fetch/fetchAll includes outputContract and
2577
+ outputContractsByEnvironment. Read the contract for the target environment before writing a
2578
+ Feature. It contains input, normalizedOutput, rowSchema, cardinality, returnedRows,
2579
+ returnsAffectedRows, createdRecordPath, followUpQueryRequired, adapter, and adapterOutput.
2580
+
2581
+ Never infer a Database Action's output shape from its operation name, generic SDK knowledge,
2582
+ or application-domain expectations. Derive it from the database adapter, action definition,
2583
+ table schema metadata, and action outputContract. If the contract confidence is "unknown" or
2584
+ normalizedOutput is null, state that the output is unknown and request platform metadata; do
2585
+ not invent a raw row, array, envelope, or field. Static discovery must never execute the action.
2586
+
2587
+ MANDATORY DUCTAPE_SCHEMA ROW LOOKUP:
2588
+ The normalized envelope does NOT reveal the fields inside Row. Before generating code that reads
2589
+ result.data[n].field (or evaluates a row field in a Feature branch), call one of these exact forms:
2590
+ Saved Database Action:
2591
+ ductape_schema({ product_tag: "<product_tag>", env_slug: "<env_slug>",
2592
+ database_tag: "<database_tag>", action_tag: "<action_tag>" })
2593
+ Direct database query/primitive:
2594
+ ductape_schema({ product_tag: "<product_tag>", env_slug: "<env_slug>",
2595
+ database_tag: "<database_tag>", table: "<table>", database_operation: "query" })
2596
+ Use output_contract only for the saved action's operation-specific envelope. Use row_schema for
2597
+ the contents of each result.data item for BOTH saved actions and direct calls. Projection/select
2598
+ may narrow those fields further. If row_schema is empty or warnings report missing synchronized
2599
+ metadata, run \`ductape db schema push --db <database_tag> --env <env_slug>\`, retry
2600
+ ductape_schema, and keep row fields unknown until it succeeds. Never execute the query/action to
2601
+ discover its shape, and never substitute a generic product asset schema for this live lookup.
2602
+
2603
+ NORMALIZED ENVELOPES DIFFER BY OPERATION:
2604
+ QUERY → { data: Row[], count: number, fields?: string[] }
2605
+ INSERT → { data: Row[], count: number, insertedIds: unknown[] }
2606
+ UPDATE → { data: Row[], count: number }
2607
+ DELETE → { count: number, data?: Row[] }
2608
+ RAW/EXECUTE → { data: Row[], count: number, fields?: string[], rowsAffected?: number }
2609
+ returning controls whether mutation data contains rows. Do not describe all operations as the
2610
+ same envelope. Adapter-native details belong in adapterOutput; Feature code uses normalizedOutput.
2611
+
2612
+ FEATURE-SAFE QUERY PATTERN:
2613
+ type FindCustomerResult = { data: Customer[]; count: number; fields?: string[] };
2614
+ const queryResult = await ctx.step("find-customer", () =>
2615
+ ctx.database.execute<FindCustomerResult>({
2616
+ database: "payment-processing",
2617
+ action: "find-customer-by-email",
2618
+ input: { email: ctx.input.customerEmail },
2619
+ }),
2620
+ );
2621
+ await ctx.branch(ctx.when.eq(ctx.transform.length(queryResult.data), 0), {
2622
+ then: async () => { /* create */ },
2623
+ else: async () => {
2624
+ const customer = queryResult.data[0];
2625
+ /* consume customer only inside this structurally valid branch */
2626
+ },
2627
+ });
2628
+ Never generate queryResult[0] or ctx.transform.length(queryResult) for an envelope contract.
2629
+ { data: [], count: 0 } is a successful empty query, not a failed action.
2630
+
2631
+ MUTATION FLOW:
2632
+ Inspect returnedRows, createdRecordPath, and followUpQueryRequired. If INSERT returns rows, use
2633
+ insertResult.data[0] directly and do not add a redundant resolve query. If it returns metadata
2634
+ only, explain that fact and add an explicit follow-up query. Never allow an undefined required
2635
+ id/email to reach a downstream app/provider action: validate it or make that step structurally
2636
+ dependent on queryResult.data[0] or the declared createdRecordPath. A thrown/failed response is
2637
+ a failed step; a successful value that violates normalizedOutput is a platform contract violation
2638
+ and must fail at the producing step boundary.
2639
+
2484
2640
  Create/update/delete are ADMINISTRATIVE (access key) — FORBIDDEN through ductape_execute
2485
2641
  (publishable key). Use ductape_cli, same as every other admin resource:
2486
2642
  ductape_cli("db actions create --action-file ductape/database/actions/<action-tag>.action.json")
@@ -3962,7 +4118,7 @@ STEP 7 — HANDLE conditionals, loops, and branching (when applicable)
3962
4118
 
3963
4119
  Prefer explicit portable branching for step results:
3964
4120
  const result = await ctx.step("find", () => ctx.database.execute(...));
3965
- await ctx.branch(ctx.when.eq(result.count, 0), {
4121
+ await ctx.branch(ctx.when.eq(ctx.transform.length(result.data), 0), {
3966
4122
  then: () => ctx.step("create", () => ctx.database.execute(...)),
3967
4123
  else: () => ctx.step("reuse", () => ctx.database.execute(...)),
3968
4124
  });
@@ -6065,6 +6221,38 @@ async function main() {
6065
6221
  const generated = addSessionAwarenessMetadata(await generateExecutablePayload({ ...args, include_session: includeSession, publishable_key: key }), args);
6066
6222
  const payload = generated?.payload ?? {};
6067
6223
  const snippet = buildSnippet(args.language, payload, args.operation_family, args.method);
6224
+ let databaseActionContract = null;
6225
+ let featureExample;
6226
+ if (args.operation_family === 'database') {
6227
+ const targets = (args.targets ?? {});
6228
+ const action = targets.action ?? targets.action_tag;
6229
+ const database = targets.database ?? targets.database_tag;
6230
+ if (action && database) {
6231
+ const databaseTag = String(database).startsWith(`${args.product_tag}:`)
6232
+ ? String(database)
6233
+ : `${args.product_tag}:${database}`;
6234
+ const fullActionTag = String(action).startsWith(`${databaseTag}:`)
6235
+ ? String(action)
6236
+ : `${databaseTag}:${action}`;
6237
+ const response = await cliHandler({
6238
+ command: `db actions get ${shellArgument(fullActionTag)} --json`,
6239
+ });
6240
+ if (!response.isError) {
6241
+ const raw = response.content[0]?.type === 'text' ? response.content[0].text : '';
6242
+ try {
6243
+ const parsed = JSON.parse(raw);
6244
+ const definition = parsed?.data ?? parsed?.result ?? parsed;
6245
+ databaseActionContract = definition?.outputContractsByEnvironment?.[args.env_slug]
6246
+ ?? definition?.outputContract
6247
+ ?? null;
6248
+ }
6249
+ catch {
6250
+ databaseActionContract = null;
6251
+ }
6252
+ }
6253
+ featureExample = buildDatabaseActionFeatureExample(targets, databaseActionContract);
6254
+ }
6255
+ }
6068
6256
  return {
6069
6257
  content: [
6070
6258
  {
@@ -6072,6 +6260,8 @@ async function main() {
6072
6260
  text: JSON.stringify({
6073
6261
  payload: generated,
6074
6262
  snippet,
6263
+ ...(databaseActionContract ? { database_action_contract: databaseActionContract } : {}),
6264
+ ...(featureExample ? { feature_example: featureExample } : {}),
6075
6265
  }, null, 2),
6076
6266
  },
6077
6267
  ],
@@ -6084,6 +6274,100 @@ async function main() {
6084
6274
  };
6085
6275
  const schemaHandler = async (args) => {
6086
6276
  try {
6277
+ if (args.database_tag) {
6278
+ if (!args.product_tag || !args.database_tag || !args.env_slug) {
6279
+ throw new Error('product_tag, database_tag, and env_slug are required for live database schema discovery');
6280
+ }
6281
+ let action = null;
6282
+ let table = args.table;
6283
+ if (args.action_tag) {
6284
+ const databaseTag = args.database_tag.startsWith(`${args.product_tag}:`)
6285
+ ? args.database_tag
6286
+ : `${args.product_tag}:${args.database_tag}`;
6287
+ const fullActionTag = args.action_tag.startsWith(`${databaseTag}:`)
6288
+ ? args.action_tag
6289
+ : `${databaseTag}:${args.action_tag}`;
6290
+ const response = await cliHandler({
6291
+ command: `db actions get ${shellArgument(fullActionTag)} --json`,
6292
+ });
6293
+ if (response.isError)
6294
+ return response;
6295
+ const raw = response.content[0]?.type === 'text' ? response.content[0].text : '';
6296
+ try {
6297
+ const parsed = JSON.parse(raw);
6298
+ action = parsed?.data ?? parsed?.result ?? parsed;
6299
+ table = table ?? action?.tableName;
6300
+ }
6301
+ catch {
6302
+ throw new Error(`Database Action metadata was not valid JSON: ${raw}`);
6303
+ }
6304
+ }
6305
+ if (!table) {
6306
+ throw new Error('table is required for direct database schema discovery and could not be inferred from the action');
6307
+ }
6308
+ const key = args.publishable_key || process.env.DUCTAPE_PUBLISHABLE_KEY;
6309
+ if (!key) {
6310
+ throw new Error('Set DUCTAPE_PUBLISHABLE_KEY or pass publishable_key so ductape_schema can fetch synchronized table metadata');
6311
+ }
6312
+ const generated = await generateExecutablePayload({
6313
+ publishable_key: key,
6314
+ product_tag: args.product_tag,
6315
+ env_slug: args.env_slug,
6316
+ operation_family: 'database',
6317
+ method: args.action_tag
6318
+ ? String(action?.operation ?? action?.type ?? 'query').toLowerCase()
6319
+ : (args.database_operation ?? 'query'),
6320
+ targets: {
6321
+ database: args.database_tag,
6322
+ table,
6323
+ ...(args.action_tag ? { action: args.action_tag } : {}),
6324
+ },
6325
+ include_session: false,
6326
+ execution_context: 'system',
6327
+ schema_mode: 'best_effort',
6328
+ });
6329
+ const databaseSchema = generated?.meta?.schema_context?.database ?? null;
6330
+ const actionContract = args.action_tag
6331
+ ? action?.outputContractsByEnvironment?.[args.env_slug] ?? action?.outputContract ?? null
6332
+ : null;
6333
+ const directOutputContracts = {
6334
+ query: { normalizedOutput: '{ data: Row[], count: number, fields?: string[] }', cardinality: 'many' },
6335
+ insert: { normalizedOutput: '{ data: Row[], count: number, insertedIds: unknown[] }', cardinality: 'many' },
6336
+ update: { normalizedOutput: '{ data: Row[], count: number }', cardinality: 'many' },
6337
+ delete: { normalizedOutput: '{ count: number, data?: Row[] }', cardinality: 'many' },
6338
+ upsert: { normalizedOutput: '{ data: Row[], count: number, operation: "inserted" | "updated" }', cardinality: 'many' },
6339
+ aggregate: { normalizedOutput: 'Record<aggregationAlias, number | unknown>', cardinality: 'one' },
6340
+ raw: { normalizedOutput: '{ data: Row[], count: number, fields?: string[], rowsAffected?: number }', cardinality: 'many' },
6341
+ };
6342
+ const outputContract = actionContract ?? directOutputContracts[args.database_operation ?? 'query'];
6343
+ const rowSchema = actionContract?.rowSchema ?? databaseSchema?.fields ?? {};
6344
+ return {
6345
+ content: [{
6346
+ type: 'text',
6347
+ text: JSON.stringify({
6348
+ scope: args.action_tag ? 'database_action' : 'database_primitive',
6349
+ product_tag: args.product_tag,
6350
+ env_slug: args.env_slug,
6351
+ database_tag: args.database_tag,
6352
+ table,
6353
+ operation: args.action_tag
6354
+ ? action?.operation ?? action?.type
6355
+ : args.database_operation ?? 'query',
6356
+ input_contract: args.action_tag ? action?.data ?? [] : generated?.payload?.input,
6357
+ output_contract: outputContract,
6358
+ row_schema: rowSchema,
6359
+ database_schema: databaseSchema,
6360
+ action: args.action_tag ? action : undefined,
6361
+ warnings: generated?.meta?.schema_warnings ?? [],
6362
+ discovery_only: true,
6363
+ executed_action: false,
6364
+ guidance: outputContract?.normalizedOutput
6365
+ ? 'Use output_contract for the envelope and row_schema for data item fields.'
6366
+ : 'Do not infer row fields or an action envelope when metadata is absent. Sync schema metadata and retry ductape_schema.',
6367
+ }, null, 2),
6368
+ }],
6369
+ };
6370
+ }
6087
6371
  const liveActionScope = [args.product_tag, args.app_tag, args.action_tag];
6088
6372
  if (liveActionScope.some(Boolean)) {
6089
6373
  if (!liveActionScope.every(Boolean)) {
@@ -6215,11 +6499,18 @@ async function main() {
6215
6499
  }, snippetGenerateHandler);
6216
6500
  server.registerTool('ductape_schema', {
6217
6501
  title: 'Ductape Asset Schema',
6218
- description: 'Returns a compact method index, one targeted asset schema, or one complete live app-action contract. ' +
6502
+ description: 'Returns a compact method index, one targeted asset schema, one complete live app-action contract, ' +
6503
+ 'or a live synchronized Database Action/direct database row contract. ' +
6219
6504
  'Call with module="app" or module="product" first to list method keys, then call again with ' +
6220
6505
  'module and method (for example method="databases.create") for the complete field schema. ' +
6221
6506
  'For one linked app action, call with product_tag, app_tag, and action_tag; do not fetch a full app catalogue. ' +
6507
+ 'For a saved Database Action, call with product_tag, env_slug, database_tag, and action_tag. ' +
6508
+ 'For a direct database call, call with product_tag, env_slug, database_tag, table, and database_operation. ' +
6509
+ 'Database results distinguish output_contract (the operation envelope) from row_schema (fields inside data rows). ' +
6510
+ 'This lookup is discovery-only and never executes the database action or primitive. ' +
6222
6511
  'Avoid calling without module unless you explicitly need the entire manifest.\n\n' +
6512
+ 'ALWAYS call the live database form before generating code that reads fields from a saved Database Action ' +
6513
+ 'or direct query result. If row_schema is unavailable, do not guess fields; sync the database schema and retry. ' +
6223
6514
  'ALWAYS call this before constructing a file for "resources <type> create" or any cloud ' +
6224
6515
  'import/provision operation — field shapes are not guessable from context.\n\n' +
6225
6516
  'Conditional fields: some fields are returned as oneOf (an array of variant shapes). ' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ductape/mcp",
3
- "version": "0.2.32",
3
+ "version": "0.2.33",
4
4
  "description": "MCP server that exposes Ductape SDK operations via the backend proxy",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -15,7 +15,7 @@
15
15
  ],
16
16
  "scripts": {
17
17
  "build": "tsc",
18
- "test": "npm run build && node scripts/check-cli-command-security.mjs && node scripts/check-frontend-analytics-guidance.mjs && node scripts/check-events-discovery.mjs && node scripts/check-schema-fallback.mjs && node scripts/check-paystack-action-schema.mjs && node scripts/check-portable-functions.mjs && node scripts/check-feature-control-flow.mjs && node scripts/check-project-link-guidance.mjs && node scripts/check-graph-vector-projection-guidance.mjs && node scripts/check-asset-file-guidance.mjs",
18
+ "test": "npm run build && node scripts/check-cli-command-security.mjs && node scripts/check-frontend-analytics-guidance.mjs && node scripts/check-events-discovery.mjs && node scripts/check-schema-fallback.mjs && node scripts/check-paystack-action-schema.mjs && node scripts/check-portable-functions.mjs && node scripts/check-feature-control-flow.mjs && node scripts/check-project-link-guidance.mjs && node scripts/check-graph-vector-projection-guidance.mjs && node scripts/check-asset-file-guidance.mjs && node scripts/check-database-action-contract-guidance.mjs",
19
19
  "start": "node dist/index.js",
20
20
  "dev": "tsx src/index.ts"
21
21
  },