@contractspec/bundle.library 3.8.4 → 3.8.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.
Files changed (90) hide show
  1. package/.turbo/turbo-build.log +126 -112
  2. package/CHANGELOG.md +56 -0
  3. package/dist/application/index.js +806 -131
  4. package/dist/application/mcp/cliMcp.js +21 -2
  5. package/dist/application/mcp/common.js +21 -2
  6. package/dist/application/mcp/common.test.d.ts +1 -0
  7. package/dist/application/mcp/contractsMcp.js +21 -2
  8. package/dist/application/mcp/docsMcp.catalog.d.ts +2 -0
  9. package/dist/application/mcp/docsMcp.catalog.js +382 -0
  10. package/dist/application/mcp/docsMcp.d.ts +5 -1
  11. package/dist/application/mcp/docsMcp.data.d.ts +85 -0
  12. package/dist/application/mcp/docsMcp.data.js +148 -0
  13. package/dist/application/mcp/docsMcp.js +776 -101
  14. package/dist/application/mcp/docsMcp.prompts.d.ts +3 -0
  15. package/dist/application/mcp/docsMcp.prompts.js +522 -0
  16. package/dist/application/mcp/docsMcp.reference.d.ts +24 -0
  17. package/dist/application/mcp/docsMcp.reference.js +236 -0
  18. package/dist/application/mcp/docsMcp.resources.d.ts +3 -0
  19. package/dist/application/mcp/docsMcp.resources.js +520 -0
  20. package/dist/application/mcp/docsMcp.test.d.ts +1 -0
  21. package/dist/application/mcp/docsMcp.tools.d.ts +3 -0
  22. package/dist/application/mcp/docsMcp.tools.js +519 -0
  23. package/dist/application/mcp/index.js +806 -131
  24. package/dist/application/mcp/internalMcp.js +21 -2
  25. package/dist/application/mcp/normalizeMcpRequest.d.ts +1 -0
  26. package/dist/application/mcp/normalizeMcpRequest.js +22 -0
  27. package/dist/application/mcp/providerRankingMcp.js +21 -2
  28. package/dist/features/index.js +15 -15
  29. package/dist/index.js +171 -171
  30. package/dist/node/application/index.js +806 -131
  31. package/dist/node/application/mcp/cliMcp.js +21 -2
  32. package/dist/node/application/mcp/common.js +21 -2
  33. package/dist/node/application/mcp/contractsMcp.js +21 -2
  34. package/dist/node/application/mcp/docsMcp.catalog.js +381 -0
  35. package/dist/node/application/mcp/docsMcp.data.js +147 -0
  36. package/dist/node/application/mcp/docsMcp.js +776 -101
  37. package/dist/node/application/mcp/docsMcp.prompts.js +521 -0
  38. package/dist/node/application/mcp/docsMcp.reference.js +235 -0
  39. package/dist/node/application/mcp/docsMcp.resources.js +519 -0
  40. package/dist/node/application/mcp/docsMcp.tools.js +518 -0
  41. package/dist/node/application/mcp/index.js +806 -131
  42. package/dist/node/application/mcp/internalMcp.js +21 -2
  43. package/dist/node/application/mcp/normalizeMcpRequest.js +21 -0
  44. package/dist/node/application/mcp/providerRankingMcp.js +21 -2
  45. package/dist/node/features/index.js +15 -15
  46. package/dist/node/index.js +171 -171
  47. package/dist/node/presentation/features/hooks/index.js +12 -12
  48. package/dist/node/presentation/features/hooks/useContractsRegistry.js +12 -12
  49. package/dist/node/presentation/features/index.js +12 -12
  50. package/dist/node/presentation/features/organisms/FeatureDataViewsList.js +12 -12
  51. package/dist/node/presentation/features/organisms/FeatureEventsList.js +12 -12
  52. package/dist/node/presentation/features/organisms/FeatureFormsList.js +12 -12
  53. package/dist/node/presentation/features/organisms/FeaturePresentationsList.js +12 -12
  54. package/dist/node/presentation/features/organisms/index.js +12 -12
  55. package/dist/node/presentation/features/templates/FeatureDataViewsTemplate/FeatureDataViewsTemplate.js +12 -12
  56. package/dist/node/presentation/features/templates/FeatureDataViewsTemplate/index.js +12 -12
  57. package/dist/node/presentation/features/templates/FeatureEventsTemplate/FeatureEventsTemplate.js +12 -12
  58. package/dist/node/presentation/features/templates/FeatureEventsTemplate/index.js +12 -12
  59. package/dist/node/presentation/features/templates/FeatureFormsTemplate/FeatureFormsTemplate.js +12 -12
  60. package/dist/node/presentation/features/templates/FeatureFormsTemplate/index.js +12 -12
  61. package/dist/node/presentation/features/templates/FeaturePresentationsTemplate/FeaturePresentationsTemplate.js +12 -12
  62. package/dist/node/presentation/features/templates/FeaturePresentationsTemplate/index.js +12 -12
  63. package/dist/presentation/features/hooks/index.js +12 -12
  64. package/dist/presentation/features/hooks/useContractsRegistry.js +12 -12
  65. package/dist/presentation/features/index.js +12 -12
  66. package/dist/presentation/features/organisms/FeatureDataViewsList.js +12 -12
  67. package/dist/presentation/features/organisms/FeatureEventsList.js +12 -12
  68. package/dist/presentation/features/organisms/FeatureFormsList.js +12 -12
  69. package/dist/presentation/features/organisms/FeaturePresentationsList.js +12 -12
  70. package/dist/presentation/features/organisms/index.js +12 -12
  71. package/dist/presentation/features/templates/FeatureDataViewsTemplate/FeatureDataViewsTemplate.js +12 -12
  72. package/dist/presentation/features/templates/FeatureDataViewsTemplate/index.js +12 -12
  73. package/dist/presentation/features/templates/FeatureEventsTemplate/FeatureEventsTemplate.js +12 -12
  74. package/dist/presentation/features/templates/FeatureEventsTemplate/index.js +12 -12
  75. package/dist/presentation/features/templates/FeatureFormsTemplate/FeatureFormsTemplate.js +12 -12
  76. package/dist/presentation/features/templates/FeatureFormsTemplate/index.js +12 -12
  77. package/dist/presentation/features/templates/FeaturePresentationsTemplate/FeaturePresentationsTemplate.js +12 -12
  78. package/dist/presentation/features/templates/FeaturePresentationsTemplate/index.js +12 -12
  79. package/package.json +108 -24
  80. package/src/application/mcp/common.test.ts +64 -0
  81. package/src/application/mcp/common.ts +5 -2
  82. package/src/application/mcp/docsMcp.catalog.ts +2 -0
  83. package/src/application/mcp/docsMcp.data.ts +196 -0
  84. package/src/application/mcp/docsMcp.prompts.ts +165 -0
  85. package/src/application/mcp/docsMcp.reference.ts +152 -0
  86. package/src/application/mcp/docsMcp.resources.ts +194 -0
  87. package/src/application/mcp/docsMcp.test.ts +148 -0
  88. package/src/application/mcp/docsMcp.tools.ts +183 -0
  89. package/src/application/mcp/docsMcp.ts +13 -177
  90. package/src/application/mcp/normalizeMcpRequest.ts +30 -0
