@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.
@@ -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): ScenarioDescriptor[];
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: string[];
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
- export declare function protocolsResource(protocols: string[]): {
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: string[];
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 { ExecuteLuaScenarioInput } from "@lunatest/core";
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?: Record<string, unknown>;
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 ?? ["uniswap_v2", "uniswap_v3", "curve", "aave"],
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(request.params?.id ?? "")),
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: request.params?.id === undefined
98
+ id: params?.id === undefined
46
99
  ? undefined
47
- : String(request.params.id),
48
- name: request.params?.name === undefined
100
+ : String(params.id),
101
+ name: params?.name === undefined
49
102
  ? undefined
50
- : String(request.params.name),
51
- lua: request.params?.lua === undefined
103
+ : String(params.name),
104
+ lua: params?.lua === undefined
52
105
  ? undefined
53
- : String(request.params.lua),
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: request.params?.id === undefined
114
+ id: params?.id === undefined
62
115
  ? undefined
63
- : String(request.params.id),
64
- lua: request.params?.lua === undefined
116
+ : String(params.id),
117
+ lua: params?.lua === undefined
65
118
  ? undefined
66
- : String(request.params.lua),
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(request.params?.filter === undefined
126
+ result: await scenarioTools.runAll(params?.filter === undefined
74
127
  ? undefined
75
- : String(request.params.filter)),
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(request.params?.id ?? ""),
83
- count: request.params?.count === undefined
135
+ id: String(params?.id ?? ""),
136
+ count: params?.count === undefined
84
137
  ? undefined
85
- : Number(request.params.count),
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(request.params?.state ?? {}),
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(request.params?.state ?? {}),
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(request.params?.routes ?? []),
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(request.params?.name ?? "")),
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(request.params?.uri ?? "");
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(request.params?.id ?? "");
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 = request.params?.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
- export declare function createComponentTools(tree?: ComponentNode[], states?: ComponentStateMap): {
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<string[]>;
17
+ states(name: string): Promise<{
18
+ known: string[];
19
+ covered: string[];
20
+ missing: string[];
21
+ }>;
9
22
  };
10
23
  export {};
@@ -1,10 +1,50 @@
1
- export function createComponentTools(tree = [], states = {}) {
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
- return states[name] ?? [];
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
  }
@@ -1,16 +1,26 @@
1
- export type CoverageReport = {
2
- total: number;
3
- covered: number;
4
- ratio: number;
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 declare function createCoverageTools(seed?: Partial<CoverageReport>): {
7
- report(): Promise<CoverageReport>;
8
- gaps(): Promise<{
9
- id: string;
10
- reason: string;
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 {};
@@ -1,28 +1,83 @@
1
- export function createCoverageTools(seed) {
2
- const report = {
3
- total: seed?.total ?? 0,
4
- covered: seed?.covered ?? 0,
5
- ratio: seed?.ratio ??
6
- ((seed?.total ?? 0) === 0
7
- ? 1
8
- : Number(((seed?.covered ?? 0) / (seed?.total ?? 0)).toFixed(4))),
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 report;
71
+ return resolveReport();
13
72
  },
14
73
  async gaps() {
15
- const missing = Math.max(0, report.total - report.covered);
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
- const missing = Math.max(0, report.total - report.covered);
23
- return Array.from({ length: missing }, (_, index) => ({
77
+ return toGaps(await resolveReport()).map((gap, index) => ({
24
78
  id: `suggestion-${index + 1}`,
25
- title: `Add edge case scenario ${index + 1}`,
79
+ title: `Add scenario for ${gap.kind}:${gap.id}`,
80
+ target: gap,
26
81
  }));
27
82
  },
28
83
  };
@@ -1,8 +1,24 @@
1
- export declare function createMockTools(initialState?: Record<string, unknown>): {
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 {};
@@ -1,8 +1,22 @@
1
- import { deepClone, deepMerge } from "@lunatest/contracts";
2
- export function createMockTools(initialState = {}) {
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
- const presets = ["uniswap_v2", "uniswap_v3", "curve", "aave"];
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 [...presets];
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
  }
@@ -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 {};
@@ -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 variants = mutateScenarioVariants(source, count);
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: string;
4
+ id?: string | number | null;
5
5
  method: string;
6
- params?: Record<string, unknown>;
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;
@@ -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 id !== "string" || typeof method !== "string") {
32
- return invalidRequestResponse("JSON-RPC request requires string id and method");
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
- return server.handleRequest(parsed);
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.0",
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/core": "0.1.0",
28
- "@lunatest/contracts": "0.1.0"
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 --noEmit"
38
+ "lint": "tsc -p tsconfig.lint.json --pretty false"
34
39
  }
35
40
  }