@ductape/mcp 0.2.13 → 0.2.15
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 +2 -0
- package/README.md +6 -1
- package/dist/index.js +234 -23
- package/docs/TOOLS.md +15 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
## Unreleased
|
|
8
8
|
|
|
9
|
+
- Added source-verified graph/vector projection guidance to the `features`, `events`, `graphs`, and `vector` docs topics, including exact graph/vector payloads, NestJS consumer routing, replay/idempotency, projection-state ordering, rebuild/drift repair, and an explicit stop condition for the currently missing code-first `ctx.vector` API.
|
|
10
|
+
- Added read-only `ductape_events_topic_setup` and `ductape_events_validate_project` tools and made the canonical one-topic-per-file `ductape/events/<topic-tag>.topic.json` layout explicit and enforceable through the CLI.
|
|
9
11
|
- Mark `ductape_function_setup` as read-only, non-destructive, idempotent, and closed-world in both MCP registration APIs so hosts do not incorrectly require mutation approval for its pure setup-plan generation.
|
|
10
12
|
- Broadened Feature guidance from durable/event-driven workflows to synchronous or asynchronous named product capabilities.
|
|
11
13
|
- Added evidence-backed `FEATURE`, `FEATURE_STEP`, `DOMAIN_SERVICE`, `UTILITY`, and `INFRASTRUCTURE_ADAPTER` classification guidance.
|
package/README.md
CHANGED
|
@@ -82,7 +82,12 @@ The server exposes runtime, schema, documentation, CLI, discovery, migration, an
|
|
|
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
|
-
4. **`
|
|
85
|
+
4. **`ductape_events_topic_setup` / `ductape_events_validate_project`**:
|
|
86
|
+
- Generate one canonical `ductape/events/<topic-tag>.topic.json` asset shape without writing it.
|
|
87
|
+
- Validate filenames, one-object-per-file schemas, unique qualified tags, supported fields, and publisher references before provisioning.
|
|
88
|
+
- Aggregate topic catalogues, manifests, envelope registries, and custom event registries are rejected.
|
|
89
|
+
|
|
90
|
+
5. **`ductape_function_setup`**:
|
|
86
91
|
- Produces the secure local and remote setup for application functions referenced by portable Features.
|
|
87
92
|
- Requires an externally reachable HTTPS base URL (HTTP only for localhost development).
|
|
88
93
|
- Returns deterministic well-known routes, framework raw-body requirements, HMAC-SHA256 headers,
|
package/dist/index.js
CHANGED
|
@@ -630,8 +630,8 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
630
630
|
events.list [product_tag]
|
|
631
631
|
events.delete [product_tag, broker_tag]
|
|
632
632
|
events.topics.create ← FORBIDDEN with publishable key. Use ductape_cli instead:
|
|
633
|
-
ductape_cli("events topics create -f topic.json")
|
|
634
|
-
topic
|
|
633
|
+
ductape_cli("events topics create -f ductape/events/<topic-tag>.topic.json")
|
|
634
|
+
One directly importable topic per file: { tag: "broker-tag:topic-tag", name, description?, sample?, idempotent?, queueUrls?: [{ env_slug, url }] }
|
|
635
635
|
← Always required before consuming. For SQS: must include queueUrls per env.
|
|
636
636
|
← For Pub/Sub, Kafka, RabbitMQ, Redis, NATS: the first produce call auto-registers the topic,
|
|
637
637
|
but you should still create it explicitly so consumers can subscribe before any produce occurs.
|
|
@@ -1477,6 +1477,24 @@ const portableFunctionSetupAnnotations = {
|
|
|
1477
1477
|
idempotentHint: true,
|
|
1478
1478
|
openWorldHint: false,
|
|
1479
1479
|
};
|
|
1480
|
+
const eventsTopicSetupInputSchema = z.object({
|
|
1481
|
+
broker_tag: z.string().regex(/^[A-Za-z0-9]+(?:[-_][A-Za-z0-9]+)*$/).describe('Existing Ductape Events broker tag.'),
|
|
1482
|
+
topic_tag: z.string().regex(/^[A-Za-z0-9]+(?:[-_][A-Za-z0-9]+)*$/).describe('Unqualified topic tag used as the canonical filename.'),
|
|
1483
|
+
name: z.string().min(1),
|
|
1484
|
+
description: z.string().optional(),
|
|
1485
|
+
sample: z.record(z.unknown()).optional(),
|
|
1486
|
+
idempotent: z.boolean().optional(),
|
|
1487
|
+
queueUrls: z.array(z.object({ env_slug: z.string().min(1), url: z.string().url() }).strict()).optional(),
|
|
1488
|
+
});
|
|
1489
|
+
const eventsProjectValidationInputSchema = z.object({
|
|
1490
|
+
dir: z.string().default('ductape/events').describe('Must be ductape/events relative to DUCTAPE_PROJECT_DIR.'),
|
|
1491
|
+
});
|
|
1492
|
+
const readOnlyLocalAnnotations = {
|
|
1493
|
+
readOnlyHint: true,
|
|
1494
|
+
destructiveHint: false,
|
|
1495
|
+
idempotentHint: true,
|
|
1496
|
+
openWorldHint: false,
|
|
1497
|
+
};
|
|
1480
1498
|
const migrationInputSchema = z.object({
|
|
1481
1499
|
source: z.string().describe('Absolute path to the existing codebase.'),
|
|
1482
1500
|
e2e_baseline: z.string().describe('Absolute path to a passing migration-e2e baseline manifest created before migration inspection.'),
|
|
@@ -2378,8 +2396,9 @@ Schema management:
|
|
|
2378
2396
|
graph.dropIndex / dropConstraint / listIndexes / listConstraints
|
|
2379
2397
|
|
|
2380
2398
|
Saved actions (parameterized queries stored on the product):
|
|
2381
|
-
graph.createAction [{ graphTag,
|
|
2399
|
+
graph.createAction [{ graphTag?, name, description?, operation, query, parameters: [{ name, type, required?, defaultValue?, description? }] }, productTag?]
|
|
2382
2400
|
graph.listActions [graphTag?, productTag?]
|
|
2401
|
+
graph.execute [{ product, env, graph, action, input?, session?, cache? }]
|
|
2383
2402
|
graph.dispatch [data] ← call ductape_generate_payload FIRST
|
|
2384
2403
|
|
|
2385
2404
|
Supported index types: btree | fulltext | vector | range | point | text
|
|
@@ -2494,7 +2513,7 @@ Registration (admin — ductape_cli):
|
|
|
2494
2513
|
Runtime operations:
|
|
2495
2514
|
vector.upsert [{ product, env, tag, vectors: [{id, values: number[], metadata?}], namespace? }]
|
|
2496
2515
|
vector.upsertOne [{ product, env, tag, id, values: number[], metadata?, namespace? }]
|
|
2497
|
-
vector.query [{ product, env, tag, vector: number[], topK
|
|
2516
|
+
vector.query [{ product, env, tag, vector: number[], topK: number, filter?, namespace?,
|
|
2498
2517
|
includeValues?, includeMetadata? }]
|
|
2499
2518
|
vector.findSimilar [{ product, env, vector, values: number[], topK?, filter?, namespace? }]
|
|
2500
2519
|
vector.fetchOne [{ product, env, vector, id, namespace? }]
|
|
@@ -3597,14 +3616,15 @@ STEP 8 — SET rollbacks for reversible steps
|
|
|
3597
3616
|
async (result) => ctx.api.run({ app: 'stripe', event: 'refund', input: { chargeId: result.id } })
|
|
3598
3617
|
);
|
|
3599
3618
|
|
|
3600
|
-
|
|
3601
|
-
|
|
3619
|
+
Code-first ctx step types currently record: function | action | database | graph | vector |
|
|
3620
|
+
notification | storage | produce | quota | fallback | child_feature | sleep | wait_for_signal |
|
|
3621
|
+
checkpoint.
|
|
3602
3622
|
|
|
3603
3623
|
Valid synchronous Feature candidates include generate-world, resolve-nation-turn,
|
|
3604
3624
|
calculate-route-capacity, price-subscription, evaluate-entitlement, and build-replay.
|
|
3605
3625
|
|
|
3606
3626
|
Synchronous multi-step Feature (no Event, schedule, sleep, signal, or external system):
|
|
3607
|
-
await ductape.
|
|
3627
|
+
await ductape.feature.define({
|
|
3608
3628
|
product: 'example-product',
|
|
3609
3629
|
tag: 'resolve-nation-turn',
|
|
3610
3630
|
name: 'Resolve Nation Turn',
|
|
@@ -3617,15 +3637,10 @@ Synchronous multi-step Feature (no Event, schedule, sleep, signal, or external s
|
|
|
3617
3637
|
rejectedOrders: { type: 'number' },
|
|
3618
3638
|
},
|
|
3619
3639
|
handler: async (ctx) => {
|
|
3620
|
-
const validated = await ctx.step('validate-orders',
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
return resolveOrders(validated);
|
|
3625
|
-
});
|
|
3626
|
-
return ctx.step('build-result', async () => {
|
|
3627
|
-
return buildResult(resolved);
|
|
3628
|
-
});
|
|
3640
|
+
const validated = await ctx.step('validate-orders', () =>
|
|
3641
|
+
ctx.database.execute({ database: 'orders-db', event: 'validate-orders', input: ctx.input }));
|
|
3642
|
+
return ctx.step('resolve-orders', () =>
|
|
3643
|
+
ctx.database.execute({ database: 'orders-db', event: 'resolve-orders', input: validated }));
|
|
3629
3644
|
},
|
|
3630
3645
|
});
|
|
3631
3646
|
This is a valid Feature despite requiring no signal and producing no Event. Its qualification comes
|
|
@@ -3634,7 +3649,7 @@ step-level execution history.
|
|
|
3634
3649
|
|
|
3635
3650
|
Define a feature (write this into the project's source files — do NOT use features.create):
|
|
3636
3651
|
// src/features/onboard-user.ts (or the equivalent path/language for the project)
|
|
3637
|
-
await ductape.
|
|
3652
|
+
await ductape.feature.define({
|
|
3638
3653
|
tag: "onboard-user",
|
|
3639
3654
|
name: "Onboard User",
|
|
3640
3655
|
handler: async (ctx) => {
|
|
@@ -3863,10 +3878,28 @@ Import (register an EXISTING cloud resource):
|
|
|
3863
3878
|
IMPORTANT: events.topics.create requires an access key (admin operation).
|
|
3864
3879
|
Use ductape_cli — NOT ductape_execute — to create topics.
|
|
3865
3880
|
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3881
|
+
CANONICAL ASSET CONTRACT — HARD REQUIREMENT:
|
|
3882
|
+
Ductape topic assets are individual, directly importable files at
|
|
3883
|
+
<project-root>/ductape/events/<topic-tag>.topic.json
|
|
3884
|
+
Never replace them with an aggregate catalog, manifest, envelope registry, custom event registry,
|
|
3885
|
+
or multi-topic JSON file. Exactly one topic definition is allowed per file. The filename uses the
|
|
3886
|
+
unqualified topic portion: tag "order-events:order-created" must be stored as
|
|
3887
|
+
ductape/events/order-created.topic.json.
|
|
3888
|
+
|
|
3889
|
+
Generate the exact path and body before writing a topic:
|
|
3890
|
+
ductape_events_topic_setup({ broker_tag: "order-events", topic_tag: "order-created", name: "Order Created" })
|
|
3891
|
+
Validate the complete repository before any remote mutation:
|
|
3892
|
+
ductape_events_validate_project({ dir: "ductape/events" })
|
|
3893
|
+
Or through the CLI:
|
|
3894
|
+
ductape_cli("events topics validate --dir ductape/events --json")
|
|
3895
|
+
ductape_cli("events topics create-all --dir ductape/events --json")
|
|
3896
|
+
|
|
3897
|
+
Create one topic directly from its canonical asset:
|
|
3898
|
+
ductape_cli("events topics create -f ductape/events/order-created.topic.json --json")
|
|
3899
|
+
|
|
3900
|
+
Each *.topic.json file may contain ONLY tag, name, description, sample, idempotent, and the
|
|
3901
|
+
currently supported provider-specific field queueUrls. Unknown custom fields are rejected.
|
|
3902
|
+
Canonical topic file schema:
|
|
3870
3903
|
{
|
|
3871
3904
|
"tag": "order-events:order-created", // ALWAYS "broker-tag:topic-tag" — full event string
|
|
3872
3905
|
"name": "Order Created",
|
|
@@ -4663,6 +4696,122 @@ DEPRECATED ALIASES
|
|
|
4663
4696
|
useWorkflowCancel → useFeatureCancel
|
|
4664
4697
|
`.trim(),
|
|
4665
4698
|
};
|
|
4699
|
+
const GRAPH_VECTOR_PROJECTION_GUIDANCE = `
|
|
4700
|
+
|
|
4701
|
+
━━━ EVENT → GRAPH/VECTOR PROJECTION CONTRACT (CURRENT TYPESCRIPT/NESTJS SDK) ━━━
|
|
4702
|
+
|
|
4703
|
+
SOURCE-VERIFIED CODE-FIRST SUPPORT:
|
|
4704
|
+
IFeatureContext and RecordingContext expose both ctx.graph and ctx.vector. Their calls compile to
|
|
4705
|
+
portable JSON steps and the FeatureExecutor runs those steps against the configured local graph or
|
|
4706
|
+
vector service. Keep every primitive call inside ctx.step().
|
|
4707
|
+
|
|
4708
|
+
The TypeScript SDK facade is singular: ductape.feature.define/execute/dispatch. The backend proxy
|
|
4709
|
+
module is named "features", but application code must not infer a ductape.features property.
|
|
4710
|
+
|
|
4711
|
+
EXACT CODE-FIRST GRAPH CONTEXT:
|
|
4712
|
+
ctx.graph.createNode<T>({ graph, labels: string[], properties })
|
|
4713
|
+
ctx.graph.updateNode<T>({ graph, id: string|number, properties })
|
|
4714
|
+
ctx.graph.deleteNode({ graph, id })
|
|
4715
|
+
ctx.graph.createRelationship<T>({ graph, from, to, type, properties? })
|
|
4716
|
+
ctx.graph.deleteRelationship({ graph, id })
|
|
4717
|
+
ctx.graph.query<T>({ graph, action, params? })
|
|
4718
|
+
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.
|
|
4722
|
+
|
|
4723
|
+
EXACT CODE-FIRST VECTOR CONTEXT:
|
|
4724
|
+
ctx.vector.upsert<T>({
|
|
4725
|
+
vector: "product-vectors",
|
|
4726
|
+
vectors: [{ id, values: number[], metadata? }], namespace?, wait?
|
|
4727
|
+
});
|
|
4728
|
+
ctx.vector.upsertOne<T>({
|
|
4729
|
+
vector: "product-vectors", id, values: number[], metadata?, namespace?
|
|
4730
|
+
});
|
|
4731
|
+
ctx.vector.query<T>({
|
|
4732
|
+
vector: "product-vectors", values: number[], topK: number,
|
|
4733
|
+
namespace?, filter?, includeValues?, includeMetadata?, minScore?
|
|
4734
|
+
});
|
|
4735
|
+
ctx.vector.deleteVectors<T>({ vector: "product-vectors", ids?, namespace?, deleteAll?, filter? });
|
|
4736
|
+
ctx.vector.execute<T>({ vector: "product-vectors", action: "saved-action", input });
|
|
4737
|
+
Product/env and the Feature's inherited session are supplied by the executor. Do not serialize
|
|
4738
|
+
them into the Feature definition. Note the deliberate naming: ctx.vector.query uses values for
|
|
4739
|
+
the embedding, while the lower-level ductape.vector.query runtime facade uses vector.
|
|
4740
|
+
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.
|
|
4744
|
+
|
|
4745
|
+
NESTJS CONSUMER ROUTING AND FEATURE INPUT:
|
|
4746
|
+
@Events.Consumer receives the published message object. Ductape may add
|
|
4747
|
+
__ductape_message_id; there is no automatic domain-event envelope and no automatic session token.
|
|
4748
|
+
DuctapeModule subscribes at startup using product/env defaults unless decorator overrides are set.
|
|
4749
|
+
The callback is acknowledged after it returns successfully; throwing propagates failure to the
|
|
4750
|
+
provider's retry/nack path. Do not catch and suppress a failed projection.
|
|
4751
|
+
|
|
4752
|
+
import { Injectable } from '@nestjs/common';
|
|
4753
|
+
import { DuctapeContextService, Events } from '@ductape/nestjs';
|
|
4754
|
+
|
|
4755
|
+
type ProductProjectionEvent = {
|
|
4756
|
+
eventId: string; // stable across retries and replay
|
|
4757
|
+
productId: string;
|
|
4758
|
+
version: number; // monotonic aggregate/source version
|
|
4759
|
+
name: string;
|
|
4760
|
+
embedding: number[]; // only if produced upstream; Vector Search does not generate it
|
|
4761
|
+
__ductape_message_id?: string;
|
|
4762
|
+
};
|
|
4763
|
+
|
|
4764
|
+
@Injectable()
|
|
4765
|
+
export class ProductProjectionConsumer {
|
|
4766
|
+
constructor(private readonly ductape: DuctapeContextService) {}
|
|
4767
|
+
|
|
4768
|
+
@Events.Consumer({ event: 'catalog-events:product-projection-requested' })
|
|
4769
|
+
async project(message: ProductProjectionEvent): Promise<void> {
|
|
4770
|
+
const result = await this.ductape.sdk.feature.execute({
|
|
4771
|
+
product: 'buydeck',
|
|
4772
|
+
env: 'snd',
|
|
4773
|
+
tag: 'project-product-graph',
|
|
4774
|
+
input: message,
|
|
4775
|
+
idempotency_key: 'product-projection:' + message.eventId,
|
|
4776
|
+
});
|
|
4777
|
+
if (result.status !== 'completed') {
|
|
4778
|
+
throw new Error(result.error ?? 'Product graph projection did not complete');
|
|
4779
|
+
}
|
|
4780
|
+
}
|
|
4781
|
+
}
|
|
4782
|
+
|
|
4783
|
+
The consumer passes its message explicitly as Feature input. Use a stable domain eventId for the
|
|
4784
|
+
Feature idempotency key; __ductape_message_id is transport tracking metadata and must not replace a
|
|
4785
|
+
domain idempotency key. Broker replay/redelivery means consumers and every projection write must
|
|
4786
|
+
remain independently idempotent.
|
|
4787
|
+
|
|
4788
|
+
ORDERING, PROJECTION STATE, AND FAILURE RECOVERY:
|
|
4789
|
+
There is no atomic transaction spanning MongoDB, Neo4j, and Vertex Vector Search. Never claim that
|
|
4790
|
+
ctx.step ordering creates a distributed transaction. Treat MongoDB as the canonical source and
|
|
4791
|
+
graph/vector stores as rebuildable projections.
|
|
4792
|
+
|
|
4793
|
+
Recommended state machine, keyed by projection name + aggregate ID + source version/event ID:
|
|
4794
|
+
1. Atomically claim/read projection_state in MongoDB; completed means return success.
|
|
4795
|
+
2. Apply an idempotent Neo4j MERGE using stable domain IDs and sourceVersion.
|
|
4796
|
+
3. Upsert the vector using the same stable ID and sourceVersion metadata.
|
|
4797
|
+
4. Mark projection_state completed LAST, with graph/vector versions, checksum, and timestamps.
|
|
4798
|
+
On failure, retain pending/failed state and throw so the broker retries. A retry repeats graph/vector
|
|
4799
|
+
upserts safely and only then marks completion. Do not mark Mongo completion before derived writes.
|
|
4800
|
+
If strict atomic visibility across all three stores is required, the platform does not provide it.
|
|
4801
|
+
|
|
4802
|
+
REBUILD AND DRIFT REPAIR:
|
|
4803
|
+
Rebuild from canonical MongoDB records, not from broker retention or processor logs. Use a named,
|
|
4804
|
+
versioned rebuild job/Feature that pages deterministically, performs the same idempotent graph/vector
|
|
4805
|
+
upserts, checkpoints the last canonical cursor, and records projection version/checksum. Drift repair
|
|
4806
|
+
compares canonical IDs/sourceVersion/checksum with both projections, repairs missing/stale entries,
|
|
4807
|
+
and removes extras only after a complete successful scan. Keep live consumer and rebuild writes
|
|
4808
|
+
monotonic so an older rebuild item cannot overwrite a newer projection.
|
|
4809
|
+
|
|
4810
|
+
IMPLEMENTATION GATE:
|
|
4811
|
+
Combined database + graph + vector code-first projection Features are supported. Still stop when
|
|
4812
|
+
an embedding source, vector dimension, saved graph/vector action, or idempotency strategy is not
|
|
4813
|
+
specified; do not invent those application-level contracts.
|
|
4814
|
+
`.trim();
|
|
4666
4815
|
const docsHandler = async (args) => {
|
|
4667
4816
|
const requested = args.topic.toLowerCase().trim();
|
|
4668
4817
|
const recoveryAliases = /^(dlq|dead[- ]?letters?|failed messages?|retry|retries|poison messages?|replay|reprocess|consumer failures?)$/;
|
|
@@ -4683,7 +4832,10 @@ const docsHandler = async (args) => {
|
|
|
4683
4832
|
canonicalDocumentationTopic: 'events',
|
|
4684
4833
|
}, null, 2)}`
|
|
4685
4834
|
: '';
|
|
4686
|
-
|
|
4835
|
+
const projectionGuidance = ['features', 'events', 'graphs', 'vector'].includes(key)
|
|
4836
|
+
? `\n\n${GRAPH_VECTOR_PROJECTION_GUIDANCE}`
|
|
4837
|
+
: '';
|
|
4838
|
+
return { content: [{ type: 'text', text: doc + structured + projectionGuidance }] };
|
|
4687
4839
|
};
|
|
4688
4840
|
const portableFunctionSetupHandler = async (args) => {
|
|
4689
4841
|
const parsed = new URL(args.base_url);
|
|
@@ -4738,6 +4890,48 @@ const portableFunctionSetupHandler = async (args) => {
|
|
|
4738
4890
|
],
|
|
4739
4891
|
}, null, 2) }] };
|
|
4740
4892
|
};
|
|
4893
|
+
const eventsTopicSetupHandler = async (args) => {
|
|
4894
|
+
const tag = `${args.broker_tag}:${args.topic_tag}`;
|
|
4895
|
+
const relativePath = `ductape/events/${args.topic_tag}.topic.json`;
|
|
4896
|
+
const definition = {
|
|
4897
|
+
tag,
|
|
4898
|
+
name: args.name,
|
|
4899
|
+
...(args.description === undefined ? {} : { description: args.description }),
|
|
4900
|
+
...(args.sample === undefined ? {} : { sample: args.sample }),
|
|
4901
|
+
...(args.idempotent === undefined ? {} : { idempotent: args.idempotent }),
|
|
4902
|
+
...(args.queueUrls === undefined ? {} : { queueUrls: args.queueUrls }),
|
|
4903
|
+
};
|
|
4904
|
+
return {
|
|
4905
|
+
content: [{ type: 'text', text: JSON.stringify({
|
|
4906
|
+
ok: true,
|
|
4907
|
+
project_root: cliCwd(),
|
|
4908
|
+
path: join(cliCwd(), relativePath),
|
|
4909
|
+
relative_path: relativePath,
|
|
4910
|
+
definition,
|
|
4911
|
+
create_command: `ductape events topics create -f ${relativePath} --json`,
|
|
4912
|
+
validate_command: 'ductape events topics validate --dir ductape/events --json',
|
|
4913
|
+
rules: [
|
|
4914
|
+
'Write exactly this one JSON object to the returned path.',
|
|
4915
|
+
'Do not create an aggregate topic catalogue, manifest, envelope registry, or multi-topic JSON file.',
|
|
4916
|
+
'Run project validation before creating any remote topic.',
|
|
4917
|
+
],
|
|
4918
|
+
}, null, 2) }],
|
|
4919
|
+
};
|
|
4920
|
+
};
|
|
4921
|
+
const eventsProjectValidationHandler = async (args) => {
|
|
4922
|
+
const dir = args.dir ?? 'ductape/events';
|
|
4923
|
+
if (dir !== 'ductape/events') {
|
|
4924
|
+
return {
|
|
4925
|
+
content: [{ type: 'text', text: 'Events topic assets must use the canonical directory ductape/events; custom directories are forbidden.' }],
|
|
4926
|
+
isError: true,
|
|
4927
|
+
};
|
|
4928
|
+
}
|
|
4929
|
+
const result = runCli('events topics validate --dir ductape/events --json');
|
|
4930
|
+
return {
|
|
4931
|
+
content: [{ type: 'text', text: result.output || '(no output)' }],
|
|
4932
|
+
...(result.success ? {} : { isError: true }),
|
|
4933
|
+
};
|
|
4934
|
+
};
|
|
4741
4935
|
const cliInputSchema = z.object({
|
|
4742
4936
|
command: z.string().describe('The ductape CLI command to run, without the leading "ductape" word. ' +
|
|
4743
4937
|
'Examples: "products list", "products create --name \\"My Product\\" --tag my-product", ' +
|
|
@@ -5230,6 +5424,21 @@ async function main() {
|
|
|
5230
5424
|
'applications own transactional outboxes, domain rejection handling, and idempotent consumer mutations.',
|
|
5231
5425
|
inputSchema: eventsDiscoveryInputSchema,
|
|
5232
5426
|
}, eventsDiscoveryHandler);
|
|
5427
|
+
server.registerTool('ductape_events_topic_setup', {
|
|
5428
|
+
title: 'Ductape Events Topic Setup',
|
|
5429
|
+
description: 'Read-only generator for one canonical Ductape topic asset. Returns the required ' +
|
|
5430
|
+
'ductape/events/<topic-tag>.topic.json path, its directly importable JSON object, and validation/create commands. ' +
|
|
5431
|
+
'It does not write files or mutate remote resources.',
|
|
5432
|
+
inputSchema: eventsTopicSetupInputSchema,
|
|
5433
|
+
annotations: readOnlyLocalAnnotations,
|
|
5434
|
+
}, eventsTopicSetupHandler);
|
|
5435
|
+
server.registerTool('ductape_events_validate_project', {
|
|
5436
|
+
title: 'Validate Ductape Events Project',
|
|
5437
|
+
description: 'Read-only validation of canonical ductape/events/*.topic.json assets and statically discoverable publisher references. ' +
|
|
5438
|
+
'Rejects aggregate catalogues, invalid filenames/shapes/tags, duplicates, unknown fields, and undefined published topics.',
|
|
5439
|
+
inputSchema: eventsProjectValidationInputSchema,
|
|
5440
|
+
annotations: readOnlyLocalAnnotations,
|
|
5441
|
+
}, eventsProjectValidationHandler);
|
|
5233
5442
|
server.registerTool('ductape_function_setup', {
|
|
5234
5443
|
title: 'Ductape Portable Function Setup',
|
|
5235
5444
|
description: 'Read-only: generate (without applying) the mandatory secure local + remote runtime setup for application functions used by Features. ' +
|
|
@@ -5345,7 +5554,7 @@ async function main() {
|
|
|
5345
5554
|
' GCP Pub/Sub service identifier is "pubsub". AWS SQS is "sqs". Azure Service Bus is "servicebus".\n' +
|
|
5346
5555
|
' Message brokers are import-only (no provision-persist). Import flow is the same as storage.\n' +
|
|
5347
5556
|
' type field = "messageBrokers" (not "messagebrokers" or "events").\n' +
|
|
5348
|
-
' After importing, create
|
|
5557
|
+
' After importing, create one topic per canonical ductape/events/<topic-tag>.topic.json file. First run "events topics validate --dir ductape/events --json", then use "events topics create-all --dir ductape/events --json" or create each file directly. SQS requires queueUrls. Aggregate topic catalogues are forbidden.\n' +
|
|
5349
5558
|
' - Listing workspaces, products, focused product components, secrets\n' +
|
|
5350
5559
|
' Prefer "products components list --product-tag <tag> --json" for compact inventory; use\n' +
|
|
5351
5560
|
' "products components get --product-tag <tag> --type notifications|events|healthchecks|features --json" for focused detail.\n' +
|
|
@@ -5372,6 +5581,8 @@ async function main() {
|
|
|
5372
5581
|
server.tool('ductape_schema', schemaInputSchema.shape, schemaHandler);
|
|
5373
5582
|
server.tool('ductape_docs', docsInputSchema.shape, docsHandler);
|
|
5374
5583
|
server.tool('ductape_events_discover', eventsDiscoveryInputSchema.shape, eventsDiscoveryHandler);
|
|
5584
|
+
server.tool('ductape_events_topic_setup', eventsTopicSetupInputSchema.shape, readOnlyLocalAnnotations, eventsTopicSetupHandler);
|
|
5585
|
+
server.tool('ductape_events_validate_project', eventsProjectValidationInputSchema.shape, readOnlyLocalAnnotations, eventsProjectValidationHandler);
|
|
5375
5586
|
server.tool('ductape_function_setup', portableFunctionSetupInputSchema.shape, portableFunctionSetupAnnotations, portableFunctionSetupHandler);
|
|
5376
5587
|
server.tool('ductape_redis_setup', redisSetupInputSchema.shape, redisSetupHandler);
|
|
5377
5588
|
server.tool('ductape_migration_plan', migrationInputSchema.shape, migrationHandler);
|
package/docs/TOOLS.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
The Ductape MCP server exposes proxy, CLI, discovery, documentation, migration, and portable-function setup tools.
|
|
4
4
|
|
|
5
|
+
## Tools: `ductape_events_topic_setup` and `ductape_events_validate_project`
|
|
6
|
+
|
|
7
|
+
`ductape_events_topic_setup` is a read-only generator for one canonical topic asset. It returns the
|
|
8
|
+
exact `<project-root>/ductape/events/<topic-tag>.topic.json` path, one directly importable JSON
|
|
9
|
+
object, and its validation/create commands. It never writes the file or creates a remote topic.
|
|
10
|
+
|
|
11
|
+
`ductape_events_validate_project` runs the CLI's repository validator. It rejects missing canonical
|
|
12
|
+
directories, non-`.topic.json` JSON files, aggregate arrays/catalogues, filename/tag disagreement,
|
|
13
|
+
unknown fields, duplicate tags, and statically discoverable publishers without a matching topic.
|
|
14
|
+
|
|
15
|
+
Ductape topic assets are always one topic per file. Never substitute an aggregate catalogue,
|
|
16
|
+
manifest, envelope registry, or custom event registry.
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
5
20
|
## Tool: `ductape_function_setup`
|
|
6
21
|
|
|
7
22
|
Produces the mandatory local registry and signed HTTPS exposure plan for portable application
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ductape/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
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-portable-functions.mjs && node scripts/check-project-link-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-portable-functions.mjs && node scripts/check-project-link-guidance.mjs && node scripts/check-graph-vector-projection-guidance.mjs",
|
|
19
19
|
"start": "node dist/index.js",
|
|
20
20
|
"dev": "tsx src/index.ts"
|
|
21
21
|
},
|