@@ -25,6 +25,25 @@ var authLogger = new Logger({
25
25
  enableContext: true,
26
26
  enableColors: false
27
27
  });
28
+ // src/application/mcp/normalizeMcpRequest.ts
29
+ var REQUIRED_ACCEPT_TYPES = ["application/json", "text/event-stream"];
30
+ function canNormalizeAcceptHeader(acceptHeader) {
31
+ return !acceptHeader || acceptHeader.includes("*/*") || acceptHeader.includes("application/*") || REQUIRED_ACCEPT_TYPES.some((value) => acceptHeader.includes(value));
32
+ }
33
+ function normalizeMcpRequest(request) {
34
+ if (request.method !== "POST")
35
+ return request;
36
+ const acceptHeader = request.headers.get("accept");
37
+ if (!canNormalizeAcceptHeader(acceptHeader))
38
+ return request;
39
+ const missingTypes = REQUIRED_ACCEPT_TYPES.filter((value) => !acceptHeader?.includes(value));
40
+ if (missingTypes.length === 0)
41
+ return request;
42
+ const headers = new Headers(request.headers);
43
+ headers.set("accept", [acceptHeader, ...missingTypes].filter(Boolean).join(", "));
44
+ return new Request(request, { headers });
45
+ }
46
+
28
47
  // src/application/mcp/common.ts
29
48
  import { randomUUID } from "node:crypto";
30
49
  import { createMcpServer } from "@contractspec/lib.contracts-runtime-server-mcp/provider-mcp";
@@ -120,7 +139,7 @@ function createMcpElysiaHandler({
120
139
  stateful: false
121
140
  });
122
141
  try {
123
- return await state.transport.handleRequest(request);
142
+ return await state.transport.handleRequest(normalizeMcpRequest(request));
124
143
  } finally {
125
144
  await closeSessionState(state);
126
145
  }
