@contractspec/bundle.library 3.0.0 → 3.2.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.
Files changed (50) hide show
  1. package/.turbo/turbo-build.log +178 -166
  2. package/AGENTS.md +19 -12
  3. package/CHANGELOG.md +74 -0
  4. package/dist/application/context-storage/index.d.ts +18 -0
  5. package/dist/application/context-storage/index.js +29 -0
  6. package/dist/application/index.d.ts +1 -0
  7. package/dist/application/index.js +662 -2
  8. package/dist/application/mcp/cliMcp.js +12 -2
  9. package/dist/application/mcp/common.d.ts +11 -1
  10. package/dist/application/mcp/common.js +12 -2
  11. package/dist/application/mcp/contractsMcp.d.ts +51 -0
  12. package/dist/application/mcp/contractsMcp.js +531 -0
  13. package/dist/application/mcp/contractsMcpResources.d.ts +7 -0
  14. package/dist/application/mcp/contractsMcpResources.js +124 -0
  15. package/dist/application/mcp/contractsMcpTools.d.ts +9 -0
  16. package/dist/application/mcp/contractsMcpTools.js +200 -0
  17. package/dist/application/mcp/contractsMcpTypes.d.ts +50 -0
  18. package/dist/application/mcp/contractsMcpTypes.js +1 -0
  19. package/dist/application/mcp/docsMcp.js +12 -2
  20. package/dist/application/mcp/index.d.ts +2 -0
  21. package/dist/application/mcp/index.js +635 -2
  22. package/dist/application/mcp/internalMcp.js +12 -2
  23. package/dist/application/mcp/providerRankingMcp.d.ts +46 -0
  24. package/dist/application/mcp/providerRankingMcp.js +494 -0
  25. package/dist/node/application/context-storage/index.js +28 -0
  26. package/dist/node/application/index.js +662 -2
  27. package/dist/node/application/mcp/cliMcp.js +12 -2
  28. package/dist/node/application/mcp/common.js +12 -2
  29. package/dist/node/application/mcp/contractsMcp.js +530 -0
  30. package/dist/node/application/mcp/contractsMcpResources.js +123 -0
  31. package/dist/node/application/mcp/contractsMcpTools.js +199 -0
  32. package/dist/node/application/mcp/contractsMcpTypes.js +0 -0
  33. package/dist/node/application/mcp/docsMcp.js +12 -2
  34. package/dist/node/application/mcp/index.js +635 -2
  35. package/dist/node/application/mcp/internalMcp.js +12 -2
  36. package/dist/node/application/mcp/providerRankingMcp.js +493 -0
  37. package/package.json +113 -25
  38. package/src/application/context-storage/index.ts +58 -0
  39. package/src/application/index.ts +1 -0
  40. package/src/application/mcp/common.ts +28 -1
  41. package/src/application/mcp/contractsMcp.ts +34 -0
  42. package/src/application/mcp/contractsMcpResources.ts +142 -0
  43. package/src/application/mcp/contractsMcpTools.ts +246 -0
  44. package/src/application/mcp/contractsMcpTypes.ts +47 -0
  45. package/src/application/mcp/index.ts +2 -0
  46. package/src/application/mcp/providerRankingMcp.ts +380 -0
  47. package/src/components/docs/generated/docs-index._common.json +879 -1
  48. package/src/components/docs/generated/docs-index.manifest.json +5 -5
  49. package/src/components/docs/generated/docs-index.metrics.json +8 -0
  50. package/src/components/docs/generated/docs-index.platform-integrations.json +8 -0
