@ductape/mcp 0.2.4 → 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 +185 -9
- package/dist/proxy-client.d.ts +1 -1
- package/dist/proxy-client.d.ts.map +1 -1
- package/dist/proxy-client.js +13 -2
- 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 } }] }]
|
|
@@ -981,7 +990,8 @@ const snippetGenerateInputSchema = payloadGenerateInputSchema.extend({
|
|
|
981
990
|
language: z.enum(['typescript', 'python']).default('typescript'),
|
|
982
991
|
});
|
|
983
992
|
const schemaInputSchema = z.object({
|
|
984
|
-
module: z.enum(['app', 'product']).optional().describe('Optional. Scope the result to
|
|
993
|
+
module: z.enum(['app', 'product']).optional().describe('Optional. Scope the result to one module. With no method, returns only the compact list of available methods.'),
|
|
994
|
+
method: z.string().optional().describe('Optional method key such as "databases.create" or "notifications.update". Use with module to return only that method schema.'),
|
|
985
995
|
});
|
|
986
996
|
function toPrettyJson(value) {
|
|
987
997
|
return JSON.stringify(value ?? {}, null, 2);
|
|
@@ -1341,6 +1351,11 @@ const docsInputSchema = z.object({
|
|
|
1341
1351
|
'graphs, storage, cloud, vector, warehouse, secrets, apps, products, sessions, caches, ' +
|
|
1342
1352
|
'notifications, resilience, features, events, logs, migration, frontend, frontend-analytics, client, react, vue'),
|
|
1343
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
|
+
});
|
|
1344
1359
|
const migrationInputSchema = z.object({
|
|
1345
1360
|
source: z.string().describe('Absolute path to the existing codebase.'),
|
|
1346
1361
|
e2e_baseline: z.string().describe('Absolute path to a passing migration-e2e baseline manifest created before migration inspection.'),
|
|
@@ -2806,6 +2821,13 @@ DUCTAPE RESILIENCE
|
|
|
2806
2821
|
Resilience covers three mechanisms: quotas (rate-limited provider pools), fallbacks (automatic
|
|
2807
2822
|
provider switching), and healthchecks (continuous probe monitoring with failure actions).
|
|
2808
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
|
+
|
|
2809
2831
|
CONFIGURATION BOUNDARY
|
|
2810
2832
|
Quotas, fallbacks, and health checks are administrative product configuration. Manage them with
|
|
2811
2833
|
the authenticated CLI:
|
|
@@ -3470,6 +3492,19 @@ When you call features.define({ handler }), the handler runs TWICE:
|
|
|
3470
3492
|
events: `
|
|
3471
3493
|
DUCTAPE EVENTS (MESSAGE BROKERS)
|
|
3472
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
|
+
|
|
3473
3508
|
ARCHITECTURE — always two separate steps:
|
|
3474
3509
|
Step 1: Register the BROKER COMPONENT (establishes the connection to the broker service).
|
|
3475
3510
|
The broker's envs[] holds connection credentials and host/project info, NOT topics.
|
|
@@ -3774,6 +3809,9 @@ Import (register an EXISTING cloud resource):
|
|
|
3774
3809
|
events.messages.getDeadLetters [{ product, env, brokerTag, topicTag?, consumerTag?, limit? }]
|
|
3775
3810
|
events.messages.getProducers [{ product, env, brokerTag, topicTag?, page?, limit? }]
|
|
3776
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 }]
|
|
3777
3815
|
events.replayEvent [{ product, env, eventId, force? }]
|
|
3778
3816
|
events.reprocessDLQ [{ product, env, brokerTag, topicTag?, messageIds?, limit? }]
|
|
3779
3817
|
events.checkIdempotency [{ product, env, brokerTag, idempotency_key }]
|
|
@@ -4370,7 +4408,9 @@ DEPRECATED ALIASES
|
|
|
4370
4408
|
`.trim(),
|
|
4371
4409
|
};
|
|
4372
4410
|
const docsHandler = async (args) => {
|
|
4373
|
-
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;
|
|
4374
4414
|
const doc = DOCS[key];
|
|
4375
4415
|
if (!doc) {
|
|
4376
4416
|
const available = Object.keys(DOCS).join(', ');
|
|
@@ -4378,7 +4418,16 @@ const docsHandler = async (args) => {
|
|
|
4378
4418
|
content: [{ type: 'text', text: `Unknown topic "${args.topic}". Available topics: ${available}` }],
|
|
4379
4419
|
};
|
|
4380
4420
|
}
|
|
4381
|
-
|
|
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 }] };
|
|
4382
4431
|
};
|
|
4383
4432
|
const cliInputSchema = z.object({
|
|
4384
4433
|
command: z.string().describe('The ductape CLI command to run, without the leading "ductape" word. ' +
|
|
@@ -4527,6 +4576,74 @@ async function main() {
|
|
|
4527
4576
|
...(result.success ? {} : { isError: true }),
|
|
4528
4577
|
};
|
|
4529
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
|
+
};
|
|
4530
4647
|
const migrationHandler = async (args) => {
|
|
4531
4648
|
if (args.mode === 'new-codebase' && !args.destination) {
|
|
4532
4649
|
return {
|
|
@@ -4683,8 +4800,58 @@ async function main() {
|
|
|
4683
4800
|
};
|
|
4684
4801
|
const schemaHandler = async (args) => {
|
|
4685
4802
|
try {
|
|
4686
|
-
|
|
4687
|
-
|
|
4803
|
+
if (args.method && !args.module) {
|
|
4804
|
+
throw new Error('module is required when method is provided');
|
|
4805
|
+
}
|
|
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);
|
|
4813
|
+
const compactData = args.module &&
|
|
4814
|
+
!args.method &&
|
|
4815
|
+
data &&
|
|
4816
|
+
typeof data === 'object' &&
|
|
4817
|
+
!Array.isArray(data.methods) &&
|
|
4818
|
+
data.methods
|
|
4819
|
+
? {
|
|
4820
|
+
module: args.module,
|
|
4821
|
+
methods: Object.keys(data.methods),
|
|
4822
|
+
hint: `Call ductape_schema with module="${args.module}" and one method for its complete field schema`,
|
|
4823
|
+
}
|
|
4824
|
+
: data;
|
|
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) }] };
|
|
4688
4855
|
}
|
|
4689
4856
|
catch (err) {
|
|
4690
4857
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -4722,10 +4889,10 @@ async function main() {
|
|
|
4722
4889
|
}, snippetGenerateHandler);
|
|
4723
4890
|
server.registerTool('ductape_schema', {
|
|
4724
4891
|
title: 'Ductape Asset Schema',
|
|
4725
|
-
description: 'Returns
|
|
4726
|
-
'
|
|
4727
|
-
'
|
|
4728
|
-
'
|
|
4892
|
+
description: 'Returns a compact method index or one targeted asset schema derived from the SDK Joi validators. ' +
|
|
4893
|
+
'Call with module="app" or module="product" first to list method keys, then call again with ' +
|
|
4894
|
+
'module and method (for example method="databases.create") for the complete field schema. ' +
|
|
4895
|
+
'Avoid calling without module unless you explicitly need the entire manifest.\n\n' +
|
|
4729
4896
|
'ALWAYS call this before constructing a file for "resources <type> create" or any cloud ' +
|
|
4730
4897
|
'import/provision operation — field shapes are not guessable from context.\n\n' +
|
|
4731
4898
|
'Conditional fields: some fields are returned as oneOf (an array of variant shapes). ' +
|
|
@@ -4746,6 +4913,14 @@ async function main() {
|
|
|
4746
4913
|
'notifications, resilience, features, events, logs, migration, frontend, frontend-analytics, client, react, vue',
|
|
4747
4914
|
inputSchema: docsInputSchema,
|
|
4748
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);
|
|
4749
4924
|
server.registerTool('ductape_migration_plan', {
|
|
4750
4925
|
title: 'Ductape AI Migration Guidance',
|
|
4751
4926
|
description: 'Inspect a TypeScript, Go, Java, or .NET repository without exposing secret values. ' +
|
|
@@ -4870,6 +5045,7 @@ async function main() {
|
|
|
4870
5045
|
server.tool('ductape_generate_snippet', snippetGenerateInputSchema.shape, snippetGenerateHandler);
|
|
4871
5046
|
server.tool('ductape_schema', schemaInputSchema.shape, schemaHandler);
|
|
4872
5047
|
server.tool('ductape_docs', docsInputSchema.shape, docsHandler);
|
|
5048
|
+
server.tool('ductape_events_discover', eventsDiscoveryInputSchema.shape, eventsDiscoveryHandler);
|
|
4873
5049
|
server.tool('ductape_migration_plan', migrationInputSchema.shape, migrationHandler);
|
|
4874
5050
|
server.tool('ductape_cli', cliInputSchema.shape, cliHandler);
|
|
4875
5051
|
}
|
package/dist/proxy-client.d.ts
CHANGED
|
@@ -24,6 +24,6 @@ export interface IGenerateExecutablePayloadResponse {
|
|
|
24
24
|
payload: Record<string, unknown>;
|
|
25
25
|
meta: Record<string, unknown>;
|
|
26
26
|
}
|
|
27
|
-
export declare function getAssetSchemas(module?: string): Promise<unknown>;
|
|
27
|
+
export declare function getAssetSchemas(module?: string, method?: string): Promise<unknown>;
|
|
28
28
|
export declare function generateExecutablePayload<T = IGenerateExecutablePayloadResponse>(request: IGenerateExecutablePayloadRequest): Promise<T>;
|
|
29
29
|
//# sourceMappingURL=proxy-client.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"proxy-client.d.ts","sourceRoot":"","sources":["../src/proxy-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,YAAY,4BAA4B,CAAC;AAEtD,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,KAAK,GACL,WAAW,GACX,OAAO,GACP,UAAU,GACV,eAAe,GACf,gBAAgB,GAChB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,SAAS,GACT,UAAU,GACV,MAAM,GACN,MAAM,GACN,YAAY,GACZ,QAAQ,GACR,UAAU,GACV,SAAS,CAAC;AAUd;;GAEG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,OAAO,EAC/C,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,OAAO,EAAO,GACrB,OAAO,CAAC,CAAC,CAAC,CAuBZ;AAED,MAAM,WAAW,iCAAiC;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACpD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AASD,wBAAsB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"proxy-client.d.ts","sourceRoot":"","sources":["../src/proxy-client.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,eAAO,MAAM,YAAY,4BAA4B,CAAC;AAEtD,MAAM,MAAM,SAAS,GACjB,SAAS,GACT,KAAK,GACL,WAAW,GACX,OAAO,GACP,UAAU,GACV,eAAe,GACf,gBAAgB,GAChB,QAAQ,GACR,SAAS,GACT,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,SAAS,GACT,UAAU,GACV,MAAM,GACN,MAAM,GACN,YAAY,GACZ,QAAQ,GACR,UAAU,GACV,SAAS,CAAC;AAUd;;GAEG;AACH,wBAAsB,eAAe,CAAC,CAAC,GAAG,OAAO,EAC/C,eAAe,EAAE,MAAM,EACvB,MAAM,EAAE,SAAS,EACjB,MAAM,EAAE,MAAM,EACd,MAAM,GAAE,OAAO,EAAO,GACrB,OAAO,CAAC,CAAC,CAAC,CAuBZ;AAED,MAAM,WAAW,iCAAiC;IAChD,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAClC,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,iBAAiB,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAC;IACpD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,WAAW,CAAC,EAAE,QAAQ,GAAG,aAAa,CAAC;IACvC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACtC;AAED,MAAM,WAAW,kCAAkC;IACjD,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CAC/B;AASD,wBAAsB,eAAe,CAAC,MAAM,CAAC,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAuBxF;AA4BD,wBAAsB,yBAAyB,CAAC,CAAC,GAAG,kCAAkC,EACpF,OAAO,EAAE,iCAAiC,GACzC,OAAO,CAAC,CAAC,CAAC,CAiCZ"}
|
package/dist/proxy-client.js
CHANGED
|
@@ -28,11 +28,22 @@ export async function executeViaProxy(publishable_key, module, method, params =
|
|
|
28
28
|
}
|
|
29
29
|
return body.data?.data;
|
|
30
30
|
}
|
|
31
|
-
export async function getAssetSchemas(module) {
|
|
32
|
-
const path = module
|
|
31
|
+
export async function getAssetSchemas(module, method) {
|
|
32
|
+
const path = module
|
|
33
|
+
? `/proxy/v1/schema/${encodeURIComponent(module)}${method ? `/${encodeURIComponent(method)}` : ''}`
|
|
34
|
+
: '/proxy/v1/schema';
|
|
33
35
|
const url = `${API_BASE_URL.replace(/\/$/, '')}${path}`;
|
|
34
36
|
const res = await fetch(url);
|
|
35
37
|
const body = (await res.json());
|
|
38
|
+
if (res.status === 404 && module && method) {
|
|
39
|
+
const fallbackUrl = `${API_BASE_URL.replace(/\/$/, '')}/proxy/v1/schema/${encodeURIComponent(module)}`;
|
|
40
|
+
const fallbackRes = await fetch(fallbackUrl);
|
|
41
|
+
const fallbackBody = (await fallbackRes.json());
|
|
42
|
+
const methodSchema = fallbackBody.data?.methods?.[method];
|
|
43
|
+
if (fallbackRes.ok && methodSchema) {
|
|
44
|
+
return { module, method, schema: methodSchema };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
36
47
|
if (!res.ok) {
|
|
37
48
|
throw new Error(body.message ?? `Schema request failed: ${res.status}`);
|
|
38
49
|
}
|
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
|
},
|