@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/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((resolve) => {
422
+ return new Promise((resolve2) => {
384
423
  const timeout = setTimeout(() => {
385
- resolve({ ok: false, error: "runtime bridge response timed out" });
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
- resolve(data);
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/pages.ts
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 relative = normalizePath(path.relative(root, fullPath));
1319
+ const relative2 = normalizePath(path.relative(root, fullPath));
695
1320
  entries.push({
696
- file: relative,
697
- pathname: relative === "index.html" ? "/" : `/${relative}`
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: z5.boolean().optional()
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: z5.string().optional(),
733
- ignoreCache: z5.boolean().optional()
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((resolve) => {
1417
+ const promise = new Promise((resolve2) => {
793
1418
  timeout = setTimeout(() => {
794
1419
  cleanup?.();
795
- resolve(null);
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
- resolve(isPageTarget(payload) ? payload : null);
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 z6 } from "zod";
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 mkdir(saveDir, { recursive: true });
1623
+ await mkdir2(saveDir, { recursive: true });
999
1624
  const filePath = path2.join(saveDir, createScreenshotFileName(payload));
1000
- await writeFile(filePath, Buffer.from(payload.data, "base64"));
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: z6.string().optional(),
1046
- target: z6.enum(["viewport", "fullPage", "element"]).optional(),
1047
- selector: z6.string().optional(),
1048
- format: z6.enum(["png", "jpeg", "webp"]).optional(),
1049
- prefer: z6.enum(["auto", "cdp", "runtime"]).optional(),
1050
- quality: z6.number().optional(),
1051
- scale: z6.number().optional(),
1052
- snapdom: z6.record(z6.string(), z6.unknown()).optional()
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 (!isPlainRecord(result)) {
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 isPlainRecord(result) && "byteLength" in result && typeof result.byteLength === "number" && result.byteLength > ctx.options.screenshot.maxBytes;
1779
+ return isPlainRecord2(result) && "byteLength" in result && typeof result.byteLength === "number" && result.byteLength > ctx.options.screenshot.maxBytes;
1155
1780
  }
1156
- function isPlainRecord(value) {
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 nanoid2 } from "nanoid";
1172
- import { z as z7 } from "zod";
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: z7.string() }
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: z7.string(),
1197
- path: z7.array(z7.string()),
1198
- value: z7.string(),
1199
- valueType: z7.enum(["string", "number", "boolean", "object", "array"])
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: z7.string() }
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: z7.string() }
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 = nanoid2();
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((resolve) => {
1892
+ return new Promise((resolve2) => {
1268
1893
  const timeout = setTimeout(() => {
1269
- resolve({ ok: false, error: "Vue runtime bridge response timed out" });
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
- resolve(data);
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 nanoid3 } from "nanoid";
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: nanoid3(),
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 nanoid4 } from "nanoid";
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: nanoid4(),
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 (!isPlainRecord2(config)) {
2542
+ if (!isPlainRecord3(config)) {
1905
2543
  warnConfigFailure(options, "config root must be a JSON object");
1906
2544
  return;
1907
2545
  }
1908
- const mcpServers = isPlainRecord2(config.mcpServers) ? config.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 isPlainRecord2(value) {
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