@@ -156,7 +175,7 @@ function createMcpElysiaHandler({
156
175
  createdState = true;
157
176
  }
158
177
  try {
159
- const response = await state.transport.handleRequest(request);
178
+ const response = await state.transport.handleRequest(normalizeMcpRequest(request));
160
179
  const activeSessionId = state.transport.sessionId;
161
180
  if (activeSessionId && !sessions.has(activeSessionId)) {
162
181
  sessions.set(activeSessionId, state);
@@ -197,90 +216,393 @@ function createMcpElysiaHandler({
197
216
  });
198
217
  }
199
218
 
200
- // src/features/docs/docs.contracts.ts
219
+ // src/application/mcp/docsMcp.data.ts
220
+ import { defaultDocRegistry } from "@contractspec/lib.contracts-spec/docs";
221
+ var DEFAULT_LIMIT = 20;
222
+ var MAX_LIMIT = 100;
223
+ function normalizeText(value) {
224
+ return value?.trim().toLowerCase() ?? "";
225
+ }
226
+ function normalizeRoute(route) {
227
+ const decoded = decodeURIComponent(route).trim();
228
+ if (!decoded)
229
+ return "/";
230
+ return decoded.startsWith("/") ? decoded : `/${decoded}`;
231
+ }
232
+ function normalizeTags(value) {
233
+ const tags = Array.isArray(value) ? value : value ? [value] : [];
234
+ return tags.map((tag) => normalizeText(tag)).filter(Boolean);
235
+ }
236
+ function clampLimit(limit) {
237
+ if (!limit || Number.isNaN(limit))
238
+ return DEFAULT_LIMIT;
239
+ return Math.min(Math.max(limit, 1), MAX_LIMIT);
240
+ }
241
+ function clampOffset(offset) {
242
+ if (!offset || Number.isNaN(offset))
243
+ return 0;
244
+ return Math.max(offset, 0);
245
+ }
246
+ function toDocSummary({ block, route }) {
247
+ return {
248
+ id: block.id,
249
+ title: block.title,
250
+ summary: block.summary ?? "",
251
+ route,
252
+ visibility: block.visibility ?? "public",
253
+ kind: block.kind ?? "reference",
254
+ version: block.version ?? "1.0.0",
255
+ tags: block.tags ?? []
256
+ };
257
+ }
258
+ function scoreDoc(route, query) {
259
+ if (!query)
260
+ return 1;
261
+ const tokens = query.split(/\s+/).filter(Boolean);
262
+ const title = normalizeText(route.block.title);
263
+ const id = normalizeText(route.block.id);
264
+ const summary = normalizeText(route.block.summary);
265
+ const body = normalizeText(route.block.body);
266
+ const path = normalizeText(route.route);
267
+ const tags = (route.block.tags ?? []).map((tag) => normalizeText(tag));
268
+ const haystack = [title, id, summary, body, path, ...tags].join(" ");
269
+ if (tokens.some((token) => !haystack.includes(token)))
270
+ return 0;
271
+ let score = 0;
272
+ for (const token of tokens) {
273
+ if (id.includes(token))
274
+ score += 8;
275
+ if (title.includes(token))
276
+ score += 7;
277
+ if (tags.some((tag) => tag.includes(token)))
278
+ score += 5;
279
+ if (summary.includes(token))
280
+ score += 4;
281
+ if (path.includes(token))
282
+ score += 3;
283
+ if (body.includes(token))
284
+ score += 2;
285
+ }
286
+ return score;
287
+ }
288
+ function searchDocs(routes, args) {
289
+ const query = normalizeText(typeof args.query === "string" ? args.query : undefined);
290
+ const tags = normalizeTags(args.tag);
291
+ const visibility = normalizeText(typeof args.visibility === "string" ? args.visibility : undefined);
292
+ const kind = normalizeText(typeof args.kind === "string" ? args.kind : undefined);
293
+ const limit = clampLimit(typeof args.limit === "number" ? args.limit : undefined);
294
+ const offset = clampOffset(typeof args.offset === "number" ? args.offset : undefined);
295
+ const ranked = routes.map((route) => ({
296
+ doc: toDocSummary(route),
297
+ score: scoreDoc(route, query)
298
+ })).filter(({ doc, score }) => {
299
+ const matchesQuery = query ? score > 0 : true;
300
+ const matchesTags = tags.length ? tags.every((tag) => doc.tags.some((docTag) => normalizeText(docTag).includes(tag))) : true;
301
+ const matchesVisibility = visibility ? normalizeText(doc.visibility) === visibility : true;
302
+ const matchesKind = kind ? normalizeText(doc.kind) === kind : true;
303
+ return matchesQuery && matchesTags && matchesVisibility && matchesKind;
304
+ }).sort((left, right) => {
305
+ if (right.score !== left.score)
306
+ return right.score - left.score;
307
+ return left.doc.title.localeCompare(right.doc.title);
308
+ });
309
+ const docs = ranked.slice(offset, offset + limit).map(({ doc }) => doc);
310
+ const nextOffset = offset + docs.length < ranked.length ? offset + docs.length : undefined;
311
+ return {
312
+ docs,
313
+ items: docs,
314
+ total: ranked.length,
315
+ ...nextOffset != null ? { nextOffset } : {}
316
+ };
317
+ }
318
+ function getDocById(id) {
319
+ const normalizedId = decodeURIComponent(id);
320
+ const found = defaultDocRegistry.get(normalizedId);
321
+ if (!found)
322
+ return;
323
+ return {
324
+ doc: toDocSummary(found),
325
+ content: String(found.block.body ?? "")
326
+ };
327
+ }
328
+ function getDocByRoute(routes, routePath) {
329
+ const normalizedPath = normalizeRoute(routePath);
330
+ const found = routes.find((route) => normalizeRoute(route.route) === normalizedPath);
331
+ if (!found)
332
+ return;
333
+ return {
334
+ doc: toDocSummary(found),
335
+ content: String(found.block.body ?? "")
336
+ };
337
+ }
338
+ function listDocFacets(routes) {
339
+ const tags = new Map;
340
+ const kinds = new Map;
341
+ const visibilities = new Map;
342
+ for (const route of routes) {
343
+ const kind = route.block.kind ?? "reference";
344
+ const visibility = route.block.visibility ?? "public";
345
+ kinds.set(kind, (kinds.get(kind) ?? 0) + 1);
346
+ visibilities.set(visibility, (visibilities.get(visibility) ?? 0) + 1);
347
+ for (const tag of route.block.tags ?? []) {
348
+ tags.set(tag, (tags.get(tag) ?? 0) + 1);
349
+ }
350
+ }
351
+ const toEntries = (values, key) => [...values.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map(([value, count]) => ({ [key]: value, count }));
352
+ return {
353
+ totalDocs: routes.length,
354
+ tags: toEntries(tags, "tag"),
355
+ kinds: toEntries(kinds, "kind"),
356
+ visibilities: toEntries(visibilities, "visibility")
357
+ };
358
+ }
359
+
360
+ // src/features/contracts-registry.ts
361
+ import {
362
+ EventRegistry,
363
+ OperationSpecRegistry
364
+ } from "@contractspec/lib.contracts-spec";
201
365
  import {
366
+ DataViewRegistry
367
+ } from "@contractspec/lib.contracts-spec/data-views";
368
+ import {
369
+ ContractReferenceDataView,
202
370
  ContractReferenceQuery,
203
- DocSummaryModel,
204
- DocsIndexInput,
205
- DocsIndexOutput,
371
+ DocsGenerateCommand,
372
+ DocsGeneratedEvent,
373
+ DocsIndexDataView,
206
374
  DocsIndexQuery,
207
- DocsIndexQuery as DocsIndexQuery2
375
+ DocsLayoutPresentation,
376
+ DocsPublishCommand,
377
+ DocsPublishedEvent,
378
+ DocsReferencePagePresentation,
379
+ DocsSearchForm,
380
+ ExampleCatalogDataView
208
381
  } from "@contractspec/lib.contracts-spec/docs";
209
- // src/application/mcp/docsMcp.ts
382
+ import { FormRegistry } from "@contractspec/lib.contracts-spec/forms";
210
383
  import {
211
- definePrompt,
212
- defineResourceTemplate,
213
- installOp,
214
- OperationSpecRegistry,
215
- PromptRegistry,
216
- ResourceRegistry
217
- } from "@contractspec/lib.contracts-spec";
218
- import { defaultDocRegistry } from "@contractspec/lib.contracts-spec/docs";
219
- import z from "zod";
220
- var DOC_OWNERS = ["@contractspec"];
221
- var DOC_TAGS = ["docs", "mcp"];
222
- function buildDocResources(routes) {
223
- const resources = new ResourceRegistry;
224
- resources.register(defineResourceTemplate({
225
- meta: {
226
- uriTemplate: "docs://list",
227
- title: "DocBlocks index",
228
- description: "All registered DocBlocks with route, visibility, tags, and summary.",
229
- mimeType: "application/json",
230
- tags: DOC_TAGS
384
+ PresentationRegistry as PresentationRegistry2
385
+ } from "@contractspec/lib.contracts-spec/presentations";
386
+ import {
387
+ serializeDataViewSpec,
388
+ serializeEventSpec,
389
+ serializeFormSpec,
390
+ serializeOperationSpec,
391
+ serializePresentationSpec
392
+ } from "@contractspec/lib.contracts-spec/serialization";
393
+ var operationRegistry = null;
394
+ function createContractSpecOperationRegistry() {
395
+ const registry = new OperationSpecRegistry;
396
+ registry.register(DocsIndexQuery).register(ContractReferenceQuery).register(DocsGenerateCommand).register(DocsPublishCommand);
397
+ return registry;
398
+ }
399
+ function getContractSpecOperationRegistry() {
400
+ if (!operationRegistry) {
401
+ operationRegistry = createContractSpecOperationRegistry();
402
+ }
403
+ return operationRegistry;
404
+ }
405
+ function resolveOperationSpec(key, version) {
406
+ return getContractSpecOperationRegistry().get(key, version);
407
+ }
408
+ var eventRegistry = null;
409
+ function createContractSpecEventRegistry() {
410
+ const registry = new EventRegistry;
411
+ registry.register(DocsGeneratedEvent).register(DocsPublishedEvent);
412
+ return registry;
413
+ }
414
+ function getContractSpecEventRegistry() {
415
+ if (!eventRegistry) {
416
+ eventRegistry = createContractSpecEventRegistry();
417
+ }
418
+ return eventRegistry;
419
+ }
420
+ function resolveEventSpec(key, version) {
421
+ return getContractSpecEventRegistry().get(key, version);
422
+ }
423
+ var presentationRegistry = null;
424
+ function createContractSpecPresentationRegistry() {
425
+ const registry = new PresentationRegistry2;
426
+ registry.register(DocsLayoutPresentation).register(DocsReferencePagePresentation);
427
+ return registry;
428
+ }
429
+ function getContractSpecPresentationRegistry() {
430
+ if (!presentationRegistry) {
431
+ presentationRegistry = createContractSpecPresentationRegistry();
432
+ }
433
+ return presentationRegistry;
434
+ }
435
+ function resolvePresentationSpec(key, version) {
436
+ return getContractSpecPresentationRegistry().get(key, version);
437
+ }
438
+ var dataViewRegistry = null;
439
+ function createContractSpecDataViewRegistry() {
440
+ const registry = new DataViewRegistry;
441
+ registry.register(DocsIndexDataView).register(ContractReferenceDataView).register(ExampleCatalogDataView);
442
+ return registry;
443
+ }
444
+ function getContractSpecDataViewRegistry() {
445
+ if (!dataViewRegistry) {
446
+ dataViewRegistry = createContractSpecDataViewRegistry();
447
+ }
448
+ return dataViewRegistry;
449
+ }
450
+ function resolveDataViewSpec(key, version) {
451
+ return getContractSpecDataViewRegistry().get(key, version);
452
+ }
453
+ var formRegistry = null;
454
+ function createContractSpecFormRegistry() {
455
+ const registry = new FormRegistry;
456
+ registry.register(DocsSearchForm);
457
+ return registry;
458
+ }
459
+ function getContractSpecFormRegistry() {
460
+ if (!formRegistry) {
461
+ formRegistry = createContractSpecFormRegistry();
462
+ }
463
+ return formRegistry;
464
+ }
465
+ function resolveFormSpec(key, _version) {
466
+ return getContractSpecFormRegistry().get(key);
467
+ }
468
+ function resolveSerializedOperationSpec(key, version) {
469
+ const spec = resolveOperationSpec(key, version);
470
+ return serializeOperationSpec(spec) ?? undefined;
471
+ }
472
+ function resolveSerializedEventSpec(key, version) {
473
+ const spec = resolveEventSpec(key, version);
474
+ return serializeEventSpec(spec) ?? undefined;
475
+ }
476
+ function resolveSerializedPresentationSpec(key, version) {
477
+ const spec = resolvePresentationSpec(key, version);
478
+ return serializePresentationSpec(spec) ?? undefined;
479
+ }
480
+ function resolveSerializedDataViewSpec(key, version) {
481
+ const spec = resolveDataViewSpec(key, version);
482
+ return serializeDataViewSpec(spec) ?? undefined;
483
+ }
484
+ function resolveSerializedFormSpec(key, version) {
485
+ const spec = resolveFormSpec(key, version);
486
+ return serializeFormSpec(spec) ?? undefined;
487
+ }
488
+ function resetContractSpecOperationRegistry() {
489
+ operationRegistry = null;
490
+ }
491
+ function resetContractSpecEventRegistry() {
492
+ eventRegistry = null;
493
+ }
494
+ function resetContractSpecPresentationRegistry() {
495
+ presentationRegistry = null;
496
+ }
497
+ function resetContractSpecDataViewRegistry() {
498
+ dataViewRegistry = null;
499
+ }
500
+ function resetContractSpecFormRegistry() {
501
+ formRegistry = null;
502
+ }
503
+ function resetAllContractSpecRegistries() {
504
+ resetContractSpecOperationRegistry();
505
+ resetContractSpecEventRegistry();
506
+ resetContractSpecPresentationRegistry();
507
+ resetContractSpecDataViewRegistry();
508
+ resetContractSpecFormRegistry();
509
+ }
510
+
511
+ // src/application/mcp/docsMcp.reference.ts
512
+ import { defaultDocRegistry as defaultDocRegistry2 } from "@contractspec/lib.contracts-spec/docs";
513
+ function normalizeText2(value) {
514
+ return value?.trim().toLowerCase() ?? "";
515
+ }
516
+ function routeFromDocIds(docIds) {
517
+ for (const docId of docIds ?? []) {
518
+ const doc = defaultDocRegistry2.get(docId);
519
+ if (doc)
520
+ return doc.route;
521
+ }
522
+ return;
523
+ }
524
+ function toReference(spec, type, schema, policy) {
525
+ const title = spec.meta.title ?? spec.meta.key;
526
+ const route = routeFromDocIds(spec.meta.docId);
527
+ const description = spec.meta.description;
528
+ return {
529
+ key: spec.meta.key,
530
+ version: spec.meta.version,
531
+ type,
532
+ title,
533
+ description,
534
+ markdown: [
535
+ `# ${title}`,
536
+ `- Key: ${spec.meta.key}`,
537
+ `- Type: ${type}`,
538
+ `- Version: ${spec.meta.version}`,
539
+ route ? `- Docs route: ${route}` : "",
540
+ description ? `
541
+ ${description}` : ""
542
+ ].filter(Boolean).join(`
543
+ `),
544
+ ...route ? { route } : {},
545
+ ...schema ? { schema } : {},
546
+ ...policy ? { policy } : {},
547
+ tags: spec.meta.tags ?? [],
548
+ owners: spec.meta.owners ?? [],
549
+ stability: spec.meta.stability
550
+ };
551
+ }
552
+ function resolveContractReference(args) {
553
+ const includeSchema = args.includeSchema ?? false;
554
+ const requestedType = normalizeText2(args.type);
555
+ const operation = resolveOperationSpec(args.key, args.version);
556
+ if (operation && (!requestedType || requestedType === "operation" || requestedType === operation.meta.kind)) {
557
+ return {
558
+ reference: toReference(operation, operation.meta.kind, includeSchema ? resolveSerializedOperationSpec(args.key, args.version) : undefined, operation.policy)
559
+ };
560
+ }
561
+ const resolvers = [
562
+ {
563
+ type: "data-view",
564
+ spec: resolveDataViewSpec(args.key, args.version),
565
+ schema: includeSchema ? resolveSerializedDataViewSpec(args.key, args.version) : undefined
231
566
  },
232
- input: z.object({}),
233
- resolve: async () => {
234
- const docs = routes.map(({ block, route }) => ({
235
- id: block.id,
236
- title: block.title,
237
- summary: block.summary ?? "",
238
- tags: block.tags ?? [],
239
- visibility: block.visibility ?? "public",
240
- route
241
- }));
242
- return {
243
- uri: "docs://list",
244
- mimeType: "application/json",
245
- data: JSON.stringify(docs, null, 2)
246
- };
247
- }
248
- }));
249
- resources.register(defineResourceTemplate({
250
- meta: {
251
- uriTemplate: "docs://doc/{id}",
252
- title: "DocBlock markdown",
253
- description: "Fetch DocBlock body by id as markdown.",
254
- mimeType: "text/markdown",
255
- tags: DOC_TAGS
567
+ {
568
+ type: "form",
569
+ spec: resolveFormSpec(args.key, args.version),
570
+ schema: includeSchema ? resolveSerializedFormSpec(args.key, args.version) : undefined
256
571
  },
257
- input: z.object({ id: z.string() }),
258
- resolve: async ({ id }) => {
259
- const found = defaultDocRegistry.get(id);
260
- if (!found) {
261
- return {
262
- uri: `docs://doc/${encodeURIComponent(id)}`,
263
- mimeType: "text/plain",
264
- data: `DocBlock not found: ${id}`
265
- };
266
- }
572
+ {
573
+ type: "presentation",
574
+ spec: resolvePresentationSpec(args.key, args.version),
575
+ schema: includeSchema ? resolveSerializedPresentationSpec(args.key, args.version) : undefined
576
+ },
577
+ {
578
+ type: "event",
579
+ spec: resolveEventSpec(args.key, args.version),
580
+ schema: includeSchema ? resolveSerializedEventSpec(args.key, args.version) : undefined
581
+ }
582
+ ];
583
+ for (const candidate of resolvers) {
584
+ if (candidate.spec && (!requestedType || requestedType === candidate.type)) {
267
585
  return {
268
- uri: `docs://doc/${encodeURIComponent(id)}`,
269
- mimeType: "text/markdown",
270
- data: String(found.block.body ?? "")
586
+ reference: toReference(candidate.spec, candidate.type, candidate.schema)
271
587
  };
272
588
  }
273
- }));
274
- return resources;
589
+ }
590
+ throw new Error(`Contract reference not found: ${args.key}`);
275
591
  }
276
- function buildDocPrompts() {
592
+
593
+ // src/application/mcp/docsMcp.prompts.ts
594
+ import { definePrompt, PromptRegistry } from "@contractspec/lib.contracts-spec";
595
+ import z from "zod";
596
+ var DOC_OWNERS = ["@contractspec"];
597
+ var DOC_TAGS = ["docs", "mcp"];
598
+ function buildDocPrompts(routes) {
277
599
  const prompts = new PromptRegistry;
278
600
  prompts.register(definePrompt({
279
601
  meta: {
280
602
  key: "docs.navigator",
281
603
  version: "1.0.0",
282
604
  title: "Find relevant ContractSpec docs",
283
- description: "Guide agents to pick the right DocBlock by topic or tag.",
605
+ description: "Guide agents to search, filter, and open the right ContractSpec docs.",
284
606
  tags: DOC_TAGS,
285
607
  stability: "beta",
286
608
  owners: DOC_OWNERS
@@ -292,6 +614,12 @@ function buildDocPrompts() {
292
614
  required: false,
293
615
  schema: z.string().optional()
294
616
  },
617
+ {
618
+ name: "kind",
619
+ description: "Optional doc kind filter.",
620
+ required: false,
621
+ schema: z.string().optional()
622
+ },
295
623
  {
296
624
  name: "tag",
297
625
  description: "Optional tag filter.",
@@ -301,62 +629,409 @@ function buildDocPrompts() {
301
629
  ],
302
630
  input: z.object({
303
631
  topic: z.string().optional(),
632
+ kind: z.string().optional(),
304
633
  tag: z.string().optional()
305
634
  }),
306
- render: async ({ topic, tag }) => {
307
- const parts = [
635
+ render: async ({ topic, kind, tag }) => {
636
+ const matches = searchDocs(routes, {
637
+ query: topic,
638
+ kind,
639
+ tag,
640
+ limit: 3
641
+ });
642
+ const suggestedDocs = matches.docs.length ? matches.docs.map((doc) => `- ${doc.title} (${doc.id}) -> ${doc.route}`).join(`
643
+ `) : "- No direct pre-match. Use docs_list_facets-v1_0_0 to browse tags and kinds.";
644
+ return [
308
645
  {
309
646
  type: "text",
310
- text: `Use the docs index to choose DocBlocks. If a specific topic is provided, prefer docs whose id/title/summary match it.${topic ? ` Topic: ${topic}.` : ""}${tag ? ` Tag: ${tag}.` : ""}`
647
+ text: [
648
+ "Use docs_search-v1_0_0 first, then read docs://doc/{id} for the strongest matches.",
649
+ "Use docs_resolve_route-v1_0_0 when the user already gives you a docs URL or route.",
650
+ "Use docs_list_facets-v1_0_0 or docs://facets to browse the docs taxonomy before guessing.",
651
+ topic ? `Topic: ${topic}` : "",
652
+ kind ? `Kind: ${kind}` : "",
653
+ tag ? `Tag: ${tag}` : "",
654
+ "Suggested starting docs:",
655
+ suggestedDocs
656
+ ].filter(Boolean).join(`
657
+ `)
311
658
  },
312
659
  {
313
660
  type: "resource",
314
- uri: "docs://list",
661
+ uri: "docs://index",
315
662
  title: "DocBlocks index"
663
+ },
664
+ {
665
+ type: "resource",
666
+ uri: "docs://facets",
667
+ title: "Docs facets"
668
+ }
669
+ ];
670
+ }
671
+ }));
672
+ prompts.register(definePrompt({
673
+ meta: {
674
+ key: "docs.reference.guide",
675
+ version: "1.0.0",
676
+ title: "Resolve a ContractSpec reference",
677
+ description: "Guide agents to fetch the canonical reference payload for a ContractSpec surface.",
678
+ tags: DOC_TAGS,
679
+ stability: "beta",
680
+ owners: DOC_OWNERS
681
+ },
682
+ args: [
683
+ {
684
+ name: "key",
685
+ description: "ContractSpec key to resolve.",
686
+ required: true,
687
+ schema: z.string()
688
+ },
689
+ {
690
+ name: "version",
691
+ description: "Optional version override.",
692
+ required: false,
693
+ schema: z.string().optional()
694
+ },
695
+ {
696
+ name: "type",
697
+ description: "Optional surface type: command, query, form, data-view, presentation, event.",
698
+ required: false,
699
+ schema: z.string().optional()
700
+ }
701
+ ],
702
+ input: z.object({
703
+ key: z.string(),
704
+ version: z.string().optional(),
705
+ type: z.string().optional()
706
+ }),
707
+ render: async ({ key, version, type }) => {
708
+ const reference = resolveContractReference({
709
+ key,
710
+ version,
711
+ type,
712
+ includeSchema: true
713
+ }).reference;
714
+ return [
715
+ {
716
+ type: "text",
717
+ text: [
718
+ "Use docs_contract_reference-v1_0_0 when you need the canonical docs payload for a ContractSpec surface.",
719
+ "Use docs_get-v1_0_0 only when you already know the exact DocBlock id and need raw markdown.",
720
+ `Resolved key: ${reference.key}`,
721
+ `Resolved type: ${reference.type}`,
722
+ reference.route ? `Docs route: ${reference.route}` : "",
723
+ `Resource URI: docs://contract-reference/${encodeURIComponent(key)}`
724
+ ].filter(Boolean).join(`
725
+ `)
726
+ },
727
+ {
728
+ type: "resource",
729
+ uri: `docs://contract-reference/${encodeURIComponent(key)}`,
730
+ title: "Contract reference"
316
731
  }
317
732
  ];
318
- return parts;
319
733
  }
320
734
  }));
321
735
  return prompts;
322
736
  }
737
+
738
+ // src/application/mcp/docsMcp.resources.ts
739
+ import {
740
+ defineResourceTemplate,
741
+ ResourceRegistry
742
+ } from "@contractspec/lib.contracts-spec";
743
+ import z2 from "zod";
744
+ var DOC_TAGS2 = ["docs", "mcp"];
745
+ function buildDocResources(routes) {
746
+ const resources = new ResourceRegistry;
747
+ const readDocIndex = (input) => searchDocs(routes, input);
748
+ resources.register(defineResourceTemplate({
749
+ meta: {
750
+ uriTemplate: "docs://index",
751
+ title: "DocBlocks index",
752
+ description: "Default ContractSpec docs index resource.",
753
+ mimeType: "application/json",
754
+ tags: DOC_TAGS2
755
+ },
756
+ input: z2.object({}),
757
+ resolve: async () => ({
758
+ uri: "docs://index",
759
+ mimeType: "application/json",
760
+ data: JSON.stringify(readDocIndex({}), null, 2)
761
+ })
762
+ }));
763
+ resources.register(defineResourceTemplate({
764
+ meta: {
765
+ uriTemplate: "docs://index{?query,tag,kind,visibility,limit,offset}",
766
+ title: "DocBlocks index",
767
+ description: "Search and paginate ContractSpec docs by query, tag, kind, or visibility.",
768
+ mimeType: "application/json",
769
+ tags: DOC_TAGS2
770
+ },
771
+ input: z2.object({
772
+ query: z2.string().optional(),
773
+ tag: z2.string().optional(),
774
+ kind: z2.string().optional(),
775
+ visibility: z2.string().optional(),
776
+ limit: z2.coerce.number().optional(),
777
+ offset: z2.coerce.number().optional()
778
+ }),
779
+ resolve: async (input) => ({
780
+ uri: "docs://index",
781
+ mimeType: "application/json",
782
+ data: JSON.stringify(readDocIndex(input), null, 2)
783
+ })
784
+ }));
785
+ resources.register(defineResourceTemplate({
786
+ meta: {
787
+ uriTemplate: "docs://list",
788
+ title: "DocBlocks index (legacy alias)",
789
+ description: "Compatibility alias for the docs index resource.",
790
+ mimeType: "application/json",
791
+ tags: DOC_TAGS2
792
+ },
793
+ input: z2.object({}),
794
+ resolve: async () => ({
795
+ uri: "docs://list",
796
+ mimeType: "application/json",
797
+ data: JSON.stringify(readDocIndex({}), null, 2)
798
+ })
799
+ }));
800
+ resources.register(defineResourceTemplate({
801
+ meta: {
802
+ uriTemplate: "docs://doc/{id}",
803
+ title: "Doc markdown",
804
+ description: "Fetch a single DocBlock body by id as markdown.",
805
+ mimeType: "text/markdown",
806
+ tags: DOC_TAGS2
807
+ },
808
+ input: z2.object({ id: z2.string() }),
809
+ resolve: async ({ id }) => {
810
+ const found = getDocById(id);
811
+ if (!found) {
812
+ return {
813
+ uri: `docs://doc/${encodeURIComponent(id)}`,
814
+ mimeType: "text/plain",
815
+ data: `DocBlock not found: ${id}`
816
+ };
817
+ }
818
+ return {
819
+ uri: `docs://doc/${encodeURIComponent(id)}`,
820
+ mimeType: "text/markdown",
821
+ data: found.content
822
+ };
823
+ }
824
+ }));
825
+ resources.register(defineResourceTemplate({
826
+ meta: {
827
+ uriTemplate: "docs://route/{routePath}",
828
+ title: "Doc by route",
829
+ description: "Resolve a docs route to the matching DocBlock summary and body.",
830
+ mimeType: "application/json",
831
+ tags: DOC_TAGS2
832
+ },
833
+ input: z2.object({ routePath: z2.string() }),
834
+ resolve: async ({ routePath }) => ({
835
+ uri: `docs://route/${encodeURIComponent(routePath)}`,
836
+ mimeType: "application/json",
837
+ data: JSON.stringify(getDocByRoute(routes, routePath) ?? {
838
+ error: "not_found",
839
+ route: routePath
840
+ }, null, 2)
841
+ })
842
+ }));
843
+ resources.register(defineResourceTemplate({
844
+ meta: {
845
+ uriTemplate: "docs://facets",
846
+ title: "Docs facets",
847
+ description: "Counts of available tags, kinds, and visibilities across docs.",
848
+ mimeType: "application/json",
849
+ tags: DOC_TAGS2
850
+ },
851
+ input: z2.object({}),
852
+ resolve: async () => ({
853
+ uri: "docs://facets",
854
+ mimeType: "application/json",
855
+ data: JSON.stringify(listDocFacets(routes), null, 2)
856
+ })
857
+ }));
858
+ resources.register(defineResourceTemplate({
859
+ meta: {
860
+ uriTemplate: "docs://contract-reference/{key}{?version,type,includeSchema}",
861
+ title: "Contract reference",
862
+ description: "Resolve a ContractSpec surface into a docs-ready reference payload.",
863
+ mimeType: "application/json",
864
+ tags: DOC_TAGS2
865
+ },
866
+ input: z2.object({
867
+ key: z2.string(),
868
+ version: z2.string().optional(),
869
+ type: z2.string().optional(),
870
+ includeSchema: z2.coerce.boolean().optional()
871
+ }),
872
+ resolve: async ({ key, version, type, includeSchema }) => ({
873
+ uri: `docs://contract-reference/${encodeURIComponent(key)}`,
874
+ mimeType: "application/json",
875
+ data: JSON.stringify(resolveContractReference({ key, version, type, includeSchema }), null, 2)
876
+ })
877
+ }));
878
+ return resources;
879
+ }
880
+
881
+ // src/application/mcp/docsMcp.tools.ts
882
+ import {
883
+ defineCommand,
884
+ defineSchemaModel,
885
+ installOp,
886
+ OperationSpecRegistry as OperationSpecRegistry2
887
+ } from "@contractspec/lib.contracts-spec";
888
+ import {
889
+ ContractReferenceInput,
890
+ ContractReferenceOutput,
891
+ DocsIndexInput,
892
+ DocsIndexOutput
893
+ } from "@contractspec/lib.contracts-spec/docs";
894
+ import { ScalarTypeEnum } from "@contractspec/lib.schema";
895
+ var DOC_OWNERS2 = ["@contractspec"];
896
+ var DOC_TAGS3 = ["docs", "mcp"];
897
+ var DocsGetInput = defineSchemaModel({
898
+ name: "DocsGetInput",
899
+ fields: {
900
+ id: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
901
+ }
902
+ });
903
+ var DocsGetOutput = defineSchemaModel({
904
+ name: "DocsGetOutput",
905
+ fields: {
906
+ doc: { type: ScalarTypeEnum.JSON(), isOptional: false },
907
+ content: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
908
+ }
909
+ });
910
+ var DocsResolveRouteInput = defineSchemaModel({
911
+ name: "DocsResolveRouteInput",
912
+ fields: {
913
+ route: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
914
+ }
915
+ });
916
+ var DocsResolveRouteOutput = defineSchemaModel({
917
+ name: "DocsResolveRouteOutput",
918
+ fields: {
919
+ doc: { type: ScalarTypeEnum.JSON(), isOptional: false },
920
+ content: { type: ScalarTypeEnum.String_unsecure(), isOptional: false }
921
+ }
922
+ });
923
+ var DocsFacetsInput = defineSchemaModel({
924
+ name: "DocsFacetsInput",
925
+ fields: {}
926
+ });
927
+ var DocsFacetsOutput = defineSchemaModel({
928
+ name: "DocsFacetsOutput",
929
+ fields: {
930
+ facets: { type: ScalarTypeEnum.JSON(), isOptional: false }
931
+ }
932
+ });
323
933
  function buildDocOps(routes) {
324
- const registry = new OperationSpecRegistry;
325
- installOp(registry, DocsIndexQuery, async (args) => {
326
- const query = args.query?.toLowerCase().trim();
327
- const tagsFilter = args.tag?.map((t) => t.toLowerCase().trim()) ?? [];
328
- const visibility = args.visibility?.toLowerCase().trim();
329
- const docs = routes.map(({ block, route }) => ({
330
- id: block.id,
331
- title: block.title,
332
- summary: block.summary ?? "",
333
- tags: block.tags ?? [],
334
- visibility: (block.visibility ?? "public").toLowerCase(),
335
- route
336
- })).filter((doc) => {
337
- const matchesQuery = query ? doc.id.toLowerCase().includes(query) || doc.title.toLowerCase().includes(query) || doc.summary.toLowerCase().includes(query) : true;
338
- const matchesTags = tagsFilter.length ? tagsFilter.every((t) => doc.tags.some((tag) => tag.toLowerCase().includes(t))) : true;
339
- const matchesVisibility = visibility ? doc.visibility === visibility : true;
340
- return matchesQuery && matchesTags && matchesVisibility;
341
- });
342
- return {
343
- docs,
344
- items: docs,
345
- total: docs.length
346
- };
934
+ const registry = new OperationSpecRegistry2;
935
+ installOp(registry, defineCommand({
936
+ meta: {
937
+ key: "docs.search",
938
+ version: "1.0.0",
939
+ stability: "beta",
940
+ owners: DOC_OWNERS2,
941
+ tags: DOC_TAGS3,
942
+ description: "Search ContractSpec docs by query, tag, kind, or visibility.",
943
+ goal: "Find the most relevant DocBlocks without browsing the full corpus.",
944
+ context: "Read-only docs MCP search surface."
945
+ },
946
+ io: { input: DocsIndexInput, output: DocsIndexOutput },
947
+ policy: { auth: "anonymous" },
948
+ transport: { mcp: { toolName: "docs_search-v1_0_0" } }
949
+ }), async (args) => searchDocs(routes, args));
950
+ installOp(registry, defineCommand({
951
+ meta: {
952
+ key: "docs.get",
953
+ version: "1.0.0",
954
+ stability: "beta",
955
+ owners: DOC_OWNERS2,
956
+ tags: DOC_TAGS3,
957
+ description: "Read a single DocBlock by id.",
958
+ goal: "Fetch the exact markdown content and metadata for a known doc id.",
959
+ context: "Read-only docs MCP surface."
960
+ },
961
+ io: { input: DocsGetInput, output: DocsGetOutput },
962
+ policy: { auth: "anonymous" },
963
+ transport: { mcp: { toolName: "docs_get-v1_0_0" } }
964
+ }), async ({ id }) => {
965
+ const found = getDocById(id);
966
+ if (!found)
967
+ throw new Error(`DocBlock not found: ${id}`);
968
+ return found;
969
+ });
970
+ installOp(registry, defineCommand({
971
+ meta: {
972
+ key: "docs.resolveRoute",
973
+ version: "1.0.0",
974
+ stability: "beta",
975
+ owners: DOC_OWNERS2,
976
+ tags: DOC_TAGS3,
977
+ description: "Resolve a docs route to the matching DocBlock.",
978
+ goal: "Turn a route or URL path into a canonical doc id and markdown body.",
979
+ context: "Read-only docs MCP surface."
980
+ },
981
+ io: { input: DocsResolveRouteInput, output: DocsResolveRouteOutput },
982
+ policy: { auth: "anonymous" },
983
+ transport: { mcp: { toolName: "docs_resolve_route-v1_0_0" } }
984
+ }), async ({ route }) => {
985
+ const found = getDocByRoute(routes, route);
986
+ if (!found)
987
+ throw new Error(`Doc route not found: ${route}`);
988
+ return found;
347
989
  });
990
+ installOp(registry, defineCommand({
991
+ meta: {
992
+ key: "docs.contract.lookup",
993
+ version: "1.0.0",
994
+ stability: "beta",
995
+ owners: DOC_OWNERS2,
996
+ tags: DOC_TAGS3,
997
+ description: "Resolve a ContractSpec surface into a docs-ready reference payload.",
998
+ goal: "Get canonical docs metadata, route, and optional schema for a spec key.",
999
+ context: "Read-only docs MCP surface."
1000
+ },
1001
+ io: { input: ContractReferenceInput, output: ContractReferenceOutput },
1002
+ policy: { auth: "anonymous" },
1003
+ transport: { mcp: { toolName: "docs_contract_reference-v1_0_0" } }
1004
+ }), async (args) => resolveContractReference(args));
1005
+ installOp(registry, defineCommand({
1006
+ meta: {
1007
+ key: "docs.list.facets",
1008
+ version: "1.0.0",
1009
+ stability: "beta",
1010
+ owners: DOC_OWNERS2,
1011
+ tags: DOC_TAGS3,
1012
+ description: "List docs taxonomy facets such as tags, kinds, and visibilities.",
1013
+ goal: "Help agents browse the docs corpus before making targeted reads.",
1014
+ context: "Read-only docs MCP surface."
1015
+ },
1016
+ io: { input: DocsFacetsInput, output: DocsFacetsOutput },
1017
+ policy: { auth: "anonymous" },
1018
+ transport: { mcp: { toolName: "docs_list_facets-v1_0_0" } }
1019
+ }), async () => ({ facets: listDocFacets(routes) }));
348
1020
  return registry;
349
1021
  }
350
- function createDocsMcpHandler(path = "/api/mcp/docs") {
351
- const routes = defaultDocRegistry.list();
1022
+
1023
+ // src/application/mcp/docsMcp.ts
1024
+ import { defaultDocRegistry as defaultDocRegistry3 } from "@contractspec/lib.contracts-spec/docs";
1025
+ function createDocsMcpHandler(path = "/api/mcp/docs", options = {}) {
1026
+ const routes = defaultDocRegistry3.list();
352
1027
  return createMcpElysiaHandler({
353
1028
  logger: appLogger,
354
1029
  path,
355
1030
  serverName: "contractspec-docs-mcp",
356
1031
  ops: buildDocOps(routes),
357
1032
  resources: buildDocResources(routes),
358
- prompts: buildDocPrompts(),
359
- presentations: routes.map(({ descriptor }) => descriptor)
1033
+ prompts: buildDocPrompts(routes),
1034
+ presentations: options.includePresentations ? routes.map(({ descriptor }) => descriptor) : undefined
360
1035
  });
361
1036
  }
362
1037
  export {