@xiaou66/vite-plugin-vue-mcp-next 1.0.3 → 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
  }
@@ -194,24 +229,64 @@ function createRingBuffer(capacity) {
194
229
  }
195
230
 
196
231
  // src/context.ts
232
+ var MINUTE_MS = 60 * 1e3;
233
+ var DISCONNECTED_RUNTIME_TARGET_RETENTION_MS = 5 * MINUTE_MS;
234
+ function shouldPruneDisconnectedRuntimeTarget(target, now) {
235
+ return target.source === "runtime" && !target.connected && typeof target.disconnectedAt === "number" && now - target.disconnectedAt > DISCONNECTED_RUNTIME_TARGET_RETENTION_MS;
236
+ }
237
+ function pruneDisconnectedRuntimeTargets(targets, now) {
238
+ for (const [pageId, target] of targets) {
239
+ if (shouldPruneDisconnectedRuntimeTarget(target, now)) {
240
+ targets.delete(pageId);
241
+ }
242
+ }
243
+ }
244
+ function markTargetDisconnected(target, now) {
245
+ return {
246
+ ...target,
247
+ connected: false,
248
+ disconnectedAt: target.disconnectedAt ?? now
249
+ };
250
+ }
251
+ function shouldListPageTarget(target, options) {
252
+ return options.includeDisconnected === true || target.source !== "runtime" || target.connected;
253
+ }
254
+ function disconnectPreviousRuntimeClientTarget(targets, target, now) {
255
+ if (target.source !== "runtime" || !target.runtimeClientId) {
256
+ return;
257
+ }
258
+ for (const [pageId, currentTarget] of targets) {
259
+ if (pageId === target.pageId || currentTarget.source !== "runtime" || currentTarget.runtimeClientId !== target.runtimeClientId || !currentTarget.connected) {
260
+ continue;
261
+ }
262
+ targets.set(pageId, markTargetDisconnected(currentTarget, now));
263
+ }
264
+ }
197
265
  function createPageTargetRegistry() {
198
266
  const targets = /* @__PURE__ */ new Map();
199
267
  return {
200
- upsert(target) {
268
+ upsert(target, now = Date.now()) {
269
+ pruneDisconnectedRuntimeTargets(targets, now);
270
+ disconnectPreviousRuntimeClientTarget(targets, target, now);
201
271
  targets.set(target.pageId, target);
202
272
  },
203
273
  get(pageId) {
204
274
  return targets.get(pageId);
205
275
  },
206
- list() {
207
- return [...targets.values()];
276
+ list(options = {}) {
277
+ const now = options.now ?? Date.now();
278
+ pruneDisconnectedRuntimeTargets(targets, now);
279
+ return [...targets.values()].filter(
280
+ (target) => shouldListPageTarget(target, options)
281
+ );
208
282
  },
209
- disconnect(pageId) {
283
+ disconnect(pageId, now = Date.now()) {
284
+ pruneDisconnectedRuntimeTargets(targets, now);
210
285
  const target = targets.get(pageId);
211
286
  if (!target) {
212
287
  return;
213
288
  }
214
- targets.set(pageId, { ...target, connected: false });
289
+ targets.set(pageId, markTargetDisconnected(target, now));
215
290
  }
216
291
  };
217
292
  }
@@ -222,7 +297,11 @@ function createVueMcpNextContext(options) {
222
297
  rpcServer: void 0,
223
298
  pages: createPageTargetRegistry(),
224
299
  consoleRecords: createRingBuffer(options.console.maxRecords),
225
- 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()
226
305
  };
227
306
  }
228
307
 
@@ -340,13 +419,13 @@ async function requestRuntimeData(ctx, trigger) {
340
419
  return { ok: false, error: "runtime bridge is not connected" };
341
420
  }
342
421
  const event = nanoid();
343
- return new Promise((resolve) => {
422
+ return new Promise((resolve2) => {
344
423
  const timeout = setTimeout(() => {
345
- resolve({ ok: false, error: "runtime bridge response timed out" });
424
+ resolve2({ ok: false, error: "runtime bridge response timed out" });
346
425
  }, 5e3);
347
426
  ctx.hooks.hookOnce(event, (data) => {
348
427
  clearTimeout(timeout);
349
- resolve(data);
428
+ resolve2(data);
350
429
  });
351
430
  trigger(event);
352
431
  });
@@ -625,9 +704,595 @@ function registerNetworkTools(server, ctx) {
625
704
  );
626
705
  }
627
706
 
628
- // src/mcp/tools/pages.ts
707
+ // src/mcp/tools/performance.ts
629
708
  import { z as z5 } from "zod";
630
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
+
631
1296
  // src/plugin/entryDiscovery.ts
632
1297
  import fs from "fs";
633
1298
  import path from "path";
@@ -651,10 +1316,10 @@ function walkHtmlEntries(root, dir, entries) {
651
1316
  if (!item.isFile() || !item.name.endsWith(".html")) {
652
1317
  continue;
653
1318
  }
654
- const relative = normalizePath(path.relative(root, fullPath));
1319
+ const relative2 = normalizePath(path.relative(root, fullPath));
655
1320
  entries.push({
656
- file: relative,
657
- pathname: relative === "index.html" ? "/" : `/${relative}`
1321
+ file: relative2,
1322
+ pathname: relative2 === "index.html" ? "/" : `/${relative2}`
658
1323
  });
659
1324
  }
660
1325
  }
@@ -664,9 +1329,12 @@ function registerPageTools(server, ctx, vite) {
664
1329
  server.registerTool(
665
1330
  MCP_TOOL_NAMES.listPages,
666
1331
  {
667
- description: "List Vite page entries and connected runtime/CDP targets."
1332
+ description: "List Vite page entries and connected runtime/CDP targets.",
1333
+ inputSchema: {
1334
+ includeDisconnected: z6.boolean().optional()
1335
+ }
668
1336
  },
669
- async () => {
1337
+ async (input) => {
670
1338
  const cdpResult = await listCdpPageTargets(ctx);
671
1339
  for (const target of cdpResult.pages) {
672
1340
  ctx.pages.upsert(target);
@@ -674,7 +1342,9 @@ function registerPageTools(server, ctx, vite) {
674
1342
  }
675
1343
  return createToolResponse({
676
1344
  entries: discoverHtmlEntries(vite),
677
- pages: ctx.pages.list(),
1345
+ pages: ctx.pages.list({
1346
+ includeDisconnected: input.includeDisconnected
1347
+ }),
678
1348
  cdpError: cdpResult.error
679
1349
  });
680
1350
  }
@@ -684,8 +1354,8 @@ function registerPageTools(server, ctx, vite) {
684
1354
  {
685
1355
  description: "Reload the selected page. CDP uses ignoreCache; Runtime Hook falls back to normal reload.",
686
1356
  inputSchema: {
687
- pageId: z5.string().optional(),
688
- ignoreCache: z5.boolean().optional()
1357
+ pageId: z6.string().optional(),
1358
+ ignoreCache: z6.boolean().optional()
689
1359
  }
690
1360
  },
691
1361
  async (input) => {
@@ -744,16 +1414,16 @@ function resolveRuntimeReloadTarget(ctx, pageId) {
744
1414
  function waitForRuntimePageReconnect(ctx) {
745
1415
  let timeout;
746
1416
  let cleanup;
747
- const promise = new Promise((resolve) => {
1417
+ const promise = new Promise((resolve2) => {
748
1418
  timeout = setTimeout(() => {
749
1419
  cleanup?.();
750
- resolve(null);
1420
+ resolve2(null);
751
1421
  }, 5e3);
752
1422
  cleanup = ctx.hooks.hookOnce(RUNTIME_PAGE_RECONNECTED_EVENT, (payload) => {
753
1423
  if (timeout) {
754
1424
  clearTimeout(timeout);
755
1425
  }
756
- resolve(isPageTarget(payload) ? payload : null);
1426
+ resolve2(isPageTarget(payload) ? payload : null);
757
1427
  });
758
1428
  });
759
1429
  return {
@@ -837,7 +1507,7 @@ function getPathname(url) {
837
1507
  }
838
1508
 
839
1509
  // src/mcp/tools/screenshot.ts
840
- import { z as z6 } from "zod";
1510
+ import { z as z7 } from "zod";
841
1511
 
842
1512
  // src/cdp/cdpScreenshot.ts
843
1513
  async function cdpCaptureScreenshot(options) {
@@ -943,16 +1613,16 @@ function isElementRect(value) {
943
1613
 
944
1614
  // src/mcp/tools/screenshotOutput.ts
945
1615
  import { randomUUID } from "crypto";
946
- import { mkdir, writeFile } from "fs/promises";
1616
+ import { mkdir as mkdir2, writeFile as writeFile2 } from "fs/promises";
947
1617
  import path2 from "path";
948
1618
  async function createScreenshotOutput(ctx, payload) {
949
1619
  if (ctx.options.screenshot.type === "base64") {
950
1620
  return payload;
951
1621
  }
952
1622
  const saveDir = resolveScreenshotSaveDir(ctx);
953
- await mkdir(saveDir, { recursive: true });
1623
+ await mkdir2(saveDir, { recursive: true });
954
1624
  const filePath = path2.join(saveDir, createScreenshotFileName(payload));
955
- await writeFile(filePath, Buffer.from(payload.data, "base64"));
1625
+ await writeFile2(filePath, Buffer.from(payload.data, "base64"));
956
1626
  return {
957
1627
  source: payload.source,
958
1628
  target: payload.target,
@@ -997,14 +1667,14 @@ function createProjectRelativePath(ctx, filePath) {
997
1667
  var DEFAULT_SCREENSHOT_TARGET = "viewport";
998
1668
  var DEFAULT_SCREENSHOT_FORMAT = "png";
999
1669
  var screenshotInputSchema = {
1000
- pageId: z6.string().optional(),
1001
- target: z6.enum(["viewport", "fullPage", "element"]).optional(),
1002
- selector: z6.string().optional(),
1003
- format: z6.enum(["png", "jpeg", "webp"]).optional(),
1004
- prefer: z6.enum(["auto", "cdp", "runtime"]).optional(),
1005
- quality: z6.number().optional(),
1006
- scale: z6.number().optional(),
1007
- 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()
1008
1678
  };
1009
1679
  function registerScreenshotTools(server, ctx) {
1010
1680
  server.registerTool(
@@ -1077,7 +1747,7 @@ async function createRuntimeScreenshot(ctx, input, normalized) {
1077
1747
  `screenshot is too large: ${String(result.byteLength)} bytes`
1078
1748
  );
1079
1749
  }
1080
- if (!isPlainRecord(result)) {
1750
+ if (!isPlainRecord2(result)) {
1081
1751
  return createToolError("runtime screenshot returned an invalid response");
1082
1752
  }
1083
1753
  if (result.ok === false) {
@@ -1106,9 +1776,9 @@ async function createScreenshotResponse(ctx, result) {
1106
1776
  }
1107
1777
  }
1108
1778
  function isScreenshotTooLarge(ctx, result) {
1109
- 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;
1110
1780
  }
1111
- function isPlainRecord(value) {
1781
+ function isPlainRecord2(value) {
1112
1782
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1113
1783
  }
1114
1784
  function createMimeType(format) {
@@ -1123,8 +1793,8 @@ function isScreenshotImagePayload(result) {
1123
1793
  }
1124
1794
 
1125
1795
  // src/mcp/tools/vue.ts
1126
- import { nanoid as nanoid2 } from "nanoid";
1127
- import { z as z7 } from "zod";
1796
+ import { nanoid as nanoid3 } from "nanoid";
1797
+ import { z as z8 } from "zod";
1128
1798
  function registerVueTools(server, ctx) {
1129
1799
  server.registerTool(
1130
1800
  MCP_TOOL_NAMES.getComponentTree,
@@ -1137,7 +1807,7 @@ function registerVueTools(server, ctx) {
1137
1807
  MCP_TOOL_NAMES.getComponentState,
1138
1808
  {
1139
1809
  description: "Get Vue component state.",
1140
- inputSchema: { componentName: z7.string() }
1810
+ inputSchema: { componentName: z8.string() }
1141
1811
  },
1142
1812
  async ({ componentName }) => requestVueData(ctx, (event) => {
1143
1813
  void ctx.rpcServer?.getInspectorState({ event, componentName });
@@ -1148,10 +1818,10 @@ function registerVueTools(server, ctx) {
1148
1818
  {
1149
1819
  description: "Edit Vue component state.",
1150
1820
  inputSchema: {
1151
- componentName: z7.string(),
1152
- path: z7.array(z7.string()),
1153
- value: z7.string(),
1154
- 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"])
1155
1825
  }
1156
1826
  },
1157
1827
  ({ componentName, path: path8, value, valueType }) => {
@@ -1171,7 +1841,7 @@ function registerVueTools(server, ctx) {
1171
1841
  MCP_TOOL_NAMES.highlightComponent,
1172
1842
  {
1173
1843
  description: "Highlight a Vue component.",
1174
- inputSchema: { componentName: z7.string() }
1844
+ inputSchema: { componentName: z8.string() }
1175
1845
  },
1176
1846
  ({ componentName }) => {
1177
1847
  if (!ctx.rpcServer) {
@@ -1199,7 +1869,7 @@ function registerVueTools(server, ctx) {
1199
1869
  MCP_TOOL_NAMES.getPiniaState,
1200
1870
  {
1201
1871
  description: "Get Pinia store state.",
1202
- inputSchema: { storeName: z7.string() }
1872
+ inputSchema: { storeName: z8.string() }
1203
1873
  },
1204
1874
  async ({ storeName }) => requestVueData(ctx, (event) => {
1205
1875
  void ctx.rpcServer?.getPiniaState({ event, storeName });
@@ -1210,7 +1880,7 @@ async function requestVueData(ctx, trigger) {
1210
1880
  if (!ctx.rpcServer) {
1211
1881
  return vueBridgeUnavailable();
1212
1882
  }
1213
- const event = nanoid2();
1883
+ const event = nanoid3();
1214
1884
  const data = await waitForVueHook(ctx, event, () => {
1215
1885
  trigger(event);
1216
1886
  });
@@ -1219,13 +1889,13 @@ async function requestVueData(ctx, trigger) {
1219
1889
  };
1220
1890
  }
1221
1891
  function waitForVueHook(ctx, event, trigger) {
1222
- return new Promise((resolve) => {
1892
+ return new Promise((resolve2) => {
1223
1893
  const timeout = setTimeout(() => {
1224
- resolve({ ok: false, error: "Vue runtime bridge response timed out" });
1894
+ resolve2({ ok: false, error: "Vue runtime bridge response timed out" });
1225
1895
  }, 5e3);
1226
1896
  ctx.hooks.hookOnce(event, (data) => {
1227
1897
  clearTimeout(timeout);
1228
- resolve(data);
1898
+ resolve2(data);
1229
1899
  });
1230
1900
  trigger();
1231
1901
  });
@@ -1246,6 +1916,7 @@ function createMcpServer(ctx, vite) {
1246
1916
  registerConsoleTools(server, ctx);
1247
1917
  registerEvaluateTools(server, ctx);
1248
1918
  registerNetworkTools(server, ctx);
1919
+ registerPerformanceTools(server, ctx);
1249
1920
  registerVueTools(server, ctx);
1250
1921
  return server;
1251
1922
  }
@@ -1333,6 +2004,18 @@ function createServerVueRuntimeRpc(ctx) {
1333
2004
  onScreenshotTaken: (event, data) => {
1334
2005
  void ctx.hooks.callHook(event, data);
1335
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
+ },
1336
2019
  getInspectorTree: () => void 0,
1337
2020
  onInspectorTreeUpdated: (event, data) => {
1338
2021
  void ctx.hooks.callHook(event, data);
@@ -1359,7 +2042,7 @@ function createServerVueRuntimeRpc(ctx) {
1359
2042
  }
1360
2043
 
1361
2044
  // src/cdp/cdpConsole.ts
1362
- import { nanoid as nanoid3 } from "nanoid";
2045
+ import { nanoid as nanoid4 } from "nanoid";
1363
2046
 
1364
2047
  // src/shared/serialization.ts
1365
2048
  function safeStringify(value) {
@@ -1388,7 +2071,7 @@ async function startCdpConsole(options) {
1388
2071
  await options.client.Runtime.enable();
1389
2072
  options.client.Runtime.consoleAPICalled((event) => {
1390
2073
  options.push({
1391
- id: nanoid3(),
2074
+ id: nanoid4(),
1392
2075
  pageId: options.pageId,
1393
2076
  source: "cdp",
1394
2077
  level: normalizeConsoleLevel(event.type),
@@ -1408,7 +2091,7 @@ function normalizeConsoleLevel(level) {
1408
2091
  }
1409
2092
 
1410
2093
  // src/cdp/cdpNetwork.ts
1411
- import { nanoid as nanoid4 } from "nanoid";
2094
+ import { nanoid as nanoid5 } from "nanoid";
1412
2095
 
1413
2096
  // src/shared/sanitize.ts
1414
2097
  function maskHeaders(headers = {}, maskNames = []) {
@@ -1448,7 +2131,7 @@ async function startCdpNetwork(options) {
1448
2131
  await options.client.Network.enable();
1449
2132
  options.client.Network.requestWillBeSent((event) => {
1450
2133
  records.set(event.requestId, {
1451
- id: nanoid4(),
2134
+ id: nanoid5(),
1452
2135
  pageId: options.pageId,
1453
2136
  source: "cdp",
1454
2137
  url: event.request.url,
@@ -1856,11 +2539,11 @@ import path4 from "path";
1856
2539
  async function updateJsonMcpClientConfig(options) {
1857
2540
  try {
1858
2541
  const config = await readJsonConfig(options.configPath);
1859
- if (!isPlainRecord2(config)) {
2542
+ if (!isPlainRecord3(config)) {
1860
2543
  warnConfigFailure(options, "config root must be a JSON object");
1861
2544
  return;
1862
2545
  }
1863
- const mcpServers = isPlainRecord2(config.mcpServers) ? config.mcpServers : {};
2546
+ const mcpServers = isPlainRecord3(config.mcpServers) ? config.mcpServers : {};
1864
2547
  if (Object.hasOwn(mcpServers, options.serverName)) {
1865
2548
  return;
1866
2549
  }
@@ -1913,7 +2596,7 @@ async function readOptionalTextFile2(filePath) {
1913
2596
  throw error;
1914
2597
  }
1915
2598
  }
1916
- function isPlainRecord2(value) {
2599
+ function isPlainRecord3(value) {
1917
2600
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
1918
2601
  }
1919
2602
  function warnConfigFailure(options, reason) {
@@ -2255,6 +2938,14 @@ function vueMcpNext(userOptions = {}) {
2255
2938
  }
2256
2939
  }
2257
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
+ );
2258
2949
  const port = String(server.config.server.port || 5173);
2259
2950
  const mcpSseUrl = `http://${options.host}:${port}${options.mcpPath}/sse`;
2260
2951
  const mcpStreamableHttpUrl = `http://${options.host}:${port}${options.mcpPath}/mcp`;
@@ -2298,7 +2989,7 @@ function isRuntimePageTarget(payload) {
2298
2989
  return false;
2299
2990
  }
2300
2991
  const target = payload;
2301
- return target.source === "runtime" && typeof target.pageId === "string" && typeof target.url === "string" && typeof target.pathname === "string" && typeof target.connected === "boolean";
2992
+ return target.source === "runtime" && typeof target.pageId === "string" && typeof target.url === "string" && typeof target.pathname === "string" && typeof target.connected === "boolean" && (target.runtimeClientId === void 0 || typeof target.runtimeClientId === "string");
2302
2993
  }
2303
2994
  function isConsoleRecord(payload) {
2304
2995
  if (!payload || typeof payload !== "object") {
@@ -2317,6 +3008,13 @@ function isNetworkRecord(payload) {
2317
3008
  const record = payload;
2318
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";
2319
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
+ }
2320
3018
  export {
2321
3019
  vueMcpNext as default,
2322
3020
  vueMcpNext