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