@fragments-sdk/mcp 0.10.0 → 1.0.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/README.md +34 -84
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +7 -154
- package/dist/bin.js.map +1 -1
- package/dist/index.d.ts +1443 -0
- package/dist/index.js +1107 -207
- package/dist/index.js.map +1 -1
- package/package.json +9 -33
- package/dist/chunk-4SVS3AA3.js +0 -78
- package/dist/chunk-4SVS3AA3.js.map +0 -1
- package/dist/chunk-7D4SUZUM.js +0 -38
- package/dist/chunk-7D4SUZUM.js.map +0 -1
- package/dist/chunk-VRPDT3Y6.js +0 -52
- package/dist/chunk-VRPDT3Y6.js.map +0 -1
- package/dist/chunk-WDQPNHZ2.js +0 -143
- package/dist/chunk-WDQPNHZ2.js.map +0 -1
- package/dist/chunk-YJTMK4JY.js +0 -4270
- package/dist/chunk-YJTMK4JY.js.map +0 -1
- package/dist/constants-BLN4SSNH.js +0 -10
- package/dist/constants-BLN4SSNH.js.map +0 -1
- package/dist/dist-TTCI6TME.js +0 -60962
- package/dist/dist-TTCI6TME.js.map +0 -1
- package/dist/init.js +0 -175
- package/dist/init.js.map +0 -1
- package/dist/rules-JUZ3RABB.js +0 -8
- package/dist/rules-JUZ3RABB.js.map +0 -1
- package/dist/sass.node-4XJK6YBF-CPK77BO6.js +0 -130797
- package/dist/sass.node-4XJK6YBF-CPK77BO6.js.map +0 -1
- package/dist/server.js +0 -13
- package/dist/server.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,223 +1,1123 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
blockFromCompiledBlock,
|
|
10
|
-
buildCapabilities,
|
|
11
|
-
componentFromCompiledFragment,
|
|
12
|
-
createMcpServer,
|
|
13
|
-
createSandboxServer,
|
|
14
|
-
executeWithMiddleware,
|
|
15
|
-
loadConfigFile,
|
|
16
|
-
startMcpServer,
|
|
17
|
-
telemetryMiddleware,
|
|
18
|
-
tokensFromCompiledTokenData,
|
|
19
|
-
validateSnapshot
|
|
20
|
-
} from "./chunk-YJTMK4JY.js";
|
|
21
|
-
import {
|
|
22
|
-
BRAND
|
|
23
|
-
} from "./chunk-4SVS3AA3.js";
|
|
24
|
-
import {
|
|
25
|
-
generateRulesFiles
|
|
26
|
-
} from "./chunk-WDQPNHZ2.js";
|
|
27
|
-
import "./chunk-7D4SUZUM.js";
|
|
1
|
+
// src/apps/resources.ts
|
|
2
|
+
var MCP_APP_RESOURCES = [];
|
|
3
|
+
function getResource(_uri) {
|
|
4
|
+
return null;
|
|
5
|
+
}
|
|
6
|
+
function listResources() {
|
|
7
|
+
return [];
|
|
8
|
+
}
|
|
28
9
|
|
|
29
|
-
// src/
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
10
|
+
// src/tasks/store.ts
|
|
11
|
+
var nextTaskId = 1;
|
|
12
|
+
function createTaskId() {
|
|
13
|
+
const id = `task_${String(nextTaskId).padStart(6, "0")}`;
|
|
14
|
+
nextTaskId += 1;
|
|
15
|
+
return id;
|
|
16
|
+
}
|
|
17
|
+
var MemoryTaskStore = class {
|
|
18
|
+
tasks = /* @__PURE__ */ new Map();
|
|
19
|
+
async create(input) {
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
const result = input.result ?? completedWorkflowResult(input.workflow?.kind);
|
|
22
|
+
const task = {
|
|
23
|
+
taskId: createTaskId(),
|
|
24
|
+
title: input.title,
|
|
25
|
+
status: result ? "completed" : "working",
|
|
26
|
+
createdAt: now,
|
|
27
|
+
updatedAt: now,
|
|
28
|
+
ttlMs: input.ttlMs ?? 15 * 60 * 1e3,
|
|
29
|
+
pollIntervalMs: input.pollIntervalMs ?? 1e3,
|
|
30
|
+
result,
|
|
31
|
+
progress: result ? { current: 1, total: 1, message: "Completed" } : { current: 0, total: 1, message: "Queued" }
|
|
32
|
+
};
|
|
33
|
+
this.tasks.set(task.taskId, task);
|
|
34
|
+
return task;
|
|
35
|
+
}
|
|
36
|
+
async get(taskId) {
|
|
37
|
+
return this.tasks.get(taskId) ?? null;
|
|
38
|
+
}
|
|
39
|
+
async update(taskId, patch) {
|
|
40
|
+
const existing = this.tasks.get(taskId);
|
|
41
|
+
if (!existing) return null;
|
|
42
|
+
const next = {
|
|
43
|
+
...existing,
|
|
44
|
+
...patch,
|
|
45
|
+
taskId,
|
|
46
|
+
updatedAt: Date.now()
|
|
47
|
+
};
|
|
48
|
+
this.tasks.set(taskId, next);
|
|
49
|
+
return next;
|
|
50
|
+
}
|
|
51
|
+
async cancel(taskId) {
|
|
52
|
+
return this.update(taskId, {
|
|
53
|
+
status: "cancelled",
|
|
54
|
+
progress: { message: "Cancellation requested" }
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
};
|
|
58
|
+
function completedWorkflowResult(kind) {
|
|
59
|
+
if (!kind) return void 0;
|
|
60
|
+
return {
|
|
61
|
+
content: [{ type: "text", text: `${kind} queued.` }],
|
|
62
|
+
structuredContent: {
|
|
63
|
+
kind,
|
|
64
|
+
status: "queued",
|
|
65
|
+
message: "Fragments accepted the workflow request. The hosted runtime attaches concrete job details."
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// src/spec/generated/schema.ts
|
|
71
|
+
var LATEST_PROTOCOL_VERSION = "DRAFT-2026-v1";
|
|
72
|
+
var JSONRPC_VERSION = "2.0";
|
|
73
|
+
|
|
74
|
+
// src/tools/primitives.ts
|
|
75
|
+
var primitiveSchema = {
|
|
76
|
+
type: "object",
|
|
77
|
+
properties: {
|
|
78
|
+
id: { type: "string" },
|
|
79
|
+
name: { type: "string" },
|
|
80
|
+
primitiveName: { type: "string" },
|
|
81
|
+
filePath: { type: ["string", "null"] },
|
|
82
|
+
importPath: { type: ["string", "null"] },
|
|
83
|
+
packageName: { type: ["string", "null"] },
|
|
84
|
+
description: { type: "string" },
|
|
85
|
+
usageGuidance: { type: "string" },
|
|
86
|
+
props: { type: "object" },
|
|
87
|
+
examples: { type: "array" },
|
|
88
|
+
sourceRepoFullName: { type: ["string", "null"] }
|
|
89
|
+
},
|
|
90
|
+
required: ["id", "name", "primitiveName", "filePath", "importPath"],
|
|
91
|
+
additionalProperties: true
|
|
92
|
+
};
|
|
93
|
+
var primitiveTools = [
|
|
94
|
+
{
|
|
95
|
+
name: "design_system/list_primitives",
|
|
96
|
+
title: "List Primitives",
|
|
97
|
+
description: "List the design-system primitives curated in Fragments Cloud. Returns only reviewed primitives with their source file paths.",
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: "object",
|
|
100
|
+
properties: {},
|
|
101
|
+
additionalProperties: false,
|
|
102
|
+
maxProperties: 0
|
|
103
|
+
},
|
|
104
|
+
outputSchema: {
|
|
105
|
+
type: "object",
|
|
106
|
+
properties: {
|
|
107
|
+
primitiveCount: { type: "integer" },
|
|
108
|
+
primitives: { type: "array", items: primitiveSchema }
|
|
109
|
+
},
|
|
110
|
+
required: ["primitiveCount", "primitives"],
|
|
111
|
+
additionalProperties: false
|
|
112
|
+
},
|
|
113
|
+
annotations: {
|
|
114
|
+
readOnlyHint: true,
|
|
115
|
+
idempotentHint: true
|
|
116
|
+
},
|
|
117
|
+
async call(_args, context) {
|
|
118
|
+
const primitives = await context.provider.listPrimitives();
|
|
119
|
+
return primitiveResult(primitives);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
];
|
|
123
|
+
function primitiveResult(primitives) {
|
|
124
|
+
return {
|
|
125
|
+
content: [
|
|
126
|
+
{
|
|
127
|
+
type: "text",
|
|
128
|
+
text: primitives.length === 0 ? "No primitives have been selected in Fragments Cloud yet." : `Found ${primitives.length} primitives.`
|
|
129
|
+
}
|
|
130
|
+
],
|
|
131
|
+
structuredContent: {
|
|
132
|
+
primitiveCount: primitives.length,
|
|
133
|
+
primitives: primitives.map(normalizePrimitive)
|
|
134
|
+
}
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
function normalizePrimitive(primitive) {
|
|
138
|
+
return {
|
|
139
|
+
id: primitive.id,
|
|
140
|
+
name: primitive.name,
|
|
141
|
+
primitiveName: primitive.primitiveName ?? primitive.name,
|
|
142
|
+
filePath: primitive.filePath ?? null,
|
|
143
|
+
importPath: primitive.importPath ?? null,
|
|
144
|
+
packageName: primitive.packageName ?? null,
|
|
145
|
+
description: primitive.description ?? "",
|
|
146
|
+
usageGuidance: primitive.usageGuidance ?? "",
|
|
147
|
+
props: primitive.props ?? {},
|
|
148
|
+
examples: primitive.examples ?? [],
|
|
149
|
+
sourceRepoFullName: primitive.sourceRepoFullName ?? null
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// src/tools/conform.ts
|
|
154
|
+
var locationSchema = {
|
|
155
|
+
type: "object",
|
|
156
|
+
properties: {
|
|
157
|
+
line: { type: "integer" },
|
|
158
|
+
column: { type: "integer" },
|
|
159
|
+
endLine: { type: "integer" },
|
|
160
|
+
endColumn: { type: "integer" }
|
|
161
|
+
},
|
|
162
|
+
additionalProperties: false
|
|
163
|
+
};
|
|
164
|
+
var changeSchema = {
|
|
165
|
+
type: "object",
|
|
166
|
+
properties: {
|
|
167
|
+
kind: {
|
|
168
|
+
type: "string",
|
|
169
|
+
enum: ["swap-component", "rewrite-import", "rename-prop", "replace-token", "add-fallback"]
|
|
170
|
+
},
|
|
171
|
+
ruleId: { type: "string" },
|
|
172
|
+
code: { type: "string" },
|
|
173
|
+
severity: { type: "string", enum: ["error", "warning", "info"] },
|
|
174
|
+
message: { type: "string" },
|
|
175
|
+
before: { type: "string" },
|
|
176
|
+
after: { type: "string" },
|
|
177
|
+
canonical: { type: "string" },
|
|
178
|
+
confidence: { type: "string", enum: ["high", "medium", "low"] },
|
|
179
|
+
location: locationSchema
|
|
180
|
+
},
|
|
181
|
+
required: ["kind", "severity", "message", "confidence"],
|
|
182
|
+
additionalProperties: false
|
|
183
|
+
};
|
|
184
|
+
var suggestionSchema = {
|
|
185
|
+
type: "object",
|
|
186
|
+
properties: {
|
|
187
|
+
kind: {
|
|
188
|
+
type: "string",
|
|
189
|
+
enum: ["map-prop-value", "choose-among-alternates", "review-low-confidence"]
|
|
190
|
+
},
|
|
191
|
+
message: { type: "string" },
|
|
192
|
+
canonical: { type: "string" },
|
|
193
|
+
hint: { type: "string" },
|
|
194
|
+
confidence: { type: "string", enum: ["high", "medium", "low"] },
|
|
195
|
+
location: locationSchema
|
|
196
|
+
},
|
|
197
|
+
required: ["kind", "message", "confidence"],
|
|
198
|
+
additionalProperties: false
|
|
199
|
+
};
|
|
200
|
+
var unresolvedSchema = {
|
|
201
|
+
type: "object",
|
|
202
|
+
properties: {
|
|
203
|
+
element: { type: "string" },
|
|
204
|
+
canonical: { type: "string" },
|
|
205
|
+
reason: { type: "string" },
|
|
206
|
+
location: locationSchema
|
|
207
|
+
},
|
|
208
|
+
required: ["reason"],
|
|
209
|
+
additionalProperties: false
|
|
210
|
+
};
|
|
211
|
+
var conformTools = [
|
|
212
|
+
{
|
|
213
|
+
name: "design_system/conform",
|
|
214
|
+
title: "Conform to Design System",
|
|
215
|
+
description: "Rewrite a snippet of UI code so it matches THIS project's design system. Swaps raw HTML elements and components from other libraries (Material UI, shadcn/ui, Chakra, or custom) to the project's own components and import paths, replaces hardcoded colors/spacing/radii with the project's design tokens, and flags accessibility gaps. Call this whenever the user says 'fix this', 'make this match our design system', 'use our components here', right after pasting markup from elsewhere, or before committing UI you wrote. Input is the code itself \u2014 no repo access needed. Returns a conformed version plus a list of changes with rationale; ambiguous cross-library prop changes come back as suggestions for you to apply.",
|
|
216
|
+
inputSchema: {
|
|
217
|
+
type: "object",
|
|
218
|
+
properties: {
|
|
219
|
+
code: {
|
|
220
|
+
type: "string",
|
|
221
|
+
description: "The UI code to conform (JSX/TSX, HTML, or a CSS/SCSS block)."
|
|
222
|
+
},
|
|
223
|
+
filename: {
|
|
224
|
+
type: "string",
|
|
225
|
+
description: "Filename or extension hint for parser selection (e.g. 'Form.tsx', 'styles.scss'). Defaults to TSX."
|
|
226
|
+
},
|
|
227
|
+
apply: {
|
|
228
|
+
type: "string",
|
|
229
|
+
enum: ["deterministic", "none"],
|
|
230
|
+
description: "deterministic (default): return code with safe edits applied. none: findings only."
|
|
231
|
+
}
|
|
232
|
+
},
|
|
233
|
+
required: ["code"],
|
|
234
|
+
additionalProperties: false
|
|
235
|
+
},
|
|
236
|
+
outputSchema: {
|
|
237
|
+
type: "object",
|
|
238
|
+
properties: {
|
|
239
|
+
conformed: { type: "string" },
|
|
240
|
+
changed: { type: "boolean" },
|
|
241
|
+
summary: { type: "string" },
|
|
242
|
+
designSystem: {
|
|
243
|
+
type: "object",
|
|
244
|
+
properties: {
|
|
245
|
+
name: { type: "string" },
|
|
246
|
+
importsAdded: { type: "array", items: { type: "string" } }
|
|
247
|
+
},
|
|
248
|
+
required: ["importsAdded"],
|
|
249
|
+
additionalProperties: false
|
|
250
|
+
},
|
|
251
|
+
changes: { type: "array", items: changeSchema },
|
|
252
|
+
suggestions: { type: "array", items: suggestionSchema },
|
|
253
|
+
unresolved: { type: "array", items: unresolvedSchema }
|
|
254
|
+
},
|
|
255
|
+
required: ["conformed", "changed", "changes", "suggestions"],
|
|
256
|
+
additionalProperties: false
|
|
257
|
+
},
|
|
258
|
+
annotations: {
|
|
259
|
+
readOnlyHint: true,
|
|
260
|
+
idempotentHint: true,
|
|
261
|
+
// Output depends on the `code` argument, so it must not be served from a
|
|
262
|
+
// tenant-wide cache the way argument-free tools (list_primitives) are.
|
|
263
|
+
cacheScope: "none",
|
|
264
|
+
ttlMs: 0
|
|
265
|
+
},
|
|
266
|
+
async call(args, context) {
|
|
267
|
+
const input = {
|
|
268
|
+
code: typeof args.code === "string" ? args.code : "",
|
|
269
|
+
filename: typeof args.filename === "string" ? args.filename : void 0,
|
|
270
|
+
apply: args.apply === "none" ? "none" : "deterministic"
|
|
60
271
|
};
|
|
272
|
+
const result = await context.provider.conform(input);
|
|
273
|
+
return conformResult(result);
|
|
61
274
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
275
|
+
}
|
|
276
|
+
];
|
|
277
|
+
function conformResult(result) {
|
|
278
|
+
const summary = result.summary || defaultSummary(result);
|
|
279
|
+
return {
|
|
280
|
+
content: [{ type: "text", text: summary }],
|
|
281
|
+
structuredContent: {
|
|
282
|
+
conformed: result.conformed,
|
|
283
|
+
changed: result.changed,
|
|
284
|
+
summary,
|
|
285
|
+
...result.designSystem ? {
|
|
286
|
+
designSystem: {
|
|
287
|
+
...result.designSystem.name ? { name: result.designSystem.name } : {},
|
|
288
|
+
importsAdded: result.designSystem.importsAdded ?? []
|
|
289
|
+
}
|
|
290
|
+
} : {},
|
|
291
|
+
changes: result.changes,
|
|
292
|
+
suggestions: result.suggestions,
|
|
293
|
+
unresolved: result.unresolved
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
function defaultSummary(result) {
|
|
298
|
+
if (!result.changed && result.changes.length === 0) {
|
|
299
|
+
return "No conformance changes were applied.";
|
|
300
|
+
}
|
|
301
|
+
return `Applied ${result.changes.length} change(s); ${result.suggestions.length} suggestion(s) left for review.`;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// src/tools/prove-compliant-schema.ts
|
|
305
|
+
var locationSchema2 = {
|
|
306
|
+
type: "object",
|
|
307
|
+
properties: {
|
|
308
|
+
line: { type: "integer" },
|
|
309
|
+
column: { type: "integer" },
|
|
310
|
+
endLine: { type: "integer" },
|
|
311
|
+
endColumn: { type: "integer" }
|
|
312
|
+
},
|
|
313
|
+
additionalProperties: false
|
|
314
|
+
};
|
|
315
|
+
var residualSchema = {
|
|
316
|
+
type: "object",
|
|
317
|
+
properties: {
|
|
318
|
+
reason: { type: "string" },
|
|
319
|
+
message: { type: "string" },
|
|
320
|
+
canonical: { type: "string" },
|
|
321
|
+
element: { type: "string" },
|
|
322
|
+
hint: { type: "string" },
|
|
323
|
+
confidence: { type: "string" },
|
|
324
|
+
location: locationSchema2
|
|
325
|
+
},
|
|
326
|
+
additionalProperties: true
|
|
327
|
+
};
|
|
328
|
+
var proveCompliantOutputSchema = {
|
|
329
|
+
type: "object",
|
|
330
|
+
properties: {
|
|
331
|
+
provedCompliant: { type: "boolean" },
|
|
332
|
+
conformed: { type: "string" },
|
|
333
|
+
changed: { type: "boolean" },
|
|
334
|
+
passes: { type: "integer" },
|
|
335
|
+
maxPasses: { type: "integer" },
|
|
336
|
+
initialIssueCount: { type: "integer" },
|
|
337
|
+
finalIssueCount: { type: "integer" },
|
|
338
|
+
corrections: { type: "integer" },
|
|
339
|
+
summary: { type: "string" },
|
|
340
|
+
taskId: { type: "string" },
|
|
341
|
+
changes: { type: "array", items: { type: "object", additionalProperties: true } },
|
|
342
|
+
suggestions: { type: "array", items: residualSchema },
|
|
343
|
+
unresolved: { type: "array", items: residualSchema },
|
|
344
|
+
passHistory: { type: "array", items: { type: "object", additionalProperties: true } }
|
|
345
|
+
},
|
|
346
|
+
required: [
|
|
347
|
+
"provedCompliant",
|
|
348
|
+
"conformed",
|
|
349
|
+
"changed",
|
|
350
|
+
"passes",
|
|
351
|
+
"maxPasses",
|
|
352
|
+
"initialIssueCount",
|
|
353
|
+
"finalIssueCount",
|
|
354
|
+
"corrections",
|
|
355
|
+
"summary",
|
|
356
|
+
"changes",
|
|
357
|
+
"suggestions",
|
|
358
|
+
"unresolved",
|
|
359
|
+
"passHistory"
|
|
360
|
+
],
|
|
361
|
+
additionalProperties: false
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// src/tools/prove-compliant.ts
|
|
365
|
+
var TOOL_NAME = "design_system/prove_compliant";
|
|
366
|
+
var DEFAULT_MAX_PASSES = 4;
|
|
367
|
+
var MAX_PASSES = 8;
|
|
368
|
+
var DEFAULT_TTL_MS = 15 * 60 * 1e3;
|
|
369
|
+
var proveCompliantTools = [
|
|
370
|
+
{
|
|
371
|
+
name: TOOL_NAME,
|
|
372
|
+
title: "Prove Design-System Compliance",
|
|
373
|
+
description: "Run a closed validate-fix-validate loop over generated UI code until Fragments can prove it has zero design-system findings, or return the remaining issues. Uses deterministic fixes first, then MCP Sampling when the connected client supports it.",
|
|
374
|
+
inputSchema: {
|
|
375
|
+
type: "object",
|
|
376
|
+
properties: {
|
|
377
|
+
code: {
|
|
378
|
+
type: "string",
|
|
379
|
+
description: "The UI code to prove compliant."
|
|
380
|
+
},
|
|
381
|
+
filename: {
|
|
382
|
+
type: "string",
|
|
383
|
+
description: "Filename or extension hint. Defaults to TSX."
|
|
384
|
+
},
|
|
385
|
+
maxPasses: {
|
|
386
|
+
type: "integer",
|
|
387
|
+
minimum: 1,
|
|
388
|
+
maximum: MAX_PASSES,
|
|
389
|
+
description: "Maximum validation/fix passes. Defaults to 4."
|
|
390
|
+
},
|
|
391
|
+
allowSampling: {
|
|
392
|
+
type: "boolean",
|
|
393
|
+
description: "Allow MCP Sampling for residual issues. Defaults to true."
|
|
394
|
+
}
|
|
107
395
|
},
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
396
|
+
required: ["code"],
|
|
397
|
+
additionalProperties: false
|
|
398
|
+
},
|
|
399
|
+
outputSchema: proveCompliantOutputSchema,
|
|
400
|
+
annotations: {
|
|
401
|
+
readOnlyHint: true,
|
|
402
|
+
idempotentHint: false,
|
|
403
|
+
cacheScope: "none",
|
|
404
|
+
ttlMs: 0
|
|
405
|
+
},
|
|
406
|
+
async call(args, context) {
|
|
407
|
+
return proveCompliant(args, context);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
];
|
|
411
|
+
async function proveCompliant(args, context) {
|
|
412
|
+
const restored = decodeState(context.requestState);
|
|
413
|
+
const input = restored ?? initialState(args);
|
|
414
|
+
const task = await ensureTask(context, input);
|
|
415
|
+
if (task) input.taskId = task.taskId;
|
|
416
|
+
const sampled = consumeSamplingResponse(input, context.inputResponses);
|
|
417
|
+
if (sampled) {
|
|
418
|
+
input.samplingAttempts += 1;
|
|
419
|
+
if (sampled.code) {
|
|
420
|
+
input.code = sampled.code;
|
|
421
|
+
}
|
|
422
|
+
const last = input.passHistory[input.passHistory.length - 1];
|
|
423
|
+
if (last) {
|
|
424
|
+
last.sampling = {
|
|
425
|
+
...last.sampling ?? {},
|
|
426
|
+
used: Boolean(sampled.code),
|
|
427
|
+
model: sampled.model,
|
|
428
|
+
stopReason: sampled.stopReason,
|
|
429
|
+
reason: sampled.reason
|
|
430
|
+
};
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
let current = input.code;
|
|
434
|
+
let lastResult = null;
|
|
435
|
+
while (input.passHistory.length < input.maxPasses) {
|
|
436
|
+
const pass = input.passHistory.length + 1;
|
|
437
|
+
await updateTask(context, input.taskId, {
|
|
438
|
+
status: "working",
|
|
439
|
+
progress: {
|
|
440
|
+
current: pass,
|
|
441
|
+
total: input.maxPasses,
|
|
442
|
+
message: `Validating pass ${pass}`
|
|
443
|
+
}
|
|
444
|
+
});
|
|
445
|
+
const result = await context.provider.conform({
|
|
446
|
+
code: current,
|
|
447
|
+
filename: input.filename,
|
|
448
|
+
apply: "deterministic"
|
|
115
449
|
});
|
|
450
|
+
lastResult = result;
|
|
451
|
+
const issues = issueCount(result);
|
|
452
|
+
input.passHistory.push({
|
|
453
|
+
pass,
|
|
454
|
+
issues,
|
|
455
|
+
deterministicChanges: result.changes.length,
|
|
456
|
+
suggestions: result.suggestions.length,
|
|
457
|
+
unresolved: result.unresolved.length,
|
|
458
|
+
changed: result.changed
|
|
459
|
+
});
|
|
460
|
+
input.corrections += result.changes.length;
|
|
461
|
+
if (issues === 0) {
|
|
462
|
+
const final2 = completeResult({
|
|
463
|
+
state: input,
|
|
464
|
+
result,
|
|
465
|
+
originalCode: args.code,
|
|
466
|
+
provedCompliant: true,
|
|
467
|
+
reason: `0 findings after ${pass} pass${pass === 1 ? "" : "es"}.`
|
|
468
|
+
});
|
|
469
|
+
return finish(context, input.taskId, final2);
|
|
470
|
+
}
|
|
471
|
+
if (result.changed && result.conformed !== current) {
|
|
472
|
+
current = result.conformed;
|
|
473
|
+
input.code = current;
|
|
474
|
+
continue;
|
|
475
|
+
}
|
|
476
|
+
const residual = {
|
|
477
|
+
suggestions: result.suggestions,
|
|
478
|
+
unresolved: result.unresolved
|
|
479
|
+
};
|
|
480
|
+
if (canUseSampling(input, context, residual)) {
|
|
481
|
+
return requestSampling({ state: input, context, current, residual });
|
|
482
|
+
}
|
|
483
|
+
break;
|
|
484
|
+
}
|
|
485
|
+
const fallback = lastResult ?? emptyConformResult(current);
|
|
486
|
+
const final = completeResult({
|
|
487
|
+
state: input,
|
|
488
|
+
result: fallback,
|
|
489
|
+
originalCode: args.code,
|
|
490
|
+
provedCompliant: false,
|
|
491
|
+
reason: incompleteReason(input, context, fallback)
|
|
492
|
+
});
|
|
493
|
+
return finish(context, input.taskId, final);
|
|
494
|
+
}
|
|
495
|
+
function initialState(args) {
|
|
496
|
+
return {
|
|
497
|
+
tool: TOOL_NAME,
|
|
498
|
+
code: typeof args.code === "string" ? args.code : "",
|
|
499
|
+
filename: typeof args.filename === "string" ? args.filename : void 0,
|
|
500
|
+
maxPasses: typeof args.maxPasses === "number" ? Math.min(MAX_PASSES, Math.max(1, Math.trunc(args.maxPasses))) : DEFAULT_MAX_PASSES,
|
|
501
|
+
allowSampling: args.allowSampling !== false,
|
|
502
|
+
passHistory: [],
|
|
503
|
+
corrections: 0,
|
|
504
|
+
samplingAttempts: 0
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
async function ensureTask(context, state) {
|
|
508
|
+
try {
|
|
509
|
+
if (state.taskId) return await context.tasks.get(state.taskId);
|
|
510
|
+
return await context.tasks.create({
|
|
511
|
+
title: "Prove UI compliance",
|
|
512
|
+
ttlMs: DEFAULT_TTL_MS,
|
|
513
|
+
pollIntervalMs: 500
|
|
514
|
+
});
|
|
515
|
+
} catch {
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
async function requestSampling(args) {
|
|
520
|
+
const requestKey = `prove_compliant_sampling_${args.state.passHistory.length}`;
|
|
521
|
+
args.state.requestKey = requestKey;
|
|
522
|
+
const last = args.state.passHistory[args.state.passHistory.length - 1];
|
|
523
|
+
if (last) last.sampling = { requested: true };
|
|
524
|
+
await updateTask(args.context, args.state.taskId, {
|
|
525
|
+
status: "input_required",
|
|
526
|
+
progress: {
|
|
527
|
+
current: args.state.passHistory.length,
|
|
528
|
+
total: args.state.maxPasses,
|
|
529
|
+
message: "Waiting for MCP Sampling to repair residual issues"
|
|
530
|
+
}
|
|
531
|
+
});
|
|
532
|
+
return {
|
|
533
|
+
resultType: "input_required",
|
|
534
|
+
inputRequests: {
|
|
535
|
+
[requestKey]: {
|
|
536
|
+
method: "sampling/createMessage",
|
|
537
|
+
params: {
|
|
538
|
+
messages: [
|
|
539
|
+
{
|
|
540
|
+
role: "user",
|
|
541
|
+
content: {
|
|
542
|
+
type: "text",
|
|
543
|
+
text: samplingPrompt(args.current, args.residual)
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
],
|
|
547
|
+
systemPrompt: "You repair UI code for Fragments design-system governance. Return only JSON with a single string field named code.",
|
|
548
|
+
includeContext: "none",
|
|
549
|
+
maxTokens: 4e3,
|
|
550
|
+
temperature: 0.1,
|
|
551
|
+
modelPreferences: {
|
|
552
|
+
costPriority: 0.2,
|
|
553
|
+
speedPriority: 0.3,
|
|
554
|
+
intelligencePriority: 0.8
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
requestState: encodeState(args.state)
|
|
560
|
+
};
|
|
561
|
+
}
|
|
562
|
+
function canUseSampling(state, context, residual) {
|
|
563
|
+
if (!state.allowSampling) return false;
|
|
564
|
+
if (state.samplingAttempts >= state.maxPasses - 1) return false;
|
|
565
|
+
if (residual.suggestions.length === 0 && residual.unresolved.length === 0) return false;
|
|
566
|
+
const sampling = context.clientCapabilities?.sampling;
|
|
567
|
+
return Boolean(sampling && typeof sampling === "object");
|
|
568
|
+
}
|
|
569
|
+
function consumeSamplingResponse(state, responses) {
|
|
570
|
+
if (!state.requestKey || !responses) return null;
|
|
571
|
+
const response = responses[state.requestKey];
|
|
572
|
+
if (!response) return null;
|
|
573
|
+
const text = extractText(response.content);
|
|
574
|
+
const code = extractSampledCode(text);
|
|
575
|
+
state.requestKey = void 0;
|
|
576
|
+
if (!code) {
|
|
116
577
|
return {
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
graph: snapshot.graph,
|
|
122
|
-
performanceSummary: snapshot.performanceSummary,
|
|
123
|
-
packageMap: snapshot.packageMap,
|
|
124
|
-
defaultPackageName: snapshot.defaultPackageName,
|
|
125
|
-
capabilities: new Set(snapshot.capabilities)
|
|
578
|
+
attempted: true,
|
|
579
|
+
reason: "sampling response did not include code",
|
|
580
|
+
model: typeof response.model === "string" ? response.model : void 0,
|
|
581
|
+
stopReason: typeof response.stopReason === "string" ? response.stopReason : void 0
|
|
126
582
|
};
|
|
127
583
|
}
|
|
128
|
-
|
|
584
|
+
return {
|
|
585
|
+
attempted: true,
|
|
586
|
+
code,
|
|
587
|
+
model: typeof response.model === "string" ? response.model : void 0,
|
|
588
|
+
stopReason: typeof response.stopReason === "string" ? response.stopReason : void 0
|
|
589
|
+
};
|
|
590
|
+
}
|
|
591
|
+
function completeResult(args) {
|
|
592
|
+
const initialIssueCount = args.state.passHistory[0]?.issues ?? 0;
|
|
593
|
+
const finalIssueCount = issueCount(args.result);
|
|
594
|
+
const changed = typeof args.originalCode === "string" && args.result.conformed !== args.originalCode;
|
|
595
|
+
const summary = args.provedCompliant ? `Proved compliant: ${args.reason}` : `Could not prove compliance: ${args.reason}`;
|
|
596
|
+
return {
|
|
597
|
+
content: [{ type: "text", text: summary }],
|
|
598
|
+
structuredContent: {
|
|
599
|
+
provedCompliant: args.provedCompliant,
|
|
600
|
+
conformed: args.result.conformed,
|
|
601
|
+
changed,
|
|
602
|
+
passes: args.state.passHistory.length,
|
|
603
|
+
maxPasses: args.state.maxPasses,
|
|
604
|
+
initialIssueCount,
|
|
605
|
+
finalIssueCount,
|
|
606
|
+
corrections: args.state.corrections,
|
|
607
|
+
summary,
|
|
608
|
+
taskId: args.state.taskId,
|
|
609
|
+
changes: args.result.changes,
|
|
610
|
+
suggestions: args.result.suggestions,
|
|
611
|
+
unresolved: args.result.unresolved,
|
|
612
|
+
passHistory: args.state.passHistory
|
|
613
|
+
}
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
async function finish(context, taskId, result) {
|
|
617
|
+
await updateTask(context, taskId, {
|
|
618
|
+
status: "completed",
|
|
619
|
+
result,
|
|
620
|
+
progress: { current: 1, total: 1, message: "Proof loop completed" }
|
|
621
|
+
});
|
|
622
|
+
return result;
|
|
623
|
+
}
|
|
624
|
+
async function updateTask(context, taskId, patch) {
|
|
625
|
+
if (!taskId) return null;
|
|
626
|
+
try {
|
|
627
|
+
return await context.tasks.update(taskId, patch);
|
|
628
|
+
} catch {
|
|
629
|
+
return null;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
function issueCount(result) {
|
|
633
|
+
return result.changes.length + result.suggestions.length + result.unresolved.length;
|
|
634
|
+
}
|
|
635
|
+
function incompleteReason(state, context, result) {
|
|
636
|
+
if (state.passHistory.length >= state.maxPasses) {
|
|
637
|
+
return `stopped after ${state.maxPasses} passes with ${issueCount(result)} finding(s).`;
|
|
638
|
+
}
|
|
639
|
+
if (!state.allowSampling) return "sampling was disabled and residual issues remain.";
|
|
640
|
+
if (!context.clientCapabilities?.sampling) {
|
|
641
|
+
return "the MCP client did not declare sampling support and residual issues remain.";
|
|
642
|
+
}
|
|
643
|
+
return `${issueCount(result)} residual finding(s) remain after deterministic and sampled fixes.`;
|
|
644
|
+
}
|
|
645
|
+
function samplingPrompt(code, residual) {
|
|
646
|
+
return [
|
|
647
|
+
"Repair this UI code so it satisfies the remaining Fragments findings.",
|
|
648
|
+
"Preserve behavior, text, event handlers, and existing imports unless a finding requires a change.",
|
|
649
|
+
'Return JSON only: {"code":"...full revised code..."}.',
|
|
650
|
+
"",
|
|
651
|
+
"Remaining findings:",
|
|
652
|
+
JSON.stringify(residual, null, 2),
|
|
653
|
+
"",
|
|
654
|
+
"Current code:",
|
|
655
|
+
"```tsx",
|
|
656
|
+
code,
|
|
657
|
+
"```"
|
|
658
|
+
].join("\n");
|
|
659
|
+
}
|
|
660
|
+
function extractText(content) {
|
|
661
|
+
if (!content) return "";
|
|
662
|
+
if (Array.isArray(content)) return content.map(extractText).join("\n");
|
|
663
|
+
if (typeof content === "object") {
|
|
664
|
+
const record = content;
|
|
665
|
+
return typeof record.text === "string" ? record.text : "";
|
|
666
|
+
}
|
|
667
|
+
return typeof content === "string" ? content : "";
|
|
668
|
+
}
|
|
669
|
+
function extractSampledCode(text) {
|
|
670
|
+
const trimmed = text.trim();
|
|
671
|
+
if (!trimmed) return null;
|
|
672
|
+
try {
|
|
673
|
+
const parsed = JSON.parse(trimmed);
|
|
674
|
+
if (typeof parsed.code === "string" && parsed.code.trim()) return parsed.code;
|
|
675
|
+
} catch {
|
|
676
|
+
}
|
|
677
|
+
const fence = /```(?:[A-Za-z0-9_-]+)?\n([\s\S]*?)```/.exec(trimmed);
|
|
678
|
+
if (fence?.[1]?.trim()) return fence[1].trim();
|
|
679
|
+
return trimmed;
|
|
680
|
+
}
|
|
681
|
+
function encodeState(state) {
|
|
682
|
+
return JSON.stringify(state);
|
|
683
|
+
}
|
|
684
|
+
function decodeState(value) {
|
|
685
|
+
if (!value) return null;
|
|
686
|
+
try {
|
|
687
|
+
const parsed = JSON.parse(value);
|
|
688
|
+
return parsed?.tool === TOOL_NAME ? parsed : null;
|
|
689
|
+
} catch {
|
|
690
|
+
return null;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
function emptyConformResult(code) {
|
|
694
|
+
return {
|
|
695
|
+
conformed: code,
|
|
696
|
+
changed: false,
|
|
697
|
+
summary: "No conform result was produced.",
|
|
698
|
+
changes: [],
|
|
699
|
+
suggestions: [],
|
|
700
|
+
unresolved: []
|
|
701
|
+
};
|
|
702
|
+
}
|
|
129
703
|
|
|
130
|
-
// src/
|
|
131
|
-
|
|
132
|
-
var
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
704
|
+
// src/tools/registry.ts
|
|
705
|
+
var MAX_SCHEMA_DEPTH = 16;
|
|
706
|
+
var MCP_TOOLS = [
|
|
707
|
+
...primitiveTools,
|
|
708
|
+
...conformTools,
|
|
709
|
+
...proveCompliantTools
|
|
710
|
+
];
|
|
711
|
+
function getTool(name) {
|
|
712
|
+
return MCP_TOOLS.find((tool) => tool.name === name) ?? null;
|
|
713
|
+
}
|
|
714
|
+
function listToolDescriptors() {
|
|
715
|
+
return MCP_TOOLS.map(({ call: _call, ...tool }) => ({
|
|
716
|
+
...tool,
|
|
717
|
+
inputSchema: validateToolSchema(tool.name, tool.inputSchema),
|
|
718
|
+
...tool.outputSchema ? { outputSchema: validateToolSchema(tool.name, tool.outputSchema) } : {},
|
|
719
|
+
annotations: {
|
|
720
|
+
cacheScope: "tenant",
|
|
721
|
+
ttlMs: 6e4,
|
|
722
|
+
...tool.annotations ?? {}
|
|
723
|
+
}
|
|
724
|
+
}));
|
|
725
|
+
}
|
|
726
|
+
function validateToolSchema(toolName, schema) {
|
|
727
|
+
assertSchemaNode(toolName, schema, 0);
|
|
728
|
+
return schema;
|
|
729
|
+
}
|
|
730
|
+
function assertSchemaNode(toolName, value, depth) {
|
|
731
|
+
if (depth > MAX_SCHEMA_DEPTH) {
|
|
732
|
+
throw new Error(`Tool ${toolName} schema exceeds maximum depth`);
|
|
733
|
+
}
|
|
734
|
+
if (!value || typeof value !== "object") return;
|
|
735
|
+
if (Array.isArray(value)) {
|
|
736
|
+
for (const item of value) assertSchemaNode(toolName, item, depth + 1);
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const record = value;
|
|
740
|
+
const ref = record.$ref;
|
|
741
|
+
if (typeof ref === "string" && !ref.startsWith("#/")) {
|
|
742
|
+
throw new Error(`Tool ${toolName} schema contains external $ref`);
|
|
743
|
+
}
|
|
744
|
+
for (const child of Object.values(record)) {
|
|
745
|
+
assertSchemaNode(toolName, child, depth + 1);
|
|
746
|
+
}
|
|
747
|
+
}
|
|
748
|
+
|
|
749
|
+
// src/protocol/server.ts
|
|
750
|
+
var McpProtocolError = class extends Error {
|
|
751
|
+
code;
|
|
752
|
+
data;
|
|
753
|
+
constructor(code, message, data) {
|
|
754
|
+
super(message);
|
|
755
|
+
this.code = code;
|
|
756
|
+
this.data = data;
|
|
757
|
+
}
|
|
166
758
|
};
|
|
167
|
-
var
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
759
|
+
var FragmentsMcpServer = class {
|
|
760
|
+
provider;
|
|
761
|
+
tasks;
|
|
762
|
+
constructor(options) {
|
|
763
|
+
this.provider = options.provider;
|
|
764
|
+
this.tasks = options.tasks ?? new MemoryTaskStore();
|
|
765
|
+
}
|
|
766
|
+
async handleJsonRpc(request) {
|
|
767
|
+
try {
|
|
768
|
+
const result = await this.dispatch(request);
|
|
769
|
+
return success(request.id ?? null, result);
|
|
770
|
+
} catch (error) {
|
|
771
|
+
return failure(request.id ?? null, toProtocolError(error));
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
async dispatch(request) {
|
|
775
|
+
const params = request.params ?? {};
|
|
776
|
+
switch (request.method) {
|
|
777
|
+
case "server/discover":
|
|
778
|
+
return this.discover();
|
|
779
|
+
case "tools/list":
|
|
780
|
+
return { tools: listToolDescriptors() };
|
|
781
|
+
case "tools/call":
|
|
782
|
+
return this.callTool(params);
|
|
783
|
+
case "resources/list":
|
|
784
|
+
return { resources: listResources() };
|
|
785
|
+
case "resources/read":
|
|
786
|
+
return this.readResource(params);
|
|
787
|
+
case "tasks/get":
|
|
788
|
+
return this.getTask(params);
|
|
789
|
+
case "tasks/update":
|
|
790
|
+
return this.updateTask(params);
|
|
791
|
+
case "tasks/cancel":
|
|
792
|
+
return this.cancelTask(params);
|
|
793
|
+
default:
|
|
794
|
+
throw new McpProtocolError(-32601, `Unknown MCP method ${request.method}`);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
discover() {
|
|
798
|
+
return {
|
|
799
|
+
protocolVersion: LATEST_PROTOCOL_VERSION,
|
|
800
|
+
serverInfo: {
|
|
801
|
+
name: "@fragments-sdk/mcp",
|
|
802
|
+
version: "0.11.0-rc"
|
|
803
|
+
},
|
|
804
|
+
capabilities: {
|
|
805
|
+
tools: { listChanged: false }
|
|
806
|
+
}
|
|
807
|
+
};
|
|
808
|
+
}
|
|
809
|
+
async callTool(params) {
|
|
810
|
+
const name = requireString(params.name, "name");
|
|
811
|
+
const tool = getTool(name);
|
|
812
|
+
if (!tool) {
|
|
813
|
+
throw new McpProtocolError(-32602, `Unknown tool ${name}`);
|
|
814
|
+
}
|
|
815
|
+
const context = {
|
|
816
|
+
provider: this.provider,
|
|
817
|
+
tasks: this.tasks,
|
|
818
|
+
requestMeta: readMeta(params),
|
|
819
|
+
clientCapabilities: readClientCapabilities(params),
|
|
820
|
+
inputResponses: readInputResponses(params),
|
|
821
|
+
requestState: readRequestState(params)
|
|
822
|
+
};
|
|
823
|
+
const args = readArgs(params);
|
|
824
|
+
validateArgs(tool.name, tool.inputSchema, args);
|
|
825
|
+
return tool.call(args, context);
|
|
826
|
+
}
|
|
827
|
+
async readResource(params) {
|
|
828
|
+
const uri = requireString(params.uri, "uri");
|
|
829
|
+
const resource = getResource(uri);
|
|
830
|
+
if (!resource) {
|
|
831
|
+
throw new McpProtocolError(-32602, `Unknown resource ${uri}`);
|
|
832
|
+
}
|
|
833
|
+
return resource.read({
|
|
834
|
+
provider: this.provider,
|
|
835
|
+
tasks: this.tasks,
|
|
836
|
+
requestMeta: readMeta(params)
|
|
837
|
+
});
|
|
838
|
+
}
|
|
839
|
+
async getTask(params) {
|
|
840
|
+
const taskId = requireString(params.taskId, "taskId");
|
|
841
|
+
const task = await this.tasks.get(taskId);
|
|
842
|
+
if (!task) {
|
|
843
|
+
throw new McpProtocolError(-32602, `Unknown task ${taskId}`);
|
|
844
|
+
}
|
|
845
|
+
return { task };
|
|
846
|
+
}
|
|
847
|
+
async updateTask(params) {
|
|
848
|
+
const taskId = requireString(params.taskId, "taskId");
|
|
849
|
+
const task = await this.tasks.update(taskId, {});
|
|
850
|
+
if (!task) {
|
|
851
|
+
throw new McpProtocolError(-32602, `Unknown task ${taskId}`);
|
|
852
|
+
}
|
|
853
|
+
return { task };
|
|
854
|
+
}
|
|
855
|
+
async cancelTask(params) {
|
|
856
|
+
const taskId = requireString(params.taskId, "taskId");
|
|
857
|
+
const task = await this.tasks.cancel(taskId);
|
|
858
|
+
if (!task) {
|
|
859
|
+
throw new McpProtocolError(-32602, `Unknown task ${taskId}`);
|
|
860
|
+
}
|
|
861
|
+
return { task };
|
|
862
|
+
}
|
|
189
863
|
};
|
|
864
|
+
async function handleMcpJsonRpc(request, options) {
|
|
865
|
+
return new FragmentsMcpServer(options).handleJsonRpc(request);
|
|
866
|
+
}
|
|
867
|
+
async function handleMcpHttpRequest(request, options) {
|
|
868
|
+
let body;
|
|
869
|
+
try {
|
|
870
|
+
body = await request.json();
|
|
871
|
+
} catch {
|
|
872
|
+
return Response.json(failure(null, new McpProtocolError(-32700, "Invalid JSON")), {
|
|
873
|
+
status: 400
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
const response = await handleMcpJsonRpc(body, options);
|
|
877
|
+
return Response.json(response, {
|
|
878
|
+
status: "error" in response ? 400 : 200,
|
|
879
|
+
headers: {
|
|
880
|
+
"Cache-Control": "no-store",
|
|
881
|
+
"Mcp-Protocol-Version": LATEST_PROTOCOL_VERSION
|
|
882
|
+
}
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
function success(id, result) {
|
|
886
|
+
return { jsonrpc: "2.0", id, result };
|
|
887
|
+
}
|
|
888
|
+
function failure(id, error) {
|
|
889
|
+
return {
|
|
890
|
+
jsonrpc: "2.0",
|
|
891
|
+
id,
|
|
892
|
+
error: {
|
|
893
|
+
code: error.code,
|
|
894
|
+
message: error.message,
|
|
895
|
+
data: error.data
|
|
896
|
+
}
|
|
897
|
+
};
|
|
898
|
+
}
|
|
899
|
+
function toProtocolError(error) {
|
|
900
|
+
if (error instanceof McpProtocolError) return error;
|
|
901
|
+
return new McpProtocolError(
|
|
902
|
+
-32603,
|
|
903
|
+
error instanceof Error ? error.message : "Internal MCP error"
|
|
904
|
+
);
|
|
905
|
+
}
|
|
906
|
+
function requireString(value, key) {
|
|
907
|
+
if (typeof value === "string" && value.trim()) return value.trim();
|
|
908
|
+
throw new McpProtocolError(-32602, `Missing ${key}`);
|
|
909
|
+
}
|
|
910
|
+
function readArgs(params) {
|
|
911
|
+
const args = params.arguments ?? {};
|
|
912
|
+
if (!args || typeof args !== "object" || Array.isArray(args)) return {};
|
|
913
|
+
return args;
|
|
914
|
+
}
|
|
915
|
+
function readMeta(params) {
|
|
916
|
+
const meta = params._meta;
|
|
917
|
+
if (!meta || typeof meta !== "object" || Array.isArray(meta)) return void 0;
|
|
918
|
+
return meta;
|
|
919
|
+
}
|
|
920
|
+
function readClientCapabilities(params) {
|
|
921
|
+
const meta = readMeta(params);
|
|
922
|
+
const capabilities = meta?.["io.modelcontextprotocol/clientCapabilities"];
|
|
923
|
+
if (!capabilities || typeof capabilities !== "object" || Array.isArray(capabilities)) {
|
|
924
|
+
return void 0;
|
|
925
|
+
}
|
|
926
|
+
return capabilities;
|
|
927
|
+
}
|
|
928
|
+
function readInputResponses(params) {
|
|
929
|
+
const responses = params.inputResponses;
|
|
930
|
+
if (!responses || typeof responses !== "object" || Array.isArray(responses)) {
|
|
931
|
+
return void 0;
|
|
932
|
+
}
|
|
933
|
+
return responses;
|
|
934
|
+
}
|
|
935
|
+
function readRequestState(params) {
|
|
936
|
+
return typeof params.requestState === "string" ? params.requestState : void 0;
|
|
937
|
+
}
|
|
938
|
+
function validateArgs(toolName, schema, args) {
|
|
939
|
+
if (schema.type !== "object") return;
|
|
940
|
+
const properties = readProperties(schema.properties);
|
|
941
|
+
const required = Array.isArray(schema.required) ? schema.required.filter((item) => typeof item === "string") : [];
|
|
942
|
+
const argCount = Object.keys(args).filter(
|
|
943
|
+
(key) => args[key] !== void 0 && args[key] !== null
|
|
944
|
+
).length;
|
|
945
|
+
if (typeof schema.minProperties === "number" && argCount < schema.minProperties) {
|
|
946
|
+
throw new McpProtocolError(
|
|
947
|
+
-32602,
|
|
948
|
+
`Invalid arguments for ${toolName}: expected at least ${schema.minProperties} argument(s)`
|
|
949
|
+
);
|
|
950
|
+
}
|
|
951
|
+
if (typeof schema.maxProperties === "number" && argCount > schema.maxProperties) {
|
|
952
|
+
throw new McpProtocolError(
|
|
953
|
+
-32602,
|
|
954
|
+
`Invalid arguments for ${toolName}: expected at most ${schema.maxProperties} argument(s)`
|
|
955
|
+
);
|
|
956
|
+
}
|
|
957
|
+
for (const key of required) {
|
|
958
|
+
if (!(key in args) || args[key] === void 0 || args[key] === null) {
|
|
959
|
+
throw new McpProtocolError(-32602, `Invalid arguments for ${toolName}: missing ${key}`);
|
|
960
|
+
}
|
|
961
|
+
if (properties[key]?.type === "string" && typeof args[key] === "string" && !args[key].trim()) {
|
|
962
|
+
throw new McpProtocolError(-32602, `Invalid arguments for ${toolName}: missing ${key}`);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
965
|
+
if (schema.additionalProperties === false) {
|
|
966
|
+
for (const key of Object.keys(args)) {
|
|
967
|
+
if (!(key in properties)) {
|
|
968
|
+
throw new McpProtocolError(-32602, `Invalid arguments for ${toolName}: unknown ${key}`);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
for (const [key, value] of Object.entries(args)) {
|
|
973
|
+
const property = properties[key];
|
|
974
|
+
if (!property || value === void 0 || value === null) continue;
|
|
975
|
+
validateValue(toolName, key, property, value);
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
function readProperties(value) {
|
|
979
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
980
|
+
const properties = {};
|
|
981
|
+
for (const [key, child] of Object.entries(value)) {
|
|
982
|
+
if (child && typeof child === "object" && !Array.isArray(child)) {
|
|
983
|
+
properties[key] = child;
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
return properties;
|
|
987
|
+
}
|
|
988
|
+
function validateValue(toolName, key, schema, value) {
|
|
989
|
+
if (Array.isArray(schema.enum) && !schema.enum.includes(value)) {
|
|
990
|
+
throw new McpProtocolError(
|
|
991
|
+
-32602,
|
|
992
|
+
`Invalid arguments for ${toolName}: ${key} must be one of ${schema.enum.join(", ")}`
|
|
993
|
+
);
|
|
994
|
+
}
|
|
995
|
+
const type = schema.type;
|
|
996
|
+
if (typeof type !== "string") return;
|
|
997
|
+
const valid = type === "integer" ? typeof value === "number" && Number.isInteger(value) : type === "number" ? typeof value === "number" && Number.isFinite(value) : type === "string" ? typeof value === "string" : type === "boolean" ? typeof value === "boolean" : type === "object" ? !!value && typeof value === "object" && !Array.isArray(value) : true;
|
|
998
|
+
if (!valid) {
|
|
999
|
+
throw new McpProtocolError(-32602, `Invalid arguments for ${toolName}: ${key} must be ${type}`);
|
|
1000
|
+
}
|
|
1001
|
+
if (typeof value === "number" && typeof schema.minimum === "number" && value < schema.minimum) {
|
|
1002
|
+
throw new McpProtocolError(
|
|
1003
|
+
-32602,
|
|
1004
|
+
`Invalid arguments for ${toolName}: ${key} must be >= ${schema.minimum}`
|
|
1005
|
+
);
|
|
1006
|
+
}
|
|
1007
|
+
if (typeof value === "number" && typeof schema.maximum === "number" && value > schema.maximum) {
|
|
1008
|
+
throw new McpProtocolError(
|
|
1009
|
+
-32602,
|
|
1010
|
+
`Invalid arguments for ${toolName}: ${key} must be <= ${schema.maximum}`
|
|
1011
|
+
);
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
190
1014
|
|
|
191
|
-
// src/
|
|
192
|
-
var
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
1015
|
+
// src/testing/fixtures.ts
|
|
1016
|
+
var fixturePrimitives = [
|
|
1017
|
+
{
|
|
1018
|
+
id: "button",
|
|
1019
|
+
name: "Button",
|
|
1020
|
+
primitiveName: "Button",
|
|
1021
|
+
category: "Actions",
|
|
1022
|
+
status: "ready",
|
|
1023
|
+
description: "Primary action trigger.",
|
|
1024
|
+
usageGuidance: "Use for explicit user actions.",
|
|
1025
|
+
importPath: "@fixture/ui/button",
|
|
1026
|
+
packageName: "@fixture/ui",
|
|
1027
|
+
filePath: "src/components/Button.tsx",
|
|
1028
|
+
sourceRepoFullName: "fixture/ui",
|
|
1029
|
+
props: {
|
|
1030
|
+
variant: {
|
|
1031
|
+
type: '"primary" | "secondary"',
|
|
1032
|
+
required: false,
|
|
1033
|
+
description: "Visual emphasis."
|
|
1034
|
+
}
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
];
|
|
1038
|
+
var fixtureConformResult = {
|
|
1039
|
+
conformed: 'import { Button } from "@fixture/ui";\n\n<Button variant="primary" style={{ padding: "var(--space-4)" }}>Send</Button>',
|
|
1040
|
+
changed: true,
|
|
1041
|
+
summary: "Swapped <button> to Button (@fixture/ui), mapped #2563eb to --brand-600 and 16px to --space-4, flagged a missing accessible name.",
|
|
1042
|
+
designSystem: {
|
|
1043
|
+
name: "Fixture UI",
|
|
1044
|
+
importsAdded: ['import { Button } from "@fixture/ui";']
|
|
1045
|
+
},
|
|
1046
|
+
changes: [
|
|
1047
|
+
{
|
|
1048
|
+
kind: "swap-component",
|
|
1049
|
+
ruleId: "components/preferred-component",
|
|
1050
|
+
code: "FUI0410",
|
|
1051
|
+
severity: "warning",
|
|
1052
|
+
message: "Replaced native <button> with the design-system Button.",
|
|
1053
|
+
before: "<button>",
|
|
1054
|
+
after: '<Button variant="primary">',
|
|
1055
|
+
canonical: "Button",
|
|
1056
|
+
confidence: "high",
|
|
1057
|
+
location: { line: 1, column: 1 }
|
|
1058
|
+
},
|
|
1059
|
+
{
|
|
1060
|
+
kind: "replace-token",
|
|
1061
|
+
ruleId: "styles/no-raw-color",
|
|
1062
|
+
code: "FUI0601",
|
|
1063
|
+
severity: "error",
|
|
1064
|
+
message: "Replaced hardcoded color #2563eb with the matching design token.",
|
|
1065
|
+
before: "#2563eb",
|
|
1066
|
+
after: "var(--brand-600)",
|
|
1067
|
+
confidence: "high",
|
|
1068
|
+
location: { line: 1, column: 16 }
|
|
1069
|
+
},
|
|
1070
|
+
{
|
|
1071
|
+
kind: "replace-token",
|
|
1072
|
+
ruleId: "styles/no-raw-spacing",
|
|
1073
|
+
code: "FUI0602",
|
|
1074
|
+
severity: "warning",
|
|
1075
|
+
message: "Replaced hardcoded 16px with the matching spacing token.",
|
|
1076
|
+
before: "16px",
|
|
1077
|
+
after: "var(--space-4)",
|
|
1078
|
+
confidence: "high",
|
|
1079
|
+
location: { line: 1, column: 38 }
|
|
1080
|
+
}
|
|
1081
|
+
],
|
|
1082
|
+
suggestions: [
|
|
1083
|
+
{
|
|
1084
|
+
kind: "review-low-confidence",
|
|
1085
|
+
confidence: "medium",
|
|
1086
|
+
message: 'Button has no accessible name beyond its text. Confirm "Send" is descriptive or add an aria-label.',
|
|
1087
|
+
canonical: "Button",
|
|
1088
|
+
location: { line: 1, column: 1 }
|
|
1089
|
+
}
|
|
1090
|
+
],
|
|
1091
|
+
unresolved: []
|
|
203
1092
|
};
|
|
1093
|
+
function createFixtureProvider(args) {
|
|
1094
|
+
const primitives = args?.primitives ?? fixturePrimitives;
|
|
1095
|
+
const conformResult2 = args?.conform ?? fixtureConformResult;
|
|
1096
|
+
return {
|
|
1097
|
+
async listPrimitives() {
|
|
1098
|
+
return primitives;
|
|
1099
|
+
},
|
|
1100
|
+
async conform() {
|
|
1101
|
+
return conformResult2;
|
|
1102
|
+
}
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
204
1105
|
export {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
telemetryMiddleware
|
|
1106
|
+
FragmentsMcpServer,
|
|
1107
|
+
JSONRPC_VERSION,
|
|
1108
|
+
LATEST_PROTOCOL_VERSION,
|
|
1109
|
+
MCP_APP_RESOURCES,
|
|
1110
|
+
MCP_TOOLS,
|
|
1111
|
+
McpProtocolError,
|
|
1112
|
+
MemoryTaskStore,
|
|
1113
|
+
createFixtureProvider,
|
|
1114
|
+
fixtureConformResult,
|
|
1115
|
+
fixturePrimitives,
|
|
1116
|
+
getResource,
|
|
1117
|
+
getTool,
|
|
1118
|
+
handleMcpHttpRequest,
|
|
1119
|
+
handleMcpJsonRpc,
|
|
1120
|
+
listResources,
|
|
1121
|
+
listToolDescriptors
|
|
222
1122
|
};
|
|
223
1123
|
//# sourceMappingURL=index.js.map
|