@ductape/mcp 0.2.6 → 0.2.7
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/dist/events-capabilities.d.ts +133 -0
- package/dist/events-capabilities.d.ts.map +1 -0
- package/dist/events-capabilities.js +311 -0
- package/dist/index.js +164 -4
- package/package.json +2 -2
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
export declare const EVENTS_IMPLEMENTATION_WARNING = "Ductape manages consumer delivery retries and dead-letter queues. Do not create a parallel application transport DLQ. Use an application transactional outbox only to bridge canonical database commits to Ductape publication. Consumers must remain idempotent and replay-safe.";
|
|
2
|
+
export declare const EVENTS_RECOVERY_SEARCH_TERMS: string[];
|
|
3
|
+
export declare const EVENTS_DELIVERY_SEMANTICS: {
|
|
4
|
+
publisherAcceptance: string;
|
|
5
|
+
consumerRetriesManagedBy: string;
|
|
6
|
+
deadLetterQueueManagedBy: string;
|
|
7
|
+
applicationDlqRequired: boolean;
|
|
8
|
+
applicationOutboxRecommended: boolean;
|
|
9
|
+
applicationOutboxPurpose: string;
|
|
10
|
+
orderingGuarantees: string;
|
|
11
|
+
deliveryGuarantee: string;
|
|
12
|
+
deduplicationGuidance: string;
|
|
13
|
+
idempotentConsumerRequired: boolean;
|
|
14
|
+
responsibilities: {
|
|
15
|
+
applicationTransactionalOutbox: string;
|
|
16
|
+
brokerDeliveryRetries: string;
|
|
17
|
+
exhaustedDeliveryDlq: string;
|
|
18
|
+
replayAndReprocessing: string;
|
|
19
|
+
domainRejectedOrInvalidCommands: string;
|
|
20
|
+
consumerMutationIdempotency: string;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
export declare const EVENTS_PROVIDER_CAPABILITIES: Record<string, Record<string, unknown>>;
|
|
24
|
+
type Operation = {
|
|
25
|
+
name: string;
|
|
26
|
+
description: string;
|
|
27
|
+
inputSchema: Record<string, string>;
|
|
28
|
+
outputSchema: Record<string, string>;
|
|
29
|
+
requiredIdentifiers: string[];
|
|
30
|
+
environmentRequirements: string;
|
|
31
|
+
connectionRequirements: string;
|
|
32
|
+
permissions: string;
|
|
33
|
+
idempotency: string;
|
|
34
|
+
pagination: string;
|
|
35
|
+
applicableStates: string[];
|
|
36
|
+
commonErrors: Array<{
|
|
37
|
+
error: string;
|
|
38
|
+
remediation: string;
|
|
39
|
+
}>;
|
|
40
|
+
mutatesState: boolean;
|
|
41
|
+
mayIncurProviderCost: boolean;
|
|
42
|
+
};
|
|
43
|
+
export declare const EVENTS_OPERATION_REGISTRY: Operation[];
|
|
44
|
+
export declare function eventsCapabilityOverview(): {
|
|
45
|
+
capability: string;
|
|
46
|
+
aliases: string[];
|
|
47
|
+
implementationWarning: string;
|
|
48
|
+
deliverySemantics: {
|
|
49
|
+
publisherAcceptance: string;
|
|
50
|
+
consumerRetriesManagedBy: string;
|
|
51
|
+
deadLetterQueueManagedBy: string;
|
|
52
|
+
applicationDlqRequired: boolean;
|
|
53
|
+
applicationOutboxRecommended: boolean;
|
|
54
|
+
applicationOutboxPurpose: string;
|
|
55
|
+
orderingGuarantees: string;
|
|
56
|
+
deliveryGuarantee: string;
|
|
57
|
+
deduplicationGuidance: string;
|
|
58
|
+
idempotentConsumerRequired: boolean;
|
|
59
|
+
responsibilities: {
|
|
60
|
+
applicationTransactionalOutbox: string;
|
|
61
|
+
brokerDeliveryRetries: string;
|
|
62
|
+
exhaustedDeliveryDlq: string;
|
|
63
|
+
replayAndReprocessing: string;
|
|
64
|
+
domainRejectedOrInvalidCommands: string;
|
|
65
|
+
consumerMutationIdempotency: string;
|
|
66
|
+
};
|
|
67
|
+
};
|
|
68
|
+
recoverySearchTerms: string[];
|
|
69
|
+
documentationTopics: string[];
|
|
70
|
+
providerCapabilities: Record<string, Record<string, unknown>>;
|
|
71
|
+
operations: Operation[];
|
|
72
|
+
};
|
|
73
|
+
export declare function searchEventsCapabilities(query: string): {
|
|
74
|
+
query: string;
|
|
75
|
+
canonicalTopic: string;
|
|
76
|
+
recoveryMatch: boolean;
|
|
77
|
+
deliverySemantics: {
|
|
78
|
+
publisherAcceptance: string;
|
|
79
|
+
consumerRetriesManagedBy: string;
|
|
80
|
+
deadLetterQueueManagedBy: string;
|
|
81
|
+
applicationDlqRequired: boolean;
|
|
82
|
+
applicationOutboxRecommended: boolean;
|
|
83
|
+
applicationOutboxPurpose: string;
|
|
84
|
+
orderingGuarantees: string;
|
|
85
|
+
deliveryGuarantee: string;
|
|
86
|
+
deduplicationGuidance: string;
|
|
87
|
+
idempotentConsumerRequired: boolean;
|
|
88
|
+
responsibilities: {
|
|
89
|
+
applicationTransactionalOutbox: string;
|
|
90
|
+
brokerDeliveryRetries: string;
|
|
91
|
+
exhaustedDeliveryDlq: string;
|
|
92
|
+
replayAndReprocessing: string;
|
|
93
|
+
domainRejectedOrInvalidCommands: string;
|
|
94
|
+
consumerMutationIdempotency: string;
|
|
95
|
+
};
|
|
96
|
+
};
|
|
97
|
+
operations: Operation[];
|
|
98
|
+
};
|
|
99
|
+
export declare function inspectEventsComponent(component: any): {
|
|
100
|
+
component: any;
|
|
101
|
+
provider: any;
|
|
102
|
+
environments: any;
|
|
103
|
+
topics: any;
|
|
104
|
+
subscriptions: any;
|
|
105
|
+
consumerBindings: any;
|
|
106
|
+
messageStatistics: string;
|
|
107
|
+
availableOperationalActions: Operation[];
|
|
108
|
+
deliverySemantics: {
|
|
109
|
+
publisherAcceptance: string;
|
|
110
|
+
consumerRetriesManagedBy: string;
|
|
111
|
+
deadLetterQueueManagedBy: string;
|
|
112
|
+
applicationDlqRequired: boolean;
|
|
113
|
+
applicationOutboxRecommended: boolean;
|
|
114
|
+
applicationOutboxPurpose: string;
|
|
115
|
+
orderingGuarantees: string;
|
|
116
|
+
deliveryGuarantee: string;
|
|
117
|
+
deduplicationGuidance: string;
|
|
118
|
+
idempotentConsumerRequired: boolean;
|
|
119
|
+
responsibilities: {
|
|
120
|
+
applicationTransactionalOutbox: string;
|
|
121
|
+
brokerDeliveryRetries: string;
|
|
122
|
+
exhaustedDeliveryDlq: string;
|
|
123
|
+
replayAndReprocessing: string;
|
|
124
|
+
domainRejectedOrInvalidCommands: string;
|
|
125
|
+
consumerMutationIdempotency: string;
|
|
126
|
+
};
|
|
127
|
+
};
|
|
128
|
+
implementationWarning: string;
|
|
129
|
+
configurationWarnings: any;
|
|
130
|
+
documentationTopics: string[];
|
|
131
|
+
};
|
|
132
|
+
export {};
|
|
133
|
+
//# sourceMappingURL=events-capabilities.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events-capabilities.d.ts","sourceRoot":"","sources":["../src/events-capabilities.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,6BAA6B,sRAC2O,CAAC;AAEtR,eAAO,MAAM,4BAA4B,UAWxC,CAAC;AAEF,eAAO,MAAM,yBAAyB;;;;;;;;;;;;;;;;;;;CAmBrC,CAAC;AAEF,eAAO,MAAM,4BAA4B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAkDhF,CAAC;AAEF,KAAK,SAAS,GAAG;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACpC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACrC,mBAAmB,EAAE,MAAM,EAAE,CAAC;IAC9B,uBAAuB,EAAE,MAAM,CAAC;IAChC,sBAAsB,EAAE,MAAM,CAAC;IAC/B,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC5D,YAAY,EAAE,OAAO,CAAC;IACtB,oBAAoB,EAAE,OAAO,CAAC;CAC/B,CAAC;AAyCF,eAAO,MAAM,yBAAyB,EAAE,SAAS,EA8IhD,CAAC;AAEF,wBAAgB,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAWvC;AAED,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;EASrD;AAED,wBAAgB,sBAAsB,CAAC,SAAS,EAAE,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAiCpD"}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
export const EVENTS_IMPLEMENTATION_WARNING = 'Ductape manages consumer delivery retries and dead-letter queues. Do not create a parallel application transport DLQ. Use an application transactional outbox only to bridge canonical database commits to Ductape publication. Consumers must remain idempotent and replay-safe.';
|
|
2
|
+
export const EVENTS_RECOVERY_SEARCH_TERMS = [
|
|
3
|
+
'dlq',
|
|
4
|
+
'dead letter',
|
|
5
|
+
'dead letters',
|
|
6
|
+
'failed messages',
|
|
7
|
+
'retry',
|
|
8
|
+
'retries',
|
|
9
|
+
'poison message',
|
|
10
|
+
'replay',
|
|
11
|
+
'reprocess',
|
|
12
|
+
'consumer failure',
|
|
13
|
+
];
|
|
14
|
+
export const EVENTS_DELIVERY_SEMANTICS = {
|
|
15
|
+
publisherAcceptance: 'Ductape acknowledges events accepted for publication; acceptance is not proof of consumer-side mutation.',
|
|
16
|
+
consumerRetriesManagedBy: 'ductape',
|
|
17
|
+
deadLetterQueueManagedBy: 'ductape',
|
|
18
|
+
applicationDlqRequired: false,
|
|
19
|
+
applicationOutboxRecommended: true,
|
|
20
|
+
applicationOutboxPurpose: 'Protect the canonical database transaction-to-Ductape publication boundary.',
|
|
21
|
+
orderingGuarantees: 'Provider and configuration dependent; inspect each environment. Never infer global ordering.',
|
|
22
|
+
deliveryGuarantee: 'At-least-once processing should be assumed unless the inspected provider/configuration explicitly proves otherwise.',
|
|
23
|
+
deduplicationGuidance: 'Use publishIdempotent with a stable logical-operation key where appropriate and deduplicate consumer mutations.',
|
|
24
|
+
idempotentConsumerRequired: true,
|
|
25
|
+
responsibilities: {
|
|
26
|
+
applicationTransactionalOutbox: 'application',
|
|
27
|
+
brokerDeliveryRetries: 'ductape',
|
|
28
|
+
exhaustedDeliveryDlq: 'ductape',
|
|
29
|
+
replayAndReprocessing: 'ductape',
|
|
30
|
+
domainRejectedOrInvalidCommands: 'application (not a transport DLQ)',
|
|
31
|
+
consumerMutationIdempotency: 'application',
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
export const EVENTS_PROVIDER_CAPABILITIES = {
|
|
35
|
+
google_pubsub: {
|
|
36
|
+
ordering: 'supported only when provider ordering keys/configuration are enabled; not inferred by Ductape',
|
|
37
|
+
acknowledgement: 'provider subscription acknowledgement deadline',
|
|
38
|
+
retriesAndBackoff: 'provider subscription policy; inspect the deployed subscription',
|
|
39
|
+
dlq: 'Ductape recovery operations are available, but provider dead-letter policy must be verified before claiming a provider DLQ exists',
|
|
40
|
+
retention: 'provider topic/subscription retention; inspect live provider configuration',
|
|
41
|
+
},
|
|
42
|
+
aws_sqs: {
|
|
43
|
+
ordering: 'FIFO only for FIFO queues; standard queues do not guarantee strict ordering',
|
|
44
|
+
acknowledgement: 'visibility timeout',
|
|
45
|
+
retriesAndBackoff: 'receive count and visibility timeout/provider redrive policy',
|
|
46
|
+
dlq: 'provider redrive/DLQ must be configured and verified; Ductape exposes tracked dead letters and reprocessing',
|
|
47
|
+
retention: 'provider queue retention',
|
|
48
|
+
},
|
|
49
|
+
azure_servicebus: {
|
|
50
|
+
ordering: 'sessions can provide ordered handling when configured; otherwise provider-controlled',
|
|
51
|
+
acknowledgement: 'message lock duration',
|
|
52
|
+
retriesAndBackoff: 'max delivery count and lock/redelivery behavior',
|
|
53
|
+
dlq: 'provider entity DLQ exists, but its configured state and access must be verified',
|
|
54
|
+
retention: 'provider entity configuration',
|
|
55
|
+
},
|
|
56
|
+
kafka: {
|
|
57
|
+
ordering: 'within a partition only',
|
|
58
|
+
acknowledgement: 'consumer offset commit',
|
|
59
|
+
retriesAndBackoff: 'Ductape consumer failure tracking plus application/provider configuration',
|
|
60
|
+
dlq: 'do not assume a native broker DLQ topic exists unless it is configured and verified',
|
|
61
|
+
retention: 'topic retention configuration',
|
|
62
|
+
},
|
|
63
|
+
rabbitmq: {
|
|
64
|
+
ordering: 'queue ordering can be affected by redelivery and multiple consumers',
|
|
65
|
+
acknowledgement: 'broker acknowledgement/nack',
|
|
66
|
+
retriesAndBackoff: 'redelivery and configured exchange/queue policies',
|
|
67
|
+
dlq: 'dead-letter exchange/queue must be configured and verified before claiming provider DLQ availability',
|
|
68
|
+
retention: 'queue/message TTL configuration',
|
|
69
|
+
},
|
|
70
|
+
redis: {
|
|
71
|
+
ordering: 'provider/mode dependent',
|
|
72
|
+
acknowledgement: 'not uniformly supported across Redis pub/sub and stream modes',
|
|
73
|
+
retriesAndBackoff: 'Ductape tracking; provider-native retry semantics may be unsupported',
|
|
74
|
+
dlq: 'no provider DLQ should be implied without a verified stream/consumer-group recovery configuration',
|
|
75
|
+
retention: 'provider/mode dependent',
|
|
76
|
+
},
|
|
77
|
+
nats: {
|
|
78
|
+
ordering: 'subject/stream configuration dependent',
|
|
79
|
+
acknowledgement: 'JetStream configuration dependent; core NATS has different semantics',
|
|
80
|
+
retriesAndBackoff: 'provider mode/configuration dependent',
|
|
81
|
+
dlq: 'do not imply a provider DLQ without verified JetStream/recovery configuration',
|
|
82
|
+
retention: 'stream configuration dependent',
|
|
83
|
+
},
|
|
84
|
+
};
|
|
85
|
+
const runtimeRead = {
|
|
86
|
+
environmentRequirements: 'product environment must exist and the Events component must be configured for it',
|
|
87
|
+
connectionRequirements: 'configured provider connection for the selected Events environment',
|
|
88
|
+
permissions: 'publishable-key runtime access plus product/environment access',
|
|
89
|
+
applicableStates: ['active', 'provider-ready'],
|
|
90
|
+
commonErrors: [
|
|
91
|
+
{ error: 'broker/topic not found', remediation: 'inspect the Events component and use its exact broker/topic tags' },
|
|
92
|
+
{ error: 'environment not configured', remediation: 'configure that environment on the Events component' },
|
|
93
|
+
{ error: 'authentication/permission denied', remediation: 'verify publishable key, workspace, product, and environment access' },
|
|
94
|
+
],
|
|
95
|
+
};
|
|
96
|
+
function adminOperation(name, description, inputSchema, requiredIdentifiers, mutatesState) {
|
|
97
|
+
return {
|
|
98
|
+
name, description, inputSchema,
|
|
99
|
+
outputSchema: mutatesState ? { resource: 'persisted Events component/topic' } : { resource: 'Events component/topic or array' },
|
|
100
|
+
requiredIdentifiers,
|
|
101
|
+
environmentRequirements: 'administrative product access; create/update must cover every product environment where required',
|
|
102
|
+
connectionRequirements: 'provider-specific credentials or a verified cloud connection for every configured environment',
|
|
103
|
+
permissions: 'authenticated access-key CLI session with workspace/product administration permission',
|
|
104
|
+
idempotency: mutatesState ? 'not guaranteed; fetch first and use update rather than duplicate create' : 'read-only',
|
|
105
|
+
pagination: name.endsWith('.list') ? 'provider/component list; pagination is not currently exposed by this admin method' : 'none',
|
|
106
|
+
applicableStates: ['draft', 'active', 'provider-ready'],
|
|
107
|
+
commonErrors: [
|
|
108
|
+
{ error: 'missing environment/provider fields', remediation: 'read the targeted live events.create/events.update schema and cover every product environment' },
|
|
109
|
+
{ error: 'unsupported provider configuration', remediation: 'use the selected env type’s live config branch; do not copy fields between providers' },
|
|
110
|
+
{ error: 'permission denied', remediation: 'authenticate the CLI and select the correct workspace; do not use ductape_execute for admin writes' },
|
|
111
|
+
],
|
|
112
|
+
mutatesState,
|
|
113
|
+
mayIncurProviderCost: mutatesState,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
export const EVENTS_OPERATION_REGISTRY = [
|
|
117
|
+
adminOperation('events.create', 'Register an Events component and its per-environment provider connections.', { product: 'string', tag: 'string', name: 'string', description: 'string?', envs: 'provider-specific env[]' }, ['product', 'tag', 'name', 'envs'], true),
|
|
118
|
+
adminOperation('events.update', 'Update an Events component/provider configuration.', { product: 'string', brokerTag: 'string', data: 'targeted live events.update schema' }, ['product', 'brokerTag'], true),
|
|
119
|
+
adminOperation('events.fetch', 'Fetch one Events component.', { product: 'string', brokerTag: 'string' }, ['product', 'brokerTag'], false),
|
|
120
|
+
adminOperation('events.list', 'List Events components for a product.', { product: 'string' }, ['product'], false),
|
|
121
|
+
adminOperation('events.delete', 'Delete or retire an Events component.', { product: 'string', brokerTag: 'string' }, ['product', 'brokerTag'], true),
|
|
122
|
+
adminOperation('events.topics.create', 'Create a topic definition on a registered Events component.', { product: 'string', tag: 'broker:topic', name: 'string', sample: 'object?', idempotent: 'boolean?', queueUrls: 'array?' }, ['product', 'tag', 'name'], true),
|
|
123
|
+
adminOperation('events.topics.update', 'Update an Events topic definition.', { product: 'string', topicTag: 'broker:topic', data: 'topic patch' }, ['product', 'topicTag'], true),
|
|
124
|
+
adminOperation('events.topics.fetch', 'Fetch one Events topic definition.', { product: 'string', topicTag: 'broker:topic' }, ['product', 'topicTag'], false),
|
|
125
|
+
adminOperation('events.topics.list', 'List topic definitions for an Events component.', { product: 'string', brokerTag: 'string' }, ['product', 'brokerTag'], false),
|
|
126
|
+
{
|
|
127
|
+
name: 'events.produce', description: 'Publish an event immediately.',
|
|
128
|
+
inputSchema: { product: 'string', env: 'string', event: 'broker:topic', message: 'object', session: 'string?' },
|
|
129
|
+
outputSchema: { accepted: 'boolean', messageId: 'string?' }, requiredIdentifiers: ['product', 'env', 'event'],
|
|
130
|
+
...runtimeRead, idempotency: 'not inherently idempotent; use events.publishIdempotent when duplicate publication matters',
|
|
131
|
+
pagination: 'none', mutatesState: true, mayIncurProviderCost: true,
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
name: 'events.publishIdempotent', description: 'Publish with a stable deduplication key.',
|
|
135
|
+
inputSchema: { product: 'string', env: 'string', event: 'broker:topic', message: 'object', idempotencyKey: 'string', idempotencyTtl: 'number?' },
|
|
136
|
+
outputSchema: { accepted: 'boolean', duplicate: 'boolean?', messageId: 'string?' }, requiredIdentifiers: ['product', 'env', 'event', 'idempotencyKey'],
|
|
137
|
+
...runtimeRead, idempotency: 'deduplicates the logical publication for the configured TTL',
|
|
138
|
+
pagination: 'none', mutatesState: true, mayIncurProviderCost: true,
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
name: 'events.consume', description: 'Register a server-side consumer; successful return acknowledges and thrown errors trigger managed failure handling.',
|
|
142
|
+
inputSchema: { product: 'string', env: 'string', event: 'broker:topic', callback: 'function' },
|
|
143
|
+
outputSchema: { subscribed: 'boolean', consumerTag: 'string?' }, requiredIdentifiers: ['product', 'env', 'event'],
|
|
144
|
+
...runtimeRead, permissions: 'server-side SDK access; browser clients cannot consume',
|
|
145
|
+
idempotency: 'consumer mutations must be idempotent and replay-safe', pagination: 'none',
|
|
146
|
+
mutatesState: true, mayIncurProviderCost: true,
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
name: 'events.dispatch', description: 'Queue or schedule an Events publication.',
|
|
150
|
+
inputSchema: { product: 'string', env: 'string', event: 'broker:topic', input: '{message: object}', retries: 'number?', schedule: 'object?' },
|
|
151
|
+
outputSchema: { job_id: 'string', status: 'scheduled|queued', scheduled_at: 'string?', recurring: 'boolean' },
|
|
152
|
+
requiredIdentifiers: ['product', 'env', 'event'], ...runtimeRead,
|
|
153
|
+
connectionRequirements: 'Events provider connection plus redis_url for dispatch scheduling',
|
|
154
|
+
idempotency: 'job/publication idempotency depends on stable job and message keys; consumers remain idempotent',
|
|
155
|
+
pagination: 'none', mutatesState: true, mayIncurProviderCost: true,
|
|
156
|
+
},
|
|
157
|
+
{
|
|
158
|
+
name: 'events.messages.query', description: 'Query tracked broker messages and delivery state.',
|
|
159
|
+
inputSchema: { product: 'string', env: 'string', brokerTag: 'string', topicTag: 'string?', producerTag: 'string?', consumerTag: 'string?', status: 'string?', startDate: 'ISO date?', endDate: 'ISO date?', page: 'number?', limit: 'number?' },
|
|
160
|
+
outputSchema: { messages: 'array', total: 'number', page: 'number', limit: 'number', hasMore: 'boolean' },
|
|
161
|
+
requiredIdentifiers: ['product', 'env', 'brokerTag'], ...runtimeRead,
|
|
162
|
+
idempotency: 'read-only', pagination: 'page/limit with total and hasMore', mutatesState: false, mayIncurProviderCost: false,
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
name: 'events.getEvents', description: 'Query tracked Events records by broker, topic, category, status, idempotency, and date range.',
|
|
166
|
+
inputSchema: { product: 'string', env: 'string', brokerTag: 'string', topic: 'string?', category: 'string?', status: 'string?', idempotent: 'boolean?', startDate: 'Date?', endDate: 'Date?', page: 'number?', limit: 'number?' },
|
|
167
|
+
outputSchema: { events: 'array', total: 'number', page: 'number', limit: 'number', hasMore: 'boolean' },
|
|
168
|
+
requiredIdentifiers: ['product', 'env', 'brokerTag'], ...runtimeRead,
|
|
169
|
+
idempotency: 'read-only', pagination: 'page/limit with total and hasMore',
|
|
170
|
+
mutatesState: false, mayIncurProviderCost: false,
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
name: 'events.getEvent', description: 'Fetch one tracked Events record by ID.',
|
|
174
|
+
inputSchema: { product: 'string', eventId: 'string' },
|
|
175
|
+
outputSchema: { event: 'tracked event or null' },
|
|
176
|
+
requiredIdentifiers: ['product', 'eventId'], ...runtimeRead,
|
|
177
|
+
environmentRequirements: 'the event ID must belong to the selected product',
|
|
178
|
+
idempotency: 'read-only', pagination: 'none',
|
|
179
|
+
mutatesState: false, mayIncurProviderCost: false,
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: 'events.getEventStats', description: 'Fetch aggregate tracked event statistics.',
|
|
183
|
+
inputSchema: { product: 'string', env: 'string', brokerTag: 'string' },
|
|
184
|
+
outputSchema: { total_events: 'number', failed_count: 'number', success_count: 'number', status_breakdown: 'object?' },
|
|
185
|
+
requiredIdentifiers: ['product', 'env', 'brokerTag'], ...runtimeRead,
|
|
186
|
+
idempotency: 'read-only', pagination: 'none',
|
|
187
|
+
mutatesState: false, mayIncurProviderCost: false,
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: 'events.messages.getDeadLetters', description: 'List messages whose managed consumer delivery attempts were exhausted.',
|
|
191
|
+
inputSchema: { product: 'string', env: 'string', brokerTag: 'string', topicTag: 'string?', consumerTag: 'string?', startDate: 'ISO date?', endDate: 'ISO date?', page: 'number?', limit: 'number?' },
|
|
192
|
+
outputSchema: { deadLetters: 'array', total: 'number', page: 'number', limit: 'number', hasMore: 'boolean' },
|
|
193
|
+
requiredIdentifiers: ['product', 'env', 'brokerTag'], ...runtimeRead,
|
|
194
|
+
idempotency: 'read-only', pagination: 'page/limit with total and hasMore', mutatesState: false, mayIncurProviderCost: false,
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
name: 'events.reprocessDLQ', description: 'Requeue selected or bounded dead-lettered messages for Ductape-managed delivery.',
|
|
198
|
+
inputSchema: { product: 'string', env: 'string', brokerTag: 'string', topicTag: 'string?', messageIds: 'string[]?', limit: 'number?' },
|
|
199
|
+
outputSchema: { reprocessed: 'number', failed: 'number', messageIds: 'string[]?' },
|
|
200
|
+
requiredIdentifiers: ['product', 'env', 'brokerTag'], ...runtimeRead,
|
|
201
|
+
permissions: 'publishable-key runtime mutation access plus product/environment access',
|
|
202
|
+
idempotency: 'may redeliver; consumer mutation must be idempotent', pagination: 'bounded by messageIds or limit, not page-based',
|
|
203
|
+
applicableStates: ['dead-lettered', 'provider-ready'], mutatesState: true, mayIncurProviderCost: true,
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'events.replayEvent', description: 'Replay one tracked event by event identifier.',
|
|
207
|
+
inputSchema: { product: 'string', env: 'string', eventId: 'string', force: 'boolean?' },
|
|
208
|
+
outputSchema: { replayed: 'boolean', eventId: 'string', messageId: 'string?' },
|
|
209
|
+
requiredIdentifiers: ['product', 'env', 'eventId'], ...runtimeRead,
|
|
210
|
+
permissions: 'publishable-key runtime mutation access plus product/environment access',
|
|
211
|
+
idempotency: 'replay intentionally redelivers; force may bypass replay guards; consumer mutation must be idempotent',
|
|
212
|
+
pagination: 'none', applicableStates: ['tracked', 'failed', 'dead-lettered'],
|
|
213
|
+
mutatesState: true, mayIncurProviderCost: true,
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
name: 'events.messages.getStats', description: 'Return aggregate delivery and failure statistics.',
|
|
217
|
+
inputSchema: { product: 'string', env: 'string', brokerTag: 'string' },
|
|
218
|
+
outputSchema: { totals: 'object', delivery: 'object', failures: 'object' },
|
|
219
|
+
requiredIdentifiers: ['product', 'env', 'brokerTag'], ...runtimeRead,
|
|
220
|
+
idempotency: 'read-only', pagination: 'none', mutatesState: false, mayIncurProviderCost: false,
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
name: 'events.messages.getDashboard', description: 'Return operational overview, recent messages, and recovery indicators.',
|
|
224
|
+
inputSchema: { product: 'string', env: 'string', brokerTag: 'string' },
|
|
225
|
+
outputSchema: { overview: 'object', recent_messages: 'array', health: 'object?' },
|
|
226
|
+
requiredIdentifiers: ['product', 'env', 'brokerTag'], ...runtimeRead,
|
|
227
|
+
idempotency: 'read-only', pagination: 'fixed dashboard window', mutatesState: false, mayIncurProviderCost: false,
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
name: 'events.messages.getProducers', description: 'List observed producers.',
|
|
231
|
+
inputSchema: { product: 'string', env: 'string', brokerTag: 'string', topicTag: 'string?', page: 'number?', limit: 'number?' },
|
|
232
|
+
outputSchema: { producers: 'array', total: 'number', page: 'number', limit: 'number', hasMore: 'boolean' },
|
|
233
|
+
requiredIdentifiers: ['product', 'env', 'brokerTag'], ...runtimeRead,
|
|
234
|
+
idempotency: 'read-only', pagination: 'page/limit', mutatesState: false, mayIncurProviderCost: false,
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
name: 'events.messages.getConsumers', description: 'List observed consumers and bindings.',
|
|
238
|
+
inputSchema: { product: 'string', env: 'string', brokerTag: 'string', topicTag: 'string?', page: 'number?', limit: 'number?' },
|
|
239
|
+
outputSchema: { consumers: 'array', total: 'number', page: 'number', limit: 'number', hasMore: 'boolean' },
|
|
240
|
+
requiredIdentifiers: ['product', 'env', 'brokerTag'], ...runtimeRead,
|
|
241
|
+
idempotency: 'read-only', pagination: 'page/limit', mutatesState: false, mayIncurProviderCost: false,
|
|
242
|
+
},
|
|
243
|
+
{
|
|
244
|
+
name: 'events.checkIdempotency', description: 'Check whether a publication idempotency key has already been accepted.',
|
|
245
|
+
inputSchema: { product: 'string', env: 'string', idempotencyKey: 'string' },
|
|
246
|
+
outputSchema: { exists: 'boolean', event_id: 'string?' }, requiredIdentifiers: ['product', 'env', 'idempotencyKey'],
|
|
247
|
+
...runtimeRead, idempotency: 'read-only idempotency lookup', pagination: 'none',
|
|
248
|
+
mutatesState: false, mayIncurProviderCost: false,
|
|
249
|
+
},
|
|
250
|
+
{
|
|
251
|
+
name: 'events.testConnection', description: 'Test the configured provider connection for one Events environment.',
|
|
252
|
+
inputSchema: { product: 'string', env: 'string', broker: 'string' },
|
|
253
|
+
outputSchema: { connected: 'boolean', latency: 'number?', error: 'string?' },
|
|
254
|
+
requiredIdentifiers: ['product', 'env', 'broker'], ...runtimeRead,
|
|
255
|
+
idempotency: 'read-only connection probe', pagination: 'none',
|
|
256
|
+
mutatesState: false, mayIncurProviderCost: false,
|
|
257
|
+
},
|
|
258
|
+
];
|
|
259
|
+
export function eventsCapabilityOverview() {
|
|
260
|
+
return {
|
|
261
|
+
capability: 'events',
|
|
262
|
+
aliases: ['messageBrokers', 'brokers', 'messaging'],
|
|
263
|
+
implementationWarning: EVENTS_IMPLEMENTATION_WARNING,
|
|
264
|
+
deliverySemantics: EVENTS_DELIVERY_SEMANTICS,
|
|
265
|
+
recoverySearchTerms: EVENTS_RECOVERY_SEARCH_TERMS,
|
|
266
|
+
documentationTopics: ['events', 'resilience'],
|
|
267
|
+
providerCapabilities: EVENTS_PROVIDER_CAPABILITIES,
|
|
268
|
+
operations: EVENTS_OPERATION_REGISTRY,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
export function searchEventsCapabilities(query) {
|
|
272
|
+
const normalized = query.toLowerCase();
|
|
273
|
+
const recoveryMatch = EVENTS_RECOVERY_SEARCH_TERMS.some(term => normalized.includes(term));
|
|
274
|
+
const operations = EVENTS_OPERATION_REGISTRY.filter(operation => recoveryMatch
|
|
275
|
+
? /deadletter|reprocess|replay|query|getstats|getdashboard/i.test(operation.name)
|
|
276
|
+
: `${operation.name} ${operation.description}`.toLowerCase().includes(normalized));
|
|
277
|
+
return { query, canonicalTopic: 'events', recoveryMatch, deliverySemantics: EVENTS_DELIVERY_SEMANTICS, operations };
|
|
278
|
+
}
|
|
279
|
+
export function inspectEventsComponent(component) {
|
|
280
|
+
const environments = (component?.envs || []).map((env) => ({
|
|
281
|
+
slug: env.slug ?? env.env_slug ?? null,
|
|
282
|
+
provider: env.type ?? component.type ?? null,
|
|
283
|
+
connection: env.cloud ?? env.instance ?? (env.config ? 'configured' : null),
|
|
284
|
+
retryPolicy: env.retryPolicy ?? component.retryPolicy ?? 'provider-default',
|
|
285
|
+
maximumDeliveryAttempts: env.maxDeliveryAttempts ?? component.maxDeliveryAttempts ?? 'provider-controlled-or-unverified',
|
|
286
|
+
acknowledgementDeadline: env.acknowledgementDeadline ?? component.acknowledgementDeadline ?? 'provider-controlled-or-unverified',
|
|
287
|
+
backoff: env.backoff ?? component.backoff ?? 'provider-controlled-or-unverified',
|
|
288
|
+
dlq: env.dlq ?? component.dlq ?? { configured: 'unverified', managedBy: 'ductape' },
|
|
289
|
+
retention: env.retention ?? component.retention ?? 'provider-controlled-or-unverified',
|
|
290
|
+
ordering: env.ordering ?? component.ordering ?? 'provider-controlled-or-unverified',
|
|
291
|
+
filters: env.filters ?? component.filters ?? 'none-configured-or-unverified',
|
|
292
|
+
providerCapabilities: EVENTS_PROVIDER_CAPABILITIES[env.type ?? component.type] ??
|
|
293
|
+
{ status: 'unsupported-or-unknown-provider; inspect live provider configuration' },
|
|
294
|
+
}));
|
|
295
|
+
return {
|
|
296
|
+
component,
|
|
297
|
+
provider: component?.type ?? 'environment-specific',
|
|
298
|
+
environments,
|
|
299
|
+
topics: component?.topics ?? [],
|
|
300
|
+
subscriptions: component?.subscriptions ?? 'provider-controlled-or-unverified',
|
|
301
|
+
consumerBindings: component?.consumers ?? [],
|
|
302
|
+
messageStatistics: 'Call events.messages.getStats or events.messages.getDashboard with product, env, and brokerTag.',
|
|
303
|
+
availableOperationalActions: EVENTS_OPERATION_REGISTRY,
|
|
304
|
+
deliverySemantics: EVENTS_DELIVERY_SEMANTICS,
|
|
305
|
+
implementationWarning: EVENTS_IMPLEMENTATION_WARNING,
|
|
306
|
+
configurationWarnings: environments
|
|
307
|
+
.filter((env) => env.retryPolicy === 'provider-default' || env.dlq?.configured === 'unverified')
|
|
308
|
+
.map((env) => `${env.slug ?? 'unknown env'}: retry/DLQ configuration is not verified; provider defaults may apply.`),
|
|
309
|
+
documentationTopics: ['events', 'resilience'],
|
|
310
|
+
};
|
|
311
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { homedir } from 'os';
|
|
|
15
15
|
import { delimiter, join } from 'path';
|
|
16
16
|
import { z } from 'zod';
|
|
17
17
|
import { executeViaProxy, generateExecutablePayload, getAssetSchemas, } from './proxy-client.js';
|
|
18
|
+
import { EVENTS_DELIVERY_SEMANTICS, EVENTS_IMPLEMENTATION_WARNING, eventsCapabilityOverview, inspectEventsComponent, searchEventsCapabilities, } from './events-capabilities.js';
|
|
18
19
|
const MODULES = [
|
|
19
20
|
'product', 'app', 'databases', 'graph', 'webhooks', 'notifications',
|
|
20
21
|
'events', 'messageBrokers', 'storage', 'vector', 'caches', 'sessions', 'quotas',
|
|
@@ -556,6 +557,14 @@ ALL params are passed as a JSON array in positional order matching the SDK signa
|
|
|
556
557
|
events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, startDate?, endDate?, page?, limit? }]
|
|
557
558
|
events.messages.getStats [{ product, env, brokerTag }]
|
|
558
559
|
events.messages.getDashboard [{ product, env, brokerTag }]
|
|
560
|
+
events.getEvents [{ product, env, brokerTag, topic?, category?, status?, idempotent?, startDate?, endDate?, page?, limit? }]
|
|
561
|
+
events.getEvent [{ product, eventId }]
|
|
562
|
+
events.getEventStats [{ product, env, brokerTag }]
|
|
563
|
+
events.replayEvent [{ product, env, eventId, force? }]
|
|
564
|
+
events.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
|
|
565
|
+
events.publishIdempotent [{ product, env, event, message, idempotencyKey, idempotencyTtl? }]
|
|
566
|
+
events.checkIdempotency [{ product, env, idempotencyKey }]
|
|
567
|
+
events.testConnection [{ product, env, broker }]
|
|
559
568
|
|
|
560
569
|
━━━ MODULE: storage ━━━
|
|
561
570
|
storage.create [{ product: string, tag: string, name: string, description?: string, envs: [{ slug: string, type: "aws"|"azure"|"gcp", config: { bucket?: string, region?: string, accessKeyId?: string, secretAccessKey?: string, containerName?: string, connectionString?: string, projectId?: string, keyFilename?: string } }] }]
|
|
@@ -1342,6 +1351,11 @@ const docsInputSchema = z.object({
|
|
|
1342
1351
|
'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
|
|
1343
1352
|
'notifications, resilience, features, events, logs, migration, frontend, frontend-analytics, client, react, vue'),
|
|
1344
1353
|
});
|
|
1354
|
+
const eventsDiscoveryInputSchema = z.object({
|
|
1355
|
+
query: z.string().optional().describe('Capability or recovery search, including DLQ, dead letter, failed messages, retry, poison message, replay, or consumer failure.'),
|
|
1356
|
+
product: z.string().optional().describe('Product tag used for live component inspection.'),
|
|
1357
|
+
component: z.string().optional().describe('Events component/broker tag used for live inspection.'),
|
|
1358
|
+
});
|
|
1345
1359
|
const migrationInputSchema = z.object({
|
|
1346
1360
|
source: z.string().describe('Absolute path to the existing codebase.'),
|
|
1347
1361
|
e2e_baseline: z.string().describe('Absolute path to a passing migration-e2e baseline manifest created before migration inspection.'),
|
|
@@ -2807,6 +2821,13 @@ DUCTAPE RESILIENCE
|
|
|
2807
2821
|
Resilience covers three mechanisms: quotas (rate-limited provider pools), fallbacks (automatic
|
|
2808
2822
|
provider switching), and healthchecks (continuous probe monitoring with failure actions).
|
|
2809
2823
|
|
|
2824
|
+
EVENTS DELIVERY BOUNDARY
|
|
2825
|
+
Events transport retries, exhausted-delivery DLQs, replay, and reprocessing are Ductape
|
|
2826
|
+
responsibilities. Applications own transactional outboxes at the database-to-publication
|
|
2827
|
+
boundary, domain rejection records, and idempotent/replay-safe consumer mutations. Never advise
|
|
2828
|
+
a parallel application transport DLQ. See ductape_docs({ topic: "events" }) and
|
|
2829
|
+
ductape_events_discover({ query: "consumer failure" }).
|
|
2830
|
+
|
|
2810
2831
|
CONFIGURATION BOUNDARY
|
|
2811
2832
|
Quotas, fallbacks, and health checks are administrative product configuration. Manage them with
|
|
2812
2833
|
the authenticated CLI:
|
|
@@ -3471,6 +3492,19 @@ When you call features.define({ handler }), the handler runs TWICE:
|
|
|
3471
3492
|
events: `
|
|
3472
3493
|
DUCTAPE EVENTS (MESSAGE BROKERS)
|
|
3473
3494
|
|
|
3495
|
+
RELIABILITY OWNERSHIP — DO NOT DUPLICATE THE TRANSPORT DLQ
|
|
3496
|
+
Ductape owns broker delivery retries, exhausted-delivery dead-letter handling, replay,
|
|
3497
|
+
reprocessing, and message observability. Do not build a parallel MongoDB/application transport
|
|
3498
|
+
DLQ. The application may use a transactional outbox only to bridge its canonical database commit
|
|
3499
|
+
to Ductape publication. Domain-invalid or rejected commands remain application records and are
|
|
3500
|
+
not transport dead letters. Every consumer mutation must be idempotent and replay-safe.
|
|
3501
|
+
Recovery operations: events.messages.getDeadLetters, events.reprocessDLQ, events.replayEvent,
|
|
3502
|
+
events.messages.query, events.messages.getStats, and events.messages.getDashboard.
|
|
3503
|
+
Structured contracts and live component inspection:
|
|
3504
|
+
ductape_events_discover({ query: "DLQ" })
|
|
3505
|
+
ductape_events_discover({ product: "<product-tag>", component: "<events-tag>" })
|
|
3506
|
+
Resilience boundary reference: ductape_docs({ topic: "resilience" }).
|
|
3507
|
+
|
|
3474
3508
|
ARCHITECTURE — always two separate steps:
|
|
3475
3509
|
Step 1: Register the BROKER COMPONENT (establishes the connection to the broker service).
|
|
3476
3510
|
The broker's envs[] holds connection credentials and host/project info, NOT topics.
|
|
@@ -3775,6 +3809,9 @@ Import (register an EXISTING cloud resource):
|
|
|
3775
3809
|
events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
|
|
3776
3810
|
events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
3777
3811
|
events.messages.getConsumers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
3812
|
+
events.getEvents [{ product, env, brokerTag, topic?, category?, status?, idempotent?, startDate?, endDate?, page?, limit? }]
|
|
3813
|
+
events.getEvent [{ product, eventId }]
|
|
3814
|
+
events.getEventStats [{ product, env, brokerTag }]
|
|
3778
3815
|
events.replayEvent [{ product, env, eventId, force? }]
|
|
3779
3816
|
events.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
|
|
3780
3817
|
events.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
|
|
@@ -4371,7 +4408,9 @@ DEPRECATED ALIASES
|
|
|
4371
4408
|
`.trim(),
|
|
4372
4409
|
};
|
|
4373
4410
|
const docsHandler = async (args) => {
|
|
4374
|
-
const
|
|
4411
|
+
const requested = args.topic.toLowerCase().trim();
|
|
4412
|
+
const recoveryAliases = /^(dlq|dead[- ]?letters?|failed messages?|retry|retries|poison messages?|replay|reprocess|consumer failures?)$/;
|
|
4413
|
+
const key = recoveryAliases.test(requested) ? 'events' : requested;
|
|
4375
4414
|
const doc = DOCS[key];
|
|
4376
4415
|
if (!doc) {
|
|
4377
4416
|
const available = Object.keys(DOCS).join(', ');
|
|
@@ -4379,7 +4418,16 @@ const docsHandler = async (args) => {
|
|
|
4379
4418
|
content: [{ type: 'text', text: `Unknown topic "${args.topic}". Available topics: ${available}` }],
|
|
4380
4419
|
};
|
|
4381
4420
|
}
|
|
4382
|
-
|
|
4421
|
+
const structured = key === 'events'
|
|
4422
|
+
? `\n\nSTRUCTURED EVENTS CAPABILITY INDEX\n${JSON.stringify(eventsCapabilityOverview(), null, 2)}`
|
|
4423
|
+
: key === 'resilience'
|
|
4424
|
+
? `\n\nEVENTS DELIVERY RESPONSIBILITY BOUNDARY\n${JSON.stringify({
|
|
4425
|
+
implementationWarning: EVENTS_IMPLEMENTATION_WARNING,
|
|
4426
|
+
deliverySemantics: EVENTS_DELIVERY_SEMANTICS,
|
|
4427
|
+
canonicalDocumentationTopic: 'events',
|
|
4428
|
+
}, null, 2)}`
|
|
4429
|
+
: '';
|
|
4430
|
+
return { content: [{ type: 'text', text: doc + structured }] };
|
|
4383
4431
|
};
|
|
4384
4432
|
const cliInputSchema = z.object({
|
|
4385
4433
|
command: z.string().describe('The ductape CLI command to run, without the leading "ductape" word. ' +
|
|
@@ -4528,6 +4576,74 @@ async function main() {
|
|
|
4528
4576
|
...(result.success ? {} : { isError: true }),
|
|
4529
4577
|
};
|
|
4530
4578
|
};
|
|
4579
|
+
const eventsDiscoveryHandler = async (args) => {
|
|
4580
|
+
try {
|
|
4581
|
+
if (args.product || args.component) {
|
|
4582
|
+
if (!args.product || !args.component) {
|
|
4583
|
+
throw new Error('Both product and component are required for live Events inspection.');
|
|
4584
|
+
}
|
|
4585
|
+
const result = await cliHandler({
|
|
4586
|
+
command: `products components get --product-tag ${shellArgument(args.product)} --type events --json`,
|
|
4587
|
+
});
|
|
4588
|
+
if (result.isError)
|
|
4589
|
+
return result;
|
|
4590
|
+
const raw = result.content?.[0]?.text ?? '';
|
|
4591
|
+
let parsed;
|
|
4592
|
+
try {
|
|
4593
|
+
parsed = JSON.parse(raw);
|
|
4594
|
+
}
|
|
4595
|
+
catch {
|
|
4596
|
+
throw new Error(`Events inspection did not return JSON. ${raw}`);
|
|
4597
|
+
}
|
|
4598
|
+
const candidates = Array.isArray(parsed)
|
|
4599
|
+
? parsed
|
|
4600
|
+
: Array.isArray(parsed?.events)
|
|
4601
|
+
? parsed.events
|
|
4602
|
+
: Array.isArray(parsed?.messageBrokers)
|
|
4603
|
+
? parsed.messageBrokers
|
|
4604
|
+
: [parsed];
|
|
4605
|
+
const component = candidates.find((item) => item?.tag === args.component || item?._id === args.component);
|
|
4606
|
+
if (!component) {
|
|
4607
|
+
throw new Error(`Events component "${args.component}" was not found in product "${args.product}". ` +
|
|
4608
|
+
'Use the exact component tag or ID returned by products components get --type events.');
|
|
4609
|
+
}
|
|
4610
|
+
const inspection = inspectEventsComponent(component);
|
|
4611
|
+
const key = process.env.DUCTAPE_PUBLISHABLE_KEY;
|
|
4612
|
+
if (key) {
|
|
4613
|
+
inspection.messageStatistics = {};
|
|
4614
|
+
for (const environment of inspection.environments) {
|
|
4615
|
+
if (!environment.slug)
|
|
4616
|
+
continue;
|
|
4617
|
+
try {
|
|
4618
|
+
inspection.messageStatistics[environment.slug] = await executeViaProxy(key, 'events', 'messages.getStats', [{ product: args.product, env: environment.slug, brokerTag: component.tag }]);
|
|
4619
|
+
}
|
|
4620
|
+
catch (error) {
|
|
4621
|
+
inspection.messageStatistics[environment.slug] = {
|
|
4622
|
+
status: 'unavailable',
|
|
4623
|
+
error: error instanceof Error ? error.message : String(error),
|
|
4624
|
+
remediation: 'Verify publishable-key permission, environment configuration, provider readiness, and the exact broker tag.',
|
|
4625
|
+
};
|
|
4626
|
+
}
|
|
4627
|
+
}
|
|
4628
|
+
}
|
|
4629
|
+
return {
|
|
4630
|
+
content: [{ type: 'text', text: JSON.stringify(inspection, null, 2) }],
|
|
4631
|
+
};
|
|
4632
|
+
}
|
|
4633
|
+
const data = args.query ? searchEventsCapabilities(args.query) : eventsCapabilityOverview();
|
|
4634
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
4635
|
+
}
|
|
4636
|
+
catch (err) {
|
|
4637
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
4638
|
+
return {
|
|
4639
|
+
content: [{
|
|
4640
|
+
type: 'text',
|
|
4641
|
+
text: `Error: ${message}\nRemediation: verify product/component identifiers, CLI authentication, workspace selection, and environment/provider configuration.`,
|
|
4642
|
+
}],
|
|
4643
|
+
isError: true,
|
|
4644
|
+
};
|
|
4645
|
+
}
|
|
4646
|
+
};
|
|
4531
4647
|
const migrationHandler = async (args) => {
|
|
4532
4648
|
if (args.mode === 'new-codebase' && !args.destination) {
|
|
4533
4649
|
return {
|
|
@@ -4687,7 +4803,13 @@ async function main() {
|
|
|
4687
4803
|
if (args.method && !args.module) {
|
|
4688
4804
|
throw new Error('module is required when method is provided');
|
|
4689
4805
|
}
|
|
4690
|
-
const
|
|
4806
|
+
const requestedMethod = args.method;
|
|
4807
|
+
const schemaMethod = requestedMethod === 'events.create'
|
|
4808
|
+
? 'messageBrokers.create'
|
|
4809
|
+
: requestedMethod === 'events.update'
|
|
4810
|
+
? 'messageBrokers.update'
|
|
4811
|
+
: requestedMethod;
|
|
4812
|
+
const data = await getAssetSchemas(args.module, schemaMethod);
|
|
4691
4813
|
const compactData = args.module &&
|
|
4692
4814
|
!args.method &&
|
|
4693
4815
|
data &&
|
|
@@ -4700,7 +4822,36 @@ async function main() {
|
|
|
4700
4822
|
hint: `Call ductape_schema with module="${args.module}" and one method for its complete field schema`,
|
|
4701
4823
|
}
|
|
4702
4824
|
: data;
|
|
4703
|
-
|
|
4825
|
+
const isEventsSchema = args.module === 'product' &&
|
|
4826
|
+
Boolean(requestedMethod && /^(events|messageBrokers)\.(create|update)$/.test(requestedMethod));
|
|
4827
|
+
const enrichedData = isEventsSchema
|
|
4828
|
+
? {
|
|
4829
|
+
...compactData,
|
|
4830
|
+
method: requestedMethod,
|
|
4831
|
+
eventsCapability: {
|
|
4832
|
+
implementationWarning: EVENTS_IMPLEMENTATION_WARNING,
|
|
4833
|
+
deliverySemantics: EVENTS_DELIVERY_SEMANTICS,
|
|
4834
|
+
operations: eventsCapabilityOverview().operations,
|
|
4835
|
+
configurationValidation: {
|
|
4836
|
+
retryOrDlqUnspecified: 'Warn that provider defaults apply and inspect the deployed provider asset before claiming DLQ availability.',
|
|
4837
|
+
unsupportedFields: 'The live Joi schema rejects provider settings not supported by the selected env type; use the reported field path and provider matrix for remediation.',
|
|
4838
|
+
costSensitive: 'Azure Service Bus tier must be confirmed; provider operations may incur usage charges.',
|
|
4839
|
+
},
|
|
4840
|
+
documentationTopics: ['events', 'resilience'],
|
|
4841
|
+
},
|
|
4842
|
+
}
|
|
4843
|
+
: args.module === 'product' && !requestedMethod
|
|
4844
|
+
? {
|
|
4845
|
+
...compactData,
|
|
4846
|
+
eventsOwnership: {
|
|
4847
|
+
implementationWarning: EVENTS_IMPLEMENTATION_WARNING,
|
|
4848
|
+
deliverySemantics: EVENTS_DELIVERY_SEMANTICS,
|
|
4849
|
+
schemaMethods: ['events.create', 'events.update', 'messageBrokers.create', 'messageBrokers.update'],
|
|
4850
|
+
discoveryTool: 'ductape_events_discover',
|
|
4851
|
+
},
|
|
4852
|
+
}
|
|
4853
|
+
: compactData;
|
|
4854
|
+
return { content: [{ type: 'text', text: JSON.stringify(enrichedData ?? null, null, 2) }] };
|
|
4704
4855
|
}
|
|
4705
4856
|
catch (err) {
|
|
4706
4857
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -4762,6 +4913,14 @@ async function main() {
|
|
|
4762
4913
|
'notifications, resilience, features, events, logs, migration, frontend, frontend-analytics, client, react, vue',
|
|
4763
4914
|
inputSchema: docsInputSchema,
|
|
4764
4915
|
}, docsHandler);
|
|
4916
|
+
server.registerTool('ductape_events_discover', {
|
|
4917
|
+
title: 'Ductape Events Capability Discovery',
|
|
4918
|
+
description: 'Discover Ductape Events delivery ownership, retries, dead-letter queues, replay/reprocessing, observability operations, ' +
|
|
4919
|
+
'live input/output contracts, and provider-aware component configuration. Search recovery synonyms with query, or pass ' +
|
|
4920
|
+
'product and component together for live inspection. Ductape owns transport retries and exhausted-delivery DLQs; ' +
|
|
4921
|
+
'applications own transactional outboxes, domain rejection handling, and idempotent consumer mutations.',
|
|
4922
|
+
inputSchema: eventsDiscoveryInputSchema,
|
|
4923
|
+
}, eventsDiscoveryHandler);
|
|
4765
4924
|
server.registerTool('ductape_migration_plan', {
|
|
4766
4925
|
title: 'Ductape AI Migration Guidance',
|
|
4767
4926
|
description: 'Inspect a TypeScript, Go, Java, or .NET repository without exposing secret values. ' +
|
|
@@ -4886,6 +5045,7 @@ async function main() {
|
|
|
4886
5045
|
server.tool('ductape_generate_snippet', snippetGenerateInputSchema.shape, snippetGenerateHandler);
|
|
4887
5046
|
server.tool('ductape_schema', schemaInputSchema.shape, schemaHandler);
|
|
4888
5047
|
server.tool('ductape_docs', docsInputSchema.shape, docsHandler);
|
|
5048
|
+
server.tool('ductape_events_discover', eventsDiscoveryInputSchema.shape, eventsDiscoveryHandler);
|
|
4889
5049
|
server.tool('ductape_migration_plan', migrationInputSchema.shape, migrationHandler);
|
|
4890
5050
|
server.tool('ductape_cli', cliInputSchema.shape, cliHandler);
|
|
4891
5051
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ductape/mcp",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.7",
|
|
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": "node scripts/check-frontend-analytics-guidance.mjs",
|
|
18
|
+
"test": "npm run build && node scripts/check-frontend-analytics-guidance.mjs && node scripts/check-events-discovery.mjs",
|
|
19
19
|
"start": "node dist/index.js",
|
|
20
20
|
"dev": "tsx src/index.ts"
|
|
21
21
|
},
|