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