@contractspec/bundle.library 2.9.1 → 3.1.1
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 +240 -214
- package/AGENTS.md +19 -12
- package/CHANGELOG.md +84 -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/components/docs/DocsIndexPage.js +1 -1
- package/dist/components/docs/architecture/ArchitectureControlPlanePage.d.ts +1 -0
- package/dist/components/docs/architecture/ArchitectureControlPlanePage.js +204 -0
- package/dist/components/docs/architecture/ArchitectureOverviewPage.js +17 -1
- package/dist/components/docs/architecture/index.d.ts +1 -0
- package/dist/components/docs/architecture/index.js +507 -289
- package/dist/components/docs/ecosystem/IntegrationsPage.js +6 -3
- package/dist/components/docs/ecosystem/PluginsPage.js +98 -98
- package/dist/components/docs/ecosystem/RegistryPage.js +39 -42
- package/dist/components/docs/ecosystem/TemplatesPage.js +26 -21
- package/dist/components/docs/ecosystem/ecosystem.docblocks.js +10 -10
- package/dist/components/docs/ecosystem/index.js +179 -174
- package/dist/components/docs/index.js +6795 -5376
- package/dist/components/docs/integrations/IntegrationsElevenLabsPage.js +2 -2
- package/dist/components/docs/integrations/IntegrationsGithubPage.d.ts +1 -0
- package/dist/components/docs/integrations/IntegrationsGithubPage.js +155 -0
- package/dist/components/docs/integrations/IntegrationsHealthRoutingPage.d.ts +1 -0
- package/dist/components/docs/integrations/IntegrationsHealthRoutingPage.js +168 -0
- package/dist/components/docs/integrations/IntegrationsMistralPage.d.ts +1 -0
- package/dist/components/docs/integrations/IntegrationsMistralPage.js +203 -0
- package/dist/components/docs/integrations/IntegrationsOpenAIPage.js +2 -2
- package/dist/components/docs/integrations/IntegrationsOverviewPage.js +136 -9
- package/dist/components/docs/integrations/IntegrationsSlackPage.d.ts +1 -0
- package/dist/components/docs/integrations/IntegrationsSlackPage.js +161 -0
- package/dist/components/docs/integrations/IntegrationsSpecModelPage.js +72 -0
- package/dist/components/docs/integrations/IntegrationsTwilioPage.js +2 -2
- package/dist/components/docs/integrations/IntegrationsWhatsappMetaPage.d.ts +1 -0
- package/dist/components/docs/integrations/IntegrationsWhatsappMetaPage.js +157 -0
- package/dist/components/docs/integrations/IntegrationsWhatsappTwilioPage.d.ts +1 -0
- package/dist/components/docs/integrations/IntegrationsWhatsappTwilioPage.js +165 -0
- package/dist/components/docs/integrations/index.d.ts +6 -0
- package/dist/components/docs/integrations/index.js +1688 -492
- package/dist/index.js +8016 -6597
- 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/dist/node/components/docs/DocsIndexPage.js +1 -1
- package/dist/node/components/docs/architecture/ArchitectureControlPlanePage.js +203 -0
- package/dist/node/components/docs/architecture/ArchitectureOverviewPage.js +17 -1
- package/dist/node/components/docs/architecture/index.js +507 -289
- package/dist/node/components/docs/ecosystem/IntegrationsPage.js +6 -3
- package/dist/node/components/docs/ecosystem/PluginsPage.js +98 -98
- package/dist/node/components/docs/ecosystem/RegistryPage.js +39 -42
- package/dist/node/components/docs/ecosystem/TemplatesPage.js +26 -21
- package/dist/node/components/docs/ecosystem/ecosystem.docblocks.js +10 -10
- package/dist/node/components/docs/ecosystem/index.js +179 -174
- package/dist/node/components/docs/index.js +6795 -5376
- package/dist/node/components/docs/integrations/IntegrationsElevenLabsPage.js +2 -2
- package/dist/node/components/docs/integrations/IntegrationsGithubPage.js +154 -0
- package/dist/node/components/docs/integrations/IntegrationsHealthRoutingPage.js +167 -0
- package/dist/node/components/docs/integrations/IntegrationsMistralPage.js +202 -0
- package/dist/node/components/docs/integrations/IntegrationsOpenAIPage.js +2 -2
- package/dist/node/components/docs/integrations/IntegrationsOverviewPage.js +136 -9
- package/dist/node/components/docs/integrations/IntegrationsSlackPage.js +160 -0
- package/dist/node/components/docs/integrations/IntegrationsSpecModelPage.js +72 -0
- package/dist/node/components/docs/integrations/IntegrationsTwilioPage.js +2 -2
- package/dist/node/components/docs/integrations/IntegrationsWhatsappMetaPage.js +156 -0
- package/dist/node/components/docs/integrations/IntegrationsWhatsappTwilioPage.js +164 -0
- package/dist/node/components/docs/integrations/index.js +1688 -492
- package/dist/node/index.js +8016 -6597
- package/package.json +195 -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/DocsIndexPage.tsx +2 -1
- package/src/components/docs/architecture/ArchitectureControlPlanePage.tsx +136 -0
- package/src/components/docs/architecture/ArchitectureOverviewPage.tsx +13 -1
- package/src/components/docs/architecture/index.ts +1 -0
- package/src/components/docs/ecosystem/IntegrationsPage.tsx +4 -3
- package/src/components/docs/ecosystem/PluginsPage.tsx +68 -87
- package/src/components/docs/ecosystem/RegistryPage.tsx +35 -43
- package/src/components/docs/ecosystem/TemplatesPage.tsx +28 -21
- package/src/components/docs/ecosystem/ecosystem.docblocks.ts +12 -10
- package/src/components/docs/generated/docs-index._common.json +1119 -1
- package/src/components/docs/generated/docs-index.health.json +98 -0
- package/src/components/docs/generated/docs-index.manifest.json +15 -5
- package/src/components/docs/generated/docs-index.metrics.json +8 -0
- package/src/components/docs/generated/docs-index.platform-integrations.json +89 -1
- package/src/components/docs/generated/docs-index.video-api-showcase.json +26 -0
- package/src/components/docs/integrations/IntegrationsElevenLabsPage.tsx +2 -2
- package/src/components/docs/integrations/IntegrationsGithubPage.tsx +90 -0
- package/src/components/docs/integrations/IntegrationsHealthRoutingPage.tsx +112 -0
- package/src/components/docs/integrations/IntegrationsMistralPage.tsx +133 -0
- package/src/components/docs/integrations/IntegrationsOpenAIPage.tsx +2 -2
- package/src/components/docs/integrations/IntegrationsOverviewPage.tsx +108 -9
- package/src/components/docs/integrations/IntegrationsSlackPage.tsx +98 -0
- package/src/components/docs/integrations/IntegrationsSpecModelPage.tsx +59 -0
- package/src/components/docs/integrations/IntegrationsTwilioPage.tsx +2 -2
- package/src/components/docs/integrations/IntegrationsWhatsappMetaPage.tsx +90 -0
- package/src/components/docs/integrations/IntegrationsWhatsappTwilioPage.tsx +92 -0
- package/src/components/docs/integrations/index.ts +6 -0
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contract management MCP tool definitions.
|
|
3
|
+
*
|
|
4
|
+
* Each tool delegates to an injected service so the bundle stays
|
|
5
|
+
* decoupled from bundle.workspace (apps layer does the wiring).
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
defineCommand,
|
|
10
|
+
installOp,
|
|
11
|
+
OperationSpecRegistry,
|
|
12
|
+
} from '@contractspec/lib.contracts-spec';
|
|
13
|
+
import { defineSchemaModel, ScalarTypeEnum } from '@contractspec/lib.schema';
|
|
14
|
+
import type { ContractsMcpServices } from './contractsMcpTypes';
|
|
15
|
+
|
|
16
|
+
const OWNERS = ['@contractspec'];
|
|
17
|
+
const TAGS = ['contracts', 'mcp'];
|
|
18
|
+
|
|
19
|
+
export function buildContractsOps(services: ContractsMcpServices) {
|
|
20
|
+
const registry = new OperationSpecRegistry();
|
|
21
|
+
|
|
22
|
+
// -- contracts.list --
|
|
23
|
+
const ListInput = defineSchemaModel({
|
|
24
|
+
name: 'ContractsListInput',
|
|
25
|
+
fields: {
|
|
26
|
+
pattern: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
27
|
+
type: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const ListOutput = defineSchemaModel({
|
|
31
|
+
name: 'ContractsListOutput',
|
|
32
|
+
fields: {
|
|
33
|
+
specs: { type: ScalarTypeEnum.JSON(), isOptional: false },
|
|
34
|
+
total: { type: ScalarTypeEnum.Int_unsecure(), isOptional: false },
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
installOp(
|
|
38
|
+
registry,
|
|
39
|
+
defineCommand({
|
|
40
|
+
meta: {
|
|
41
|
+
key: 'contracts.list',
|
|
42
|
+
version: '1.0.0',
|
|
43
|
+
stability: 'beta',
|
|
44
|
+
owners: OWNERS,
|
|
45
|
+
tags: TAGS,
|
|
46
|
+
description: 'List contract specs in the workspace.',
|
|
47
|
+
goal: 'Discover available contracts by type, pattern, or owner.',
|
|
48
|
+
context: 'Contracts MCP server.',
|
|
49
|
+
},
|
|
50
|
+
io: { input: ListInput, output: ListOutput },
|
|
51
|
+
policy: { auth: 'anonymous' },
|
|
52
|
+
}),
|
|
53
|
+
async ({ pattern, type }) => {
|
|
54
|
+
const specs = await services.listSpecs({ pattern, type });
|
|
55
|
+
return { specs, total: specs.length };
|
|
56
|
+
}
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// -- contracts.get --
|
|
60
|
+
const GetInput = defineSchemaModel({
|
|
61
|
+
name: 'ContractsGetInput',
|
|
62
|
+
fields: {
|
|
63
|
+
path: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
const GetOutput = defineSchemaModel({
|
|
67
|
+
name: 'ContractsGetOutput',
|
|
68
|
+
fields: {
|
|
69
|
+
content: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
70
|
+
info: { type: ScalarTypeEnum.JSON(), isOptional: false },
|
|
71
|
+
},
|
|
72
|
+
});
|
|
73
|
+
installOp(
|
|
74
|
+
registry,
|
|
75
|
+
defineCommand({
|
|
76
|
+
meta: {
|
|
77
|
+
key: 'contracts.get',
|
|
78
|
+
version: '1.0.0',
|
|
79
|
+
stability: 'beta',
|
|
80
|
+
owners: OWNERS,
|
|
81
|
+
tags: TAGS,
|
|
82
|
+
description: 'Read a single contract spec file.',
|
|
83
|
+
goal: 'Fetch spec content and parsed metadata.',
|
|
84
|
+
context: 'Contracts MCP server.',
|
|
85
|
+
},
|
|
86
|
+
io: { input: GetInput, output: GetOutput },
|
|
87
|
+
policy: { auth: 'anonymous' },
|
|
88
|
+
}),
|
|
89
|
+
async ({ path }) => {
|
|
90
|
+
const result = await services.getSpec(path);
|
|
91
|
+
if (!result) throw new Error(`Spec not found: ${path}`);
|
|
92
|
+
return result;
|
|
93
|
+
}
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
// -- contracts.validate --
|
|
97
|
+
const ValidateInput = defineSchemaModel({
|
|
98
|
+
name: 'ContractsValidateInput',
|
|
99
|
+
fields: {
|
|
100
|
+
path: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
101
|
+
},
|
|
102
|
+
});
|
|
103
|
+
const ValidateOutput = defineSchemaModel({
|
|
104
|
+
name: 'ContractsValidateOutput',
|
|
105
|
+
fields: {
|
|
106
|
+
valid: { type: ScalarTypeEnum.Boolean(), isOptional: false },
|
|
107
|
+
errors: { type: ScalarTypeEnum.JSON(), isOptional: false },
|
|
108
|
+
warnings: { type: ScalarTypeEnum.JSON(), isOptional: false },
|
|
109
|
+
},
|
|
110
|
+
});
|
|
111
|
+
installOp(
|
|
112
|
+
registry,
|
|
113
|
+
defineCommand({
|
|
114
|
+
meta: {
|
|
115
|
+
key: 'contracts.validate',
|
|
116
|
+
version: '1.0.0',
|
|
117
|
+
stability: 'beta',
|
|
118
|
+
owners: OWNERS,
|
|
119
|
+
tags: TAGS,
|
|
120
|
+
description: 'Validate a contract spec structure.',
|
|
121
|
+
goal: 'Check spec for structural or policy issues.',
|
|
122
|
+
context: 'Contracts MCP server.',
|
|
123
|
+
},
|
|
124
|
+
io: { input: ValidateInput, output: ValidateOutput },
|
|
125
|
+
policy: { auth: 'anonymous' },
|
|
126
|
+
}),
|
|
127
|
+
async ({ path }) => services.validateSpec(path)
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// -- contracts.build --
|
|
131
|
+
const BuildInput = defineSchemaModel({
|
|
132
|
+
name: 'ContractsBuildInput',
|
|
133
|
+
fields: {
|
|
134
|
+
path: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
135
|
+
dryRun: { type: ScalarTypeEnum.Boolean(), isOptional: true },
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
const BuildOutput = defineSchemaModel({
|
|
139
|
+
name: 'ContractsBuildOutput',
|
|
140
|
+
fields: {
|
|
141
|
+
results: { type: ScalarTypeEnum.JSON(), isOptional: false },
|
|
142
|
+
},
|
|
143
|
+
});
|
|
144
|
+
installOp(
|
|
145
|
+
registry,
|
|
146
|
+
defineCommand({
|
|
147
|
+
meta: {
|
|
148
|
+
key: 'contracts.build',
|
|
149
|
+
version: '1.0.0',
|
|
150
|
+
stability: 'beta',
|
|
151
|
+
owners: OWNERS,
|
|
152
|
+
tags: TAGS,
|
|
153
|
+
description: 'Generate implementation code from a contract spec.',
|
|
154
|
+
goal: 'Produce handler, component, or test skeletons.',
|
|
155
|
+
context: 'Contracts MCP server.',
|
|
156
|
+
},
|
|
157
|
+
io: { input: BuildInput, output: BuildOutput },
|
|
158
|
+
policy: { auth: 'user' },
|
|
159
|
+
}),
|
|
160
|
+
async ({ path, dryRun }) => services.buildSpec(path, { dryRun })
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
registerMutationTools(registry, services);
|
|
164
|
+
|
|
165
|
+
return registry;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function registerMutationTools(
|
|
169
|
+
registry: OperationSpecRegistry,
|
|
170
|
+
services: ContractsMcpServices
|
|
171
|
+
) {
|
|
172
|
+
// -- contracts.update --
|
|
173
|
+
const UpdateInput = defineSchemaModel({
|
|
174
|
+
name: 'ContractsUpdateInput',
|
|
175
|
+
fields: {
|
|
176
|
+
path: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
177
|
+
content: { type: ScalarTypeEnum.String_unsecure(), isOptional: true },
|
|
178
|
+
fields: { type: ScalarTypeEnum.JSON(), isOptional: true },
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
const UpdateOutput = defineSchemaModel({
|
|
182
|
+
name: 'ContractsUpdateOutput',
|
|
183
|
+
fields: {
|
|
184
|
+
updated: { type: ScalarTypeEnum.Boolean(), isOptional: false },
|
|
185
|
+
errors: { type: ScalarTypeEnum.JSON(), isOptional: false },
|
|
186
|
+
warnings: { type: ScalarTypeEnum.JSON(), isOptional: false },
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
installOp(
|
|
190
|
+
registry,
|
|
191
|
+
defineCommand({
|
|
192
|
+
meta: {
|
|
193
|
+
key: 'contracts.update',
|
|
194
|
+
version: '1.0.0',
|
|
195
|
+
stability: 'beta',
|
|
196
|
+
owners: OWNERS,
|
|
197
|
+
tags: TAGS,
|
|
198
|
+
description: 'Update an existing contract spec.',
|
|
199
|
+
goal: 'Modify spec content or individual fields with validation.',
|
|
200
|
+
context: 'Contracts MCP server.',
|
|
201
|
+
},
|
|
202
|
+
io: { input: UpdateInput, output: UpdateOutput },
|
|
203
|
+
policy: { auth: 'user' },
|
|
204
|
+
}),
|
|
205
|
+
async ({ path, content, fields }) =>
|
|
206
|
+
services.updateSpec(path, {
|
|
207
|
+
content,
|
|
208
|
+
fields: Array.isArray(fields) ? (fields as unknown[]) : undefined,
|
|
209
|
+
})
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
// -- contracts.delete --
|
|
213
|
+
const DeleteInput = defineSchemaModel({
|
|
214
|
+
name: 'ContractsDeleteInput',
|
|
215
|
+
fields: {
|
|
216
|
+
path: { type: ScalarTypeEnum.String_unsecure(), isOptional: false },
|
|
217
|
+
clean: { type: ScalarTypeEnum.Boolean(), isOptional: true },
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
const DeleteOutput = defineSchemaModel({
|
|
221
|
+
name: 'ContractsDeleteOutput',
|
|
222
|
+
fields: {
|
|
223
|
+
deleted: { type: ScalarTypeEnum.Boolean(), isOptional: false },
|
|
224
|
+
cleanedFiles: { type: ScalarTypeEnum.JSON(), isOptional: false },
|
|
225
|
+
errors: { type: ScalarTypeEnum.JSON(), isOptional: false },
|
|
226
|
+
},
|
|
227
|
+
});
|
|
228
|
+
installOp(
|
|
229
|
+
registry,
|
|
230
|
+
defineCommand({
|
|
231
|
+
meta: {
|
|
232
|
+
key: 'contracts.delete',
|
|
233
|
+
version: '1.0.0',
|
|
234
|
+
stability: 'beta',
|
|
235
|
+
owners: OWNERS,
|
|
236
|
+
tags: TAGS,
|
|
237
|
+
description: 'Delete a contract spec and optionally its artifacts.',
|
|
238
|
+
goal: 'Remove a spec file and clean generated handlers/tests.',
|
|
239
|
+
context: 'Contracts MCP server.',
|
|
240
|
+
},
|
|
241
|
+
io: { input: DeleteInput, output: DeleteOutput },
|
|
242
|
+
policy: { auth: 'user' },
|
|
243
|
+
}),
|
|
244
|
+
async ({ path, clean }) => services.deleteSpec(path, { clean })
|
|
245
|
+
);
|
|
246
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Contracts MCP service interface.
|
|
3
|
+
*
|
|
4
|
+
* Defines the dependency-injection boundary so `bundle.library` stays
|
|
5
|
+
* decoupled from `bundle.workspace`. The app layer wires real impls.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export interface ContractInfo {
|
|
9
|
+
specType: string;
|
|
10
|
+
filePath: string;
|
|
11
|
+
key?: string;
|
|
12
|
+
version?: string;
|
|
13
|
+
kind?: string;
|
|
14
|
+
description?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface ContractsMcpServices {
|
|
18
|
+
listSpecs(options?: {
|
|
19
|
+
pattern?: string;
|
|
20
|
+
type?: string;
|
|
21
|
+
}): Promise<ContractInfo[]>;
|
|
22
|
+
|
|
23
|
+
getSpec(
|
|
24
|
+
path: string
|
|
25
|
+
): Promise<{ content: string; info: ContractInfo } | null>;
|
|
26
|
+
|
|
27
|
+
validateSpec(
|
|
28
|
+
path: string
|
|
29
|
+
): Promise<{ valid: boolean; errors: string[]; warnings: string[] }>;
|
|
30
|
+
|
|
31
|
+
buildSpec(
|
|
32
|
+
path: string,
|
|
33
|
+
options?: { dryRun?: boolean }
|
|
34
|
+
): Promise<{ results: unknown[] }>;
|
|
35
|
+
|
|
36
|
+
updateSpec(
|
|
37
|
+
path: string,
|
|
38
|
+
options: { content?: string; fields?: unknown[] }
|
|
39
|
+
): Promise<{ updated: boolean; errors: string[]; warnings: string[] }>;
|
|
40
|
+
|
|
41
|
+
deleteSpec(
|
|
42
|
+
path: string,
|
|
43
|
+
options?: { clean?: boolean }
|
|
44
|
+
): Promise<{ deleted: boolean; cleanedFiles: string[]; errors: string[] }>;
|
|
45
|
+
|
|
46
|
+
fetchRegistryManifest(): Promise<unknown>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,380 @@
|
|
|
1
|
+
import {
|
|
2
|
+
definePrompt,
|
|
3
|
+
defineResourceTemplate,
|
|
4
|
+
installOp,
|
|
5
|
+
OperationSpecRegistry,
|
|
6
|
+
PromptRegistry,
|
|
7
|
+
ResourceRegistry,
|
|
8
|
+
} from '@contractspec/lib.contracts-spec';
|
|
9
|
+
import {
|
|
10
|
+
BenchmarkIngestCommand,
|
|
11
|
+
BenchmarkRunCustomCommand,
|
|
12
|
+
RankingRefreshCommand,
|
|
13
|
+
} from '@contractspec/lib.contracts-spec/provider-ranking';
|
|
14
|
+
import z from 'zod';
|
|
15
|
+
import { createMcpElysiaHandler } from './common';
|
|
16
|
+
import { appLogger } from '../../infrastructure/elysia/logger';
|
|
17
|
+
import type { ProviderRankingStore } from '@contractspec/lib.provider-ranking/store';
|
|
18
|
+
import type {
|
|
19
|
+
ProviderTransportSupport,
|
|
20
|
+
ProviderAuthSupport,
|
|
21
|
+
} from '@contractspec/lib.provider-ranking/types';
|
|
22
|
+
import { InMemoryProviderRankingStore } from '@contractspec/lib.provider-ranking/in-memory-store';
|
|
23
|
+
import { createDefaultIngesterRegistry } from '@contractspec/lib.provider-ranking/ingesters';
|
|
24
|
+
import { computeModelRankings } from '@contractspec/lib.provider-ranking/scoring';
|
|
25
|
+
import { normalizeBenchmarkResults } from '@contractspec/lib.provider-ranking/scoring';
|
|
26
|
+
|
|
27
|
+
const TransportFilterSchema = z
|
|
28
|
+
.enum(['rest', 'mcp', 'webhook', 'sdk'])
|
|
29
|
+
.optional();
|
|
30
|
+
const AuthFilterSchema = z
|
|
31
|
+
.enum([
|
|
32
|
+
'api-key',
|
|
33
|
+
'oauth2',
|
|
34
|
+
'bearer',
|
|
35
|
+
'header',
|
|
36
|
+
'basic',
|
|
37
|
+
'webhook-signing',
|
|
38
|
+
'service-account',
|
|
39
|
+
])
|
|
40
|
+
.optional();
|
|
41
|
+
|
|
42
|
+
const RANKING_TAGS = ['ranking', 'mcp', 'ai'];
|
|
43
|
+
const RANKING_OWNERS = ['platform.ai'];
|
|
44
|
+
|
|
45
|
+
let sharedStore: ProviderRankingStore | null = null;
|
|
46
|
+
|
|
47
|
+
function getStore(): ProviderRankingStore {
|
|
48
|
+
if (!sharedStore) {
|
|
49
|
+
sharedStore = new InMemoryProviderRankingStore();
|
|
50
|
+
}
|
|
51
|
+
return sharedStore;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function buildRankingResources() {
|
|
55
|
+
const resources = new ResourceRegistry();
|
|
56
|
+
|
|
57
|
+
resources.register(
|
|
58
|
+
defineResourceTemplate({
|
|
59
|
+
meta: {
|
|
60
|
+
uriTemplate: 'ranking://leaderboard',
|
|
61
|
+
title: 'AI Model Leaderboard',
|
|
62
|
+
description:
|
|
63
|
+
'Current ranked list of AI models by composite score. Supports optional transport and authMethod query filters.',
|
|
64
|
+
mimeType: 'application/json',
|
|
65
|
+
tags: RANKING_TAGS,
|
|
66
|
+
},
|
|
67
|
+
input: z.object({
|
|
68
|
+
transport: TransportFilterSchema,
|
|
69
|
+
authMethod: AuthFilterSchema,
|
|
70
|
+
}),
|
|
71
|
+
resolve: async ({ transport, authMethod }) => {
|
|
72
|
+
const store = getStore();
|
|
73
|
+
const result = await store.listModelRankings({
|
|
74
|
+
limit: 100,
|
|
75
|
+
requiredTransport: transport as ProviderTransportSupport | undefined,
|
|
76
|
+
requiredAuthMethod: authMethod as ProviderAuthSupport | undefined,
|
|
77
|
+
});
|
|
78
|
+
return {
|
|
79
|
+
uri: 'ranking://leaderboard',
|
|
80
|
+
mimeType: 'application/json',
|
|
81
|
+
data: JSON.stringify(result, null, 2),
|
|
82
|
+
};
|
|
83
|
+
},
|
|
84
|
+
})
|
|
85
|
+
);
|
|
86
|
+
|
|
87
|
+
resources.register(
|
|
88
|
+
defineResourceTemplate({
|
|
89
|
+
meta: {
|
|
90
|
+
uriTemplate: 'ranking://leaderboard/{dimension}',
|
|
91
|
+
title: 'AI Model Leaderboard by Dimension',
|
|
92
|
+
description:
|
|
93
|
+
'Ranked list of AI models filtered by a specific dimension. Supports optional transport and authMethod query filters.',
|
|
94
|
+
mimeType: 'application/json',
|
|
95
|
+
tags: RANKING_TAGS,
|
|
96
|
+
},
|
|
97
|
+
input: z.object({
|
|
98
|
+
dimension: z.string(),
|
|
99
|
+
transport: TransportFilterSchema,
|
|
100
|
+
authMethod: AuthFilterSchema,
|
|
101
|
+
}),
|
|
102
|
+
resolve: async ({ dimension, transport, authMethod }) => {
|
|
103
|
+
const store = getStore();
|
|
104
|
+
const result = await store.listModelRankings({
|
|
105
|
+
dimension: dimension as
|
|
106
|
+
| 'coding'
|
|
107
|
+
| 'reasoning'
|
|
108
|
+
| 'agentic'
|
|
109
|
+
| 'cost'
|
|
110
|
+
| 'latency'
|
|
111
|
+
| 'context'
|
|
112
|
+
| 'safety'
|
|
113
|
+
| 'custom',
|
|
114
|
+
limit: 100,
|
|
115
|
+
requiredTransport: transport as ProviderTransportSupport | undefined,
|
|
116
|
+
requiredAuthMethod: authMethod as ProviderAuthSupport | undefined,
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
uri: `ranking://leaderboard/${encodeURIComponent(dimension)}`,
|
|
120
|
+
mimeType: 'application/json',
|
|
121
|
+
data: JSON.stringify(result, null, 2),
|
|
122
|
+
};
|
|
123
|
+
},
|
|
124
|
+
})
|
|
125
|
+
);
|
|
126
|
+
|
|
127
|
+
resources.register(
|
|
128
|
+
defineResourceTemplate({
|
|
129
|
+
meta: {
|
|
130
|
+
uriTemplate: 'ranking://model/{modelId}',
|
|
131
|
+
title: 'AI Model Profile',
|
|
132
|
+
description:
|
|
133
|
+
'Detailed profile for a specific AI model including scores and benchmarks.',
|
|
134
|
+
mimeType: 'application/json',
|
|
135
|
+
tags: RANKING_TAGS,
|
|
136
|
+
},
|
|
137
|
+
input: z.object({ modelId: z.string() }),
|
|
138
|
+
resolve: async ({ modelId }) => {
|
|
139
|
+
const store = getStore();
|
|
140
|
+
const profile = await store.getModelProfile(modelId);
|
|
141
|
+
if (!profile) {
|
|
142
|
+
return {
|
|
143
|
+
uri: `ranking://model/${encodeURIComponent(modelId)}`,
|
|
144
|
+
mimeType: 'application/json',
|
|
145
|
+
data: JSON.stringify({ error: 'not_found', modelId }),
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return {
|
|
149
|
+
uri: `ranking://model/${encodeURIComponent(modelId)}`,
|
|
150
|
+
mimeType: 'application/json',
|
|
151
|
+
data: JSON.stringify(profile, null, 2),
|
|
152
|
+
};
|
|
153
|
+
},
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
resources.register(
|
|
158
|
+
defineResourceTemplate({
|
|
159
|
+
meta: {
|
|
160
|
+
uriTemplate: 'ranking://results',
|
|
161
|
+
title: 'Benchmark Results',
|
|
162
|
+
description: 'List of raw benchmark results from all ingested sources.',
|
|
163
|
+
mimeType: 'application/json',
|
|
164
|
+
tags: RANKING_TAGS,
|
|
165
|
+
},
|
|
166
|
+
input: z.object({}),
|
|
167
|
+
resolve: async () => {
|
|
168
|
+
const store = getStore();
|
|
169
|
+
const result = await store.listBenchmarkResults({ limit: 200 });
|
|
170
|
+
return {
|
|
171
|
+
uri: 'ranking://results',
|
|
172
|
+
mimeType: 'application/json',
|
|
173
|
+
data: JSON.stringify(result, null, 2),
|
|
174
|
+
};
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
return resources;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function buildRankingPrompts() {
|
|
183
|
+
const prompts = new PromptRegistry();
|
|
184
|
+
|
|
185
|
+
prompts.register(
|
|
186
|
+
definePrompt({
|
|
187
|
+
meta: {
|
|
188
|
+
key: 'ranking.advisor',
|
|
189
|
+
version: '1.0.0',
|
|
190
|
+
title: 'AI Model Advisor',
|
|
191
|
+
description:
|
|
192
|
+
'Which AI model is best for a given task? Uses the leaderboard to recommend.',
|
|
193
|
+
tags: RANKING_TAGS,
|
|
194
|
+
stability: 'beta',
|
|
195
|
+
owners: RANKING_OWNERS,
|
|
196
|
+
},
|
|
197
|
+
args: [
|
|
198
|
+
{
|
|
199
|
+
name: 'task',
|
|
200
|
+
description: 'The task or use case to recommend a model for.',
|
|
201
|
+
required: true,
|
|
202
|
+
schema: z.string(),
|
|
203
|
+
},
|
|
204
|
+
{
|
|
205
|
+
name: 'priority',
|
|
206
|
+
description:
|
|
207
|
+
'Priority dimension (coding, reasoning, cost, latency, etc.).',
|
|
208
|
+
required: false,
|
|
209
|
+
schema: z.string().optional(),
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: 'transport',
|
|
213
|
+
description: 'Required transport type (rest, mcp, webhook, sdk).',
|
|
214
|
+
required: false,
|
|
215
|
+
schema: TransportFilterSchema,
|
|
216
|
+
},
|
|
217
|
+
{
|
|
218
|
+
name: 'authMethod',
|
|
219
|
+
description: 'Required auth method (api-key, oauth2, bearer, etc.).',
|
|
220
|
+
required: false,
|
|
221
|
+
schema: AuthFilterSchema,
|
|
222
|
+
},
|
|
223
|
+
],
|
|
224
|
+
input: z.object({
|
|
225
|
+
task: z.string(),
|
|
226
|
+
priority: z.string().optional(),
|
|
227
|
+
transport: TransportFilterSchema,
|
|
228
|
+
authMethod: AuthFilterSchema,
|
|
229
|
+
}),
|
|
230
|
+
render: async ({ task, priority, transport, authMethod }) => {
|
|
231
|
+
const constraints: string[] = [];
|
|
232
|
+
if (priority) constraints.push(`Prioritize: ${priority}.`);
|
|
233
|
+
if (transport) constraints.push(`Required transport: ${transport}.`);
|
|
234
|
+
if (authMethod) constraints.push(`Required auth: ${authMethod}.`);
|
|
235
|
+
|
|
236
|
+
return [
|
|
237
|
+
{
|
|
238
|
+
type: 'text' as const,
|
|
239
|
+
text: `Recommend the best AI model for: "${task}".${constraints.length ? ` ${constraints.join(' ')}` : ''} Use the leaderboard data to justify your recommendation.`,
|
|
240
|
+
},
|
|
241
|
+
{
|
|
242
|
+
type: 'resource' as const,
|
|
243
|
+
uri: priority
|
|
244
|
+
? `ranking://leaderboard/${priority}`
|
|
245
|
+
: 'ranking://leaderboard',
|
|
246
|
+
title: 'Leaderboard',
|
|
247
|
+
},
|
|
248
|
+
];
|
|
249
|
+
},
|
|
250
|
+
})
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
return prompts;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function buildRankingOps() {
|
|
257
|
+
const registry = new OperationSpecRegistry();
|
|
258
|
+
const ingesterRegistry = createDefaultIngesterRegistry();
|
|
259
|
+
|
|
260
|
+
installOp(registry, BenchmarkIngestCommand, async (args) => {
|
|
261
|
+
const store = getStore();
|
|
262
|
+
const source = args.source as string;
|
|
263
|
+
const ingester = ingesterRegistry.get(
|
|
264
|
+
source as Parameters<typeof ingesterRegistry.get>[0]
|
|
265
|
+
);
|
|
266
|
+
if (!ingester) {
|
|
267
|
+
throw new Error(`No ingester registered for source: ${source}`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const rawResults = await ingester.ingest({
|
|
271
|
+
sourceUrl: args.sourceUrl as string | undefined,
|
|
272
|
+
dimensions: args.dimensions as string[] | undefined as Parameters<
|
|
273
|
+
typeof ingester.ingest
|
|
274
|
+
>[0] extends undefined
|
|
275
|
+
? never
|
|
276
|
+
: NonNullable<Parameters<typeof ingester.ingest>[0]>['dimensions'],
|
|
277
|
+
});
|
|
278
|
+
const normalized = normalizeBenchmarkResults(rawResults);
|
|
279
|
+
|
|
280
|
+
for (const result of normalized) {
|
|
281
|
+
await store.upsertBenchmarkResult(result);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
return {
|
|
285
|
+
ingestionId: `ingest-${source}-${Date.now()}`,
|
|
286
|
+
source,
|
|
287
|
+
resultsCount: normalized.length,
|
|
288
|
+
status: 'completed',
|
|
289
|
+
ingestedAt: new Date(),
|
|
290
|
+
};
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
installOp(registry, BenchmarkRunCustomCommand, async (args) => {
|
|
294
|
+
return {
|
|
295
|
+
runId: `custom-${Date.now()}`,
|
|
296
|
+
evalSuiteKey: args.evalSuiteKey,
|
|
297
|
+
modelId: args.modelId,
|
|
298
|
+
status: 'started',
|
|
299
|
+
startedAt: new Date(),
|
|
300
|
+
};
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
installOp(registry, RankingRefreshCommand, async (args) => {
|
|
304
|
+
const store = getStore();
|
|
305
|
+
const allResults = [];
|
|
306
|
+
let offset = 0;
|
|
307
|
+
const pageSize = 500;
|
|
308
|
+
|
|
309
|
+
while (true) {
|
|
310
|
+
const page = await store.listBenchmarkResults({
|
|
311
|
+
limit: pageSize,
|
|
312
|
+
offset,
|
|
313
|
+
});
|
|
314
|
+
allResults.push(...page.results);
|
|
315
|
+
if (allResults.length >= page.total || page.results.length < pageSize)
|
|
316
|
+
break;
|
|
317
|
+
offset += pageSize;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const existingRankings = new Map(
|
|
321
|
+
(await store.listModelRankings({ limit: 10000 })).rankings.map((r) => [
|
|
322
|
+
r.modelId,
|
|
323
|
+
r,
|
|
324
|
+
])
|
|
325
|
+
);
|
|
326
|
+
|
|
327
|
+
const weightOverrides = args.weightOverrides
|
|
328
|
+
? Array.isArray(args.weightOverrides)
|
|
329
|
+
? args.weightOverrides
|
|
330
|
+
: [args.weightOverrides]
|
|
331
|
+
: undefined;
|
|
332
|
+
|
|
333
|
+
const newRankings = computeModelRankings(
|
|
334
|
+
allResults,
|
|
335
|
+
weightOverrides
|
|
336
|
+
? {
|
|
337
|
+
weightOverrides: weightOverrides as Parameters<
|
|
338
|
+
typeof computeModelRankings
|
|
339
|
+
>[1] extends undefined
|
|
340
|
+
? never
|
|
341
|
+
: NonNullable<
|
|
342
|
+
Parameters<typeof computeModelRankings>[1]
|
|
343
|
+
>['weightOverrides'],
|
|
344
|
+
}
|
|
345
|
+
: undefined,
|
|
346
|
+
existingRankings
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
for (const ranking of newRankings) {
|
|
350
|
+
await store.upsertModelRanking(ranking);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
modelsRanked: newRankings.length,
|
|
355
|
+
updatedAt: new Date(),
|
|
356
|
+
status: 'completed',
|
|
357
|
+
};
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
return registry;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
export function createProviderRankingMcpHandler(path = '/api/mcp/ranking') {
|
|
364
|
+
return createMcpElysiaHandler({
|
|
365
|
+
logger: appLogger,
|
|
366
|
+
path,
|
|
367
|
+
serverName: 'contractspec-ranking-mcp',
|
|
368
|
+
ops: buildRankingOps(),
|
|
369
|
+
resources: buildRankingResources(),
|
|
370
|
+
prompts: buildRankingPrompts(),
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Allows external callers to inject a custom store
|
|
376
|
+
* (e.g. PostgresProviderRankingStore from the module layer).
|
|
377
|
+
*/
|
|
378
|
+
export function setProviderRankingStore(store: ProviderRankingStore): void {
|
|
379
|
+
sharedStore = store;
|
|
380
|
+
}
|
|
@@ -195,7 +195,8 @@ bun contractspec build src/contracts/mySpec.ts`}
|
|
|
195
195
|
},
|
|
196
196
|
{
|
|
197
197
|
title: 'Ecosystem',
|
|
198
|
-
description:
|
|
198
|
+
description:
|
|
199
|
+
'Cursor marketplace plugins, templates, manifest, and integrations',
|
|
199
200
|
href: '/docs/ecosystem/plugins',
|
|
200
201
|
},
|
|
201
202
|
{
|