@@ -73,9 +73,13 @@ function createMcpElysiaHandler({
73
73
  ops,
74
74
  resources,
75
75
  prompts,
76
- presentations
76
+ presentations,
77
+ validateAuth,
78
+ requiredAuthMethods
77
79
  }) {
78
- logger.info("Setting up MCP handler...");
80
+ logger.info("Setting up MCP handler...", {
81
+ requiredAuthMethods: requiredAuthMethods ?? []
82
+ });
79
83
  const isStateful = process.env.CONTRACTSPEC_MCP_STATEFUL === "1";
80
84
  const sessions = new Map;
81
85
  async function handleStateless(request) {
@@ -146,6 +150,12 @@ function createMcpElysiaHandler({
146
150
  }
147
151
  return new Elysia({ name: `mcp-${serverName}` }).all(path, async ({ request }) => {
148
152
  try {
153
+ if (validateAuth) {
154
+ const authResult = await validateAuth(request);
155
+ if (!authResult.valid) {
156
+ return createJsonRpcErrorResponse(401, -32002, "Authentication failed", authResult.reason);
157
+ }
158
+ }
149
159
  if (isStateful) {
150
160
  return await handleStateful(request);
151
161
  }
@@ -777,8 +787,631 @@ function createInternalMcpHandler(path2 = "/api/mcp/internal") {
777
787
  prompts: buildInternalPrompts()
778
788
  });
779
789
  }
790
+
791
+ // src/application/mcp/providerRankingMcp.ts
792
+ import {
793
+ definePrompt as definePrompt4,
794
+ defineResourceTemplate as defineResourceTemplate4,
795
+ installOp as installOp4,
796
+ OperationSpecRegistry as OperationSpecRegistry4,
797
+ PromptRegistry as PromptRegistry4,
798
+ ResourceRegistry as ResourceRegistry4
799
+ } from "@contractspec/lib.contracts-spec";
800
+ import {
801
+ BenchmarkIngestCommand,
802
+ BenchmarkRunCustomCommand,
803
+ RankingRefreshCommand
804
+ } from "@contractspec/lib.contracts-spec/provider-ranking";
805
+ import z4 from "zod";
806
+ import { InMemoryProviderRankingStore } from "@contractspec/lib.provider-ranking/in-memory-store";
807
+ import { createDefaultIngesterRegistry } from "@contractspec/lib.provider-ranking/ingesters";
808
+ import { computeModelRankings } from "@contractspec/lib.provider-ranking/scoring";
809
+ import { normalizeBenchmarkResults } from "@contractspec/lib.provider-ranking/scoring";
810
+ var TransportFilterSchema = z4.enum(["rest", "mcp", "webhook", "sdk"]).optional();
811
+ var AuthFilterSchema = z4.enum([
812
+ "api-key",
813
+ "oauth2",
814
+ "bearer",
815
+ "header",
816
+ "basic",
817
+ "webhook-signing",
818
+ "service-account"
819
+ ]).optional();
820
+ var RANKING_TAGS = ["ranking", "mcp", "ai"];
821
+ var RANKING_OWNERS = ["platform.ai"];
822
+ var sharedStore = null;
823
+ function getStore() {
824
+ if (!sharedStore) {
825
+ sharedStore = new InMemoryProviderRankingStore;
826
+ }
827
+ return sharedStore;
828
+ }
829
+ function buildRankingResources() {
830
+ const resources = new ResourceRegistry4;
831
+ resources.register(defineResourceTemplate4({
832
+ meta: {
833
+ uriTemplate: "ranking://leaderboard",
834
+ title: "AI Model Leaderboard",
835
+ description: "Current ranked list of AI models by composite score. Supports optional transport and authMethod query filters.",
836
+ mimeType: "application/json",
837
+ tags: RANKING_TAGS
838
+ },
839
+ input: z4.object({
840
+ transport: TransportFilterSchema,
841
+ authMethod: AuthFilterSchema
842
+ }),
843
+ resolve: async ({ transport, authMethod }) => {
844
+ const store = getStore();
845
+ const result = await store.listModelRankings({
846
+ limit: 100,
847
+ requiredTransport: transport,
848
+ requiredAuthMethod: authMethod
849
+ });
850
+ return {
851
+ uri: "ranking://leaderboard",
852
+ mimeType: "application/json",
853
+ data: JSON.stringify(result, null, 2)
854
+ };
855
+ }
856
+ }));
857
+ resources.register(defineResourceTemplate4({
858
+ meta: {
859
+ uriTemplate: "ranking://leaderboard/{dimension}",
860
+ title: "AI Model Leaderboard by Dimension",
861
+ description: "Ranked list of AI models filtered by a specific dimension. Supports optional transport and authMethod query filters.",
862
+ mimeType: "application/json",
863
+ tags: RANKING_TAGS
864
+ },
865
+ input: z4.object({
866
+ dimension: z4.string(),
867
+ transport: TransportFilterSchema,
868
+ authMethod: AuthFilterSchema
869
+ }),
870
+ resolve: async ({ dimension, transport, authMethod }) => {
871
+ const store = getStore();
872
+ const result = await store.listModelRankings({
873
+ dimension,
874
+ limit: 100,
875
+ requiredTransport: transport,
876
+ requiredAuthMethod: authMethod
877
+ });
878
+ return {
879
+ uri: `ranking://leaderboard/${encodeURIComponent(dimension)}`,
880
+ mimeType: "application/json",
881
+ data: JSON.stringify(result, null, 2)
882
+ };
883
+ }
884
+ }));
885
+ resources.register(defineResourceTemplate4({
886
+ meta: {
887
+ uriTemplate: "ranking://model/{modelId}",
888
+ title: "AI Model Profile",
889
+ description: "Detailed profile for a specific AI model including scores and benchmarks.",
890
+ mimeType: "application/json",
891
+ tags: RANKING_TAGS
892
+ },
893
+ input: z4.object({ modelId: z4.string() }),
894
+ resolve: async ({ modelId }) => {
895
+ const store = getStore();
896
+ const profile = await store.getModelProfile(modelId);
897
+ if (!profile) {
898
+ return {
899
+ uri: `ranking://model/${encodeURIComponent(modelId)}`,
900
+ mimeType: "application/json",
901
+ data: JSON.stringify({ error: "not_found", modelId })
902
+ };
903
+ }
904
+ return {
905
+ uri: `ranking://model/${encodeURIComponent(modelId)}`,
906
+ mimeType: "application/json",
907
+ data: JSON.stringify(profile, null, 2)
908
+ };
909
+ }
910
+ }));
911
+ resources.register(defineResourceTemplate4({
912
+ meta: {
913
+ uriTemplate: "ranking://results",
914
+ title: "Benchmark Results",
915
+ description: "List of raw benchmark results from all ingested sources.",
916
+ mimeType: "application/json",
917
+ tags: RANKING_TAGS
918
+ },
919
+ input: z4.object({}),
920
+ resolve: async () => {
921
+ const store = getStore();
922
+ const result = await store.listBenchmarkResults({ limit: 200 });
923
+ return {
924
+ uri: "ranking://results",
925
+ mimeType: "application/json",
926
+ data: JSON.stringify(result, null, 2)
927
+ };
928
+ }
929
+ }));
930
+ return resources;
931
+ }
932
+ function buildRankingPrompts() {
933
+ const prompts = new PromptRegistry4;
934
+ prompts.register(definePrompt4({
935
+ meta: {
936
+ key: "ranking.advisor",
937
+ version: "1.0.0",
938
+ title: "AI Model Advisor",
939
+ description: "Which AI model is best for a given task? Uses the leaderboard to recommend.",
940
+ tags: RANKING_TAGS,
941
+ stability: "beta",
942
+ owners: RANKING_OWNERS
943
+ },
944
+ args: [
945
+ {
946
+ name: "task",
947
+ description: "The task or use case to recommend a model for.",
948
+ required: true,
949
+ schema: z4.string()
950
+ },
951
+ {
952
+ name: "priority",
953
+ description: "Priority dimension (coding, reasoning, cost, latency, etc.).",
954
+ required: false,
955
+ schema: z4.string().optional()
956
+ },
957
+ {
958
+ name: "transport",
959
+ description: "Required transport type (rest, mcp, webhook, sdk).",
960
+ required: false,
961
+ schema: TransportFilterSchema
962
+ },
963
+ {
964
+ name: "authMethod",
965
+ description: "Required auth method (api-key, oauth2, bearer, etc.).",
966
+ required: false,
967
+ schema: AuthFilterSchema
968
+ }
969
+ ],
970
+ input: z4.object({
971
+ task: z4.string(),
972
+ priority: z4.string().optional(),
973
+ transport: TransportFilterSchema,
974
+ authMethod: AuthFilterSchema
975
+ }),
976
+ render: async ({ task, priority, transport, authMethod }) => {
977
+ const constraints = [];
978
+ if (priority)
979
+ constraints.push(`Prioritize: ${priority}.`);
980
+ if (transport)
981
+ constraints.push(`Required transport: ${transport}.`);
982
+ if (authMethod)
983
+ constraints.push(`Required auth: ${authMethod}.`);
984
+ return [
985
+ {
986
+ type: "text",
987
+ text: `Recommend the best AI model for: "${task}".${constraints.length ? ` ${constraints.join(" ")}` : ""} Use the leaderboard data to justify your recommendation.`
988
+ },
989
+ {
990
+ type: "resource",
991
+ uri: priority ? `ranking://leaderboard/${priority}` : "ranking://leaderboard",
992
+ title: "Leaderboard"
993
+ }
994
+ ];
995
+ }
996
+ }));
997
+ return prompts;
998
+ }
999
+ function buildRankingOps() {
1000
+ const registry = new OperationSpecRegistry4;
1001
+ const ingesterRegistry = createDefaultIngesterRegistry();
1002
+ installOp4(registry, BenchmarkIngestCommand, async (args) => {
1003
+ const store = getStore();
1004
+ const source = args.source;
1005
+ const ingester = ingesterRegistry.get(source);
1006
+ if (!ingester) {
1007
+ throw new Error(`No ingester registered for source: ${source}`);
1008
+ }
1009
+ const rawResults = await ingester.ingest({
1010
+ sourceUrl: args.sourceUrl,
1011
+ dimensions: args.dimensions
1012
+ });
1013
+ const normalized = normalizeBenchmarkResults(rawResults);
1014
+ for (const result of normalized) {
1015
+ await store.upsertBenchmarkResult(result);
1016
+ }
1017
+ return {
1018
+ ingestionId: `ingest-${source}-${Date.now()}`,
1019
+ source,
1020
+ resultsCount: normalized.length,
1021
+ status: "completed",
1022
+ ingestedAt: new Date
1023
+ };
1024
+ });
1025
+ installOp4(registry, BenchmarkRunCustomCommand, async (args) => {
1026
+ return {
1027
+ runId: `custom-${Date.now()}`,
1028
+ evalSuiteKey: args.evalSuiteKey,
1029
+ modelId: args.modelId,
1030
+ status: "started",
1031
+ startedAt: new Date
1032
+ };
1033
+ });
1034
+ installOp4(registry, RankingRefreshCommand, async (args) => {
1035
+ const store = getStore();
1036
+ const allResults = [];
1037
+ let offset = 0;
1038
+ const pageSize = 500;
1039
+ while (true) {
1040
+ const page = await store.listBenchmarkResults({
1041
+ limit: pageSize,
1042
+ offset
1043
+ });
1044
+ allResults.push(...page.results);
1045
+ if (allResults.length >= page.total || page.results.length < pageSize)
1046
+ break;
1047
+ offset += pageSize;
1048
+ }
1049
+ const existingRankings = new Map((await store.listModelRankings({ limit: 1e4 })).rankings.map((r) => [
1050
+ r.modelId,
1051
+ r
1052
+ ]));
1053
+ const weightOverrides = args.weightOverrides ? Array.isArray(args.weightOverrides) ? args.weightOverrides : [args.weightOverrides] : undefined;
1054
+ const newRankings = computeModelRankings(allResults, weightOverrides ? {
1055
+ weightOverrides
1056
+ } : undefined, existingRankings);
1057
+ for (const ranking of newRankings) {
1058
+ await store.upsertModelRanking(ranking);
1059
+ }
1060
+ return {
1061
+ modelsRanked: newRankings.length,
1062
+ updatedAt: new Date,
1063
+ status: "completed"
1064
+ };
1065
+ });
1066
+ return registry;
1067
+ }
1068
+ function createProviderRankingMcpHandler(path2 = "/api/mcp/ranking") {
1069
+ return createMcpElysiaHandler({
1070
+ logger: appLogger,
1071
+ path: path2,
1072
+ serverName: "contractspec-ranking-mcp",
1073
+ ops: buildRankingOps(),
1074
+ resources: buildRankingResources(),
1075
+ prompts: buildRankingPrompts()
1076
+ });
1077
+ }
1078
+ function setProviderRankingStore(store) {
1079
+ sharedStore = store;
1080
+ }
1081
+
1082
+ // src/application/mcp/contractsMcpTools.ts
1083
+ import {
1084
+ defineCommand as defineCommand3,
1085
+ installOp as installOp5,
1086
+ OperationSpecRegistry as OperationSpecRegistry5
1087
+ } from "@contractspec/lib.contracts-spec";
1088
+ import { defineSchemaModel as defineSchemaModel3, ScalarTypeEnum as ScalarTypeEnum3 } from "@contractspec/lib.schema";
1089
+ var OWNERS = ["@contractspec"];
1090
+ var TAGS = ["contracts", "mcp"];
1091
+ function buildContractsOps(services) {
1092
+ const registry = new OperationSpecRegistry5;
1093
+ const ListInput = defineSchemaModel3({
1094
+ name: "ContractsListInput",
1095
+ fields: {
1096
+ pattern: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
1097
+ type: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true }
1098
+ }
1099
+ });
1100
+ const ListOutput = defineSchemaModel3({
1101
+ name: "ContractsListOutput",
1102
+ fields: {
1103
+ specs: { type: ScalarTypeEnum3.JSON(), isOptional: false },
1104
+ total: { type: ScalarTypeEnum3.Int_unsecure(), isOptional: false }
1105
+ }
1106
+ });
1107
+ installOp5(registry, defineCommand3({
1108
+ meta: {
1109
+ key: "contracts.list",
1110
+ version: "1.0.0",
1111
+ stability: "beta",
1112
+ owners: OWNERS,
1113
+ tags: TAGS,
1114
+ description: "List contract specs in the workspace.",
1115
+ goal: "Discover available contracts by type, pattern, or owner.",
1116
+ context: "Contracts MCP server."
1117
+ },
1118
+ io: { input: ListInput, output: ListOutput },
1119
+ policy: { auth: "anonymous" }
1120
+ }), async ({ pattern, type }) => {
1121
+ const specs = await services.listSpecs({ pattern, type });
1122
+ return { specs, total: specs.length };
1123
+ });
1124
+ const GetInput = defineSchemaModel3({
1125
+ name: "ContractsGetInput",
1126
+ fields: {
1127
+ path: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false }
1128
+ }
1129
+ });
1130
+ const GetOutput = defineSchemaModel3({
1131
+ name: "ContractsGetOutput",
1132
+ fields: {
1133
+ content: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1134
+ info: { type: ScalarTypeEnum3.JSON(), isOptional: false }
1135
+ }
1136
+ });
1137
+ installOp5(registry, defineCommand3({
1138
+ meta: {
1139
+ key: "contracts.get",
1140
+ version: "1.0.0",
1141
+ stability: "beta",
1142
+ owners: OWNERS,
1143
+ tags: TAGS,
1144
+ description: "Read a single contract spec file.",
1145
+ goal: "Fetch spec content and parsed metadata.",
1146
+ context: "Contracts MCP server."
1147
+ },
1148
+ io: { input: GetInput, output: GetOutput },
1149
+ policy: { auth: "anonymous" }
1150
+ }), async ({ path: path2 }) => {
1151
+ const result = await services.getSpec(path2);
1152
+ if (!result)
1153
+ throw new Error(`Spec not found: ${path2}`);
1154
+ return result;
1155
+ });
1156
+ const ValidateInput = defineSchemaModel3({
1157
+ name: "ContractsValidateInput",
1158
+ fields: {
1159
+ path: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false }
1160
+ }
1161
+ });
1162
+ const ValidateOutput = defineSchemaModel3({
1163
+ name: "ContractsValidateOutput",
1164
+ fields: {
1165
+ valid: { type: ScalarTypeEnum3.Boolean(), isOptional: false },
1166
+ errors: { type: ScalarTypeEnum3.JSON(), isOptional: false },
1167
+ warnings: { type: ScalarTypeEnum3.JSON(), isOptional: false }
1168
+ }
1169
+ });
1170
+ installOp5(registry, defineCommand3({
1171
+ meta: {
1172
+ key: "contracts.validate",
1173
+ version: "1.0.0",
1174
+ stability: "beta",
1175
+ owners: OWNERS,
1176
+ tags: TAGS,
1177
+ description: "Validate a contract spec structure.",
1178
+ goal: "Check spec for structural or policy issues.",
1179
+ context: "Contracts MCP server."
1180
+ },
1181
+ io: { input: ValidateInput, output: ValidateOutput },
1182
+ policy: { auth: "anonymous" }
1183
+ }), async ({ path: path2 }) => services.validateSpec(path2));
1184
+ const BuildInput = defineSchemaModel3({
1185
+ name: "ContractsBuildInput",
1186
+ fields: {
1187
+ path: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1188
+ dryRun: { type: ScalarTypeEnum3.Boolean(), isOptional: true }
1189
+ }
1190
+ });
1191
+ const BuildOutput = defineSchemaModel3({
1192
+ name: "ContractsBuildOutput",
1193
+ fields: {
1194
+ results: { type: ScalarTypeEnum3.JSON(), isOptional: false }
1195
+ }
1196
+ });
1197
+ installOp5(registry, defineCommand3({
1198
+ meta: {
1199
+ key: "contracts.build",
1200
+ version: "1.0.0",
1201
+ stability: "beta",
1202
+ owners: OWNERS,
1203
+ tags: TAGS,
1204
+ description: "Generate implementation code from a contract spec.",
1205
+ goal: "Produce handler, component, or test skeletons.",
1206
+ context: "Contracts MCP server."
1207
+ },
1208
+ io: { input: BuildInput, output: BuildOutput },
1209
+ policy: { auth: "user" }
1210
+ }), async ({ path: path2, dryRun }) => services.buildSpec(path2, { dryRun }));
1211
+ registerMutationTools(registry, services);
1212
+ return registry;
1213
+ }
1214
+ function registerMutationTools(registry, services) {
1215
+ const UpdateInput = defineSchemaModel3({
1216
+ name: "ContractsUpdateInput",
1217
+ fields: {
1218
+ path: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1219
+ content: { type: ScalarTypeEnum3.String_unsecure(), isOptional: true },
1220
+ fields: { type: ScalarTypeEnum3.JSON(), isOptional: true }
1221
+ }
1222
+ });
1223
+ const UpdateOutput = defineSchemaModel3({
1224
+ name: "ContractsUpdateOutput",
1225
+ fields: {
1226
+ updated: { type: ScalarTypeEnum3.Boolean(), isOptional: false },
1227
+ errors: { type: ScalarTypeEnum3.JSON(), isOptional: false },
1228
+ warnings: { type: ScalarTypeEnum3.JSON(), isOptional: false }
1229
+ }
1230
+ });
1231
+ installOp5(registry, defineCommand3({
1232
+ meta: {
1233
+ key: "contracts.update",
1234
+ version: "1.0.0",
1235
+ stability: "beta",
1236
+ owners: OWNERS,
1237
+ tags: TAGS,
1238
+ description: "Update an existing contract spec.",
1239
+ goal: "Modify spec content or individual fields with validation.",
1240
+ context: "Contracts MCP server."
1241
+ },
1242
+ io: { input: UpdateInput, output: UpdateOutput },
1243
+ policy: { auth: "user" }
1244
+ }), async ({ path: path2, content, fields }) => services.updateSpec(path2, {
1245
+ content,
1246
+ fields: Array.isArray(fields) ? fields : undefined
1247
+ }));
1248
+ const DeleteInput = defineSchemaModel3({
1249
+ name: "ContractsDeleteInput",
1250
+ fields: {
1251
+ path: { type: ScalarTypeEnum3.String_unsecure(), isOptional: false },
1252
+ clean: { type: ScalarTypeEnum3.Boolean(), isOptional: true }
1253
+ }
1254
+ });
1255
+ const DeleteOutput = defineSchemaModel3({
1256
+ name: "ContractsDeleteOutput",
1257
+ fields: {
1258
+ deleted: { type: ScalarTypeEnum3.Boolean(), isOptional: false },
1259
+ cleanedFiles: { type: ScalarTypeEnum3.JSON(), isOptional: false },
1260
+ errors: { type: ScalarTypeEnum3.JSON(), isOptional: false }
1261
+ }
1262
+ });
1263
+ installOp5(registry, defineCommand3({
1264
+ meta: {
1265
+ key: "contracts.delete",
1266
+ version: "1.0.0",
1267
+ stability: "beta",
1268
+ owners: OWNERS,
1269
+ tags: TAGS,
1270
+ description: "Delete a contract spec and optionally its artifacts.",
1271
+ goal: "Remove a spec file and clean generated handlers/tests.",
1272
+ context: "Contracts MCP server."
1273
+ },
1274
+ io: { input: DeleteInput, output: DeleteOutput },
1275
+ policy: { auth: "user" }
1276
+ }), async ({ path: path2, clean }) => services.deleteSpec(path2, { clean }));
1277
+ }
1278
+
1279
+ // src/application/mcp/contractsMcpResources.ts
1280
+ import {
1281
+ definePrompt as definePrompt5,
1282
+ defineResourceTemplate as defineResourceTemplate5,
1283
+ PromptRegistry as PromptRegistry5,
1284
+ ResourceRegistry as ResourceRegistry5
1285
+ } from "@contractspec/lib.contracts-spec";
1286
+ import z5 from "zod";
1287
+ var OWNERS2 = ["@contractspec"];
1288
+ var TAGS2 = ["contracts", "mcp"];
1289
+ function buildContractsResources(services) {
1290
+ const resources = new ResourceRegistry5;
1291
+ resources.register(defineResourceTemplate5({
1292
+ meta: {
1293
+ uriTemplate: "contracts://list",
1294
+ title: "Contract specs list",
1295
+ description: "JSON list of all contract specs in the workspace.",
1296
+ mimeType: "application/json",
1297
+ tags: TAGS2
1298
+ },
1299
+ input: z5.object({}),
1300
+ resolve: async () => {
1301
+ const specs = await services.listSpecs();
1302
+ return {
1303
+ uri: "contracts://list",
1304
+ mimeType: "application/json",
1305
+ data: JSON.stringify(specs, null, 2)
1306
+ };
1307
+ }
1308
+ }));
1309
+ resources.register(defineResourceTemplate5({
1310
+ meta: {
1311
+ uriTemplate: "contracts://spec/{path}",
1312
+ title: "Contract spec content",
1313
+ description: "Read a single contract spec file by path.",
1314
+ mimeType: "text/plain",
1315
+ tags: TAGS2
1316
+ },
1317
+ input: z5.object({ path: z5.string() }),
1318
+ resolve: async ({ path: path2 }) => {
1319
+ const result = await services.getSpec(path2);
1320
+ if (!result) {
1321
+ return {
1322
+ uri: `contracts://spec/${encodeURIComponent(path2)}`,
1323
+ mimeType: "text/plain",
1324
+ data: `Spec not found: ${path2}`
1325
+ };
1326
+ }
1327
+ return {
1328
+ uri: `contracts://spec/${encodeURIComponent(path2)}`,
1329
+ mimeType: "text/plain",
1330
+ data: result.content
1331
+ };
1332
+ }
1333
+ }));
1334
+ resources.register(defineResourceTemplate5({
1335
+ meta: {
1336
+ uriTemplate: "contracts://registry/manifest",
1337
+ title: "Remote registry manifest",
1338
+ description: "Contract registry manifest from the remote server.",
1339
+ mimeType: "application/json",
1340
+ tags: TAGS2
1341
+ },
1342
+ input: z5.object({}),
1343
+ resolve: async () => {
1344
+ const manifest = await services.fetchRegistryManifest();
1345
+ return {
1346
+ uri: "contracts://registry/manifest",
1347
+ mimeType: "application/json",
1348
+ data: JSON.stringify(manifest, null, 2)
1349
+ };
1350
+ }
1351
+ }));
1352
+ return resources;
1353
+ }
1354
+ function buildContractsPrompts() {
1355
+ const prompts = new PromptRegistry5;
1356
+ prompts.register(definePrompt5({
1357
+ meta: {
1358
+ key: "contracts.editor",
1359
+ version: "1.0.0",
1360
+ title: "Contract editing guide",
1361
+ description: "Guide AI agents through reading, editing, and validating contracts.",
1362
+ tags: TAGS2,
1363
+ stability: "beta",
1364
+ owners: OWNERS2
1365
+ },
1366
+ args: [
1367
+ {
1368
+ name: "goal",
1369
+ description: "What the agent wants to achieve with the contract.",
1370
+ required: false,
1371
+ schema: z5.string().optional()
1372
+ }
1373
+ ],
1374
+ input: z5.object({ goal: z5.string().optional() }),
1375
+ render: async ({ goal }) => [
1376
+ {
1377
+ type: "text",
1378
+ text: [
1379
+ "Contract editing workflow:",
1380
+ "1. Use contracts.list to discover specs",
1381
+ "2. Use contracts.get to read a spec",
1382
+ "3. Edit content and call contracts.update",
1383
+ "4. Run contracts.validate to verify changes",
1384
+ "5. Run contracts.build to regenerate artifacts",
1385
+ goal ? `Agent goal: ${goal}` : ""
1386
+ ].filter(Boolean).join(`
1387
+ `)
1388
+ },
1389
+ {
1390
+ type: "resource",
1391
+ uri: "contracts://list",
1392
+ title: "Available contracts"
1393
+ }
1394
+ ]
1395
+ }));
1396
+ return prompts;
1397
+ }
1398
+
1399
+ // src/application/mcp/contractsMcp.ts
1400
+ function createContractsMcpHandler(path2 = "/api/mcp/contracts", services) {
1401
+ return createMcpElysiaHandler({
1402
+ logger: appLogger,
1403
+ path: path2,
1404
+ serverName: "contractspec-contracts-mcp",
1405
+ ops: buildContractsOps(services),
1406
+ resources: buildContractsResources(services),
1407
+ prompts: buildContractsPrompts()
1408
+ });
1409
+ }
780
1410
  export {
1411
+ setProviderRankingStore,
1412
+ createProviderRankingMcpHandler,
781
1413
  createInternalMcpHandler,
782
1414
  createDocsMcpHandler,
1415
+ createContractsMcpHandler,
783
1416
  createCliMcpHandler
784
1417
  };
@@ -73,9 +73,13 @@ function createMcpElysiaHandler({
73
73
  ops,
74
74
  resources,
75
75
  prompts,
76
- presentations
76
+ presentations,
77
+ validateAuth,
78
+ requiredAuthMethods
77
79
  }) {
78
- logger.info("Setting up MCP handler...");
80
+ logger.info("Setting up MCP handler...", {
81
+ requiredAuthMethods: requiredAuthMethods ?? []
82
+ });
79
83
  const isStateful = process.env.CONTRACTSPEC_MCP_STATEFUL === "1";
80
84
  const sessions = new Map;
81
85
  async function handleStateless(request) {
@@ -146,6 +150,12 @@ function createMcpElysiaHandler({
146
150
  }
147
151
  return new Elysia({ name: `mcp-${serverName}` }).all(path, async ({ request }) => {
148
152
  try {
153
+ if (validateAuth) {
154
+ const authResult = await validateAuth(request);
155
+ if (!authResult.valid) {
156
+ return createJsonRpcErrorResponse(401, -32002, "Authentication failed", authResult.reason);
157
+ }
158
+ }
149
159
  if (isStateful) {
150
160
  return await handleStateful(request);
151
161
  }