@xiaou66/vite-plugin-vue-mcp-next 1.0.4 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/dist/index.cjs +751 -98
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +257 -0
- package/dist/index.d.ts +257 -0
- package/dist/index.js +702 -49
- package/dist/index.js.map +1 -1
- package/dist/runtime/client.cjs +585 -1
- package/dist/runtime/client.cjs.map +1 -1
- package/dist/runtime/client.js +585 -1
- package/dist/runtime/client.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -18,6 +18,11 @@ var DEFAULT_MASK_HEADERS = [
|
|
|
18
18
|
var DEFAULT_MCP_PATH = "/__mcp";
|
|
19
19
|
var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
|
|
20
20
|
var DEFAULT_SCREENSHOT_SAVE_DIR = ".vite-mcp/screenshot";
|
|
21
|
+
var DEFAULT_PERFORMANCE_SAVE_DIR = ".vite-mcp/performance";
|
|
22
|
+
var DEFAULT_PERFORMANCE_MAX_REPORTS = 100;
|
|
23
|
+
var DEFAULT_PERFORMANCE_MAX_DURATION_MS = 3e4;
|
|
24
|
+
var DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS = 250;
|
|
25
|
+
var DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS = 50;
|
|
21
26
|
var MCP_TOOL_NAMES = {
|
|
22
27
|
listPages: "list_pages",
|
|
23
28
|
reloadPage: "reload_page",
|
|
@@ -30,6 +35,11 @@ var MCP_TOOL_NAMES = {
|
|
|
30
35
|
getNetworkRequests: "get_network_requests",
|
|
31
36
|
getNetworkRequestDetail: "get_network_request_detail",
|
|
32
37
|
clearNetworkRequests: "clear_network_requests",
|
|
38
|
+
recordPerformance: "record_performance",
|
|
39
|
+
startPerformanceRecording: "start_performance_recording",
|
|
40
|
+
stopPerformanceRecording: "stop_performance_recording",
|
|
41
|
+
getPerformanceReport: "get_performance_report",
|
|
42
|
+
takeHeapSnapshot: "take_heap_snapshot",
|
|
33
43
|
getComponentTree: "get_component_tree",
|
|
34
44
|
getComponentState: "get_component_state",
|
|
35
45
|
editComponentState: "edit_component_state",
|
|
@@ -98,6 +108,19 @@ var DEFAULT_OPTIONS = {
|
|
|
98
108
|
options: {},
|
|
99
109
|
plugins: []
|
|
100
110
|
}
|
|
111
|
+
},
|
|
112
|
+
performance: {
|
|
113
|
+
mode: "auto",
|
|
114
|
+
maxDurationMs: DEFAULT_PERFORMANCE_MAX_DURATION_MS,
|
|
115
|
+
sampleIntervalMs: DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS,
|
|
116
|
+
longTaskThresholdMs: DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS,
|
|
117
|
+
saveDir: DEFAULT_PERFORMANCE_SAVE_DIR,
|
|
118
|
+
memory: {
|
|
119
|
+
enabled: true
|
|
120
|
+
},
|
|
121
|
+
stacks: {
|
|
122
|
+
enabled: true
|
|
123
|
+
}
|
|
101
124
|
}
|
|
102
125
|
};
|
|
103
126
|
function mergeMcpClientOptions(cursorConfig, mcpClients) {
|
|
@@ -167,6 +190,18 @@ function mergeOptions(options = {}) {
|
|
|
167
190
|
...DEFAULT_OPTIONS.screenshot.snapdom.plugins
|
|
168
191
|
]
|
|
169
192
|
}
|
|
193
|
+
},
|
|
194
|
+
performance: {
|
|
195
|
+
...DEFAULT_OPTIONS.performance,
|
|
196
|
+
...options.performance,
|
|
197
|
+
memory: {
|
|
198
|
+
...DEFAULT_OPTIONS.performance.memory,
|
|
199
|
+
...options.performance?.memory
|
|
200
|
+
},
|
|
201
|
+
stacks: {
|
|
202
|
+
...DEFAULT_OPTIONS.performance.stacks,
|
|
203
|
+
...options.performance?.stacks
|
|
204
|
+
}
|
|
170
205
|
}
|
|
171
206
|
};
|
|
172
207
|
}
|
|
@@ -262,7 +297,11 @@ function createVueMcpNextContext(options) {
|
|
|
262
297
|
rpcServer: void 0,
|
|
263
298
|
pages: createPageTargetRegistry(),
|
|
264
299
|
consoleRecords: createRingBuffer(options.console.maxRecords),
|
|
265
|
-
networkRecords: createRingBuffer(options.network.maxRecords)
|
|
300
|
+
networkRecords: createRingBuffer(options.network.maxRecords),
|
|
301
|
+
performanceReports: createRingBuffer(
|
|
302
|
+
DEFAULT_PERFORMANCE_MAX_REPORTS
|
|
303
|
+
),
|
|
304
|
+
performanceSessions: /* @__PURE__ */ new Map()
|
|
266
305
|
};
|
|
267
306
|
}
|
|
268
307
|
|
|
@@ -380,13 +419,13 @@ async function requestRuntimeData(ctx, trigger) {
|
|
|
380
419
|
return { ok: false, error: "runtime bridge is not connected" };
|
|
381
420
|
}
|
|
382
421
|
const event = nanoid();
|
|
383
|
-
return new Promise((
|
|
422
|
+
return new Promise((resolve2) => {
|
|
384
423
|
const timeout = setTimeout(() => {
|
|
385
|
-
|
|
424
|
+
resolve2({ ok: false, error: "runtime bridge response timed out" });
|
|
386
425
|
}, 5e3);
|
|
387
426
|
ctx.hooks.hookOnce(event, (data) => {
|
|
388
427
|
clearTimeout(timeout);
|
|
389
|
-
|
|
428
|
+
resolve2(data);
|
|
390
429
|
});
|
|
391
430
|
trigger(event);
|
|
392
431
|
});
|
|
@@ -665,9 +704,595 @@ function registerNetworkTools(server, ctx) {
|
|
|
665
704
|
);
|
|
666
705
|
}
|
|
667
706
|
|
|
668
|
-
// src/mcp/tools/
|
|
707
|
+
// src/mcp/tools/performance.ts
|
|
669
708
|
import { z as z5 } from "zod";
|
|
670
709
|
|
|
710
|
+
// src/performance/summary.ts
|
|
711
|
+
function buildPerformanceSummary(input) {
|
|
712
|
+
const memory = buildMemorySummary(input.memorySamples);
|
|
713
|
+
const blockedTimeMs = input.longTasks.reduce((total, task) => {
|
|
714
|
+
return total + Math.max(0, task.durationMs - DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS);
|
|
715
|
+
}, 0);
|
|
716
|
+
const longTaskCount = input.longTasks.length;
|
|
717
|
+
const durations = input.longTasks.map((task) => task.durationMs);
|
|
718
|
+
const maxTaskDurationMs = durations.length ? Math.max(...durations) : 0;
|
|
719
|
+
const averageTaskDurationMs = durations.length ? Math.round(
|
|
720
|
+
durations.reduce((total, duration) => total + duration, 0) / durations.length
|
|
721
|
+
) : void 0;
|
|
722
|
+
const suspectedJank = blockedTimeMs > 0 || memory.trend === "growing" || longTaskCount > 0;
|
|
723
|
+
const severity = resolveSeverity({
|
|
724
|
+
blockedTimeMs,
|
|
725
|
+
longTaskCount,
|
|
726
|
+
memoryTrend: memory.trend
|
|
727
|
+
});
|
|
728
|
+
return {
|
|
729
|
+
blockedTimeMs,
|
|
730
|
+
longTaskCount,
|
|
731
|
+
maxTaskDurationMs,
|
|
732
|
+
averageTaskDurationMs,
|
|
733
|
+
suspectedJank,
|
|
734
|
+
severity
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
function buildMemorySummary(samples) {
|
|
738
|
+
const first = samples[0]?.usedJSHeapSize;
|
|
739
|
+
const last = samples.at(-1)?.usedJSHeapSize;
|
|
740
|
+
const peak = samples.reduce((currentPeak, sample) => {
|
|
741
|
+
if (typeof sample.usedJSHeapSize !== "number") {
|
|
742
|
+
return currentPeak;
|
|
743
|
+
}
|
|
744
|
+
return Math.max(currentPeak, sample.usedJSHeapSize);
|
|
745
|
+
}, 0);
|
|
746
|
+
const trend = resolveMemoryTrend(first, last);
|
|
747
|
+
return {
|
|
748
|
+
samples,
|
|
749
|
+
initialUsedJSHeapSize: first,
|
|
750
|
+
finalUsedJSHeapSize: last,
|
|
751
|
+
peakUsedJSHeapSize: peak || void 0,
|
|
752
|
+
deltaUsedJSHeapSize: typeof first === "number" && typeof last === "number" ? last - first : void 0,
|
|
753
|
+
trend
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
function buildStackSummary(frames, options = {}) {
|
|
757
|
+
return {
|
|
758
|
+
topFrames: [...frames].sort(sortByHotness).slice(0, 10),
|
|
759
|
+
rawProfilePath: options.rawProfilePath,
|
|
760
|
+
limitation: options.limitation
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
function resolveSeverity(input) {
|
|
764
|
+
if (input.blockedTimeMs >= 1e3 || input.longTaskCount >= 10) {
|
|
765
|
+
return "critical";
|
|
766
|
+
}
|
|
767
|
+
if (input.blockedTimeMs > 0 || input.memoryTrend === "growing") {
|
|
768
|
+
return "warning";
|
|
769
|
+
}
|
|
770
|
+
return "ok";
|
|
771
|
+
}
|
|
772
|
+
function resolveMemoryTrend(first, last) {
|
|
773
|
+
if (typeof first !== "number" || typeof last !== "number") {
|
|
774
|
+
return "unknown";
|
|
775
|
+
}
|
|
776
|
+
if (last > first) {
|
|
777
|
+
return "growing";
|
|
778
|
+
}
|
|
779
|
+
return "stable";
|
|
780
|
+
}
|
|
781
|
+
function sortByHotness(left, right) {
|
|
782
|
+
return compareNumber(right.totalTimeMs, left.totalTimeMs) || compareNumber(right.selfTimeMs, left.selfTimeMs) || compareNumber(right.hitCount, left.hitCount);
|
|
783
|
+
}
|
|
784
|
+
function compareNumber(left, right) {
|
|
785
|
+
return (left ?? -1) - (right ?? -1);
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
// src/performance/output.ts
|
|
789
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
790
|
+
import { relative, resolve } from "path";
|
|
791
|
+
import { nanoid as nanoid2 } from "nanoid";
|
|
792
|
+
async function writePerformanceArtifact(options) {
|
|
793
|
+
const root = resolve(options.root ?? process.cwd());
|
|
794
|
+
const dir = resolveOutputDirectory(root, options.saveDir);
|
|
795
|
+
const path8 = resolve(dir, createSafeFileName(options.fileName));
|
|
796
|
+
const data = typeof options.data === "string" ? Buffer.from(options.data) : options.data;
|
|
797
|
+
await mkdir(dir, { recursive: true });
|
|
798
|
+
await writeFile(path8, data);
|
|
799
|
+
return {
|
|
800
|
+
kind: options.kind,
|
|
801
|
+
path: path8,
|
|
802
|
+
relativePath: relative(root, path8),
|
|
803
|
+
byteLength: data.byteLength,
|
|
804
|
+
source: "cdp"
|
|
805
|
+
};
|
|
806
|
+
}
|
|
807
|
+
function resolveOutputDirectory(root, saveDir) {
|
|
808
|
+
return resolve(root, saveDir);
|
|
809
|
+
}
|
|
810
|
+
function createSafeFileName(fileName) {
|
|
811
|
+
const trimmed = fileName.trim();
|
|
812
|
+
if (!trimmed) {
|
|
813
|
+
return `${String(Date.now())}-${nanoid2()}`;
|
|
814
|
+
}
|
|
815
|
+
const suffix = nanoid2(6);
|
|
816
|
+
const dotIndex = trimmed.lastIndexOf(".");
|
|
817
|
+
if (dotIndex === -1) {
|
|
818
|
+
return `${trimmed}-${suffix}`;
|
|
819
|
+
}
|
|
820
|
+
const base = trimmed.slice(0, dotIndex);
|
|
821
|
+
const extension = trimmed.slice(dotIndex);
|
|
822
|
+
return `${base}-${suffix}${extension}`;
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
// src/cdp/cdpPerformance.ts
|
|
826
|
+
async function recordCdpPerformance(options) {
|
|
827
|
+
const session = await startCdpPerformanceRecording(options);
|
|
828
|
+
await waitForDuration(options.durationMs);
|
|
829
|
+
return stopCdpPerformanceRecording({
|
|
830
|
+
client: options.client,
|
|
831
|
+
session
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
async function startCdpPerformanceRecording(options) {
|
|
835
|
+
await options.client.Performance.enable();
|
|
836
|
+
await options.client.Profiler.enable();
|
|
837
|
+
await options.client.Profiler.start();
|
|
838
|
+
return {
|
|
839
|
+
recordingId: createRecordingId(options.pageId),
|
|
840
|
+
pageId: options.pageId,
|
|
841
|
+
startedAt: Date.now(),
|
|
842
|
+
includeMemory: options.includeMemory,
|
|
843
|
+
includeStacks: options.includeStacks,
|
|
844
|
+
saveDir: options.saveDir
|
|
845
|
+
};
|
|
846
|
+
}
|
|
847
|
+
async function stopCdpPerformanceRecording(options) {
|
|
848
|
+
const endedAt = Date.now();
|
|
849
|
+
const [metricsResult, profileResult] = await Promise.all([
|
|
850
|
+
options.client.Performance.getMetrics(),
|
|
851
|
+
options.client.Profiler.stop()
|
|
852
|
+
]);
|
|
853
|
+
const metrics = toMetricMap(metricsResult.metrics);
|
|
854
|
+
const longTasks = toLongTaskRecords(metrics);
|
|
855
|
+
const memorySamples = options.session.includeMemory ? [toMemorySample(metrics)] : [];
|
|
856
|
+
const memory = options.session.includeMemory ? buildMemorySummary(memorySamples) : void 0;
|
|
857
|
+
const stacks = options.session.includeStacks ? buildStackSummary(aggregateCpuProfile(profileResult.profile)) : void 0;
|
|
858
|
+
const artifact = await writeCpuProfileArtifact(
|
|
859
|
+
{
|
|
860
|
+
pageId: options.session.pageId,
|
|
861
|
+
saveDir: options.session.saveDir
|
|
862
|
+
},
|
|
863
|
+
profileResult.profile
|
|
864
|
+
);
|
|
865
|
+
const report = {
|
|
866
|
+
recordingId: options.session.recordingId,
|
|
867
|
+
pageId: options.session.pageId,
|
|
868
|
+
source: "cdp",
|
|
869
|
+
startedAt: options.session.startedAt,
|
|
870
|
+
endedAt,
|
|
871
|
+
durationMs: endedAt - options.session.startedAt,
|
|
872
|
+
summary: buildPerformanceSummary({
|
|
873
|
+
longTasks,
|
|
874
|
+
memorySamples
|
|
875
|
+
}),
|
|
876
|
+
longTasks,
|
|
877
|
+
memory,
|
|
878
|
+
stacks,
|
|
879
|
+
artifacts: [artifact],
|
|
880
|
+
limitations: [
|
|
881
|
+
"CDP path returns sampled CPU profile data, not a full instruction trace"
|
|
882
|
+
]
|
|
883
|
+
};
|
|
884
|
+
return report;
|
|
885
|
+
}
|
|
886
|
+
async function takeHeapSnapshot(options) {
|
|
887
|
+
const chunks = [];
|
|
888
|
+
await options.client.HeapProfiler.enable();
|
|
889
|
+
options.client.HeapProfiler.addHeapSnapshotChunk((event) => {
|
|
890
|
+
const payload = event;
|
|
891
|
+
if (payload.chunk) {
|
|
892
|
+
chunks.push(payload.chunk);
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
await options.client.HeapProfiler.takeHeapSnapshot({
|
|
896
|
+
reportProgress: false
|
|
897
|
+
});
|
|
898
|
+
const artifact = await writePerformanceArtifact({
|
|
899
|
+
root: process.cwd(),
|
|
900
|
+
saveDir: options.saveDir,
|
|
901
|
+
fileName: `${options.pageId}-heap-snapshot.heapsnapshot`,
|
|
902
|
+
kind: "heap-snapshot",
|
|
903
|
+
data: Buffer.from(chunks.join(""))
|
|
904
|
+
});
|
|
905
|
+
return artifact;
|
|
906
|
+
}
|
|
907
|
+
function createRecordingId(pageId) {
|
|
908
|
+
return `cdp-${pageId}-${String(Date.now())}`;
|
|
909
|
+
}
|
|
910
|
+
function waitForDuration(durationMs) {
|
|
911
|
+
return new Promise((resolve2) => {
|
|
912
|
+
setTimeout(() => {
|
|
913
|
+
resolve2();
|
|
914
|
+
}, durationMs);
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
function toMetricMap(metrics) {
|
|
918
|
+
return new Map(metrics.map((metric) => [metric.name, metric.value]));
|
|
919
|
+
}
|
|
920
|
+
function toLongTaskRecords(metrics) {
|
|
921
|
+
const taskDuration = metrics.get("TaskDuration") ?? 0;
|
|
922
|
+
if (taskDuration <= 0) {
|
|
923
|
+
return [];
|
|
924
|
+
}
|
|
925
|
+
return [
|
|
926
|
+
{
|
|
927
|
+
startTime: 0,
|
|
928
|
+
durationMs: taskDuration,
|
|
929
|
+
name: "TaskDuration",
|
|
930
|
+
source: "cpu-profile"
|
|
931
|
+
}
|
|
932
|
+
];
|
|
933
|
+
}
|
|
934
|
+
function toMemorySample(metrics) {
|
|
935
|
+
return {
|
|
936
|
+
timestamp: Date.now(),
|
|
937
|
+
usedJSHeapSize: metrics.get("JSHeapUsedSize"),
|
|
938
|
+
totalJSHeapSize: metrics.get("JSHeapTotalSize"),
|
|
939
|
+
jsHeapSizeLimit: metrics.get("JSHeapSizeLimit")
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
function aggregateCpuProfile(profile) {
|
|
943
|
+
const nodes = profile.nodes ?? [];
|
|
944
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
945
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
946
|
+
nodes.forEach((node) => nodeIndex.set(node.id, node));
|
|
947
|
+
(profile.samples ?? []).forEach((nodeId, index) => {
|
|
948
|
+
const node = nodeIndex.get(nodeId);
|
|
949
|
+
if (!node) {
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
const delta = profile.timeDeltas?.[index] ?? 0;
|
|
953
|
+
const current = nodeMap.get(nodeId) ?? {
|
|
954
|
+
functionName: node.callFrame.functionName || "<anonymous>",
|
|
955
|
+
url: node.callFrame.url,
|
|
956
|
+
lineNumber: node.callFrame.lineNumber,
|
|
957
|
+
columnNumber: node.callFrame.columnNumber,
|
|
958
|
+
selfTimeMs: 0,
|
|
959
|
+
totalTimeMs: 0,
|
|
960
|
+
hitCount: 0
|
|
961
|
+
};
|
|
962
|
+
nodeMap.set(nodeId, {
|
|
963
|
+
...current,
|
|
964
|
+
selfTimeMs: current.selfTimeMs + delta,
|
|
965
|
+
totalTimeMs: current.totalTimeMs + delta,
|
|
966
|
+
hitCount: current.hitCount + 1
|
|
967
|
+
});
|
|
968
|
+
});
|
|
969
|
+
return [...nodeMap.values()];
|
|
970
|
+
}
|
|
971
|
+
async function writeCpuProfileArtifact(options, profile) {
|
|
972
|
+
return writePerformanceArtifact({
|
|
973
|
+
root: process.cwd(),
|
|
974
|
+
saveDir: options.saveDir ?? ".vite-mcp/performance",
|
|
975
|
+
fileName: `${options.pageId}-cpu-profile.cpuprofile`,
|
|
976
|
+
kind: "cpu-profile",
|
|
977
|
+
data: Buffer.from(JSON.stringify(profile, null, 2))
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
// src/mcp/tools/performance.ts
|
|
982
|
+
var PERFORMANCE_NOT_AVAILABLE_ERROR = "Performance diagnostics are disabled by configuration";
|
|
983
|
+
var activeCdpPerformanceClients = /* @__PURE__ */ new Map();
|
|
984
|
+
function registerPerformanceTools(server, ctx) {
|
|
985
|
+
server.registerTool(
|
|
986
|
+
MCP_TOOL_NAMES.recordPerformance,
|
|
987
|
+
{
|
|
988
|
+
description: "Record a performance sample for the selected page.",
|
|
989
|
+
inputSchema: {
|
|
990
|
+
pageId: z5.string().optional(),
|
|
991
|
+
durationMs: z5.number().optional(),
|
|
992
|
+
includeMemory: z5.boolean().optional(),
|
|
993
|
+
includeStacks: z5.boolean().optional()
|
|
994
|
+
}
|
|
995
|
+
},
|
|
996
|
+
async (input) => handleRecordPerformance(ctx, input)
|
|
997
|
+
);
|
|
998
|
+
server.registerTool(
|
|
999
|
+
MCP_TOOL_NAMES.startPerformanceRecording,
|
|
1000
|
+
{
|
|
1001
|
+
description: "Start a performance recording session.",
|
|
1002
|
+
inputSchema: {
|
|
1003
|
+
pageId: z5.string().optional(),
|
|
1004
|
+
includeMemory: z5.boolean().optional(),
|
|
1005
|
+
includeStacks: z5.boolean().optional()
|
|
1006
|
+
}
|
|
1007
|
+
},
|
|
1008
|
+
async (input) => handleStartPerformanceRecording(ctx, input)
|
|
1009
|
+
);
|
|
1010
|
+
server.registerTool(
|
|
1011
|
+
MCP_TOOL_NAMES.stopPerformanceRecording,
|
|
1012
|
+
{
|
|
1013
|
+
description: "Stop a performance recording session.",
|
|
1014
|
+
inputSchema: {
|
|
1015
|
+
recordingId: z5.string()
|
|
1016
|
+
}
|
|
1017
|
+
},
|
|
1018
|
+
async (input) => handleStopPerformanceRecording(ctx, input)
|
|
1019
|
+
);
|
|
1020
|
+
server.registerTool(
|
|
1021
|
+
MCP_TOOL_NAMES.getPerformanceReport,
|
|
1022
|
+
{
|
|
1023
|
+
description: "Get cached performance reports and active sessions.",
|
|
1024
|
+
inputSchema: {
|
|
1025
|
+
pageId: z5.string().optional(),
|
|
1026
|
+
recordingId: z5.string().optional(),
|
|
1027
|
+
limit: z5.number().optional()
|
|
1028
|
+
}
|
|
1029
|
+
},
|
|
1030
|
+
(input) => handleGetPerformanceReport(ctx, input)
|
|
1031
|
+
);
|
|
1032
|
+
server.registerTool(
|
|
1033
|
+
MCP_TOOL_NAMES.takeHeapSnapshot,
|
|
1034
|
+
{
|
|
1035
|
+
description: "Take a heap snapshot with CDP.",
|
|
1036
|
+
inputSchema: {
|
|
1037
|
+
pageId: z5.string().optional()
|
|
1038
|
+
}
|
|
1039
|
+
},
|
|
1040
|
+
async (input) => handleTakeHeapSnapshot(ctx, input)
|
|
1041
|
+
);
|
|
1042
|
+
}
|
|
1043
|
+
function appendPerformanceReport(ctx, report) {
|
|
1044
|
+
if (ctx.performanceReports.all().some((item) => item.recordingId === report.recordingId)) {
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
ctx.performanceReports.push(report);
|
|
1048
|
+
}
|
|
1049
|
+
async function handleRecordPerformance(ctx, input) {
|
|
1050
|
+
const durationMs = input.durationMs ?? ctx.options.performance.maxDurationMs;
|
|
1051
|
+
const includeMemory = input.includeMemory ?? ctx.options.performance.memory.enabled;
|
|
1052
|
+
const includeStacks = input.includeStacks ?? ctx.options.performance.stacks.enabled;
|
|
1053
|
+
if (ctx.options.performance.mode === "off") {
|
|
1054
|
+
return createToolError(PERFORMANCE_NOT_AVAILABLE_ERROR);
|
|
1055
|
+
}
|
|
1056
|
+
if (ctx.options.performance.mode !== "hook") {
|
|
1057
|
+
const cdpResult = await connectPerformanceCdp(ctx, input.pageId);
|
|
1058
|
+
const cdp = cdpResult.cdp;
|
|
1059
|
+
if (cdp) {
|
|
1060
|
+
try {
|
|
1061
|
+
const report = await recordCdpPerformance({
|
|
1062
|
+
client: cdp.client,
|
|
1063
|
+
pageId: input.pageId ?? "cdp",
|
|
1064
|
+
durationMs,
|
|
1065
|
+
includeMemory,
|
|
1066
|
+
includeStacks,
|
|
1067
|
+
saveDir: ctx.options.performance.saveDir
|
|
1068
|
+
});
|
|
1069
|
+
appendPerformanceReport(ctx, report);
|
|
1070
|
+
return createToolResponse(toStructuredRecord(report));
|
|
1071
|
+
} finally {
|
|
1072
|
+
await closeCdpClient(cdp.client);
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
if (ctx.options.performance.mode === "cdp") {
|
|
1076
|
+
return createToolError(
|
|
1077
|
+
formatCdpUnavailableError(
|
|
1078
|
+
"CDP performance collection is unavailable",
|
|
1079
|
+
cdpResult.error
|
|
1080
|
+
)
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
const result = await requestRuntimeData(ctx, (event) => {
|
|
1085
|
+
void ctx.rpcServer?.recordPerformance({
|
|
1086
|
+
event,
|
|
1087
|
+
durationMs,
|
|
1088
|
+
includeMemory,
|
|
1089
|
+
includeStacks
|
|
1090
|
+
});
|
|
1091
|
+
});
|
|
1092
|
+
if (isPerformanceReport(result)) {
|
|
1093
|
+
appendPerformanceReport(ctx, result);
|
|
1094
|
+
}
|
|
1095
|
+
if (isPlainRecord(result)) {
|
|
1096
|
+
return createToolResponse(toStructuredRecord(result));
|
|
1097
|
+
}
|
|
1098
|
+
return createToolError("runtime bridge returned an invalid response");
|
|
1099
|
+
}
|
|
1100
|
+
async function handleStartPerformanceRecording(ctx, input) {
|
|
1101
|
+
const includeMemory = input.includeMemory ?? ctx.options.performance.memory.enabled;
|
|
1102
|
+
const includeStacks = input.includeStacks ?? ctx.options.performance.stacks.enabled;
|
|
1103
|
+
if (ctx.options.performance.mode === "off") {
|
|
1104
|
+
return createToolError(PERFORMANCE_NOT_AVAILABLE_ERROR);
|
|
1105
|
+
}
|
|
1106
|
+
if (ctx.options.performance.mode !== "hook") {
|
|
1107
|
+
const cdpResult = await connectPerformanceCdp(ctx, input.pageId);
|
|
1108
|
+
const cdp = cdpResult.cdp;
|
|
1109
|
+
if (cdp) {
|
|
1110
|
+
try {
|
|
1111
|
+
const session = await startCdpPerformanceRecording({
|
|
1112
|
+
client: cdp.client,
|
|
1113
|
+
pageId: input.pageId ?? "cdp",
|
|
1114
|
+
includeMemory,
|
|
1115
|
+
includeStacks,
|
|
1116
|
+
saveDir: ctx.options.performance.saveDir
|
|
1117
|
+
});
|
|
1118
|
+
ctx.performanceSessions.set(session.recordingId, {
|
|
1119
|
+
recordingId: session.recordingId,
|
|
1120
|
+
pageId: session.pageId,
|
|
1121
|
+
source: "cdp",
|
|
1122
|
+
startedAt: session.startedAt,
|
|
1123
|
+
includeMemory,
|
|
1124
|
+
includeStacks,
|
|
1125
|
+
mode: ctx.options.performance.mode
|
|
1126
|
+
});
|
|
1127
|
+
activeCdpPerformanceClients.set(session.recordingId, cdp.client);
|
|
1128
|
+
return createToolResponse(
|
|
1129
|
+
toStructuredRecord({
|
|
1130
|
+
...session,
|
|
1131
|
+
source: "cdp"
|
|
1132
|
+
})
|
|
1133
|
+
);
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
await closeCdpClient(cdp.client);
|
|
1136
|
+
return createToolError(
|
|
1137
|
+
error instanceof Error ? error.message : String(error)
|
|
1138
|
+
);
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
if (ctx.options.performance.mode === "cdp") {
|
|
1142
|
+
return createToolError(
|
|
1143
|
+
formatCdpUnavailableError(
|
|
1144
|
+
"CDP performance collection is unavailable",
|
|
1145
|
+
cdpResult.error
|
|
1146
|
+
)
|
|
1147
|
+
);
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
const result = await requestRuntimeData(ctx, (event) => {
|
|
1151
|
+
void ctx.rpcServer?.startPerformanceRecording({
|
|
1152
|
+
event,
|
|
1153
|
+
includeMemory,
|
|
1154
|
+
includeStacks
|
|
1155
|
+
});
|
|
1156
|
+
});
|
|
1157
|
+
if (isPerformanceStartResult(result)) {
|
|
1158
|
+
ctx.performanceSessions.set(result.recordingId, {
|
|
1159
|
+
recordingId: result.recordingId,
|
|
1160
|
+
pageId: input.pageId ?? "runtime",
|
|
1161
|
+
source: "hook",
|
|
1162
|
+
startedAt: result.startedAt,
|
|
1163
|
+
includeMemory,
|
|
1164
|
+
includeStacks,
|
|
1165
|
+
mode: ctx.options.performance.mode
|
|
1166
|
+
});
|
|
1167
|
+
}
|
|
1168
|
+
if (isPlainRecord(result)) {
|
|
1169
|
+
return createToolResponse(toStructuredRecord(result));
|
|
1170
|
+
}
|
|
1171
|
+
return createToolError("runtime bridge returned an invalid response");
|
|
1172
|
+
}
|
|
1173
|
+
async function handleStopPerformanceRecording(ctx, input) {
|
|
1174
|
+
const session = ctx.performanceSessions.get(input.recordingId);
|
|
1175
|
+
if (!session) {
|
|
1176
|
+
return createToolError(`Performance recording not found: ${input.recordingId}`);
|
|
1177
|
+
}
|
|
1178
|
+
try {
|
|
1179
|
+
if (session.source === "cdp") {
|
|
1180
|
+
const client = activeCdpPerformanceClients.get(input.recordingId);
|
|
1181
|
+
if (!client) {
|
|
1182
|
+
return createToolError(
|
|
1183
|
+
`CDP client not found for recording: ${input.recordingId}`
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
const report = await stopCdpPerformanceRecording({
|
|
1187
|
+
client,
|
|
1188
|
+
session
|
|
1189
|
+
});
|
|
1190
|
+
appendPerformanceReport(ctx, report);
|
|
1191
|
+
return createToolResponse(toStructuredRecord(report));
|
|
1192
|
+
}
|
|
1193
|
+
const result = await requestRuntimeData(ctx, (event) => {
|
|
1194
|
+
void ctx.rpcServer?.stopPerformanceRecording({
|
|
1195
|
+
event,
|
|
1196
|
+
recordingId: input.recordingId
|
|
1197
|
+
});
|
|
1198
|
+
});
|
|
1199
|
+
if (isPerformanceReport(result)) {
|
|
1200
|
+
appendPerformanceReport(ctx, result);
|
|
1201
|
+
}
|
|
1202
|
+
if (isPlainRecord(result)) {
|
|
1203
|
+
return createToolResponse(toStructuredRecord(result));
|
|
1204
|
+
}
|
|
1205
|
+
return createToolError("runtime bridge returned an invalid response");
|
|
1206
|
+
} finally {
|
|
1207
|
+
ctx.performanceSessions.delete(input.recordingId);
|
|
1208
|
+
const client = activeCdpPerformanceClients.get(input.recordingId);
|
|
1209
|
+
if (client) {
|
|
1210
|
+
activeCdpPerformanceClients.delete(input.recordingId);
|
|
1211
|
+
await closeCdpClient(client);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1215
|
+
function handleGetPerformanceReport(ctx, input) {
|
|
1216
|
+
const reports = ctx.performanceReports.all().filter((report) => !input.pageId || report.pageId === input.pageId).filter(
|
|
1217
|
+
(report) => !input.recordingId || report.recordingId === input.recordingId
|
|
1218
|
+
);
|
|
1219
|
+
const limit = input.limit ?? reports.length;
|
|
1220
|
+
return createToolResponse(toStructuredRecord({
|
|
1221
|
+
report: reports.at(-1) ?? null,
|
|
1222
|
+
reports: reports.slice(-limit),
|
|
1223
|
+
sessions: [...ctx.performanceSessions.values()].filter(
|
|
1224
|
+
(session) => (!input.pageId || session.pageId === input.pageId) && (!input.recordingId || session.recordingId === input.recordingId)
|
|
1225
|
+
)
|
|
1226
|
+
}));
|
|
1227
|
+
}
|
|
1228
|
+
async function handleTakeHeapSnapshot(ctx, input) {
|
|
1229
|
+
if (ctx.options.performance.mode === "off" || ctx.options.performance.mode === "hook") {
|
|
1230
|
+
return createToolError(PERFORMANCE_NOT_AVAILABLE_ERROR);
|
|
1231
|
+
}
|
|
1232
|
+
const cdpResult = await connectPerformanceCdp(ctx, input.pageId);
|
|
1233
|
+
const cdp = cdpResult.cdp;
|
|
1234
|
+
if (!cdp) {
|
|
1235
|
+
return createToolError(
|
|
1236
|
+
formatCdpUnavailableError(
|
|
1237
|
+
"CDP heap snapshot is unavailable",
|
|
1238
|
+
cdpResult.error
|
|
1239
|
+
)
|
|
1240
|
+
);
|
|
1241
|
+
}
|
|
1242
|
+
try {
|
|
1243
|
+
return createToolResponse(
|
|
1244
|
+
toStructuredRecord(await takeHeapSnapshot({
|
|
1245
|
+
client: cdp.client,
|
|
1246
|
+
pageId: input.pageId ?? "cdp",
|
|
1247
|
+
saveDir: ctx.options.performance.saveDir
|
|
1248
|
+
}))
|
|
1249
|
+
);
|
|
1250
|
+
} finally {
|
|
1251
|
+
await closeCdpClient(cdp.client);
|
|
1252
|
+
}
|
|
1253
|
+
}
|
|
1254
|
+
async function connectPerformanceCdp(ctx, pageId) {
|
|
1255
|
+
try {
|
|
1256
|
+
return { cdp: await connectCdpForPage(ctx, pageId) };
|
|
1257
|
+
} catch (error) {
|
|
1258
|
+
return {
|
|
1259
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1260
|
+
};
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
function formatCdpUnavailableError(message, reason) {
|
|
1264
|
+
if (!reason) {
|
|
1265
|
+
return message;
|
|
1266
|
+
}
|
|
1267
|
+
return `${message}: ${reason}`;
|
|
1268
|
+
}
|
|
1269
|
+
function isPerformanceReport(value) {
|
|
1270
|
+
if (!value || typeof value !== "object") {
|
|
1271
|
+
return false;
|
|
1272
|
+
}
|
|
1273
|
+
const report = value;
|
|
1274
|
+
return typeof report.recordingId === "string" && typeof report.pageId === "string" && (report.source === "hook" || report.source === "cdp") && typeof report.startedAt === "number" && typeof report.endedAt === "number" && typeof report.durationMs === "number" && Boolean(report.summary) && Array.isArray(report.longTasks) && Array.isArray(report.limitations);
|
|
1275
|
+
}
|
|
1276
|
+
function isPerformanceStartResult(value) {
|
|
1277
|
+
if (!value || typeof value !== "object") {
|
|
1278
|
+
return false;
|
|
1279
|
+
}
|
|
1280
|
+
const result = value;
|
|
1281
|
+
return typeof result.recordingId === "string" && typeof result.startedAt === "number";
|
|
1282
|
+
}
|
|
1283
|
+
function isPlainRecord(value) {
|
|
1284
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1285
|
+
}
|
|
1286
|
+
function toStructuredRecord(value) {
|
|
1287
|
+
if (isPlainRecord(value)) {
|
|
1288
|
+
return value;
|
|
1289
|
+
}
|
|
1290
|
+
return {};
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// src/mcp/tools/pages.ts
|
|
1294
|
+
import { z as z6 } from "zod";
|
|
1295
|
+
|
|
671
1296
|
// src/plugin/entryDiscovery.ts
|
|
672
1297
|
import fs from "fs";
|
|
673
1298
|
import path from "path";
|
|
@@ -691,10 +1316,10 @@ function walkHtmlEntries(root, dir, entries) {
|
|
|
691
1316
|
if (!item.isFile() || !item.name.endsWith(".html")) {
|
|
692
1317
|
continue;
|
|
693
1318
|
}
|
|
694
|
-
const
|
|
1319
|
+
const relative2 = normalizePath(path.relative(root, fullPath));
|
|
695
1320
|
entries.push({
|
|
696
|
-
file:
|
|
697
|
-
pathname:
|
|
1321
|
+
file: relative2,
|
|
1322
|
+
pathname: relative2 === "index.html" ? "/" : `/${relative2}`
|
|
698
1323
|
});
|
|
699
1324
|
}
|
|
700
1325
|
}
|
|
@@ -706,7 +1331,7 @@ function registerPageTools(server, ctx, vite) {
|
|
|
706
1331
|
{
|
|
707
1332
|
description: "List Vite page entries and connected runtime/CDP targets.",
|
|
708
1333
|
inputSchema: {
|
|
709
|
-
includeDisconnected:
|
|
1334
|
+
includeDisconnected: z6.boolean().optional()
|
|
710
1335
|
}
|
|
711
1336
|
},
|
|
712
1337
|
async (input) => {
|
|
@@ -729,8 +1354,8 @@ function registerPageTools(server, ctx, vite) {
|
|
|
729
1354
|
{
|
|
730
1355
|
description: "Reload the selected page. CDP uses ignoreCache; Runtime Hook falls back to normal reload.",
|
|
731
1356
|
inputSchema: {
|
|
732
|
-
pageId:
|
|
733
|
-
ignoreCache:
|
|
1357
|
+
pageId: z6.string().optional(),
|
|
1358
|
+
ignoreCache: z6.boolean().optional()
|
|
734
1359
|
}
|
|
735
1360
|
},
|
|
736
1361
|
async (input) => {
|
|
@@ -789,16 +1414,16 @@ function resolveRuntimeReloadTarget(ctx, pageId) {
|
|
|
789
1414
|
function waitForRuntimePageReconnect(ctx) {
|
|
790
1415
|
let timeout;
|
|
791
1416
|
let cleanup;
|
|
792
|
-
const promise = new Promise((
|
|
1417
|
+
const promise = new Promise((resolve2) => {
|
|
793
1418
|
timeout = setTimeout(() => {
|
|
794
1419
|
cleanup?.();
|
|
795
|
-
|
|
1420
|
+
resolve2(null);
|
|
796
1421
|
}, 5e3);
|
|
797
1422
|
cleanup = ctx.hooks.hookOnce(RUNTIME_PAGE_RECONNECTED_EVENT, (payload) => {
|
|
798
1423
|
if (timeout) {
|
|
799
1424
|
clearTimeout(timeout);
|
|
800
1425
|
}
|
|
801
|
-
|
|
1426
|
+
resolve2(isPageTarget(payload) ? payload : null);
|
|
802
1427
|
});
|
|
803
1428
|
});
|
|
804
1429
|
return {
|
|
@@ -882,7 +1507,7 @@ function getPathname(url) {
|
|
|
882
1507
|
}
|
|
883
1508
|
|
|
884
1509
|
// src/mcp/tools/screenshot.ts
|
|
885
|
-
import { z as
|
|
1510
|
+
import { z as z7 } from "zod";
|
|
886
1511
|
|
|
887
1512
|
// src/cdp/cdpScreenshot.ts
|
|
888
1513
|
async function cdpCaptureScreenshot(options) {
|
|
@@ -988,16 +1613,16 @@ function isElementRect(value) {
|
|
|
988
1613
|
|
|
989
1614
|
// src/mcp/tools/screenshotOutput.ts
|
|
990
1615
|
import { randomUUID } from "crypto";
|
|
991
|
-
import { mkdir, writeFile } from "fs/promises";
|
|
1616
|
+
import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
|
|
992
1617
|
import path2 from "path";
|
|
993
1618
|
async function createScreenshotOutput(ctx, payload) {
|
|
994
1619
|
if (ctx.options.screenshot.type === "base64") {
|
|
995
1620
|
return payload;
|
|
996
1621
|
}
|
|
997
1622
|
const saveDir = resolveScreenshotSaveDir(ctx);
|
|
998
|
-
await
|
|
1623
|
+
await mkdir2(saveDir, { recursive: true });
|
|
999
1624
|
const filePath = path2.join(saveDir, createScreenshotFileName(payload));
|
|
1000
|
-
await
|
|
1625
|
+
await writeFile2(filePath, Buffer.from(payload.data, "base64"));
|
|
1001
1626
|
return {
|
|
1002
1627
|
source: payload.source,
|
|
1003
1628
|
target: payload.target,
|
|
@@ -1042,14 +1667,14 @@ function createProjectRelativePath(ctx, filePath) {
|
|
|
1042
1667
|
var DEFAULT_SCREENSHOT_TARGET = "viewport";
|
|
1043
1668
|
var DEFAULT_SCREENSHOT_FORMAT = "png";
|
|
1044
1669
|
var screenshotInputSchema = {
|
|
1045
|
-
pageId:
|
|
1046
|
-
target:
|
|
1047
|
-
selector:
|
|
1048
|
-
format:
|
|
1049
|
-
prefer:
|
|
1050
|
-
quality:
|
|
1051
|
-
scale:
|
|
1052
|
-
snapdom:
|
|
1670
|
+
pageId: z7.string().optional(),
|
|
1671
|
+
target: z7.enum(["viewport", "fullPage", "element"]).optional(),
|
|
1672
|
+
selector: z7.string().optional(),
|
|
1673
|
+
format: z7.enum(["png", "jpeg", "webp"]).optional(),
|
|
1674
|
+
prefer: z7.enum(["auto", "cdp", "runtime"]).optional(),
|
|
1675
|
+
quality: z7.number().optional(),
|
|
1676
|
+
scale: z7.number().optional(),
|
|
1677
|
+
snapdom: z7.record(z7.string(), z7.unknown()).optional()
|
|
1053
1678
|
};
|
|
1054
1679
|
function registerScreenshotTools(server, ctx) {
|
|
1055
1680
|
server.registerTool(
|
|
@@ -1122,7 +1747,7 @@ async function createRuntimeScreenshot(ctx, input, normalized) {
|
|
|
1122
1747
|
`screenshot is too large: ${String(result.byteLength)} bytes`
|
|
1123
1748
|
);
|
|
1124
1749
|
}
|
|
1125
|
-
if (!
|
|
1750
|
+
if (!isPlainRecord2(result)) {
|
|
1126
1751
|
return createToolError("runtime screenshot returned an invalid response");
|
|
1127
1752
|
}
|
|
1128
1753
|
if (result.ok === false) {
|
|
@@ -1151,9 +1776,9 @@ async function createScreenshotResponse(ctx, result) {
|
|
|
1151
1776
|
}
|
|
1152
1777
|
}
|
|
1153
1778
|
function isScreenshotTooLarge(ctx, result) {
|
|
1154
|
-
return
|
|
1779
|
+
return isPlainRecord2(result) && "byteLength" in result && typeof result.byteLength === "number" && result.byteLength > ctx.options.screenshot.maxBytes;
|
|
1155
1780
|
}
|
|
1156
|
-
function
|
|
1781
|
+
function isPlainRecord2(value) {
|
|
1157
1782
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1158
1783
|
}
|
|
1159
1784
|
function createMimeType(format) {
|
|
@@ -1168,8 +1793,8 @@ function isScreenshotImagePayload(result) {
|
|
|
1168
1793
|
}
|
|
1169
1794
|
|
|
1170
1795
|
// src/mcp/tools/vue.ts
|
|
1171
|
-
import { nanoid as
|
|
1172
|
-
import { z as
|
|
1796
|
+
import { nanoid as nanoid3 } from "nanoid";
|
|
1797
|
+
import { z as z8 } from "zod";
|
|
1173
1798
|
function registerVueTools(server, ctx) {
|
|
1174
1799
|
server.registerTool(
|
|
1175
1800
|
MCP_TOOL_NAMES.getComponentTree,
|
|
@@ -1182,7 +1807,7 @@ function registerVueTools(server, ctx) {
|
|
|
1182
1807
|
MCP_TOOL_NAMES.getComponentState,
|
|
1183
1808
|
{
|
|
1184
1809
|
description: "Get Vue component state.",
|
|
1185
|
-
inputSchema: { componentName:
|
|
1810
|
+
inputSchema: { componentName: z8.string() }
|
|
1186
1811
|
},
|
|
1187
1812
|
async ({ componentName }) => requestVueData(ctx, (event) => {
|
|
1188
1813
|
void ctx.rpcServer?.getInspectorState({ event, componentName });
|
|
@@ -1193,10 +1818,10 @@ function registerVueTools(server, ctx) {
|
|
|
1193
1818
|
{
|
|
1194
1819
|
description: "Edit Vue component state.",
|
|
1195
1820
|
inputSchema: {
|
|
1196
|
-
componentName:
|
|
1197
|
-
path:
|
|
1198
|
-
value:
|
|
1199
|
-
valueType:
|
|
1821
|
+
componentName: z8.string(),
|
|
1822
|
+
path: z8.array(z8.string()),
|
|
1823
|
+
value: z8.string(),
|
|
1824
|
+
valueType: z8.enum(["string", "number", "boolean", "object", "array"])
|
|
1200
1825
|
}
|
|
1201
1826
|
},
|
|
1202
1827
|
({ componentName, path: path8, value, valueType }) => {
|
|
@@ -1216,7 +1841,7 @@ function registerVueTools(server, ctx) {
|
|
|
1216
1841
|
MCP_TOOL_NAMES.highlightComponent,
|
|
1217
1842
|
{
|
|
1218
1843
|
description: "Highlight a Vue component.",
|
|
1219
|
-
inputSchema: { componentName:
|
|
1844
|
+
inputSchema: { componentName: z8.string() }
|
|
1220
1845
|
},
|
|
1221
1846
|
({ componentName }) => {
|
|
1222
1847
|
if (!ctx.rpcServer) {
|
|
@@ -1244,7 +1869,7 @@ function registerVueTools(server, ctx) {
|
|
|
1244
1869
|
MCP_TOOL_NAMES.getPiniaState,
|
|
1245
1870
|
{
|
|
1246
1871
|
description: "Get Pinia store state.",
|
|
1247
|
-
inputSchema: { storeName:
|
|
1872
|
+
inputSchema: { storeName: z8.string() }
|
|
1248
1873
|
},
|
|
1249
1874
|
async ({ storeName }) => requestVueData(ctx, (event) => {
|
|
1250
1875
|
void ctx.rpcServer?.getPiniaState({ event, storeName });
|
|
@@ -1255,7 +1880,7 @@ async function requestVueData(ctx, trigger) {
|
|
|
1255
1880
|
if (!ctx.rpcServer) {
|
|
1256
1881
|
return vueBridgeUnavailable();
|
|
1257
1882
|
}
|
|
1258
|
-
const event =
|
|
1883
|
+
const event = nanoid3();
|
|
1259
1884
|
const data = await waitForVueHook(ctx, event, () => {
|
|
1260
1885
|
trigger(event);
|
|
1261
1886
|
});
|
|
@@ -1264,13 +1889,13 @@ async function requestVueData(ctx, trigger) {
|
|
|
1264
1889
|
};
|
|
1265
1890
|
}
|
|
1266
1891
|
function waitForVueHook(ctx, event, trigger) {
|
|
1267
|
-
return new Promise((
|
|
1892
|
+
return new Promise((resolve2) => {
|
|
1268
1893
|
const timeout = setTimeout(() => {
|
|
1269
|
-
|
|
1894
|
+
resolve2({ ok: false, error: "Vue runtime bridge response timed out" });
|
|
1270
1895
|
}, 5e3);
|
|
1271
1896
|
ctx.hooks.hookOnce(event, (data) => {
|
|
1272
1897
|
clearTimeout(timeout);
|
|
1273
|
-
|
|
1898
|
+
resolve2(data);
|
|
1274
1899
|
});
|
|
1275
1900
|
trigger();
|
|
1276
1901
|
});
|
|
@@ -1291,6 +1916,7 @@ function createMcpServer(ctx, vite) {
|
|
|
1291
1916
|
registerConsoleTools(server, ctx);
|
|
1292
1917
|
registerEvaluateTools(server, ctx);
|
|
1293
1918
|
registerNetworkTools(server, ctx);
|
|
1919
|
+
registerPerformanceTools(server, ctx);
|
|
1294
1920
|
registerVueTools(server, ctx);
|
|
1295
1921
|
return server;
|
|
1296
1922
|
}
|
|
@@ -1378,6 +2004,18 @@ function createServerVueRuntimeRpc(ctx) {
|
|
|
1378
2004
|
onScreenshotTaken: (event, data) => {
|
|
1379
2005
|
void ctx.hooks.callHook(event, data);
|
|
1380
2006
|
},
|
|
2007
|
+
recordPerformance: () => void 0,
|
|
2008
|
+
onPerformanceRecorded: (event, data) => {
|
|
2009
|
+
void ctx.hooks.callHook(event, data);
|
|
2010
|
+
},
|
|
2011
|
+
startPerformanceRecording: () => void 0,
|
|
2012
|
+
onPerformanceRecordingStarted: (event, data) => {
|
|
2013
|
+
void ctx.hooks.callHook(event, data);
|
|
2014
|
+
},
|
|
2015
|
+
stopPerformanceRecording: () => void 0,
|
|
2016
|
+
onPerformanceRecordingStopped: (event, data) => {
|
|
2017
|
+
void ctx.hooks.callHook(event, data);
|
|
2018
|
+
},
|
|
1381
2019
|
getInspectorTree: () => void 0,
|
|
1382
2020
|
onInspectorTreeUpdated: (event, data) => {
|
|
1383
2021
|
void ctx.hooks.callHook(event, data);
|
|
@@ -1404,7 +2042,7 @@ function createServerVueRuntimeRpc(ctx) {
|
|
|
1404
2042
|
}
|
|
1405
2043
|
|
|
1406
2044
|
// src/cdp/cdpConsole.ts
|
|
1407
|
-
import { nanoid as
|
|
2045
|
+
import { nanoid as nanoid4 } from "nanoid";
|
|
1408
2046
|
|
|
1409
2047
|
// src/shared/serialization.ts
|
|
1410
2048
|
function safeStringify(value) {
|
|
@@ -1433,7 +2071,7 @@ async function startCdpConsole(options) {
|
|
|
1433
2071
|
await options.client.Runtime.enable();
|
|
1434
2072
|
options.client.Runtime.consoleAPICalled((event) => {
|
|
1435
2073
|
options.push({
|
|
1436
|
-
id:
|
|
2074
|
+
id: nanoid4(),
|
|
1437
2075
|
pageId: options.pageId,
|
|
1438
2076
|
source: "cdp",
|
|
1439
2077
|
level: normalizeConsoleLevel(event.type),
|
|
@@ -1453,7 +2091,7 @@ function normalizeConsoleLevel(level) {
|
|
|
1453
2091
|
}
|
|
1454
2092
|
|
|
1455
2093
|
// src/cdp/cdpNetwork.ts
|
|
1456
|
-
import { nanoid as
|
|
2094
|
+
import { nanoid as nanoid5 } from "nanoid";
|
|
1457
2095
|
|
|
1458
2096
|
// src/shared/sanitize.ts
|
|
1459
2097
|
function maskHeaders(headers = {}, maskNames = []) {
|
|
@@ -1493,7 +2131,7 @@ async function startCdpNetwork(options) {
|
|
|
1493
2131
|
await options.client.Network.enable();
|
|
1494
2132
|
options.client.Network.requestWillBeSent((event) => {
|
|
1495
2133
|
records.set(event.requestId, {
|
|
1496
|
-
id:
|
|
2134
|
+
id: nanoid5(),
|
|
1497
2135
|
pageId: options.pageId,
|
|
1498
2136
|
source: "cdp",
|
|
1499
2137
|
url: event.request.url,
|
|
@@ -1901,11 +2539,11 @@ import path4 from "path";
|
|
|
1901
2539
|
async function updateJsonMcpClientConfig(options) {
|
|
1902
2540
|
try {
|
|
1903
2541
|
const config = await readJsonConfig(options.configPath);
|
|
1904
|
-
if (!
|
|
2542
|
+
if (!isPlainRecord3(config)) {
|
|
1905
2543
|
warnConfigFailure(options, "config root must be a JSON object");
|
|
1906
2544
|
return;
|
|
1907
2545
|
}
|
|
1908
|
-
const mcpServers =
|
|
2546
|
+
const mcpServers = isPlainRecord3(config.mcpServers) ? config.mcpServers : {};
|
|
1909
2547
|
if (Object.hasOwn(mcpServers, options.serverName)) {
|
|
1910
2548
|
return;
|
|
1911
2549
|
}
|
|
@@ -1958,7 +2596,7 @@ async function readOptionalTextFile2(filePath) {
|
|
|
1958
2596
|
throw error;
|
|
1959
2597
|
}
|
|
1960
2598
|
}
|
|
1961
|
-
function
|
|
2599
|
+
function isPlainRecord3(value) {
|
|
1962
2600
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1963
2601
|
}
|
|
1964
2602
|
function warnConfigFailure(options, reason) {
|
|
@@ -2300,6 +2938,14 @@ function vueMcpNext(userOptions = {}) {
|
|
|
2300
2938
|
}
|
|
2301
2939
|
}
|
|
2302
2940
|
);
|
|
2941
|
+
server.ws.on(
|
|
2942
|
+
"vite-plugin-vue-mcp-next:performance-record",
|
|
2943
|
+
(payload) => {
|
|
2944
|
+
if (isPerformanceReport2(payload)) {
|
|
2945
|
+
appendPerformanceReport(ctx, payload);
|
|
2946
|
+
}
|
|
2947
|
+
}
|
|
2948
|
+
);
|
|
2303
2949
|
const port = String(server.config.server.port || 5173);
|
|
2304
2950
|
const mcpSseUrl = `http://${options.host}:${port}${options.mcpPath}/sse`;
|
|
2305
2951
|
const mcpStreamableHttpUrl = `http://${options.host}:${port}${options.mcpPath}/mcp`;
|
|
@@ -2362,6 +3008,13 @@ function isNetworkRecord(payload) {
|
|
|
2362
3008
|
const record = payload;
|
|
2363
3009
|
return typeof record.id === "string" && typeof record.pageId === "string" && record.source === "hook" && typeof record.url === "string" && typeof record.method === "string" && typeof record.startedAt === "number";
|
|
2364
3010
|
}
|
|
3011
|
+
function isPerformanceReport2(payload) {
|
|
3012
|
+
if (!payload || typeof payload !== "object") {
|
|
3013
|
+
return false;
|
|
3014
|
+
}
|
|
3015
|
+
const report = payload;
|
|
3016
|
+
return typeof report.recordingId === "string" && typeof report.pageId === "string" && (report.source === "hook" || report.source === "cdp") && Boolean(report.summary) && Array.isArray(report.longTasks) && Array.isArray(report.limitations);
|
|
3017
|
+
}
|
|
2365
3018
|
export {
|
|
2366
3019
|
vueMcpNext as default,
|
|
2367
3020
|
vueMcpNext
|