@ductape/mcp 0.2.8 → 0.2.9
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/README.md +1 -0
- package/dist/index.js +146 -6
- package/docs/TOOLS.md +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -88,6 +88,7 @@ The server exposes runtime, schema, documentation, CLI, discovery, migration, an
|
|
|
88
88
|
- Returns deterministic well-known routes, framework raw-body requirements, HMAC-SHA256 headers,
|
|
89
89
|
runtime verification steps, and fail-closed conditions.
|
|
90
90
|
- Agents must implement and verify the route; they must not claim remote availability from local registration alone.
|
|
91
|
+
- Agents must first extract native Ductape primitives from migrated code and reserve Functions for irreducible residual domain logic while preserving original transaction boundaries.
|
|
91
92
|
|
|
92
93
|
The `ductape_cli` MCP tool also exposes public app discovery:
|
|
93
94
|
`marketplace search <capability>`, `marketplace categories`, and
|
package/dist/index.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* the env var when provided.
|
|
11
11
|
*/
|
|
12
12
|
import { createRequire } from 'module';
|
|
13
|
-
import { execSync } from 'child_process';
|
|
13
|
+
import { execFileSync, execSync } from 'child_process';
|
|
14
14
|
import { homedir } from 'os';
|
|
15
15
|
import { delimiter, join } from 'path';
|
|
16
16
|
import { z } from 'zod';
|
|
@@ -21,6 +21,60 @@ const MODULES = [
|
|
|
21
21
|
'events', 'messageBrokers', 'storage', 'vector', 'caches', 'sessions', 'quotas',
|
|
22
22
|
'actions', 'features', 'jobs', 'logs', 'resilience', 'health', 'fallback', 'secrets',
|
|
23
23
|
];
|
|
24
|
+
const redisSetupInputSchema = z.object({
|
|
25
|
+
approved: z.boolean().describe('Must be true only after the user explicitly approves creating/starting a local Docker Redis container.'),
|
|
26
|
+
port: z.number().int().min(1024).max(65535).default(6379),
|
|
27
|
+
container_name: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_.-]{0,62}$/).default('ductape-redis'),
|
|
28
|
+
});
|
|
29
|
+
const redisSetupHandler = async (args) => {
|
|
30
|
+
const port = args.port ?? 6379;
|
|
31
|
+
const name = args.container_name ?? 'ductape-redis';
|
|
32
|
+
if (!args.approved) {
|
|
33
|
+
return {
|
|
34
|
+
content: [{ type: 'text', text: [
|
|
35
|
+
'Redis materially reduces repeated Ductape bootstrap latency and is required for dispatch().',
|
|
36
|
+
'Ask the user whether they approve pulling redis:7-alpine and creating a local Docker container bound to 127.0.0.1.',
|
|
37
|
+
'Only call this tool again with approved=true after explicit approval.',
|
|
38
|
+
].join('\n') }],
|
|
39
|
+
isError: true,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
execFileSync('docker', ['version', '--format', '{{.Server.Version}}'], { encoding: 'utf8', timeout: 15_000 });
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return { content: [{ type: 'text', text: 'Docker is not installed or its daemon is unavailable. Install/start Docker, or supply a managed DUCTAPE_REDIS_URL.' }], isError: true };
|
|
47
|
+
}
|
|
48
|
+
let exists = false;
|
|
49
|
+
let running = false;
|
|
50
|
+
try {
|
|
51
|
+
exists = true;
|
|
52
|
+
running = execFileSync('docker', ['inspect', '-f', '{{.State.Running}}', name], { encoding: 'utf8', timeout: 10_000 }).trim() === 'true';
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
exists = false;
|
|
56
|
+
}
|
|
57
|
+
if (exists && !running)
|
|
58
|
+
execFileSync('docker', ['start', name], { encoding: 'utf8', timeout: 30_000 });
|
|
59
|
+
if (!exists) {
|
|
60
|
+
execFileSync('docker', [
|
|
61
|
+
'run', '-d', '--name', name, '--restart', 'unless-stopped',
|
|
62
|
+
'-p', `127.0.0.1:${port}:6379`, 'redis:7-alpine',
|
|
63
|
+
'redis-server', '--appendonly', 'yes',
|
|
64
|
+
], { encoding: 'utf8', timeout: 120_000 });
|
|
65
|
+
}
|
|
66
|
+
execFileSync('docker', ['exec', name, 'redis-cli', 'ping'], { encoding: 'utf8', timeout: 10_000 });
|
|
67
|
+
return { content: [{ type: 'text', text: JSON.stringify({
|
|
68
|
+
ready: true,
|
|
69
|
+
container: name,
|
|
70
|
+
redis_url: `redis://127.0.0.1:${port}`,
|
|
71
|
+
next_steps: [
|
|
72
|
+
`Set DUCTAPE_REDIS_URL=redis://127.0.0.1:${port}`,
|
|
73
|
+
'Pass redisUrl to @ductape/nestjs or redis_url/redis_client to the installed SDK API as supported by its types.',
|
|
74
|
+
'Add the variable to local .env only; do not commit credentials for managed Redis.',
|
|
75
|
+
],
|
|
76
|
+
}, null, 2) }] };
|
|
77
|
+
};
|
|
24
78
|
// ─── Exhaustive SDK Method & Params Reference ────────────────────────────────
|
|
25
79
|
// Built from a complete read of sdk/ts/src/index.ts (Ductape class public API).
|
|
26
80
|
// Each entry follows: [module].[method] → params array signature.
|
|
@@ -213,6 +267,10 @@ SETUP — register once in AppModule:
|
|
|
213
267
|
║ ║
|
|
214
268
|
║ *.run(), events.produce(), and @Events.Consumer do NOT need Redis. ║
|
|
215
269
|
║ Only dispatch() does — and it is non-negotiable. ║
|
|
270
|
+
║ ║
|
|
271
|
+
║ Redis is STRONGLY RECOMMENDED even without dispatch(): every bootstrap ║
|
|
272
|
+
║ domain uses memory + optional Redis caching, so shared/multi-process ║
|
|
273
|
+
║ deployments avoid repeating control-plane reads after warm-up. ║
|
|
216
274
|
╚══════════════════════════════════════════════════════════════════════════╝
|
|
217
275
|
|
|
218
276
|
import { DuctapeModule } from '@ductape/nestjs';
|
|
@@ -245,6 +303,10 @@ SETUP — register once in AppModule:
|
|
|
245
303
|
DUCTAPE_REDIS_URL=redis://localhost:6379 # local dev
|
|
246
304
|
DUCTAPE_REDIS_URL=rediss://:<password>@host:6380 # managed Redis (TLS)
|
|
247
305
|
|
|
306
|
+
If no Redis URL exists, proactively explain the latency/dispatch benefits and ask whether the
|
|
307
|
+
user wants a local Docker Redis dedicated to Ductape. After explicit approval, call
|
|
308
|
+
ductape_redis_setup({ approved: true }). Never pull an image or create/start a container before approval.
|
|
309
|
+
|
|
248
310
|
INJECTING IN SERVICES AND CONTROLLERS — use @InjectContext():
|
|
249
311
|
|
|
250
312
|
import { InjectContext, DuctapeContext } from '@ductape/nestjs';
|
|
@@ -773,11 +835,12 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
773
835
|
queries?: { queryName: { handler?: function } },
|
|
774
836
|
options?: { timeout?: number, retries?: number },
|
|
775
837
|
envs?: [{ slug: string, active?: boolean }],
|
|
776
|
-
recordInput?: object, //
|
|
838
|
+
recordInput?: object, // exposed only as ctx.sampleInput while recording
|
|
777
839
|
recordScenarios?: object[], // multiple recording scenarios for branching
|
|
778
840
|
branchOverrides?: object, // force step results during recording to reach later branches
|
|
779
841
|
handler: async (ctx) => {
|
|
780
|
-
// ctx.input – typed
|
|
842
|
+
// ctx.input – typed runtime input; always compiles to $Input{} operators
|
|
843
|
+
// ctx.sampleInput – compile-time sample for loop/branch discovery only
|
|
781
844
|
// ctx.step(tag, fn, rollback?, opts?) – define a durable step
|
|
782
845
|
// ctx.api.run({ app, event, input }) – call an app action
|
|
783
846
|
// ctx.database.query/insert/update/delete({ database, event, ... })
|
|
@@ -1115,6 +1178,7 @@ const ductape = new Ductape({
|
|
|
1115
1178
|
workspace_id: process.env.DUCTAPE_WORKSPACE_ID!,
|
|
1116
1179
|
user_id: process.env.DUCTAPE_USER_ID!,
|
|
1117
1180
|
public_key: process.env.DUCTAPE_PUBLIC_KEY!,
|
|
1181
|
+
redis_url: process.env.DUCTAPE_REDIS_URL,
|
|
1118
1182
|
});
|
|
1119
1183
|
|
|
1120
1184
|
async function run() {
|
|
@@ -1384,6 +1448,37 @@ PORTABLE APPLICATION FUNCTIONS
|
|
|
1384
1448
|
Use portable functions when a Feature needs application-owned logic that cannot be expressed with
|
|
1385
1449
|
database, action, Event, storage, graph, vector, session, quota, fallback, or transform primitives.
|
|
1386
1450
|
|
|
1451
|
+
PRIMITIVES-FIRST DECOMPOSITION — REQUIRED
|
|
1452
|
+
Before creating a Function, inspect the implementation, its callees, side effects, transaction
|
|
1453
|
+
boundary, configuration, and tests. Extract every operation already represented by a Ductape
|
|
1454
|
+
primitive. A Function is the residual application-owned behavior after that extraction; it is not
|
|
1455
|
+
a wrapper around an entire existing service method merely because that method already exists.
|
|
1456
|
+
|
|
1457
|
+
Classify each observed behavior:
|
|
1458
|
+
database read/write/transaction/index → database primitive or database action
|
|
1459
|
+
external provider/API → connected App action
|
|
1460
|
+
publish/consume asynchronous message → Events primitive
|
|
1461
|
+
session creation/validation/revocation → session primitive
|
|
1462
|
+
file/blob operation → storage primitive
|
|
1463
|
+
graph/vector operation → graph/vector primitive
|
|
1464
|
+
email/SMS/push/chat delivery → notification primitive
|
|
1465
|
+
quota/fallback/health/cache → corresponding Ductape primitive
|
|
1466
|
+
multi-step product capability → Feature orchestration
|
|
1467
|
+
reusable domain decision/transformation → portable Function candidate
|
|
1468
|
+
formatting/hash/redaction with one caller→ ordinary utility unless portability is required
|
|
1469
|
+
|
|
1470
|
+
For a mixed legacy method, split the boundary. Keep its irreducible domain decision in a Function
|
|
1471
|
+
and orchestrate extracted primitives as named Feature steps. Preserve atomicity: do not split a
|
|
1472
|
+
database transaction into independently committed steps unless the original contract permits it;
|
|
1473
|
+
use a database action or a Function with an explicitly authorized transactional capability instead.
|
|
1474
|
+
|
|
1475
|
+
Every proposed Function must include evidence for:
|
|
1476
|
+
- why no existing Ductape primitive expresses it;
|
|
1477
|
+
- exact business input/output, errors, side effects, idempotency, and transaction semantics;
|
|
1478
|
+
- whether it is pure and therefore a future signed-WASM candidate;
|
|
1479
|
+
- why it needs local, Events, or HTTPS availability;
|
|
1480
|
+
- which extracted primitives remain separate Feature steps.
|
|
1481
|
+
|
|
1387
1482
|
Non-negotiable rule: arbitrary JavaScript/TypeScript callbacks are not serializable. Never write
|
|
1388
1483
|
ctx.step('x', () => applicationService.method()) and assume the method will run elsewhere. The
|
|
1389
1484
|
Feature compiler must reject a step that records no portable operation.
|
|
@@ -1471,6 +1566,39 @@ START
|
|
|
1471
1566
|
5. Call again with write=true only after confirming the destination. This writes guidance artifacts only.
|
|
1472
1567
|
6. Call with ensure_product=true when product inventory confirms the product is absent.
|
|
1473
1568
|
|
|
1569
|
+
PRIMITIVES-FIRST CAPABILITY EXTRACTION — REQUIRED FOR EVERY MIGRATION SLICE
|
|
1570
|
+
Do not translate controllers, services, handlers, or exported functions one-for-one into Features
|
|
1571
|
+
or portable Functions. For each candidate capability, trace entry points, callees, state changes,
|
|
1572
|
+
external calls, errors, authorization, session usage, transactions, retries, and tests, then create
|
|
1573
|
+
a decomposition ledger with these classifications:
|
|
1574
|
+
|
|
1575
|
+
DUCTAPE_PRIMITIVE database, App action, Events, session, storage, graph, vector,
|
|
1576
|
+
notification, cache, quota, fallback, healthcheck, secret
|
|
1577
|
+
FEATURE user/product capability coordinating multiple meaningful operations
|
|
1578
|
+
PORTABLE_FUNCTION residual reusable domain logic that primitives cannot express
|
|
1579
|
+
CHILD_FEATURE independently meaningful capability with its own contract/lifecycle
|
|
1580
|
+
UTILITY local implementation detail with no independent product contract
|
|
1581
|
+
INFRASTRUCTURE_ADAPTER framework/provider plumbing replaced by a Ductape primitive
|
|
1582
|
+
|
|
1583
|
+
The required order is:
|
|
1584
|
+
1. Extract and inventory Ductape primitives from the existing implementation.
|
|
1585
|
+
2. Establish the capability and transaction boundary from behavior and tests.
|
|
1586
|
+
3. Design named Feature steps around those primitives.
|
|
1587
|
+
4. Put only irreducible application-owned logic behind ctx.functions.
|
|
1588
|
+
5. For each Function, record pure/WASM-candidate versus framework-dependent classification.
|
|
1589
|
+
6. Call ductape_function_setup and implement verified local plus remote availability.
|
|
1590
|
+
|
|
1591
|
+
Never create a Function that merely hides database, session, Events, storage, notification, graph,
|
|
1592
|
+
vector, quota, fallback, healthcheck, cache, secret, or connected-App work that the Feature can
|
|
1593
|
+
express directly. Never fragment an original atomic transaction just to maximize primitive count.
|
|
1594
|
+
A mixed method may legitimately become several primitive steps plus one small Function, one database
|
|
1595
|
+
action, or one capability-scoped Function when atomicity requires co-location.
|
|
1596
|
+
|
|
1597
|
+
Before implementation, present a decomposition table with: source behavior, evidence location,
|
|
1598
|
+
classification, selected Ductape primitive/function, input/output, side effects, transaction owner,
|
|
1599
|
+
session behavior, failure semantics, and verification test. If the residual Function has no clear
|
|
1600
|
+
reason to exist after primitive extraction, do not create it.
|
|
1601
|
+
|
|
1474
1602
|
FINAL E2E ACCEPTANCE GATE
|
|
1475
1603
|
End the migration by running the exact original E2E command against the migrated codebase while retaining
|
|
1476
1604
|
the original checksum-bound suite. Do not silently edit, delete, skip, quarantine, or weaken baseline tests.
|
|
@@ -3404,7 +3532,7 @@ STEP 7 — HANDLE conditionals, loops, and branching (when applicable)
|
|
|
3404
3532
|
→ Add branchOverrides: { stepTag: { field: value } } so all branches are recorded
|
|
3405
3533
|
→ At runtime the executor evaluates the real result and skips or runs later steps accordingly
|
|
3406
3534
|
Loop over input array:
|
|
3407
|
-
→ Add recordInput: { items: [{ id: "1" }, { id: "2" }] }
|
|
3535
|
+
→ Add recordInput: { items: [{ id: "1" }, { id: "2" }] } and iterate ctx.sampleInput.items
|
|
3408
3536
|
→ Use unique step tags per iteration (e.g. "process-" + item.id)
|
|
3409
3537
|
Switch/if-else on feature input values:
|
|
3410
3538
|
→ Add recordScenarios: [{ type: "a" }, { type: "b" }] — handler runs once per scenario
|
|
@@ -3514,7 +3642,8 @@ When you call features.define({ handler }), the handler runs TWICE:
|
|
|
3514
3642
|
This phase captures the step graph: which steps exist, their types, tags, and declared
|
|
3515
3643
|
inputs/outputs. No real API calls, DB queries, or side effects occur.
|
|
3516
3644
|
Arbitrary JS code OUTSIDE ctx.step() ALSO runs during recording — with proxy values.
|
|
3517
|
-
For loops:
|
|
3645
|
+
For loops: supply recordInput and iterate ctx.sampleInput so all iterations are recorded.
|
|
3646
|
+
ctx.input is always the runtime operator surface and must never expose recordInput literals.
|
|
3518
3647
|
For branches: use branchOverrides so each path is captured.
|
|
3519
3648
|
|
|
3520
3649
|
2. EXECUTION PHASE (at runtime) — handler is called with a real ExecutionContext.
|
|
@@ -4533,6 +4662,8 @@ const portableFunctionSetupHandler = async (args) => {
|
|
|
4533
4662
|
return { content: [{ type: 'text', text: JSON.stringify({
|
|
4534
4663
|
ok: true,
|
|
4535
4664
|
requiredActions: [
|
|
4665
|
+
'Prove that database, App, Events, session, storage, graph, vector, notification, cache, quota, fallback, healthcheck, and secret primitives were extracted before defining this residual application Function.',
|
|
4666
|
+
'Record whether the residual logic is pure/signed-WASM-capable or framework-dependent, including transaction and side-effect boundaries.',
|
|
4536
4667
|
'Create or locate one defineFunctions contract with exact input/output JSON Schemas.',
|
|
4537
4668
|
'Register handlers with ductape.functions.register during application startup.',
|
|
4538
4669
|
`Set DUCTAPE_FUNCTION_BASE_URL=${JSON.stringify(base)} in the application runtime.`,
|
|
@@ -5052,14 +5183,22 @@ async function main() {
|
|
|
5052
5183
|
server.registerTool('ductape_function_setup', {
|
|
5053
5184
|
title: 'Ductape Portable Function Setup',
|
|
5054
5185
|
description: 'Generate the mandatory secure local + remote runtime setup for application functions used by Features. ' +
|
|
5055
|
-
'Use this
|
|
5186
|
+
'Use this only after a primitives-first decomposition proves residual application-owned logic remains. The result requires a registered local handler, ' +
|
|
5056
5187
|
'a deterministic HTTPS endpoint, raw-body HMAC verification, runtime checks, and fail-closed behavior.',
|
|
5057
5188
|
inputSchema: portableFunctionSetupInputSchema,
|
|
5058
5189
|
}, portableFunctionSetupHandler);
|
|
5190
|
+
server.registerTool('ductape_redis_setup', {
|
|
5191
|
+
title: 'Ductape Local Redis Setup',
|
|
5192
|
+
description: 'Provision or start an isolated local Redis container for Ductape bootstrap caching and dispatch queues. ' +
|
|
5193
|
+
'First explain the latency benefit and ask for explicit approval. Never pass approved=true before the user agrees. ' +
|
|
5194
|
+
'Uses redis:7-alpine, binds only to 127.0.0.1, enables append-only persistence, and returns the redis URL to wire into Ductape initialization.',
|
|
5195
|
+
inputSchema: redisSetupInputSchema,
|
|
5196
|
+
}, redisSetupHandler);
|
|
5059
5197
|
server.registerTool('ductape_migration_plan', {
|
|
5060
5198
|
title: 'Ductape AI Migration Guidance',
|
|
5061
5199
|
description: 'Inspect a TypeScript, Go, Java, or .NET repository without exposing secret values. ' +
|
|
5062
5200
|
'Builds a relevant-file review queue, secret-name inventory, checksummed migration evidence, and low-confidence navigation hints. ' +
|
|
5201
|
+
'Migration review must extract Ductape primitives before classifying residual domain logic as portable Functions. ' +
|
|
5063
5202
|
'The review standards classify capability candidates as FEATURE, FEATURE_STEP, DOMAIN_SERVICE, UTILITY, or INFRASTRUCTURE_ADAPTER; ' +
|
|
5064
5203
|
'they inspect exports, public methods, entry points, consumers, jobs, repeated orchestration, typed operations, routes, and product terminology. ' +
|
|
5065
5204
|
'Synchronous local multi-step capabilities may be Features, while related low-level functions must be grouped rather than promoted one-by-one. ' +
|
|
@@ -5182,6 +5321,7 @@ async function main() {
|
|
|
5182
5321
|
server.tool('ductape_docs', docsInputSchema.shape, docsHandler);
|
|
5183
5322
|
server.tool('ductape_events_discover', eventsDiscoveryInputSchema.shape, eventsDiscoveryHandler);
|
|
5184
5323
|
server.tool('ductape_function_setup', portableFunctionSetupInputSchema.shape, portableFunctionSetupHandler);
|
|
5324
|
+
server.tool('ductape_redis_setup', redisSetupInputSchema.shape, redisSetupHandler);
|
|
5185
5325
|
server.tool('ductape_migration_plan', migrationInputSchema.shape, migrationHandler);
|
|
5186
5326
|
server.tool('ductape_cli', cliInputSchema.shape, cliHandler);
|
|
5187
5327
|
}
|
package/docs/TOOLS.md
CHANGED
|
@@ -8,6 +8,12 @@ Produces the mandatory local registry and signed HTTPS exposure plan for portabl
|
|
|
8
8
|
functions referenced by Features. The route is
|
|
9
9
|
`/.well-known/ductape/functions/:namespace/:version/:operation` and requests use HMAC-SHA256.
|
|
10
10
|
|
|
11
|
+
Call it only after a primitives-first migration review. Extract database, App, Events, session,
|
|
12
|
+
storage, graph, vector, notification, cache, quota, fallback, healthcheck, and secret behavior into
|
|
13
|
+
native Ductape primitives. A Function contains only the irreducible application-owned logic unless
|
|
14
|
+
the original atomic transaction requires co-location. Classify that residual logic as pure and
|
|
15
|
+
future-WASM-capable or framework-dependent.
|
|
16
|
+
|
|
11
17
|
---
|
|
12
18
|
|
|
13
19
|
## Marketplace discovery
|