@eventcatalog/core 4.2.6 → 4.3.0
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/analytics/analytics.cjs +1 -1
- package/dist/analytics/analytics.js +2 -2
- package/dist/analytics/log-build.cjs +1 -1
- package/dist/analytics/log-build.js +3 -3
- package/dist/{chunk-OBA7TFHM.js → chunk-32ZHIOUO.js} +3 -3
- package/dist/{chunk-XTSY2LTR.js → chunk-GZOFTODZ.js} +1 -1
- package/dist/{chunk-DEGSBGEF.js → chunk-RQCRNCF6.js} +1 -1
- package/dist/{chunk-OZNVNRI7.js → chunk-TE4QQSPT.js} +1 -1
- package/dist/{chunk-NZUMNTJK.js → chunk-ZLUUUTWP.js} +1 -1
- package/dist/constants.cjs +1 -1
- package/dist/constants.js +1 -1
- package/dist/docs/development/ask-your-architecture/03-mcp-server/getting-started.md +12 -1
- package/dist/eventcatalog.cjs +1 -1
- package/dist/eventcatalog.config.d.cts +5 -0
- package/dist/eventcatalog.config.d.ts +5 -0
- package/dist/eventcatalog.js +9 -9
- package/dist/generate.cjs +1 -1
- package/dist/generate.js +3 -3
- package/dist/utils/cli-logger.cjs +1 -1
- package/dist/utils/cli-logger.js +2 -2
- package/eventcatalog/src/components/CopyAsMarkdown.tsx +73 -5
- package/eventcatalog/src/components/McpConnectDialog.tsx +98 -0
- package/eventcatalog/src/components/McpIcon.tsx +14 -0
- package/eventcatalog/src/enterprise/feature.ts +1 -1
- package/eventcatalog/src/enterprise/mcp/mcp-scope.ts +159 -0
- package/eventcatalog/src/enterprise/mcp/mcp-scoped-tools.ts +197 -0
- package/eventcatalog/src/enterprise/mcp/mcp-server.ts +287 -176
- package/eventcatalog/src/pages/docs/[type]/[id]/[version]/index.astro +13 -0
- package/eventcatalog/src/types/mcp-modules.d.ts +1 -0
- package/package.json +1 -1
|
@@ -10,33 +10,11 @@ import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/
|
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
import { join } from 'node:path';
|
|
12
12
|
import { isEventCatalogScaleEnabled } from '@utils/feature';
|
|
13
|
-
import
|
|
14
|
-
getResources,
|
|
15
|
-
getResource,
|
|
16
|
-
getMessagesProducedOrConsumedByResource,
|
|
17
|
-
getSchemaForResource,
|
|
18
|
-
findResourcesByOwner,
|
|
19
|
-
getProducersOfMessage,
|
|
20
|
-
getConsumersOfMessage,
|
|
21
|
-
analyzeChangeImpact,
|
|
22
|
-
explainBusinessFlow,
|
|
23
|
-
getTeams,
|
|
24
|
-
getTeam,
|
|
25
|
-
getUsers,
|
|
26
|
-
getUser,
|
|
27
|
-
findMessageBySchemaId,
|
|
28
|
-
explainUbiquitousLanguageTerms,
|
|
29
|
-
getCustomDocs,
|
|
30
|
-
searchCustomDocs,
|
|
31
|
-
getCustomDoc,
|
|
32
|
-
collectionSchema,
|
|
33
|
-
resourceCollectionSchema,
|
|
34
|
-
messageCollectionSchema,
|
|
35
|
-
toolDescriptions,
|
|
36
|
-
getC4Diagram,
|
|
37
|
-
} from '@enterprise/tools/catalog-tools';
|
|
13
|
+
import * as catalogTools from '@enterprise/tools/catalog-tools';
|
|
38
14
|
import { getCollection } from 'astro:content';
|
|
39
15
|
import { createMcpAuthErrorResponse, validateMcpRequest } from './mcp-auth';
|
|
16
|
+
import { createScopedCatalogTools } from './mcp-scoped-tools';
|
|
17
|
+
import { McpScopeNotFoundError, resolveMcpScope, type McpScope, type McpScopeKind } from './mcp-scope';
|
|
40
18
|
|
|
41
19
|
const catalogDirectory = process.env.PROJECT_DIR || process.cwd();
|
|
42
20
|
|
|
@@ -81,196 +59,202 @@ try {
|
|
|
81
59
|
}
|
|
82
60
|
|
|
83
61
|
// Create MCP Server with tools that access Astro collections
|
|
84
|
-
function createMcpServer() {
|
|
62
|
+
function createMcpServer(scope?: McpScope) {
|
|
85
63
|
const server = new McpServer({
|
|
86
|
-
name: 'EventCatalog MCP Server',
|
|
64
|
+
name: scope ? `EventCatalog MCP Server — ${scope.name} ${scope.ref.kind}` : 'EventCatalog MCP Server',
|
|
87
65
|
version: '1.0.0',
|
|
88
66
|
});
|
|
89
67
|
|
|
68
|
+
const tools = scope ? createScopedCatalogTools(scope) : catalogTools;
|
|
69
|
+
|
|
90
70
|
// Register all built-in tools using the helper
|
|
91
71
|
server.registerTool(
|
|
92
72
|
'getResources',
|
|
93
73
|
{
|
|
94
|
-
description: toolDescriptions.getResources,
|
|
74
|
+
description: catalogTools.toolDescriptions.getResources,
|
|
95
75
|
inputSchema: z.object({
|
|
96
|
-
collection: collectionSchema.describe('The collection to get the resources from'),
|
|
76
|
+
collection: catalogTools.collectionSchema.describe('The collection to get the resources from'),
|
|
97
77
|
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
98
78
|
search: z.string().optional().describe('Search term to filter resources by name, id, or summary (case-insensitive)'),
|
|
99
79
|
}),
|
|
100
80
|
},
|
|
101
|
-
createToolHandler(getResources, 'Failed to get resources')
|
|
81
|
+
createToolHandler(tools.getResources, 'Failed to get resources')
|
|
102
82
|
);
|
|
103
83
|
|
|
104
84
|
server.registerTool(
|
|
105
85
|
'getResource',
|
|
106
86
|
{
|
|
107
|
-
description: toolDescriptions.getResource,
|
|
87
|
+
description: catalogTools.toolDescriptions.getResource,
|
|
108
88
|
inputSchema: z.object({
|
|
109
|
-
collection: collectionSchema.describe('The collection to get the resource from'),
|
|
89
|
+
collection: catalogTools.collectionSchema.describe('The collection to get the resource from'),
|
|
110
90
|
id: z.string().describe('The id of the resource to get'),
|
|
111
91
|
version: z.string().describe('The version of the resource to get'),
|
|
112
92
|
}),
|
|
113
93
|
},
|
|
114
|
-
createToolHandler(getResource, 'Failed to get resource')
|
|
94
|
+
createToolHandler(tools.getResource, 'Failed to get resource')
|
|
115
95
|
);
|
|
116
96
|
|
|
117
97
|
server.registerTool(
|
|
118
98
|
'getMessagesProducedOrConsumedByResource',
|
|
119
99
|
{
|
|
120
|
-
description: toolDescriptions.getMessagesProducedOrConsumedByResource,
|
|
100
|
+
description: catalogTools.toolDescriptions.getMessagesProducedOrConsumedByResource,
|
|
121
101
|
inputSchema: z.object({
|
|
122
102
|
resourceId: z.string().describe('The id of the resource to get the messages produced or consumed for'),
|
|
123
103
|
resourceVersion: z.string().describe('The version of the resource to get the messages produced or consumed for'),
|
|
124
|
-
resourceCollection: resourceCollectionSchema
|
|
104
|
+
resourceCollection: catalogTools.resourceCollectionSchema
|
|
125
105
|
.describe('The collection of the resource to get the messages produced or consumed for')
|
|
126
106
|
.default('services'),
|
|
127
107
|
}),
|
|
128
108
|
},
|
|
129
|
-
createToolHandler(getMessagesProducedOrConsumedByResource, 'Failed to get messages')
|
|
109
|
+
createToolHandler(tools.getMessagesProducedOrConsumedByResource, 'Failed to get messages')
|
|
130
110
|
);
|
|
131
111
|
|
|
132
112
|
server.registerTool(
|
|
133
113
|
'getSchemaForResource',
|
|
134
114
|
{
|
|
135
|
-
description: toolDescriptions.getSchemaForResource,
|
|
115
|
+
description: catalogTools.toolDescriptions.getSchemaForResource,
|
|
136
116
|
inputSchema: z.object({
|
|
137
117
|
resourceId: z.string().describe('The id of the resource to get the schema for'),
|
|
138
118
|
resourceVersion: z.string().describe('The version of the resource to get the schema for'),
|
|
139
|
-
resourceCollection: resourceCollectionSchema
|
|
119
|
+
resourceCollection: catalogTools.resourceCollectionSchema
|
|
140
120
|
.describe('The collection of the resource to get the schema for')
|
|
141
121
|
.default('services'),
|
|
142
122
|
}),
|
|
143
123
|
},
|
|
144
|
-
createToolHandler(getSchemaForResource, 'Failed to get schema')
|
|
124
|
+
createToolHandler(tools.getSchemaForResource, 'Failed to get schema')
|
|
145
125
|
);
|
|
146
126
|
|
|
147
127
|
server.registerTool(
|
|
148
128
|
'findResourcesByOwner',
|
|
149
129
|
{
|
|
150
|
-
description: toolDescriptions.findResourcesByOwner,
|
|
130
|
+
description: catalogTools.toolDescriptions.findResourcesByOwner,
|
|
151
131
|
inputSchema: z.object({
|
|
152
132
|
ownerId: z.string().describe('The id of the owner (team or user) to find resources for'),
|
|
153
133
|
}),
|
|
154
134
|
},
|
|
155
|
-
createToolHandler(findResourcesByOwner, 'Failed to find resources')
|
|
135
|
+
createToolHandler(tools.findResourcesByOwner, 'Failed to find resources')
|
|
156
136
|
);
|
|
157
137
|
|
|
158
138
|
server.registerTool(
|
|
159
139
|
'getProducersOfMessage',
|
|
160
140
|
{
|
|
161
|
-
description: toolDescriptions.getProducersOfMessage,
|
|
141
|
+
description: catalogTools.toolDescriptions.getProducersOfMessage,
|
|
162
142
|
inputSchema: z.object({
|
|
163
143
|
messageId: z.string().describe('The id of the message to find producers for'),
|
|
164
144
|
messageVersion: z.string().describe('The version of the message'),
|
|
165
|
-
messageCollection: messageCollectionSchema
|
|
145
|
+
messageCollection: catalogTools.messageCollectionSchema
|
|
166
146
|
.describe('The collection type of the message (events, commands, or queries)')
|
|
167
147
|
.default('events'),
|
|
168
148
|
}),
|
|
169
149
|
},
|
|
170
|
-
createToolHandler(getProducersOfMessage, 'Failed to get producers')
|
|
150
|
+
createToolHandler(tools.getProducersOfMessage, 'Failed to get producers')
|
|
171
151
|
);
|
|
172
152
|
|
|
173
153
|
server.registerTool(
|
|
174
154
|
'getConsumersOfMessage',
|
|
175
155
|
{
|
|
176
|
-
description: toolDescriptions.getConsumersOfMessage,
|
|
156
|
+
description: catalogTools.toolDescriptions.getConsumersOfMessage,
|
|
177
157
|
inputSchema: z.object({
|
|
178
158
|
messageId: z.string().describe('The id of the message to find consumers for'),
|
|
179
159
|
messageVersion: z.string().describe('The version of the message'),
|
|
180
|
-
messageCollection: messageCollectionSchema
|
|
160
|
+
messageCollection: catalogTools.messageCollectionSchema
|
|
181
161
|
.describe('The collection type of the message (events, commands, or queries)')
|
|
182
162
|
.default('events'),
|
|
183
163
|
}),
|
|
184
164
|
},
|
|
185
|
-
createToolHandler(getConsumersOfMessage, 'Failed to get consumers')
|
|
165
|
+
createToolHandler(tools.getConsumersOfMessage, 'Failed to get consumers')
|
|
186
166
|
);
|
|
187
167
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
168
|
+
if (!scope) {
|
|
169
|
+
server.registerTool(
|
|
170
|
+
'getC4Diagram',
|
|
171
|
+
{
|
|
172
|
+
description: catalogTools.toolDescriptions.getC4Diagram,
|
|
173
|
+
inputSchema: z.object({
|
|
174
|
+
viewId: z.string().describe('The id of the LikeC4 view to return source files for').optional(),
|
|
175
|
+
}),
|
|
176
|
+
},
|
|
177
|
+
createToolHandler(catalogTools.getC4Diagram, 'Failed to get c4 diagram')
|
|
178
|
+
);
|
|
179
|
+
}
|
|
198
180
|
|
|
199
181
|
server.registerTool(
|
|
200
182
|
'analyzeChangeImpact',
|
|
201
183
|
{
|
|
202
|
-
description: toolDescriptions.analyzeChangeImpact,
|
|
184
|
+
description: catalogTools.toolDescriptions.analyzeChangeImpact,
|
|
203
185
|
inputSchema: z.object({
|
|
204
186
|
messageId: z.string().describe('The id of the message to analyze impact for'),
|
|
205
187
|
messageVersion: z.string().describe('The version of the message'),
|
|
206
|
-
messageCollection: messageCollectionSchema
|
|
188
|
+
messageCollection: catalogTools.messageCollectionSchema
|
|
207
189
|
.describe('The collection type of the message (events, commands, or queries)')
|
|
208
190
|
.default('events'),
|
|
209
191
|
}),
|
|
210
192
|
},
|
|
211
|
-
createToolHandler(analyzeChangeImpact, 'Failed to analyze impact')
|
|
193
|
+
createToolHandler(tools.analyzeChangeImpact, 'Failed to analyze impact')
|
|
212
194
|
);
|
|
213
195
|
|
|
214
196
|
server.registerTool(
|
|
215
197
|
'explainBusinessFlow',
|
|
216
198
|
{
|
|
217
|
-
description: toolDescriptions.explainBusinessFlow,
|
|
199
|
+
description: catalogTools.toolDescriptions.explainBusinessFlow,
|
|
218
200
|
inputSchema: z.object({
|
|
219
201
|
flowId: z.string().describe('The id of the flow to explain'),
|
|
220
202
|
flowVersion: z.string().describe('The version of the flow'),
|
|
221
203
|
}),
|
|
222
204
|
},
|
|
223
|
-
createToolHandler(explainBusinessFlow, 'Failed to explain flow')
|
|
205
|
+
createToolHandler(tools.explainBusinessFlow, 'Failed to explain flow')
|
|
224
206
|
);
|
|
225
207
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
208
|
+
if (!scope) {
|
|
209
|
+
server.registerTool(
|
|
210
|
+
'getTeams',
|
|
211
|
+
{
|
|
212
|
+
description: catalogTools.toolDescriptions.getTeams,
|
|
213
|
+
inputSchema: z.object({
|
|
214
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
215
|
+
}),
|
|
216
|
+
},
|
|
217
|
+
createToolHandler(catalogTools.getTeams, 'Failed to get teams')
|
|
218
|
+
);
|
|
236
219
|
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
220
|
+
server.registerTool(
|
|
221
|
+
'getTeam',
|
|
222
|
+
{
|
|
223
|
+
description: catalogTools.toolDescriptions.getTeam,
|
|
224
|
+
inputSchema: z.object({
|
|
225
|
+
id: z.string().describe('The id of the team to get'),
|
|
226
|
+
}),
|
|
227
|
+
},
|
|
228
|
+
createToolHandler(catalogTools.getTeam, 'Failed to get team')
|
|
229
|
+
);
|
|
247
230
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
231
|
+
server.registerTool(
|
|
232
|
+
'getUsers',
|
|
233
|
+
{
|
|
234
|
+
description: catalogTools.toolDescriptions.getUsers,
|
|
235
|
+
inputSchema: z.object({
|
|
236
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
237
|
+
}),
|
|
238
|
+
},
|
|
239
|
+
createToolHandler(catalogTools.getUsers, 'Failed to get users')
|
|
240
|
+
);
|
|
258
241
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
242
|
+
server.registerTool(
|
|
243
|
+
'getUser',
|
|
244
|
+
{
|
|
245
|
+
description: catalogTools.toolDescriptions.getUser,
|
|
246
|
+
inputSchema: z.object({
|
|
247
|
+
id: z.string().describe('The id of the user to get'),
|
|
248
|
+
}),
|
|
249
|
+
},
|
|
250
|
+
createToolHandler(catalogTools.getUser, 'Failed to get user')
|
|
251
|
+
);
|
|
252
|
+
}
|
|
269
253
|
|
|
270
254
|
server.registerTool(
|
|
271
255
|
'findMessageBySchemaId',
|
|
272
256
|
{
|
|
273
|
-
description: toolDescriptions.findMessageBySchemaId,
|
|
257
|
+
description: catalogTools.toolDescriptions.findMessageBySchemaId,
|
|
274
258
|
inputSchema: z.object({
|
|
275
259
|
messageId: z.string().describe('The message id (from x-eventcatalog-id in the schema)'),
|
|
276
260
|
messageVersion: z
|
|
@@ -279,64 +263,68 @@ function createMcpServer() {
|
|
|
279
263
|
.describe(
|
|
280
264
|
'The message version (from x-eventcatalog-version in the schema). If not provided, returns the latest version.'
|
|
281
265
|
),
|
|
282
|
-
collection: messageCollectionSchema
|
|
266
|
+
collection: catalogTools.messageCollectionSchema
|
|
283
267
|
.optional()
|
|
284
268
|
.describe('Optional hint for which collection to search (events, commands, or queries)'),
|
|
285
269
|
}),
|
|
286
270
|
},
|
|
287
|
-
createToolHandler(findMessageBySchemaId, 'Failed to find message')
|
|
271
|
+
createToolHandler(tools.findMessageBySchemaId, 'Failed to find message')
|
|
288
272
|
);
|
|
289
273
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
274
|
+
if (!scope || scope.ref.kind === 'domain') {
|
|
275
|
+
server.registerTool(
|
|
276
|
+
'explainUbiquitousLanguageTerms',
|
|
277
|
+
{
|
|
278
|
+
description: catalogTools.toolDescriptions.explainUbiquitousLanguageTerms,
|
|
279
|
+
inputSchema: z.object({
|
|
280
|
+
domainId: z.string().describe('The id of the domain to get ubiquitous language terms for'),
|
|
281
|
+
domainVersion: z.string().optional().describe('The version of the domain. If not provided, uses the latest version.'),
|
|
282
|
+
}),
|
|
283
|
+
},
|
|
284
|
+
createToolHandler(tools.explainUbiquitousLanguageTerms, 'Failed to get ubiquitous language terms')
|
|
285
|
+
);
|
|
286
|
+
}
|
|
301
287
|
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
288
|
+
if (!scope) {
|
|
289
|
+
server.registerTool(
|
|
290
|
+
'getCustomDocs',
|
|
291
|
+
{
|
|
292
|
+
description: catalogTools.toolDescriptions.getCustomDocs,
|
|
293
|
+
inputSchema: z.object({
|
|
294
|
+
cursor: z.string().optional().describe('Pagination cursor from previous response'),
|
|
295
|
+
search: z.string().optional().describe('Search term to filter docs by title, id, or summary (case-insensitive)'),
|
|
296
|
+
}),
|
|
297
|
+
},
|
|
298
|
+
createToolHandler(catalogTools.getCustomDocs, 'Failed to get custom documentation pages')
|
|
299
|
+
);
|
|
313
300
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
301
|
+
server.registerTool(
|
|
302
|
+
'searchCustomDocs',
|
|
303
|
+
{
|
|
304
|
+
description: catalogTools.toolDescriptions.searchCustomDocs,
|
|
305
|
+
inputSchema: z.object({
|
|
306
|
+
query: z.string().describe('Full-text search query, e.g. keywords describing the topic to find'),
|
|
307
|
+
limit: z.number().optional().describe('Maximum number of results to return (default 10)'),
|
|
308
|
+
}),
|
|
309
|
+
},
|
|
310
|
+
createToolHandler(catalogTools.searchCustomDocs, 'Failed to search custom documentation')
|
|
311
|
+
);
|
|
325
312
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
313
|
+
server.registerTool(
|
|
314
|
+
'getCustomDoc',
|
|
315
|
+
{
|
|
316
|
+
description: catalogTools.toolDescriptions.getCustomDoc,
|
|
317
|
+
inputSchema: z.object({
|
|
318
|
+
id: z.string().describe('The id or slug of the custom documentation page'),
|
|
319
|
+
section: z.string().optional().describe('Optional section heading to return only that section of the page'),
|
|
320
|
+
}),
|
|
321
|
+
},
|
|
322
|
+
createToolHandler(catalogTools.getCustomDoc, 'Failed to get custom documentation page')
|
|
323
|
+
);
|
|
324
|
+
}
|
|
337
325
|
|
|
338
326
|
// Register extended tools from user configuration
|
|
339
|
-
for (const [toolName, toolConfig] of Object.entries(extendedTools)) {
|
|
327
|
+
for (const [toolName, toolConfig] of Object.entries(scope ? {} : extendedTools)) {
|
|
340
328
|
if (!toolConfig || typeof toolConfig !== 'object') continue;
|
|
341
329
|
|
|
342
330
|
// Extract tool properties (Vercel AI SDK format)
|
|
@@ -386,9 +374,13 @@ function createMcpServer() {
|
|
|
386
374
|
'agents',
|
|
387
375
|
'services',
|
|
388
376
|
'domains',
|
|
377
|
+
'systems',
|
|
389
378
|
'flows',
|
|
390
379
|
'channels',
|
|
391
380
|
'entities',
|
|
381
|
+
'containers',
|
|
382
|
+
'diagrams',
|
|
383
|
+
'data-products',
|
|
392
384
|
'adrs',
|
|
393
385
|
] as const,
|
|
394
386
|
},
|
|
@@ -416,6 +408,12 @@ function createMcpServer() {
|
|
|
416
408
|
description: 'All services in EventCatalog',
|
|
417
409
|
collections: ['services'] as const,
|
|
418
410
|
},
|
|
411
|
+
{
|
|
412
|
+
name: 'All Systems in EventCatalog',
|
|
413
|
+
uri: 'eventcatalog://systems',
|
|
414
|
+
description: 'All systems in EventCatalog',
|
|
415
|
+
collections: ['systems'] as const,
|
|
416
|
+
},
|
|
419
417
|
{
|
|
420
418
|
name: 'All Agents in EventCatalog',
|
|
421
419
|
uri: 'eventcatalog://agents',
|
|
@@ -464,6 +462,12 @@ function createMcpServer() {
|
|
|
464
462
|
description: 'All flows in EventCatalog',
|
|
465
463
|
collections: ['flows'] as const,
|
|
466
464
|
},
|
|
465
|
+
{
|
|
466
|
+
name: 'All Data Products in EventCatalog',
|
|
467
|
+
uri: 'eventcatalog://data-products',
|
|
468
|
+
description: 'All data products in EventCatalog',
|
|
469
|
+
collections: ['data-products'] as const,
|
|
470
|
+
},
|
|
467
471
|
{
|
|
468
472
|
name: 'All Teams in EventCatalog',
|
|
469
473
|
uri: 'eventcatalog://teams',
|
|
@@ -479,16 +483,26 @@ function createMcpServer() {
|
|
|
479
483
|
];
|
|
480
484
|
|
|
481
485
|
for (const resource of resourceDefinitions) {
|
|
486
|
+
if (scope && resource.collections.every((collection) => collection === 'teams' || collection === 'users')) continue;
|
|
487
|
+
|
|
488
|
+
const resourceUri = scope
|
|
489
|
+
? `${scope.uriPrefix}/resources${resource.uri === 'eventcatalog://all' ? '' : `/${resource.uri.replace('eventcatalog://', '')}`}`
|
|
490
|
+
: resource.uri;
|
|
491
|
+
const resourceName = scope ? resource.name.replace('in EventCatalog', `in ${scope.name} ${scope.ref.kind}`) : resource.name;
|
|
492
|
+
const resourceDescription = scope
|
|
493
|
+
? resource.description.replace('in EventCatalog', `in ${scope.name} ${scope.ref.kind}`)
|
|
494
|
+
: resource.description;
|
|
495
|
+
|
|
482
496
|
server.registerResource(
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
{ description:
|
|
497
|
+
resourceName,
|
|
498
|
+
resourceUri,
|
|
499
|
+
{ description: resourceDescription, mimeType: 'application/json' },
|
|
486
500
|
async (uri: URL) => {
|
|
487
501
|
const allResources: any[] = [];
|
|
488
502
|
|
|
489
503
|
for (const collectionName of resource.collections) {
|
|
490
504
|
try {
|
|
491
|
-
const items = await getCollection(collectionName as any);
|
|
505
|
+
const items = scope ? scope.list(collectionName) : await getCollection(collectionName as any);
|
|
492
506
|
for (const item of items) {
|
|
493
507
|
allResources.push({
|
|
494
508
|
type: collectionName,
|
|
@@ -507,7 +521,7 @@ function createMcpServer() {
|
|
|
507
521
|
contents: [
|
|
508
522
|
{
|
|
509
523
|
uri: uri.href,
|
|
510
|
-
text: JSON.stringify({ resources: allResources }, null, 2),
|
|
524
|
+
text: JSON.stringify({ ...(scope && { scope: scope.ref }), resources: allResources }, null, 2),
|
|
511
525
|
mimeType: 'application/json',
|
|
512
526
|
},
|
|
513
527
|
],
|
|
@@ -525,8 +539,39 @@ function createMcpServer() {
|
|
|
525
539
|
// Create Hono app for MCP routes
|
|
526
540
|
const app = new Hono().basePath('/docs/mcp');
|
|
527
541
|
|
|
528
|
-
|
|
529
|
-
|
|
542
|
+
const globalBuiltInTools = [
|
|
543
|
+
'getResources',
|
|
544
|
+
'getResource',
|
|
545
|
+
'getMessagesProducedOrConsumedByResource',
|
|
546
|
+
'getSchemaForResource',
|
|
547
|
+
'findResourcesByOwner',
|
|
548
|
+
'getProducersOfMessage',
|
|
549
|
+
'getConsumersOfMessage',
|
|
550
|
+
'getC4Diagram',
|
|
551
|
+
'analyzeChangeImpact',
|
|
552
|
+
'explainBusinessFlow',
|
|
553
|
+
'getTeams',
|
|
554
|
+
'getTeam',
|
|
555
|
+
'getUsers',
|
|
556
|
+
'getUser',
|
|
557
|
+
'findMessageBySchemaId',
|
|
558
|
+
'explainUbiquitousLanguageTerms',
|
|
559
|
+
'getCustomDocs',
|
|
560
|
+
'searchCustomDocs',
|
|
561
|
+
'getCustomDoc',
|
|
562
|
+
];
|
|
563
|
+
|
|
564
|
+
const scopedBuiltInTools = globalBuiltInTools.filter(
|
|
565
|
+
(tool) =>
|
|
566
|
+
!['getC4Diagram', 'getTeams', 'getTeam', 'getUsers', 'getUser', 'getCustomDocs', 'searchCustomDocs', 'getCustomDoc'].includes(
|
|
567
|
+
tool
|
|
568
|
+
)
|
|
569
|
+
);
|
|
570
|
+
|
|
571
|
+
const getScopedBuiltInTools = (scope: McpScope) =>
|
|
572
|
+
scope.ref.kind === 'system'
|
|
573
|
+
? scopedBuiltInTools.filter((tool) => tool !== 'explainUbiquitousLanguageTerms')
|
|
574
|
+
: scopedBuiltInTools;
|
|
530
575
|
|
|
531
576
|
// MCP Resource URIs
|
|
532
577
|
const mcpResources = [
|
|
@@ -538,32 +583,85 @@ const mcpResources = [
|
|
|
538
583
|
'eventcatalog://adrs',
|
|
539
584
|
'eventcatalog://services',
|
|
540
585
|
'eventcatalog://domains',
|
|
586
|
+
'eventcatalog://systems',
|
|
587
|
+
'eventcatalog://channels',
|
|
541
588
|
'eventcatalog://entities',
|
|
589
|
+
'eventcatalog://containers',
|
|
590
|
+
'eventcatalog://diagrams',
|
|
591
|
+
'eventcatalog://data-products',
|
|
542
592
|
'eventcatalog://flows',
|
|
543
593
|
'eventcatalog://teams',
|
|
544
594
|
'eventcatalog://users',
|
|
545
595
|
];
|
|
546
596
|
|
|
547
|
-
|
|
548
|
-
|
|
597
|
+
const getMcpResourceUris = (scope?: McpScope) =>
|
|
598
|
+
scope
|
|
599
|
+
? mcpResources
|
|
600
|
+
.filter((uri) => uri !== 'eventcatalog://teams' && uri !== 'eventcatalog://users')
|
|
601
|
+
.map(
|
|
602
|
+
(uri) => `${scope.uriPrefix}/resources${uri === 'eventcatalog://all' ? '' : `/${uri.replace('eventcatalog://', '')}`}`
|
|
603
|
+
)
|
|
604
|
+
: mcpResources;
|
|
605
|
+
|
|
606
|
+
const getScopeRef = (c: Context, kind: McpScopeKind) => ({
|
|
607
|
+
kind,
|
|
608
|
+
id: c.req.param('id'),
|
|
609
|
+
version: c.req.param('version') || 'latest',
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
const resolveRequestScope = async (c: Context, kind?: McpScopeKind) => (kind ? resolveMcpScope(getScopeRef(c, kind)) : undefined);
|
|
613
|
+
|
|
614
|
+
const createScopeNotFoundResponse = (error: McpScopeNotFoundError) =>
|
|
615
|
+
new Response(JSON.stringify({ error: 'scope_not_found', message: error.message }), {
|
|
616
|
+
status: 404,
|
|
617
|
+
headers: { 'Content-Type': 'application/json' },
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
const acceptsEventStream = (request: Request) =>
|
|
621
|
+
request.headers.get('accept')?.toLowerCase().includes('text/event-stream') ?? false;
|
|
622
|
+
|
|
623
|
+
const createSseNotSupportedResponse = () =>
|
|
624
|
+
new Response(
|
|
625
|
+
JSON.stringify({
|
|
626
|
+
jsonrpc: '2.0',
|
|
627
|
+
error: { code: -32000, message: 'Method not allowed: this server does not provide an SSE stream.' },
|
|
628
|
+
id: null,
|
|
629
|
+
}),
|
|
630
|
+
{
|
|
631
|
+
status: 405,
|
|
632
|
+
headers: { Allow: 'POST', 'Content-Type': 'application/json' },
|
|
633
|
+
}
|
|
634
|
+
);
|
|
635
|
+
|
|
636
|
+
const handleGetRequest = async (c: Context, kind?: McpScopeKind) => {
|
|
549
637
|
const auth = await validateMcpRequest(c.req.raw);
|
|
550
638
|
|
|
551
639
|
if (!auth.ok) {
|
|
552
640
|
return createMcpAuthErrorResponse(auth);
|
|
553
641
|
}
|
|
554
642
|
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
status: 'running',
|
|
559
|
-
tools: [...builtInTools, ...extendedToolNames],
|
|
560
|
-
extendedTools: extendedToolNames.length > 0 ? extendedToolNames : undefined,
|
|
561
|
-
resources: mcpResources,
|
|
562
|
-
});
|
|
563
|
-
});
|
|
643
|
+
// Streamable HTTP clients may open an optional GET SSE channel for server-initiated messages.
|
|
644
|
+
// This endpoint is stateless and does not support that channel, so the MCP specification requires a 405.
|
|
645
|
+
if (acceptsEventStream(c.req.raw)) return createSseNotSupportedResponse();
|
|
564
646
|
|
|
565
|
-
|
|
566
|
-
|
|
647
|
+
try {
|
|
648
|
+
const scope = await resolveRequestScope(c, kind);
|
|
649
|
+
return c.json({
|
|
650
|
+
name: scope ? `EventCatalog MCP Server — ${scope.name} ${scope.ref.kind}` : 'EventCatalog MCP Server',
|
|
651
|
+
version: '1.2.0',
|
|
652
|
+
status: 'running',
|
|
653
|
+
...(scope && { scope: scope.ref }),
|
|
654
|
+
tools: scope ? getScopedBuiltInTools(scope) : [...globalBuiltInTools, ...extendedToolNames],
|
|
655
|
+
extendedTools: !scope && extendedToolNames.length > 0 ? extendedToolNames : undefined,
|
|
656
|
+
resources: getMcpResourceUris(scope),
|
|
657
|
+
});
|
|
658
|
+
} catch (error) {
|
|
659
|
+
if (error instanceof McpScopeNotFoundError) return createScopeNotFoundResponse(error);
|
|
660
|
+
throw error;
|
|
661
|
+
}
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
const handleMcpRequest = async (c: Context, kind?: McpScopeKind) => {
|
|
567
665
|
try {
|
|
568
666
|
const auth = await validateMcpRequest(c.req.raw);
|
|
569
667
|
|
|
@@ -571,17 +669,16 @@ app.post('/', async (c: Context) => {
|
|
|
571
669
|
return createMcpAuthErrorResponse(auth);
|
|
572
670
|
}
|
|
573
671
|
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
// mode: it sets _hasHandledRequest=true after the first call and throws
|
|
577
|
-
// on any subsequent request. McpServer equally rejects reconnection.
|
|
578
|
-
const server = createMcpServer();
|
|
672
|
+
const scope = await resolveRequestScope(c, kind);
|
|
673
|
+
const server = createMcpServer(scope);
|
|
579
674
|
const transport = new WebStandardStreamableHTTPServerTransport({
|
|
580
675
|
sessionIdGenerator: undefined,
|
|
581
676
|
});
|
|
582
677
|
await server.connect(transport);
|
|
583
678
|
return await transport.handleRequest(c.req.raw);
|
|
584
679
|
} catch (error) {
|
|
680
|
+
if (error instanceof McpScopeNotFoundError) return createScopeNotFoundResponse(error);
|
|
681
|
+
|
|
585
682
|
console.error('MCP request error:', error);
|
|
586
683
|
return c.json(
|
|
587
684
|
{
|
|
@@ -595,7 +692,21 @@ app.post('/', async (c: Context) => {
|
|
|
595
692
|
500
|
|
596
693
|
);
|
|
597
694
|
}
|
|
598
|
-
}
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
// Browser GETs return health metadata; MCP SSE negotiation is rejected with 405 because this server is stateless.
|
|
698
|
+
app.get('/', (c: Context) => handleGetRequest(c));
|
|
699
|
+
|
|
700
|
+
for (const kind of ['domain', 'system'] as const) {
|
|
701
|
+
const path = `${kind}s`;
|
|
702
|
+
app.get(`/${path}/:id`, (c: Context) => handleGetRequest(c, kind));
|
|
703
|
+
app.get(`/${path}/:id/:version`, (c: Context) => handleGetRequest(c, kind));
|
|
704
|
+
app.post(`/${path}/:id`, (c: Context) => handleMcpRequest(c, kind));
|
|
705
|
+
app.post(`/${path}/:id/:version`, (c: Context) => handleMcpRequest(c, kind));
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
// MCP protocol endpoint - handles POST requests for MCP protocol
|
|
709
|
+
app.post('/', (c: Context) => handleMcpRequest(c));
|
|
599
710
|
|
|
600
711
|
// Astro API route handler - delegates all requests to Hono
|
|
601
712
|
// Note: SSR and Scale plan checks are handled at build time by the integration
|