@lunatest/mcp 0.1.0 → 0.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/dist/generation/mutator.d.ts +4 -1
- package/dist/generation/mutator.js +25 -1
- package/dist/resources/index.d.ts +7 -1
- package/dist/resources/protocols.d.ts +10 -2
- package/dist/server.d.ts +7 -2
- package/dist/server.js +128 -27
- package/dist/tools/component.d.ts +15 -2
- package/dist/tools/component.js +42 -2
- package/dist/tools/coverage.d.ts +20 -10
- package/dist/tools/coverage.js +72 -17
- package/dist/tools/mock.d.ts +17 -1
- package/dist/tools/mock.js +53 -4
- package/dist/tools/scenario.d.ts +4 -0
- package/dist/tools/scenario.js +10 -2
- package/dist/transport/stdio.d.ts +3 -3
- package/dist/transport/stdio.js +16 -7
- package/package.json +9 -4
|
@@ -1,5 +1,8 @@
|
|
|
1
|
+
import type { CoverageCatalog } from "@lunatest/contracts";
|
|
1
2
|
import type { ScenarioDescriptor } from "../tools/scenario.js";
|
|
2
3
|
export declare function mutateValues(lua: string, variantIndex: number): string;
|
|
3
4
|
export declare function mutateStages(lua: string, variantIndex: number): string;
|
|
4
5
|
export declare function mutateMocks(lua: string, variantIndex: number): string;
|
|
5
|
-
export declare function mutateScenarioVariants(source: ScenarioDescriptor, count: number
|
|
6
|
+
export declare function mutateScenarioVariants(source: ScenarioDescriptor, count: number, options?: {
|
|
7
|
+
missingTargets?: CoverageCatalog;
|
|
8
|
+
}): ScenarioDescriptor[];
|
|
@@ -31,16 +31,40 @@ export function mutateMocks(lua, variantIndex) {
|
|
|
31
31
|
}
|
|
32
32
|
return `${lua}\n-- mock-state mutation ${variantIndex}`;
|
|
33
33
|
}
|
|
34
|
-
export function mutateScenarioVariants(source, count) {
|
|
34
|
+
export function mutateScenarioVariants(source, count, options = {}) {
|
|
35
35
|
const variants = [];
|
|
36
|
+
const missingFeatures = options.missingTargets?.features ?? [];
|
|
37
|
+
const missingStates = options.missingTargets?.states ?? [];
|
|
38
|
+
const missingComponents = options.missingTargets?.components ?? [];
|
|
36
39
|
for (let index = 1; index <= count; index += 1) {
|
|
37
40
|
const variantLua = source.lua
|
|
38
41
|
? mutateMocks(mutateStages(mutateValues(source.lua, index), index), index)
|
|
39
42
|
: undefined;
|
|
43
|
+
const feature = missingFeatures[(index - 1) % Math.max(missingFeatures.length, 1)];
|
|
44
|
+
const state = missingStates[(index - 1) % Math.max(missingStates.length, 1)];
|
|
45
|
+
const component = missingComponents[(index - 1) % Math.max(missingComponents.length, 1)];
|
|
40
46
|
const variant = {
|
|
41
47
|
...source,
|
|
42
48
|
id: `${source.id}-mut-${index}`,
|
|
43
49
|
name: `${source.name} mutation ${index}`,
|
|
50
|
+
tags: Array.from(new Set([
|
|
51
|
+
...(source.tags ?? []),
|
|
52
|
+
"mutated",
|
|
53
|
+
])),
|
|
54
|
+
coverage: {
|
|
55
|
+
features: Array.from(new Set([
|
|
56
|
+
...(source.coverage?.features ?? []),
|
|
57
|
+
...(feature ? [feature] : []),
|
|
58
|
+
])),
|
|
59
|
+
states: Array.from(new Set([
|
|
60
|
+
...(source.coverage?.states ?? []),
|
|
61
|
+
...(state ? [state] : []),
|
|
62
|
+
])),
|
|
63
|
+
components: Array.from(new Set([
|
|
64
|
+
...(source.coverage?.components ?? []),
|
|
65
|
+
...(component ? [component] : []),
|
|
66
|
+
])),
|
|
67
|
+
},
|
|
44
68
|
};
|
|
45
69
|
if (variantLua !== undefined) {
|
|
46
70
|
variant.lua = variantLua;
|
|
@@ -7,5 +7,11 @@ export declare function createResourceCatalog(options: {
|
|
|
7
7
|
scenarios: ScenarioDescriptor[];
|
|
8
8
|
coverage: Record<string, unknown>;
|
|
9
9
|
components: unknown;
|
|
10
|
-
protocols:
|
|
10
|
+
protocols: Array<{
|
|
11
|
+
id: string;
|
|
12
|
+
label: string;
|
|
13
|
+
source: string;
|
|
14
|
+
kind: string;
|
|
15
|
+
supportedChains: number[];
|
|
16
|
+
}>;
|
|
11
17
|
}): McpResource[];
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
type ProtocolResourceItem = {
|
|
2
|
+
id: string;
|
|
3
|
+
label: string;
|
|
4
|
+
source: string;
|
|
5
|
+
kind: string;
|
|
6
|
+
supportedChains: number[];
|
|
7
|
+
};
|
|
8
|
+
export declare function protocolsResource(protocols: ProtocolResourceItem[]): {
|
|
2
9
|
uri: string;
|
|
3
|
-
content:
|
|
10
|
+
content: ProtocolResourceItem[];
|
|
4
11
|
};
|
|
12
|
+
export {};
|
package/dist/server.d.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { type ScenarioDescriptor } from "./tools/scenario.js";
|
|
2
|
-
import type
|
|
2
|
+
import { type ExecuteLuaScenarioInput, type PresetRegistry, type ProjectPresetSources } from "@lunatest/core";
|
|
3
|
+
import type { CoverageCatalog } from "@lunatest/contracts";
|
|
3
4
|
type JsonRpcRequest = {
|
|
4
5
|
id: string;
|
|
5
6
|
method: string;
|
|
6
|
-
params?:
|
|
7
|
+
params?: unknown;
|
|
7
8
|
};
|
|
8
9
|
type JsonRpcResponse = {
|
|
9
10
|
id: string;
|
|
@@ -19,6 +20,7 @@ type McpServerOptions = {
|
|
|
19
20
|
covered?: number;
|
|
20
21
|
ratio?: number;
|
|
21
22
|
};
|
|
23
|
+
coverageCatalog?: Partial<CoverageCatalog>;
|
|
22
24
|
mockState?: Record<string, unknown>;
|
|
23
25
|
componentTree?: Array<{
|
|
24
26
|
name: string;
|
|
@@ -29,6 +31,9 @@ type McpServerOptions = {
|
|
|
29
31
|
componentStates?: Record<string, string[]>;
|
|
30
32
|
protocols?: string[];
|
|
31
33
|
scenarioAdapter?: ExecuteLuaScenarioInput["adapter"];
|
|
34
|
+
presetRegistry?: PresetRegistry;
|
|
35
|
+
projectPresetSources?: ProjectPresetSources;
|
|
36
|
+
projectRoot?: string;
|
|
32
37
|
};
|
|
33
38
|
export declare function createMcpServer(options: McpServerOptions): {
|
|
34
39
|
handleRequest(request: JsonRpcRequest): Promise<JsonRpcResponse>;
|
package/dist/server.js
CHANGED
|
@@ -4,27 +4,80 @@ import { createComponentTools } from "./tools/component.js";
|
|
|
4
4
|
import { createCoverageTools } from "./tools/coverage.js";
|
|
5
5
|
import { createMockTools } from "./tools/mock.js";
|
|
6
6
|
import { createScenarioTools, } from "./tools/scenario.js";
|
|
7
|
+
import { createPresetRegistry, loadProjectPresetSources, } from "@lunatest/core";
|
|
8
|
+
import { isRecord } from "@lunatest/contracts";
|
|
7
9
|
export function createMcpServer(options) {
|
|
10
|
+
let coverageTools;
|
|
8
11
|
const scenarioTools = createScenarioTools(options.scenarios ?? [], {
|
|
9
12
|
adapter: options.scenarioAdapter,
|
|
13
|
+
getCoverageSnapshot: async () => coverageTools.report(),
|
|
14
|
+
});
|
|
15
|
+
coverageTools = createCoverageTools({
|
|
16
|
+
seed: options.coverage,
|
|
17
|
+
getScenarios: scenarioTools.list,
|
|
18
|
+
coverageCatalog: options.coverageCatalog,
|
|
19
|
+
});
|
|
20
|
+
let registryPromise = options.presetRegistry
|
|
21
|
+
? Promise.resolve(options.presetRegistry)
|
|
22
|
+
: null;
|
|
23
|
+
const resolveRegistry = async () => {
|
|
24
|
+
if (registryPromise) {
|
|
25
|
+
return registryPromise;
|
|
26
|
+
}
|
|
27
|
+
registryPromise = (async () => {
|
|
28
|
+
if (options.projectPresetSources) {
|
|
29
|
+
return createPresetRegistry({
|
|
30
|
+
projectSources: options.projectPresetSources,
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
if (options.projectRoot) {
|
|
34
|
+
const projectSources = await loadProjectPresetSources(options.projectRoot);
|
|
35
|
+
return createPresetRegistry({
|
|
36
|
+
projectSources,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
return createPresetRegistry();
|
|
40
|
+
})();
|
|
41
|
+
return registryPromise;
|
|
42
|
+
};
|
|
43
|
+
const mockTools = createMockTools(options.mockState, {
|
|
44
|
+
getRegistry: resolveRegistry,
|
|
45
|
+
});
|
|
46
|
+
const componentTools = createComponentTools(options.componentTree ?? [], options.componentStates ?? {}, {
|
|
47
|
+
getScenarios: scenarioTools.list,
|
|
48
|
+
coverageCatalog: options.coverageCatalog,
|
|
10
49
|
});
|
|
11
|
-
const coverageTools = createCoverageTools(options.coverage);
|
|
12
|
-
const mockTools = createMockTools(options.mockState);
|
|
13
|
-
const componentTools = createComponentTools(options.componentTree ?? [], options.componentStates ?? {});
|
|
14
50
|
const prompts = createPromptCatalog();
|
|
15
51
|
const getResources = async () => {
|
|
16
52
|
const scenarios = await scenarioTools.list();
|
|
17
53
|
const coverage = await coverageTools.report();
|
|
18
54
|
const components = await componentTools.tree();
|
|
55
|
+
const protocols = await mockTools.listProtocolPresets();
|
|
19
56
|
return createResourceCatalog({
|
|
20
57
|
scenarios,
|
|
21
58
|
coverage,
|
|
22
59
|
components,
|
|
23
|
-
protocols: options.protocols
|
|
60
|
+
protocols: options.protocols?.map((id) => ({
|
|
61
|
+
id,
|
|
62
|
+
label: id,
|
|
63
|
+
source: "custom",
|
|
64
|
+
kind: "custom",
|
|
65
|
+
supportedChains: [],
|
|
66
|
+
})) ??
|
|
67
|
+
protocols.map((preset) => ({
|
|
68
|
+
id: preset.qualifiedId,
|
|
69
|
+
label: preset.label,
|
|
70
|
+
source: preset.source,
|
|
71
|
+
kind: preset.kind,
|
|
72
|
+
supportedChains: preset.supportedChains,
|
|
73
|
+
})),
|
|
24
74
|
});
|
|
25
75
|
};
|
|
26
76
|
return {
|
|
27
77
|
async handleRequest(request) {
|
|
78
|
+
const params = isRecord(request.params)
|
|
79
|
+
? request.params
|
|
80
|
+
: undefined;
|
|
28
81
|
try {
|
|
29
82
|
if (request.method === "scenario.list") {
|
|
30
83
|
return {
|
|
@@ -35,22 +88,22 @@ export function createMcpServer(options) {
|
|
|
35
88
|
if (request.method === "scenario.get") {
|
|
36
89
|
return {
|
|
37
90
|
id: request.id,
|
|
38
|
-
result: await scenarioTools.get(String(
|
|
91
|
+
result: await scenarioTools.get(String(params?.id ?? "")),
|
|
39
92
|
};
|
|
40
93
|
}
|
|
41
94
|
if (request.method === "scenario.create") {
|
|
42
95
|
return {
|
|
43
96
|
id: request.id,
|
|
44
97
|
result: await scenarioTools.create({
|
|
45
|
-
id:
|
|
98
|
+
id: params?.id === undefined
|
|
46
99
|
? undefined
|
|
47
|
-
: String(
|
|
48
|
-
name:
|
|
100
|
+
: String(params.id),
|
|
101
|
+
name: params?.name === undefined
|
|
49
102
|
? undefined
|
|
50
|
-
: String(
|
|
51
|
-
lua:
|
|
103
|
+
: String(params.name),
|
|
104
|
+
lua: params?.lua === undefined
|
|
52
105
|
? undefined
|
|
53
|
-
: String(
|
|
106
|
+
: String(params.lua),
|
|
54
107
|
}),
|
|
55
108
|
};
|
|
56
109
|
}
|
|
@@ -58,31 +111,31 @@ export function createMcpServer(options) {
|
|
|
58
111
|
return {
|
|
59
112
|
id: request.id,
|
|
60
113
|
result: await scenarioTools.run({
|
|
61
|
-
id:
|
|
114
|
+
id: params?.id === undefined
|
|
62
115
|
? undefined
|
|
63
|
-
: String(
|
|
64
|
-
lua:
|
|
116
|
+
: String(params.id),
|
|
117
|
+
lua: params?.lua === undefined
|
|
65
118
|
? undefined
|
|
66
|
-
: String(
|
|
119
|
+
: String(params.lua),
|
|
67
120
|
}),
|
|
68
121
|
};
|
|
69
122
|
}
|
|
70
123
|
if (request.method === "scenario.runAll") {
|
|
71
124
|
return {
|
|
72
125
|
id: request.id,
|
|
73
|
-
result: await scenarioTools.runAll(
|
|
126
|
+
result: await scenarioTools.runAll(params?.filter === undefined
|
|
74
127
|
? undefined
|
|
75
|
-
: String(
|
|
128
|
+
: String(params.filter)),
|
|
76
129
|
};
|
|
77
130
|
}
|
|
78
131
|
if (request.method === "scenario.mutate") {
|
|
79
132
|
return {
|
|
80
133
|
id: request.id,
|
|
81
134
|
result: await scenarioTools.mutate({
|
|
82
|
-
id: String(
|
|
83
|
-
count:
|
|
135
|
+
id: String(params?.id ?? ""),
|
|
136
|
+
count: params?.count === undefined
|
|
84
137
|
? undefined
|
|
85
|
-
: Number(
|
|
138
|
+
: Number(params.count),
|
|
86
139
|
}),
|
|
87
140
|
};
|
|
88
141
|
}
|
|
@@ -113,19 +166,19 @@ export function createMcpServer(options) {
|
|
|
113
166
|
if (request.method === "mock.setState") {
|
|
114
167
|
return {
|
|
115
168
|
id: request.id,
|
|
116
|
-
result: await mockTools.setState(
|
|
169
|
+
result: await mockTools.setState(params?.state ?? {}),
|
|
117
170
|
};
|
|
118
171
|
}
|
|
119
172
|
if (request.method === "state.patch") {
|
|
120
173
|
return {
|
|
121
174
|
id: request.id,
|
|
122
|
-
result: await mockTools.patchState(
|
|
175
|
+
result: await mockTools.patchState(params?.state ?? {}),
|
|
123
176
|
};
|
|
124
177
|
}
|
|
125
178
|
if (request.method === "mock.routes.set") {
|
|
126
179
|
return {
|
|
127
180
|
id: request.id,
|
|
128
|
-
result: await mockTools.setRoutes(
|
|
181
|
+
result: await mockTools.setRoutes(params?.routes ?? []),
|
|
129
182
|
};
|
|
130
183
|
}
|
|
131
184
|
if (request.method === "mock.routes.get") {
|
|
@@ -140,6 +193,54 @@ export function createMcpServer(options) {
|
|
|
140
193
|
result: await mockTools.listPresets(),
|
|
141
194
|
};
|
|
142
195
|
}
|
|
196
|
+
if (request.method === "mock.listProtocolPresets") {
|
|
197
|
+
return {
|
|
198
|
+
id: request.id,
|
|
199
|
+
result: await mockTools.listProtocolPresets(),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
if (request.method === "mock.listPresetDiagnostics") {
|
|
203
|
+
return {
|
|
204
|
+
id: request.id,
|
|
205
|
+
result: await mockTools.listPresetDiagnostics(),
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
if (request.method === "mock.getPresetDiagnostic") {
|
|
209
|
+
return {
|
|
210
|
+
id: request.id,
|
|
211
|
+
result: await mockTools.getPresetDiagnostic(String(params?.code ?? "")),
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
if (request.method === "mock.getProtocolPreset") {
|
|
215
|
+
return {
|
|
216
|
+
id: request.id,
|
|
217
|
+
result: await mockTools.getProtocolPreset(String(params?.id ?? "")),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
if (request.method === "mock.applyProtocolPreset") {
|
|
221
|
+
return {
|
|
222
|
+
id: request.id,
|
|
223
|
+
result: await mockTools.applyProtocolPreset(String(params?.id ?? ""), params?.params ?? {}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
if (request.method === "mock.listWalletPresets") {
|
|
227
|
+
return {
|
|
228
|
+
id: request.id,
|
|
229
|
+
result: await mockTools.listWalletPresets(),
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
if (request.method === "mock.getWalletPreset") {
|
|
233
|
+
return {
|
|
234
|
+
id: request.id,
|
|
235
|
+
result: await mockTools.getWalletPreset(String(params?.id ?? "")),
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
if (request.method === "mock.applyWalletPreset") {
|
|
239
|
+
return {
|
|
240
|
+
id: request.id,
|
|
241
|
+
result: await mockTools.applyWalletPreset(String(params?.id ?? ""), params?.params ?? {}),
|
|
242
|
+
};
|
|
243
|
+
}
|
|
143
244
|
if (request.method === "component.tree") {
|
|
144
245
|
return {
|
|
145
246
|
id: request.id,
|
|
@@ -149,7 +250,7 @@ export function createMcpServer(options) {
|
|
|
149
250
|
if (request.method === "component.states") {
|
|
150
251
|
return {
|
|
151
252
|
id: request.id,
|
|
152
|
-
result: await componentTools.states(String(
|
|
253
|
+
result: await componentTools.states(String(params?.name ?? "")),
|
|
153
254
|
};
|
|
154
255
|
}
|
|
155
256
|
if (request.method === "resource.list") {
|
|
@@ -160,7 +261,7 @@ export function createMcpServer(options) {
|
|
|
160
261
|
};
|
|
161
262
|
}
|
|
162
263
|
if (request.method === "resource.get") {
|
|
163
|
-
const target = String(
|
|
264
|
+
const target = String(params?.uri ?? "");
|
|
164
265
|
const resources = await getResources();
|
|
165
266
|
return {
|
|
166
267
|
id: request.id,
|
|
@@ -174,12 +275,12 @@ export function createMcpServer(options) {
|
|
|
174
275
|
};
|
|
175
276
|
}
|
|
176
277
|
if (request.method === "prompt.get") {
|
|
177
|
-
const target = String(
|
|
278
|
+
const target = String(params?.id ?? "");
|
|
178
279
|
const prompt = prompts.find((item) => item.id === target);
|
|
179
280
|
if (!prompt) {
|
|
180
281
|
throw new Error(`Prompt not found: ${target}`);
|
|
181
282
|
}
|
|
182
|
-
const input =
|
|
283
|
+
const input = params?.input;
|
|
183
284
|
const normalizedInput = input === undefined || input === null ? "" : input;
|
|
184
285
|
return {
|
|
185
286
|
id: request.id,
|
|
@@ -1,10 +1,23 @@
|
|
|
1
|
+
import type { CoverageCatalog, CoverageMetadata } from "@lunatest/contracts";
|
|
1
2
|
type ComponentStateMap = Record<string, string[]>;
|
|
2
3
|
type ComponentNode = {
|
|
3
4
|
name: string;
|
|
4
5
|
children?: ComponentNode[];
|
|
5
6
|
};
|
|
6
|
-
|
|
7
|
+
type ScenarioCoverageLike = {
|
|
8
|
+
name: string;
|
|
9
|
+
lua?: string;
|
|
10
|
+
coverage?: CoverageMetadata;
|
|
11
|
+
};
|
|
12
|
+
export declare function createComponentTools(tree?: ComponentNode[], states?: ComponentStateMap, options?: {
|
|
13
|
+
getScenarios?: () => Promise<ScenarioCoverageLike[]> | ScenarioCoverageLike[];
|
|
14
|
+
coverageCatalog?: Partial<CoverageCatalog>;
|
|
15
|
+
}): {
|
|
7
16
|
tree(): Promise<ComponentNode[]>;
|
|
8
|
-
states(name: string): Promise<
|
|
17
|
+
states(name: string): Promise<{
|
|
18
|
+
known: string[];
|
|
19
|
+
covered: string[];
|
|
20
|
+
missing: string[];
|
|
21
|
+
}>;
|
|
9
22
|
};
|
|
10
23
|
export {};
|
package/dist/tools/component.js
CHANGED
|
@@ -1,10 +1,50 @@
|
|
|
1
|
-
|
|
1
|
+
import { loadLunaConfig, resolveCoverageMetadata } from "@lunatest/core";
|
|
2
|
+
export function createComponentTools(tree = [], states = {}, options = {}) {
|
|
3
|
+
const resolveCoverage = async () => {
|
|
4
|
+
if (!options.getScenarios) {
|
|
5
|
+
return {
|
|
6
|
+
known: options.coverageCatalog?.components ?? [],
|
|
7
|
+
covered: [],
|
|
8
|
+
missing: options.coverageCatalog?.components ?? [],
|
|
9
|
+
};
|
|
10
|
+
}
|
|
11
|
+
const scenarios = await options.getScenarios();
|
|
12
|
+
const covered = new Set();
|
|
13
|
+
for (const scenario of scenarios) {
|
|
14
|
+
const metadata = scenario.coverage ??
|
|
15
|
+
(scenario.lua
|
|
16
|
+
? resolveCoverageMetadata(await loadLunaConfig(scenario.lua))
|
|
17
|
+
: { components: [] });
|
|
18
|
+
for (const component of metadata.components ?? []) {
|
|
19
|
+
covered.add(component);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
const known = new Set([
|
|
23
|
+
...(options.coverageCatalog?.components ?? []),
|
|
24
|
+
...Object.keys(states),
|
|
25
|
+
...covered,
|
|
26
|
+
]);
|
|
27
|
+
return {
|
|
28
|
+
known: Array.from(known).sort(),
|
|
29
|
+
covered: Array.from(covered).sort(),
|
|
30
|
+
missing: Array.from(known).filter((item) => !covered.has(item)),
|
|
31
|
+
};
|
|
32
|
+
};
|
|
2
33
|
return {
|
|
3
34
|
async tree() {
|
|
4
35
|
return tree;
|
|
5
36
|
},
|
|
6
37
|
async states(name) {
|
|
7
|
-
|
|
38
|
+
const resolved = await resolveCoverage();
|
|
39
|
+
const known = Array.from(new Set([
|
|
40
|
+
...(states[name] ?? []),
|
|
41
|
+
...(resolved.known.includes(name) ? [name] : []),
|
|
42
|
+
])).sort();
|
|
43
|
+
return {
|
|
44
|
+
known,
|
|
45
|
+
covered: resolved.covered.includes(name) ? [name] : [],
|
|
46
|
+
missing: resolved.missing.includes(name) ? [name] : [],
|
|
47
|
+
};
|
|
8
48
|
},
|
|
9
49
|
};
|
|
10
50
|
}
|
package/dist/tools/coverage.d.ts
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import type { CoverageCatalog, CoverageMetadata, CoverageSnapshot } from "@lunatest/contracts";
|
|
2
|
+
type ScenarioCoverageLike = {
|
|
3
|
+
id: string;
|
|
4
|
+
name: string;
|
|
5
|
+
lua?: string;
|
|
6
|
+
coverage?: CoverageMetadata;
|
|
5
7
|
};
|
|
6
|
-
export
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
export type CoverageGap = {
|
|
9
|
+
kind: "feature" | "state" | "component";
|
|
10
|
+
id: string;
|
|
11
|
+
reason: "scenario not covered";
|
|
12
|
+
};
|
|
13
|
+
export declare function createCoverageTools(options?: {
|
|
14
|
+
seed?: Partial<CoverageSnapshot>;
|
|
15
|
+
getScenarios?: () => Promise<ScenarioCoverageLike[]> | ScenarioCoverageLike[];
|
|
16
|
+
coverageCatalog?: Partial<CoverageCatalog>;
|
|
17
|
+
}): {
|
|
18
|
+
report(): Promise<CoverageSnapshot>;
|
|
19
|
+
gaps(): Promise<CoverageGap[]>;
|
|
12
20
|
suggest(): Promise<{
|
|
13
21
|
id: string;
|
|
14
22
|
title: string;
|
|
23
|
+
target: CoverageGap;
|
|
15
24
|
}[]>;
|
|
16
25
|
};
|
|
26
|
+
export {};
|
package/dist/tools/coverage.js
CHANGED
|
@@ -1,28 +1,83 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
import { buildCoverageSnapshot, loadLunaConfig, resolveCoverageMetadata, } from "@lunatest/core";
|
|
2
|
+
export function createCoverageTools(options = {}) {
|
|
3
|
+
const seed = {
|
|
4
|
+
total: options.seed?.total ?? 0,
|
|
5
|
+
covered: options.seed?.covered ?? 0,
|
|
6
|
+
ratio: options.seed?.ratio ?? 1,
|
|
7
|
+
known: options.seed?.known ?? { features: [], states: [], components: [] },
|
|
8
|
+
coveredTargets: options.seed?.coveredTargets ?? { features: [], states: [], components: [] },
|
|
9
|
+
missing: options.seed?.missing ?? { features: [], states: [], components: [] },
|
|
10
|
+
};
|
|
11
|
+
const resolveReport = async () => {
|
|
12
|
+
if (!options.getScenarios) {
|
|
13
|
+
return seed;
|
|
14
|
+
}
|
|
15
|
+
const scenarios = await options.getScenarios();
|
|
16
|
+
if (scenarios.length === 0) {
|
|
17
|
+
return seed;
|
|
18
|
+
}
|
|
19
|
+
const items = await Promise.all(scenarios.map(async (scenario) => {
|
|
20
|
+
if (scenario.coverage) {
|
|
21
|
+
return {
|
|
22
|
+
coverage: scenario.coverage,
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
if (!scenario.lua) {
|
|
26
|
+
return {
|
|
27
|
+
coverage: {},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
const config = await loadLunaConfig(scenario.lua);
|
|
31
|
+
return {
|
|
32
|
+
...config,
|
|
33
|
+
coverage: resolveCoverageMetadata(config),
|
|
34
|
+
};
|
|
35
|
+
}));
|
|
36
|
+
return buildCoverageSnapshot({
|
|
37
|
+
items,
|
|
38
|
+
coverageCatalog: options.coverageCatalog,
|
|
39
|
+
});
|
|
40
|
+
};
|
|
41
|
+
const toGaps = (report) => {
|
|
42
|
+
const explicit = [
|
|
43
|
+
...report.missing.features.map((id) => ({
|
|
44
|
+
kind: "feature",
|
|
45
|
+
id,
|
|
46
|
+
reason: "scenario not covered",
|
|
47
|
+
})),
|
|
48
|
+
...report.missing.states.map((id) => ({
|
|
49
|
+
kind: "state",
|
|
50
|
+
id,
|
|
51
|
+
reason: "scenario not covered",
|
|
52
|
+
})),
|
|
53
|
+
...report.missing.components.map((id) => ({
|
|
54
|
+
kind: "component",
|
|
55
|
+
id,
|
|
56
|
+
reason: "scenario not covered",
|
|
57
|
+
})),
|
|
58
|
+
];
|
|
59
|
+
if (explicit.length > 0) {
|
|
60
|
+
return explicit;
|
|
61
|
+
}
|
|
62
|
+
const fallbackMissing = Math.max(0, report.total - report.covered);
|
|
63
|
+
return Array.from({ length: fallbackMissing }, (_, index) => ({
|
|
64
|
+
kind: "feature",
|
|
65
|
+
id: `gap-${index + 1}`,
|
|
66
|
+
reason: "scenario not covered",
|
|
67
|
+
}));
|
|
9
68
|
};
|
|
10
69
|
return {
|
|
11
70
|
async report() {
|
|
12
|
-
return
|
|
71
|
+
return resolveReport();
|
|
13
72
|
},
|
|
14
73
|
async gaps() {
|
|
15
|
-
|
|
16
|
-
return Array.from({ length: missing }, (_, index) => ({
|
|
17
|
-
id: `gap-${index + 1}`,
|
|
18
|
-
reason: "scenario not covered",
|
|
19
|
-
}));
|
|
74
|
+
return toGaps(await resolveReport());
|
|
20
75
|
},
|
|
21
76
|
async suggest() {
|
|
22
|
-
|
|
23
|
-
return Array.from({ length: missing }, (_, index) => ({
|
|
77
|
+
return toGaps(await resolveReport()).map((gap, index) => ({
|
|
24
78
|
id: `suggestion-${index + 1}`,
|
|
25
|
-
title: `Add
|
|
79
|
+
title: `Add scenario for ${gap.kind}:${gap.id}`,
|
|
80
|
+
target: gap,
|
|
26
81
|
}));
|
|
27
82
|
},
|
|
28
83
|
};
|
package/dist/tools/mock.d.ts
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
|
-
|
|
1
|
+
import { type PresetDiagnostic, type ProtocolPresetCatalogEntry, type ProtocolPresetMaterialization, type WalletPresetCatalogEntry, type WalletPresetMaterialization } from "@lunatest/contracts";
|
|
2
|
+
import { type PresetRegistry, type ProjectPresetSources } from "@lunatest/core";
|
|
3
|
+
type CreateMockToolsOptions = {
|
|
4
|
+
registry?: PresetRegistry;
|
|
5
|
+
projectPresetSources?: ProjectPresetSources;
|
|
6
|
+
getRegistry?: () => Promise<PresetRegistry>;
|
|
7
|
+
};
|
|
8
|
+
export declare function createMockTools(initialState?: Record<string, unknown>, options?: CreateMockToolsOptions): {
|
|
2
9
|
getState(): Promise<Record<string, unknown>>;
|
|
3
10
|
setState(next: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
4
11
|
patchState(partial: Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
5
12
|
setRoutes(nextRoutes: Array<Record<string, unknown>>): Promise<Record<string, unknown>[]>;
|
|
6
13
|
getRoutes(): Promise<Record<string, unknown>[]>;
|
|
7
14
|
listPresets(): Promise<string[]>;
|
|
15
|
+
listPresetDiagnostics(): Promise<PresetDiagnostic[]>;
|
|
16
|
+
getPresetDiagnostic(code: string): Promise<PresetDiagnostic | null>;
|
|
17
|
+
listProtocolPresets(): Promise<ProtocolPresetCatalogEntry[]>;
|
|
18
|
+
getProtocolPreset(id: string): Promise<ProtocolPresetCatalogEntry | null>;
|
|
19
|
+
applyProtocolPreset(id: string, params?: Record<string, unknown>): Promise<ProtocolPresetMaterialization>;
|
|
20
|
+
listWalletPresets(): Promise<WalletPresetCatalogEntry[]>;
|
|
21
|
+
getWalletPreset(id: string): Promise<WalletPresetCatalogEntry | null>;
|
|
22
|
+
applyWalletPreset(id: string, params?: Record<string, unknown>): Promise<WalletPresetMaterialization>;
|
|
8
23
|
};
|
|
24
|
+
export {};
|
package/dist/tools/mock.js
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
|
-
import { deepClone, deepMerge } from "@lunatest/contracts";
|
|
2
|
-
|
|
1
|
+
import { deepClone, deepMerge, } from "@lunatest/contracts";
|
|
2
|
+
import { createPresetRegistry, getPresetDiagnostics as coreGetPresetDiagnostics, getProtocolPreset as coreGetProtocolPreset, getWalletPreset as coreGetWalletPreset, listProtocolPresets as coreListProtocolPresets, listWalletPresets as coreListWalletPresets, materializeProtocolPreset as coreMaterializeProtocolPreset, materializeWalletPreset as coreMaterializeWalletPreset, } from "@lunatest/core";
|
|
3
|
+
export function createMockTools(initialState = {}, options = {}) {
|
|
3
4
|
let state = deepClone(initialState);
|
|
4
5
|
let routes = [];
|
|
5
|
-
|
|
6
|
+
let cachedRegistry = options.registry ?? null;
|
|
7
|
+
const resolveRegistry = async () => {
|
|
8
|
+
if (cachedRegistry) {
|
|
9
|
+
return cachedRegistry;
|
|
10
|
+
}
|
|
11
|
+
if (options.getRegistry) {
|
|
12
|
+
cachedRegistry = await options.getRegistry();
|
|
13
|
+
return cachedRegistry;
|
|
14
|
+
}
|
|
15
|
+
cachedRegistry = createPresetRegistry({
|
|
16
|
+
projectSources: options.projectPresetSources,
|
|
17
|
+
});
|
|
18
|
+
return cachedRegistry;
|
|
19
|
+
};
|
|
6
20
|
return {
|
|
7
21
|
async getState() {
|
|
8
22
|
return deepClone(state);
|
|
@@ -23,7 +37,42 @@ export function createMockTools(initialState = {}) {
|
|
|
23
37
|
return deepClone(routes);
|
|
24
38
|
},
|
|
25
39
|
async listPresets() {
|
|
26
|
-
return
|
|
40
|
+
return (await coreListProtocolPresets(await resolveRegistry())).map((preset) => preset.qualifiedId);
|
|
41
|
+
},
|
|
42
|
+
async listPresetDiagnostics() {
|
|
43
|
+
return coreGetPresetDiagnostics(await resolveRegistry());
|
|
44
|
+
},
|
|
45
|
+
async getPresetDiagnostic(code) {
|
|
46
|
+
const diagnostics = await coreGetPresetDiagnostics(await resolveRegistry());
|
|
47
|
+
return diagnostics.find((item) => item.code === code) ?? null;
|
|
48
|
+
},
|
|
49
|
+
async listProtocolPresets() {
|
|
50
|
+
return coreListProtocolPresets(await resolveRegistry());
|
|
51
|
+
},
|
|
52
|
+
async getProtocolPreset(id) {
|
|
53
|
+
return coreGetProtocolPreset(id, await resolveRegistry());
|
|
54
|
+
},
|
|
55
|
+
async applyProtocolPreset(id, params = {}) {
|
|
56
|
+
const materialized = await coreMaterializeProtocolPreset(id, params, await resolveRegistry());
|
|
57
|
+
state = deepMerge(state, {
|
|
58
|
+
walletSession: materialized.walletSession,
|
|
59
|
+
...materialized.interceptState,
|
|
60
|
+
});
|
|
61
|
+
routes = deepClone(materialized.routeMocks);
|
|
62
|
+
return materialized;
|
|
63
|
+
},
|
|
64
|
+
async listWalletPresets() {
|
|
65
|
+
return coreListWalletPresets(await resolveRegistry());
|
|
66
|
+
},
|
|
67
|
+
async getWalletPreset(id) {
|
|
68
|
+
return coreGetWalletPreset(id, await resolveRegistry());
|
|
69
|
+
},
|
|
70
|
+
async applyWalletPreset(id, params = {}) {
|
|
71
|
+
const materialized = await coreMaterializeWalletPreset(id, params, await resolveRegistry());
|
|
72
|
+
state = deepMerge(state, {
|
|
73
|
+
walletSession: materialized.walletSession,
|
|
74
|
+
});
|
|
75
|
+
return materialized;
|
|
27
76
|
},
|
|
28
77
|
};
|
|
29
78
|
}
|
package/dist/tools/scenario.d.ts
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import { type ExecuteLuaScenarioInput } from "@lunatest/core";
|
|
2
|
+
import type { CoverageMetadata, CoverageSnapshot } from "@lunatest/contracts";
|
|
2
3
|
export type ScenarioDescriptor = {
|
|
3
4
|
id: string;
|
|
4
5
|
name: string;
|
|
5
6
|
lua?: string;
|
|
6
7
|
tags?: string[];
|
|
8
|
+
coverage?: CoverageMetadata;
|
|
7
9
|
};
|
|
8
10
|
type ScenarioCreateInput = {
|
|
9
11
|
id?: string;
|
|
10
12
|
name?: string;
|
|
11
13
|
lua?: string;
|
|
12
14
|
tags?: string[];
|
|
15
|
+
coverage?: CoverageMetadata;
|
|
13
16
|
};
|
|
14
17
|
type ScenarioMutateInput = {
|
|
15
18
|
id: string;
|
|
@@ -39,6 +42,7 @@ export type ScenarioTools = {
|
|
|
39
42
|
};
|
|
40
43
|
type CreateScenarioToolsOptions = {
|
|
41
44
|
adapter?: ExecuteLuaScenarioInput["adapter"];
|
|
45
|
+
getCoverageSnapshot?: () => Promise<CoverageSnapshot>;
|
|
42
46
|
};
|
|
43
47
|
export declare function createScenarioTools(seed: ScenarioDescriptor[], options?: CreateScenarioToolsOptions): ScenarioTools;
|
|
44
48
|
export {};
|
package/dist/tools/scenario.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mutateScenarioVariants } from "../generation/mutator.js";
|
|
2
|
-
import { executeLuaScenario } from "@lunatest/core";
|
|
2
|
+
import { executeLuaScenario, loadLunaConfig, resolveCoverageMetadata } from "@lunatest/core";
|
|
3
3
|
async function runLua(source, adapter) {
|
|
4
4
|
const execution = await executeLuaScenario({
|
|
5
5
|
source,
|
|
@@ -23,11 +23,14 @@ export function createScenarioTools(seed, options = {}) {
|
|
|
23
23
|
async create(input) {
|
|
24
24
|
const nextId = input.id && input.id.length > 0 ? input.id : `scenario-${store.size + 1}`;
|
|
25
25
|
const nextName = input.name && input.name.length > 0 ? input.name : `scenario ${store.size + 1}`;
|
|
26
|
+
const coverage = input.coverage ??
|
|
27
|
+
(input.lua ? resolveCoverageMetadata(await loadLunaConfig(input.lua)) : undefined);
|
|
26
28
|
const next = {
|
|
27
29
|
id: nextId,
|
|
28
30
|
name: nextName,
|
|
29
31
|
lua: input.lua,
|
|
30
32
|
tags: input.tags,
|
|
33
|
+
coverage,
|
|
31
34
|
};
|
|
32
35
|
store.set(next.id, next);
|
|
33
36
|
return next;
|
|
@@ -86,7 +89,12 @@ export function createScenarioTools(seed, options = {}) {
|
|
|
86
89
|
throw new Error(`Scenario not found: ${input.id}`);
|
|
87
90
|
}
|
|
88
91
|
const count = Math.max(1, Math.trunc(input.count ?? 1));
|
|
89
|
-
const
|
|
92
|
+
const coverageSnapshot = options.getCoverageSnapshot
|
|
93
|
+
? await options.getCoverageSnapshot()
|
|
94
|
+
: undefined;
|
|
95
|
+
const variants = mutateScenarioVariants(source, count, {
|
|
96
|
+
missingTargets: coverageSnapshot?.missing,
|
|
97
|
+
});
|
|
90
98
|
for (const variant of variants) {
|
|
91
99
|
store.set(variant.id, variant);
|
|
92
100
|
}
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import type { createMcpServer } from "../server.js";
|
|
2
2
|
type McpServer = ReturnType<typeof createMcpServer>;
|
|
3
3
|
type JsonRpcLikeRequest = {
|
|
4
|
-
id
|
|
4
|
+
id?: string | number | null;
|
|
5
5
|
method: string;
|
|
6
|
-
params?:
|
|
6
|
+
params?: unknown;
|
|
7
7
|
};
|
|
8
8
|
type JsonRpcLikeResponse = {
|
|
9
|
-
id: string;
|
|
9
|
+
id: string | number | null;
|
|
10
10
|
result?: unknown;
|
|
11
11
|
error?: {
|
|
12
12
|
message: string;
|
package/dist/transport/stdio.js
CHANGED
|
@@ -28,16 +28,14 @@ export function parseJsonRpcLine(rawLine) {
|
|
|
28
28
|
}
|
|
29
29
|
const id = parsed.id;
|
|
30
30
|
const method = parsed.method;
|
|
31
|
-
if (typeof
|
|
32
|
-
|
|
31
|
+
if (typeof method !== "string" ||
|
|
32
|
+
(id !== undefined && id !== null && typeof id !== "string" && typeof id !== "number")) {
|
|
33
|
+
return invalidRequestResponse("JSON-RPC request requires method and optional string|number|null id");
|
|
33
34
|
}
|
|
34
|
-
const params = parsed.params !== undefined && isRecord(parsed.params)
|
|
35
|
-
? parsed.params
|
|
36
|
-
: undefined;
|
|
37
35
|
return {
|
|
38
36
|
id,
|
|
39
37
|
method,
|
|
40
|
-
params,
|
|
38
|
+
params: parsed.params,
|
|
41
39
|
};
|
|
42
40
|
}
|
|
43
41
|
export async function processJsonRpcLine(line, server) {
|
|
@@ -45,7 +43,18 @@ export async function processJsonRpcLine(line, server) {
|
|
|
45
43
|
if (!isJsonRpcLikeRequest(parsed)) {
|
|
46
44
|
return parsed;
|
|
47
45
|
}
|
|
48
|
-
|
|
46
|
+
const response = await server.handleRequest({
|
|
47
|
+
id: parsed.id === undefined ? "__notification__" : String(parsed.id),
|
|
48
|
+
method: parsed.method,
|
|
49
|
+
params: parsed.params,
|
|
50
|
+
});
|
|
51
|
+
if (parsed.id === undefined) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
return {
|
|
55
|
+
...response,
|
|
56
|
+
id: parsed.id,
|
|
57
|
+
};
|
|
49
58
|
}
|
|
50
59
|
export async function runStdioServer(options) {
|
|
51
60
|
const rl = createInterface({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lunatest/mcp",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -24,12 +24,17 @@
|
|
|
24
24
|
"dist"
|
|
25
25
|
],
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"@lunatest/
|
|
28
|
-
"@lunatest/
|
|
27
|
+
"@lunatest/contracts": "0.1.0",
|
|
28
|
+
"@lunatest/core": "0.1.1"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/songforthemute/lunatest",
|
|
33
|
+
"directory": "packages/mcp"
|
|
29
34
|
},
|
|
30
35
|
"scripts": {
|
|
31
36
|
"build": "tsc -p tsconfig.json",
|
|
32
37
|
"test": "vitest run",
|
|
33
|
-
"lint": "tsc -p tsconfig.json --
|
|
38
|
+
"lint": "tsc -p tsconfig.lint.json --pretty false"
|
|
34
39
|
}
|
|
35
40
|
}
|