@xiaou66/vite-plugin-vue-mcp-next 1.0.4 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +51 -0
- package/dist/index.cjs +809 -115
- 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 +760 -66
- package/dist/index.js.map +1 -1
- package/dist/runtime/client.cjs +615 -2
- package/dist/runtime/client.cjs.map +1 -1
- package/dist/runtime/client.js +615 -2
- package/dist/runtime/client.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -55,6 +55,11 @@ var DEFAULT_MASK_HEADERS = [
|
|
|
55
55
|
var DEFAULT_MCP_PATH = "/__mcp";
|
|
56
56
|
var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
|
|
57
57
|
var DEFAULT_SCREENSHOT_SAVE_DIR = ".vite-mcp/screenshot";
|
|
58
|
+
var DEFAULT_PERFORMANCE_SAVE_DIR = ".vite-mcp/performance";
|
|
59
|
+
var DEFAULT_PERFORMANCE_MAX_REPORTS = 100;
|
|
60
|
+
var DEFAULT_PERFORMANCE_MAX_DURATION_MS = 3e4;
|
|
61
|
+
var DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS = 250;
|
|
62
|
+
var DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS = 50;
|
|
58
63
|
var MCP_TOOL_NAMES = {
|
|
59
64
|
listPages: "list_pages",
|
|
60
65
|
reloadPage: "reload_page",
|
|
@@ -67,6 +72,11 @@ var MCP_TOOL_NAMES = {
|
|
|
67
72
|
getNetworkRequests: "get_network_requests",
|
|
68
73
|
getNetworkRequestDetail: "get_network_request_detail",
|
|
69
74
|
clearNetworkRequests: "clear_network_requests",
|
|
75
|
+
recordPerformance: "record_performance",
|
|
76
|
+
startPerformanceRecording: "start_performance_recording",
|
|
77
|
+
stopPerformanceRecording: "stop_performance_recording",
|
|
78
|
+
getPerformanceReport: "get_performance_report",
|
|
79
|
+
takeHeapSnapshot: "take_heap_snapshot",
|
|
70
80
|
getComponentTree: "get_component_tree",
|
|
71
81
|
getComponentState: "get_component_state",
|
|
72
82
|
editComponentState: "edit_component_state",
|
|
@@ -84,6 +94,11 @@ var RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID = `\0${VIRTUAL_SNAPDOM_LOADER_ID}`;
|
|
|
84
94
|
var DEFAULT_MCP_CLIENT_SERVER_NAME = "vite-mcp-next";
|
|
85
95
|
var LEGACY_MCP_CLIENT_SERVER_NAMES = ["vue-mcp-next"];
|
|
86
96
|
var RUNTIME_PAGE_RECONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-reconnected";
|
|
97
|
+
var RUNTIME_PAGE_CONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-connected";
|
|
98
|
+
var RUNTIME_PAGE_DISCONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-disconnected";
|
|
99
|
+
var RUNTIME_PAGE_HEARTBEAT_EVENT = "vite-plugin-vue-mcp-next:heartbeat";
|
|
100
|
+
var DEFAULT_RUNTIME_PAGE_HEARTBEAT_TIMEOUT_MS = 45e3;
|
|
101
|
+
var DEFAULT_RUNTIME_PAGE_HEARTBEAT_SCAN_INTERVAL_MS = 45e3;
|
|
87
102
|
var DEFAULT_OPTIONS = {
|
|
88
103
|
mcpPath: DEFAULT_MCP_PATH,
|
|
89
104
|
host: "localhost",
|
|
@@ -135,6 +150,19 @@ var DEFAULT_OPTIONS = {
|
|
|
135
150
|
options: {},
|
|
136
151
|
plugins: []
|
|
137
152
|
}
|
|
153
|
+
},
|
|
154
|
+
performance: {
|
|
155
|
+
mode: "auto",
|
|
156
|
+
maxDurationMs: DEFAULT_PERFORMANCE_MAX_DURATION_MS,
|
|
157
|
+
sampleIntervalMs: DEFAULT_PERFORMANCE_SAMPLE_INTERVAL_MS,
|
|
158
|
+
longTaskThresholdMs: DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS,
|
|
159
|
+
saveDir: DEFAULT_PERFORMANCE_SAVE_DIR,
|
|
160
|
+
memory: {
|
|
161
|
+
enabled: true
|
|
162
|
+
},
|
|
163
|
+
stacks: {
|
|
164
|
+
enabled: true
|
|
165
|
+
}
|
|
138
166
|
}
|
|
139
167
|
};
|
|
140
168
|
function mergeMcpClientOptions(cursorConfig, mcpClients) {
|
|
@@ -204,6 +232,18 @@ function mergeOptions(options = {}) {
|
|
|
204
232
|
...DEFAULT_OPTIONS.screenshot.snapdom.plugins
|
|
205
233
|
]
|
|
206
234
|
}
|
|
235
|
+
},
|
|
236
|
+
performance: {
|
|
237
|
+
...DEFAULT_OPTIONS.performance,
|
|
238
|
+
...options.performance,
|
|
239
|
+
memory: {
|
|
240
|
+
...DEFAULT_OPTIONS.performance.memory,
|
|
241
|
+
...options.performance?.memory
|
|
242
|
+
},
|
|
243
|
+
stacks: {
|
|
244
|
+
...DEFAULT_OPTIONS.performance.stacks,
|
|
245
|
+
...options.performance?.stacks
|
|
246
|
+
}
|
|
207
247
|
}
|
|
208
248
|
};
|
|
209
249
|
}
|
|
@@ -299,7 +339,11 @@ function createVueMcpNextContext(options) {
|
|
|
299
339
|
rpcServer: void 0,
|
|
300
340
|
pages: createPageTargetRegistry(),
|
|
301
341
|
consoleRecords: createRingBuffer(options.console.maxRecords),
|
|
302
|
-
networkRecords: createRingBuffer(options.network.maxRecords)
|
|
342
|
+
networkRecords: createRingBuffer(options.network.maxRecords),
|
|
343
|
+
performanceReports: createRingBuffer(
|
|
344
|
+
DEFAULT_PERFORMANCE_MAX_REPORTS
|
|
345
|
+
),
|
|
346
|
+
performanceSessions: /* @__PURE__ */ new Map()
|
|
303
347
|
};
|
|
304
348
|
}
|
|
305
349
|
|
|
@@ -417,13 +461,13 @@ async function requestRuntimeData(ctx, trigger) {
|
|
|
417
461
|
return { ok: false, error: "runtime bridge is not connected" };
|
|
418
462
|
}
|
|
419
463
|
const event = (0, import_nanoid.nanoid)();
|
|
420
|
-
return new Promise((
|
|
464
|
+
return new Promise((resolve2) => {
|
|
421
465
|
const timeout = setTimeout(() => {
|
|
422
|
-
|
|
466
|
+
resolve2({ ok: false, error: "runtime bridge response timed out" });
|
|
423
467
|
}, 5e3);
|
|
424
468
|
ctx.hooks.hookOnce(event, (data) => {
|
|
425
469
|
clearTimeout(timeout);
|
|
426
|
-
|
|
470
|
+
resolve2(data);
|
|
427
471
|
});
|
|
428
472
|
trigger(event);
|
|
429
473
|
});
|
|
@@ -702,12 +746,598 @@ function registerNetworkTools(server, ctx) {
|
|
|
702
746
|
);
|
|
703
747
|
}
|
|
704
748
|
|
|
705
|
-
// src/mcp/tools/
|
|
749
|
+
// src/mcp/tools/performance.ts
|
|
706
750
|
var import_zod5 = require("zod");
|
|
707
751
|
|
|
752
|
+
// src/performance/summary.ts
|
|
753
|
+
function buildPerformanceSummary(input) {
|
|
754
|
+
const memory = buildMemorySummary(input.memorySamples);
|
|
755
|
+
const blockedTimeMs = input.longTasks.reduce((total, task) => {
|
|
756
|
+
return total + Math.max(0, task.durationMs - DEFAULT_PERFORMANCE_LONG_TASK_THRESHOLD_MS);
|
|
757
|
+
}, 0);
|
|
758
|
+
const longTaskCount = input.longTasks.length;
|
|
759
|
+
const durations = input.longTasks.map((task) => task.durationMs);
|
|
760
|
+
const maxTaskDurationMs = durations.length ? Math.max(...durations) : 0;
|
|
761
|
+
const averageTaskDurationMs = durations.length ? Math.round(
|
|
762
|
+
durations.reduce((total, duration) => total + duration, 0) / durations.length
|
|
763
|
+
) : void 0;
|
|
764
|
+
const suspectedJank = blockedTimeMs > 0 || memory.trend === "growing" || longTaskCount > 0;
|
|
765
|
+
const severity = resolveSeverity({
|
|
766
|
+
blockedTimeMs,
|
|
767
|
+
longTaskCount,
|
|
768
|
+
memoryTrend: memory.trend
|
|
769
|
+
});
|
|
770
|
+
return {
|
|
771
|
+
blockedTimeMs,
|
|
772
|
+
longTaskCount,
|
|
773
|
+
maxTaskDurationMs,
|
|
774
|
+
averageTaskDurationMs,
|
|
775
|
+
suspectedJank,
|
|
776
|
+
severity
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
function buildMemorySummary(samples) {
|
|
780
|
+
const first = samples[0]?.usedJSHeapSize;
|
|
781
|
+
const last = samples.at(-1)?.usedJSHeapSize;
|
|
782
|
+
const peak = samples.reduce((currentPeak, sample) => {
|
|
783
|
+
if (typeof sample.usedJSHeapSize !== "number") {
|
|
784
|
+
return currentPeak;
|
|
785
|
+
}
|
|
786
|
+
return Math.max(currentPeak, sample.usedJSHeapSize);
|
|
787
|
+
}, 0);
|
|
788
|
+
const trend = resolveMemoryTrend(first, last);
|
|
789
|
+
return {
|
|
790
|
+
samples,
|
|
791
|
+
initialUsedJSHeapSize: first,
|
|
792
|
+
finalUsedJSHeapSize: last,
|
|
793
|
+
peakUsedJSHeapSize: peak || void 0,
|
|
794
|
+
deltaUsedJSHeapSize: typeof first === "number" && typeof last === "number" ? last - first : void 0,
|
|
795
|
+
trend
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
function buildStackSummary(frames, options = {}) {
|
|
799
|
+
return {
|
|
800
|
+
topFrames: [...frames].sort(sortByHotness).slice(0, 10),
|
|
801
|
+
rawProfilePath: options.rawProfilePath,
|
|
802
|
+
limitation: options.limitation
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
function resolveSeverity(input) {
|
|
806
|
+
if (input.blockedTimeMs >= 1e3 || input.longTaskCount >= 10) {
|
|
807
|
+
return "critical";
|
|
808
|
+
}
|
|
809
|
+
if (input.blockedTimeMs > 0 || input.memoryTrend === "growing") {
|
|
810
|
+
return "warning";
|
|
811
|
+
}
|
|
812
|
+
return "ok";
|
|
813
|
+
}
|
|
814
|
+
function resolveMemoryTrend(first, last) {
|
|
815
|
+
if (typeof first !== "number" || typeof last !== "number") {
|
|
816
|
+
return "unknown";
|
|
817
|
+
}
|
|
818
|
+
if (last > first) {
|
|
819
|
+
return "growing";
|
|
820
|
+
}
|
|
821
|
+
return "stable";
|
|
822
|
+
}
|
|
823
|
+
function sortByHotness(left, right) {
|
|
824
|
+
return compareNumber(right.totalTimeMs, left.totalTimeMs) || compareNumber(right.selfTimeMs, left.selfTimeMs) || compareNumber(right.hitCount, left.hitCount);
|
|
825
|
+
}
|
|
826
|
+
function compareNumber(left, right) {
|
|
827
|
+
return (left ?? -1) - (right ?? -1);
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
// src/performance/output.ts
|
|
831
|
+
var import_promises = require("fs/promises");
|
|
832
|
+
var import_node_path = require("path");
|
|
833
|
+
var import_nanoid2 = require("nanoid");
|
|
834
|
+
async function writePerformanceArtifact(options) {
|
|
835
|
+
const root = (0, import_node_path.resolve)(options.root ?? process.cwd());
|
|
836
|
+
const dir = resolveOutputDirectory(root, options.saveDir);
|
|
837
|
+
const path8 = (0, import_node_path.resolve)(dir, createSafeFileName(options.fileName));
|
|
838
|
+
const data = typeof options.data === "string" ? Buffer.from(options.data) : options.data;
|
|
839
|
+
await (0, import_promises.mkdir)(dir, { recursive: true });
|
|
840
|
+
await (0, import_promises.writeFile)(path8, data);
|
|
841
|
+
return {
|
|
842
|
+
kind: options.kind,
|
|
843
|
+
path: path8,
|
|
844
|
+
relativePath: (0, import_node_path.relative)(root, path8),
|
|
845
|
+
byteLength: data.byteLength,
|
|
846
|
+
source: "cdp"
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function resolveOutputDirectory(root, saveDir) {
|
|
850
|
+
return (0, import_node_path.resolve)(root, saveDir);
|
|
851
|
+
}
|
|
852
|
+
function createSafeFileName(fileName) {
|
|
853
|
+
const trimmed = fileName.trim();
|
|
854
|
+
if (!trimmed) {
|
|
855
|
+
return `${String(Date.now())}-${(0, import_nanoid2.nanoid)()}`;
|
|
856
|
+
}
|
|
857
|
+
const suffix = (0, import_nanoid2.nanoid)(6);
|
|
858
|
+
const dotIndex = trimmed.lastIndexOf(".");
|
|
859
|
+
if (dotIndex === -1) {
|
|
860
|
+
return `${trimmed}-${suffix}`;
|
|
861
|
+
}
|
|
862
|
+
const base = trimmed.slice(0, dotIndex);
|
|
863
|
+
const extension = trimmed.slice(dotIndex);
|
|
864
|
+
return `${base}-${suffix}${extension}`;
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
// src/cdp/cdpPerformance.ts
|
|
868
|
+
async function recordCdpPerformance(options) {
|
|
869
|
+
const session = await startCdpPerformanceRecording(options);
|
|
870
|
+
await waitForDuration(options.durationMs);
|
|
871
|
+
return stopCdpPerformanceRecording({
|
|
872
|
+
client: options.client,
|
|
873
|
+
session
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
async function startCdpPerformanceRecording(options) {
|
|
877
|
+
await options.client.Performance.enable();
|
|
878
|
+
await options.client.Profiler.enable();
|
|
879
|
+
await options.client.Profiler.start();
|
|
880
|
+
return {
|
|
881
|
+
recordingId: createRecordingId(options.pageId),
|
|
882
|
+
pageId: options.pageId,
|
|
883
|
+
startedAt: Date.now(),
|
|
884
|
+
includeMemory: options.includeMemory,
|
|
885
|
+
includeStacks: options.includeStacks,
|
|
886
|
+
saveDir: options.saveDir
|
|
887
|
+
};
|
|
888
|
+
}
|
|
889
|
+
async function stopCdpPerformanceRecording(options) {
|
|
890
|
+
const endedAt = Date.now();
|
|
891
|
+
const [metricsResult, profileResult] = await Promise.all([
|
|
892
|
+
options.client.Performance.getMetrics(),
|
|
893
|
+
options.client.Profiler.stop()
|
|
894
|
+
]);
|
|
895
|
+
const metrics = toMetricMap(metricsResult.metrics);
|
|
896
|
+
const longTasks = toLongTaskRecords(metrics);
|
|
897
|
+
const memorySamples = options.session.includeMemory ? [toMemorySample(metrics)] : [];
|
|
898
|
+
const memory = options.session.includeMemory ? buildMemorySummary(memorySamples) : void 0;
|
|
899
|
+
const stacks = options.session.includeStacks ? buildStackSummary(aggregateCpuProfile(profileResult.profile)) : void 0;
|
|
900
|
+
const artifact = await writeCpuProfileArtifact(
|
|
901
|
+
{
|
|
902
|
+
pageId: options.session.pageId,
|
|
903
|
+
saveDir: options.session.saveDir
|
|
904
|
+
},
|
|
905
|
+
profileResult.profile
|
|
906
|
+
);
|
|
907
|
+
const report = {
|
|
908
|
+
recordingId: options.session.recordingId,
|
|
909
|
+
pageId: options.session.pageId,
|
|
910
|
+
source: "cdp",
|
|
911
|
+
startedAt: options.session.startedAt,
|
|
912
|
+
endedAt,
|
|
913
|
+
durationMs: endedAt - options.session.startedAt,
|
|
914
|
+
summary: buildPerformanceSummary({
|
|
915
|
+
longTasks,
|
|
916
|
+
memorySamples
|
|
917
|
+
}),
|
|
918
|
+
longTasks,
|
|
919
|
+
memory,
|
|
920
|
+
stacks,
|
|
921
|
+
artifacts: [artifact],
|
|
922
|
+
limitations: [
|
|
923
|
+
"CDP path returns sampled CPU profile data, not a full instruction trace"
|
|
924
|
+
]
|
|
925
|
+
};
|
|
926
|
+
return report;
|
|
927
|
+
}
|
|
928
|
+
async function takeHeapSnapshot(options) {
|
|
929
|
+
const chunks = [];
|
|
930
|
+
await options.client.HeapProfiler.enable();
|
|
931
|
+
options.client.HeapProfiler.addHeapSnapshotChunk((event) => {
|
|
932
|
+
const payload = event;
|
|
933
|
+
if (payload.chunk) {
|
|
934
|
+
chunks.push(payload.chunk);
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
await options.client.HeapProfiler.takeHeapSnapshot({
|
|
938
|
+
reportProgress: false
|
|
939
|
+
});
|
|
940
|
+
const artifact = await writePerformanceArtifact({
|
|
941
|
+
root: process.cwd(),
|
|
942
|
+
saveDir: options.saveDir,
|
|
943
|
+
fileName: `${options.pageId}-heap-snapshot.heapsnapshot`,
|
|
944
|
+
kind: "heap-snapshot",
|
|
945
|
+
data: Buffer.from(chunks.join(""))
|
|
946
|
+
});
|
|
947
|
+
return artifact;
|
|
948
|
+
}
|
|
949
|
+
function createRecordingId(pageId) {
|
|
950
|
+
return `cdp-${pageId}-${String(Date.now())}`;
|
|
951
|
+
}
|
|
952
|
+
function waitForDuration(durationMs) {
|
|
953
|
+
return new Promise((resolve2) => {
|
|
954
|
+
setTimeout(() => {
|
|
955
|
+
resolve2();
|
|
956
|
+
}, durationMs);
|
|
957
|
+
});
|
|
958
|
+
}
|
|
959
|
+
function toMetricMap(metrics) {
|
|
960
|
+
return new Map(metrics.map((metric) => [metric.name, metric.value]));
|
|
961
|
+
}
|
|
962
|
+
function toLongTaskRecords(metrics) {
|
|
963
|
+
const taskDuration = metrics.get("TaskDuration") ?? 0;
|
|
964
|
+
if (taskDuration <= 0) {
|
|
965
|
+
return [];
|
|
966
|
+
}
|
|
967
|
+
return [
|
|
968
|
+
{
|
|
969
|
+
startTime: 0,
|
|
970
|
+
durationMs: taskDuration,
|
|
971
|
+
name: "TaskDuration",
|
|
972
|
+
source: "cpu-profile"
|
|
973
|
+
}
|
|
974
|
+
];
|
|
975
|
+
}
|
|
976
|
+
function toMemorySample(metrics) {
|
|
977
|
+
return {
|
|
978
|
+
timestamp: Date.now(),
|
|
979
|
+
usedJSHeapSize: metrics.get("JSHeapUsedSize"),
|
|
980
|
+
totalJSHeapSize: metrics.get("JSHeapTotalSize"),
|
|
981
|
+
jsHeapSizeLimit: metrics.get("JSHeapSizeLimit")
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
function aggregateCpuProfile(profile) {
|
|
985
|
+
const nodes = profile.nodes ?? [];
|
|
986
|
+
const nodeMap = /* @__PURE__ */ new Map();
|
|
987
|
+
const nodeIndex = /* @__PURE__ */ new Map();
|
|
988
|
+
nodes.forEach((node) => nodeIndex.set(node.id, node));
|
|
989
|
+
(profile.samples ?? []).forEach((nodeId, index) => {
|
|
990
|
+
const node = nodeIndex.get(nodeId);
|
|
991
|
+
if (!node) {
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
const delta = profile.timeDeltas?.[index] ?? 0;
|
|
995
|
+
const current = nodeMap.get(nodeId) ?? {
|
|
996
|
+
functionName: node.callFrame.functionName || "<anonymous>",
|
|
997
|
+
url: node.callFrame.url,
|
|
998
|
+
lineNumber: node.callFrame.lineNumber,
|
|
999
|
+
columnNumber: node.callFrame.columnNumber,
|
|
1000
|
+
selfTimeMs: 0,
|
|
1001
|
+
totalTimeMs: 0,
|
|
1002
|
+
hitCount: 0
|
|
1003
|
+
};
|
|
1004
|
+
nodeMap.set(nodeId, {
|
|
1005
|
+
...current,
|
|
1006
|
+
selfTimeMs: current.selfTimeMs + delta,
|
|
1007
|
+
totalTimeMs: current.totalTimeMs + delta,
|
|
1008
|
+
hitCount: current.hitCount + 1
|
|
1009
|
+
});
|
|
1010
|
+
});
|
|
1011
|
+
return [...nodeMap.values()];
|
|
1012
|
+
}
|
|
1013
|
+
async function writeCpuProfileArtifact(options, profile) {
|
|
1014
|
+
return writePerformanceArtifact({
|
|
1015
|
+
root: process.cwd(),
|
|
1016
|
+
saveDir: options.saveDir ?? ".vite-mcp/performance",
|
|
1017
|
+
fileName: `${options.pageId}-cpu-profile.cpuprofile`,
|
|
1018
|
+
kind: "cpu-profile",
|
|
1019
|
+
data: Buffer.from(JSON.stringify(profile, null, 2))
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
// src/mcp/tools/performance.ts
|
|
1024
|
+
var PERFORMANCE_NOT_AVAILABLE_ERROR = "Performance diagnostics are disabled by configuration";
|
|
1025
|
+
var activeCdpPerformanceClients = /* @__PURE__ */ new Map();
|
|
1026
|
+
function registerPerformanceTools(server, ctx) {
|
|
1027
|
+
server.registerTool(
|
|
1028
|
+
MCP_TOOL_NAMES.recordPerformance,
|
|
1029
|
+
{
|
|
1030
|
+
description: "Record a performance sample for the selected page.",
|
|
1031
|
+
inputSchema: {
|
|
1032
|
+
pageId: import_zod5.z.string().optional(),
|
|
1033
|
+
durationMs: import_zod5.z.number().optional(),
|
|
1034
|
+
includeMemory: import_zod5.z.boolean().optional(),
|
|
1035
|
+
includeStacks: import_zod5.z.boolean().optional()
|
|
1036
|
+
}
|
|
1037
|
+
},
|
|
1038
|
+
async (input) => handleRecordPerformance(ctx, input)
|
|
1039
|
+
);
|
|
1040
|
+
server.registerTool(
|
|
1041
|
+
MCP_TOOL_NAMES.startPerformanceRecording,
|
|
1042
|
+
{
|
|
1043
|
+
description: "Start a performance recording session.",
|
|
1044
|
+
inputSchema: {
|
|
1045
|
+
pageId: import_zod5.z.string().optional(),
|
|
1046
|
+
includeMemory: import_zod5.z.boolean().optional(),
|
|
1047
|
+
includeStacks: import_zod5.z.boolean().optional()
|
|
1048
|
+
}
|
|
1049
|
+
},
|
|
1050
|
+
async (input) => handleStartPerformanceRecording(ctx, input)
|
|
1051
|
+
);
|
|
1052
|
+
server.registerTool(
|
|
1053
|
+
MCP_TOOL_NAMES.stopPerformanceRecording,
|
|
1054
|
+
{
|
|
1055
|
+
description: "Stop a performance recording session.",
|
|
1056
|
+
inputSchema: {
|
|
1057
|
+
recordingId: import_zod5.z.string()
|
|
1058
|
+
}
|
|
1059
|
+
},
|
|
1060
|
+
async (input) => handleStopPerformanceRecording(ctx, input)
|
|
1061
|
+
);
|
|
1062
|
+
server.registerTool(
|
|
1063
|
+
MCP_TOOL_NAMES.getPerformanceReport,
|
|
1064
|
+
{
|
|
1065
|
+
description: "Get cached performance reports and active sessions.",
|
|
1066
|
+
inputSchema: {
|
|
1067
|
+
pageId: import_zod5.z.string().optional(),
|
|
1068
|
+
recordingId: import_zod5.z.string().optional(),
|
|
1069
|
+
limit: import_zod5.z.number().optional()
|
|
1070
|
+
}
|
|
1071
|
+
},
|
|
1072
|
+
(input) => handleGetPerformanceReport(ctx, input)
|
|
1073
|
+
);
|
|
1074
|
+
server.registerTool(
|
|
1075
|
+
MCP_TOOL_NAMES.takeHeapSnapshot,
|
|
1076
|
+
{
|
|
1077
|
+
description: "Take a heap snapshot with CDP.",
|
|
1078
|
+
inputSchema: {
|
|
1079
|
+
pageId: import_zod5.z.string().optional()
|
|
1080
|
+
}
|
|
1081
|
+
},
|
|
1082
|
+
async (input) => handleTakeHeapSnapshot(ctx, input)
|
|
1083
|
+
);
|
|
1084
|
+
}
|
|
1085
|
+
function appendPerformanceReport(ctx, report) {
|
|
1086
|
+
if (ctx.performanceReports.all().some((item) => item.recordingId === report.recordingId)) {
|
|
1087
|
+
return;
|
|
1088
|
+
}
|
|
1089
|
+
ctx.performanceReports.push(report);
|
|
1090
|
+
}
|
|
1091
|
+
async function handleRecordPerformance(ctx, input) {
|
|
1092
|
+
const durationMs = input.durationMs ?? ctx.options.performance.maxDurationMs;
|
|
1093
|
+
const includeMemory = input.includeMemory ?? ctx.options.performance.memory.enabled;
|
|
1094
|
+
const includeStacks = input.includeStacks ?? ctx.options.performance.stacks.enabled;
|
|
1095
|
+
if (ctx.options.performance.mode === "off") {
|
|
1096
|
+
return createToolError(PERFORMANCE_NOT_AVAILABLE_ERROR);
|
|
1097
|
+
}
|
|
1098
|
+
if (ctx.options.performance.mode !== "hook") {
|
|
1099
|
+
const cdpResult = await connectPerformanceCdp(ctx, input.pageId);
|
|
1100
|
+
const cdp = cdpResult.cdp;
|
|
1101
|
+
if (cdp) {
|
|
1102
|
+
try {
|
|
1103
|
+
const report = await recordCdpPerformance({
|
|
1104
|
+
client: cdp.client,
|
|
1105
|
+
pageId: input.pageId ?? "cdp",
|
|
1106
|
+
durationMs,
|
|
1107
|
+
includeMemory,
|
|
1108
|
+
includeStacks,
|
|
1109
|
+
saveDir: ctx.options.performance.saveDir
|
|
1110
|
+
});
|
|
1111
|
+
appendPerformanceReport(ctx, report);
|
|
1112
|
+
return createToolResponse(toStructuredRecord(report));
|
|
1113
|
+
} finally {
|
|
1114
|
+
await closeCdpClient(cdp.client);
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
if (ctx.options.performance.mode === "cdp") {
|
|
1118
|
+
return createToolError(
|
|
1119
|
+
formatCdpUnavailableError(
|
|
1120
|
+
"CDP performance collection is unavailable",
|
|
1121
|
+
cdpResult.error
|
|
1122
|
+
)
|
|
1123
|
+
);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
const result = await requestRuntimeData(ctx, (event) => {
|
|
1127
|
+
void ctx.rpcServer?.recordPerformance({
|
|
1128
|
+
event,
|
|
1129
|
+
durationMs,
|
|
1130
|
+
includeMemory,
|
|
1131
|
+
includeStacks
|
|
1132
|
+
});
|
|
1133
|
+
});
|
|
1134
|
+
if (isPerformanceReport(result)) {
|
|
1135
|
+
appendPerformanceReport(ctx, result);
|
|
1136
|
+
}
|
|
1137
|
+
if (isPlainRecord(result)) {
|
|
1138
|
+
return createToolResponse(toStructuredRecord(result));
|
|
1139
|
+
}
|
|
1140
|
+
return createToolError("runtime bridge returned an invalid response");
|
|
1141
|
+
}
|
|
1142
|
+
async function handleStartPerformanceRecording(ctx, input) {
|
|
1143
|
+
const includeMemory = input.includeMemory ?? ctx.options.performance.memory.enabled;
|
|
1144
|
+
const includeStacks = input.includeStacks ?? ctx.options.performance.stacks.enabled;
|
|
1145
|
+
if (ctx.options.performance.mode === "off") {
|
|
1146
|
+
return createToolError(PERFORMANCE_NOT_AVAILABLE_ERROR);
|
|
1147
|
+
}
|
|
1148
|
+
if (ctx.options.performance.mode !== "hook") {
|
|
1149
|
+
const cdpResult = await connectPerformanceCdp(ctx, input.pageId);
|
|
1150
|
+
const cdp = cdpResult.cdp;
|
|
1151
|
+
if (cdp) {
|
|
1152
|
+
try {
|
|
1153
|
+
const session = await startCdpPerformanceRecording({
|
|
1154
|
+
client: cdp.client,
|
|
1155
|
+
pageId: input.pageId ?? "cdp",
|
|
1156
|
+
includeMemory,
|
|
1157
|
+
includeStacks,
|
|
1158
|
+
saveDir: ctx.options.performance.saveDir
|
|
1159
|
+
});
|
|
1160
|
+
ctx.performanceSessions.set(session.recordingId, {
|
|
1161
|
+
recordingId: session.recordingId,
|
|
1162
|
+
pageId: session.pageId,
|
|
1163
|
+
source: "cdp",
|
|
1164
|
+
startedAt: session.startedAt,
|
|
1165
|
+
includeMemory,
|
|
1166
|
+
includeStacks,
|
|
1167
|
+
mode: ctx.options.performance.mode
|
|
1168
|
+
});
|
|
1169
|
+
activeCdpPerformanceClients.set(session.recordingId, cdp.client);
|
|
1170
|
+
return createToolResponse(
|
|
1171
|
+
toStructuredRecord({
|
|
1172
|
+
...session,
|
|
1173
|
+
source: "cdp"
|
|
1174
|
+
})
|
|
1175
|
+
);
|
|
1176
|
+
} catch (error) {
|
|
1177
|
+
await closeCdpClient(cdp.client);
|
|
1178
|
+
return createToolError(
|
|
1179
|
+
error instanceof Error ? error.message : String(error)
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
if (ctx.options.performance.mode === "cdp") {
|
|
1184
|
+
return createToolError(
|
|
1185
|
+
formatCdpUnavailableError(
|
|
1186
|
+
"CDP performance collection is unavailable",
|
|
1187
|
+
cdpResult.error
|
|
1188
|
+
)
|
|
1189
|
+
);
|
|
1190
|
+
}
|
|
1191
|
+
}
|
|
1192
|
+
const result = await requestRuntimeData(ctx, (event) => {
|
|
1193
|
+
void ctx.rpcServer?.startPerformanceRecording({
|
|
1194
|
+
event,
|
|
1195
|
+
includeMemory,
|
|
1196
|
+
includeStacks
|
|
1197
|
+
});
|
|
1198
|
+
});
|
|
1199
|
+
if (isPerformanceStartResult(result)) {
|
|
1200
|
+
ctx.performanceSessions.set(result.recordingId, {
|
|
1201
|
+
recordingId: result.recordingId,
|
|
1202
|
+
pageId: input.pageId ?? "runtime",
|
|
1203
|
+
source: "hook",
|
|
1204
|
+
startedAt: result.startedAt,
|
|
1205
|
+
includeMemory,
|
|
1206
|
+
includeStacks,
|
|
1207
|
+
mode: ctx.options.performance.mode
|
|
1208
|
+
});
|
|
1209
|
+
}
|
|
1210
|
+
if (isPlainRecord(result)) {
|
|
1211
|
+
return createToolResponse(toStructuredRecord(result));
|
|
1212
|
+
}
|
|
1213
|
+
return createToolError("runtime bridge returned an invalid response");
|
|
1214
|
+
}
|
|
1215
|
+
async function handleStopPerformanceRecording(ctx, input) {
|
|
1216
|
+
const session = ctx.performanceSessions.get(input.recordingId);
|
|
1217
|
+
if (!session) {
|
|
1218
|
+
return createToolError(`Performance recording not found: ${input.recordingId}`);
|
|
1219
|
+
}
|
|
1220
|
+
try {
|
|
1221
|
+
if (session.source === "cdp") {
|
|
1222
|
+
const client = activeCdpPerformanceClients.get(input.recordingId);
|
|
1223
|
+
if (!client) {
|
|
1224
|
+
return createToolError(
|
|
1225
|
+
`CDP client not found for recording: ${input.recordingId}`
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1228
|
+
const report = await stopCdpPerformanceRecording({
|
|
1229
|
+
client,
|
|
1230
|
+
session
|
|
1231
|
+
});
|
|
1232
|
+
appendPerformanceReport(ctx, report);
|
|
1233
|
+
return createToolResponse(toStructuredRecord(report));
|
|
1234
|
+
}
|
|
1235
|
+
const result = await requestRuntimeData(ctx, (event) => {
|
|
1236
|
+
void ctx.rpcServer?.stopPerformanceRecording({
|
|
1237
|
+
event,
|
|
1238
|
+
recordingId: input.recordingId
|
|
1239
|
+
});
|
|
1240
|
+
});
|
|
1241
|
+
if (isPerformanceReport(result)) {
|
|
1242
|
+
appendPerformanceReport(ctx, result);
|
|
1243
|
+
}
|
|
1244
|
+
if (isPlainRecord(result)) {
|
|
1245
|
+
return createToolResponse(toStructuredRecord(result));
|
|
1246
|
+
}
|
|
1247
|
+
return createToolError("runtime bridge returned an invalid response");
|
|
1248
|
+
} finally {
|
|
1249
|
+
ctx.performanceSessions.delete(input.recordingId);
|
|
1250
|
+
const client = activeCdpPerformanceClients.get(input.recordingId);
|
|
1251
|
+
if (client) {
|
|
1252
|
+
activeCdpPerformanceClients.delete(input.recordingId);
|
|
1253
|
+
await closeCdpClient(client);
|
|
1254
|
+
}
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
function handleGetPerformanceReport(ctx, input) {
|
|
1258
|
+
const reports = ctx.performanceReports.all().filter((report) => !input.pageId || report.pageId === input.pageId).filter(
|
|
1259
|
+
(report) => !input.recordingId || report.recordingId === input.recordingId
|
|
1260
|
+
);
|
|
1261
|
+
const limit = input.limit ?? reports.length;
|
|
1262
|
+
return createToolResponse(toStructuredRecord({
|
|
1263
|
+
report: reports.at(-1) ?? null,
|
|
1264
|
+
reports: reports.slice(-limit),
|
|
1265
|
+
sessions: [...ctx.performanceSessions.values()].filter(
|
|
1266
|
+
(session) => (!input.pageId || session.pageId === input.pageId) && (!input.recordingId || session.recordingId === input.recordingId)
|
|
1267
|
+
)
|
|
1268
|
+
}));
|
|
1269
|
+
}
|
|
1270
|
+
async function handleTakeHeapSnapshot(ctx, input) {
|
|
1271
|
+
if (ctx.options.performance.mode === "off" || ctx.options.performance.mode === "hook") {
|
|
1272
|
+
return createToolError(PERFORMANCE_NOT_AVAILABLE_ERROR);
|
|
1273
|
+
}
|
|
1274
|
+
const cdpResult = await connectPerformanceCdp(ctx, input.pageId);
|
|
1275
|
+
const cdp = cdpResult.cdp;
|
|
1276
|
+
if (!cdp) {
|
|
1277
|
+
return createToolError(
|
|
1278
|
+
formatCdpUnavailableError(
|
|
1279
|
+
"CDP heap snapshot is unavailable",
|
|
1280
|
+
cdpResult.error
|
|
1281
|
+
)
|
|
1282
|
+
);
|
|
1283
|
+
}
|
|
1284
|
+
try {
|
|
1285
|
+
return createToolResponse(
|
|
1286
|
+
toStructuredRecord(await takeHeapSnapshot({
|
|
1287
|
+
client: cdp.client,
|
|
1288
|
+
pageId: input.pageId ?? "cdp",
|
|
1289
|
+
saveDir: ctx.options.performance.saveDir
|
|
1290
|
+
}))
|
|
1291
|
+
);
|
|
1292
|
+
} finally {
|
|
1293
|
+
await closeCdpClient(cdp.client);
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
async function connectPerformanceCdp(ctx, pageId) {
|
|
1297
|
+
try {
|
|
1298
|
+
return { cdp: await connectCdpForPage(ctx, pageId) };
|
|
1299
|
+
} catch (error) {
|
|
1300
|
+
return {
|
|
1301
|
+
error: error instanceof Error ? error.message : String(error)
|
|
1302
|
+
};
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
function formatCdpUnavailableError(message, reason) {
|
|
1306
|
+
if (!reason) {
|
|
1307
|
+
return message;
|
|
1308
|
+
}
|
|
1309
|
+
return `${message}: ${reason}`;
|
|
1310
|
+
}
|
|
1311
|
+
function isPerformanceReport(value) {
|
|
1312
|
+
if (!value || typeof value !== "object") {
|
|
1313
|
+
return false;
|
|
1314
|
+
}
|
|
1315
|
+
const report = value;
|
|
1316
|
+
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);
|
|
1317
|
+
}
|
|
1318
|
+
function isPerformanceStartResult(value) {
|
|
1319
|
+
if (!value || typeof value !== "object") {
|
|
1320
|
+
return false;
|
|
1321
|
+
}
|
|
1322
|
+
const result = value;
|
|
1323
|
+
return typeof result.recordingId === "string" && typeof result.startedAt === "number";
|
|
1324
|
+
}
|
|
1325
|
+
function isPlainRecord(value) {
|
|
1326
|
+
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1327
|
+
}
|
|
1328
|
+
function toStructuredRecord(value) {
|
|
1329
|
+
if (isPlainRecord(value)) {
|
|
1330
|
+
return value;
|
|
1331
|
+
}
|
|
1332
|
+
return {};
|
|
1333
|
+
}
|
|
1334
|
+
|
|
1335
|
+
// src/mcp/tools/pages.ts
|
|
1336
|
+
var import_zod6 = require("zod");
|
|
1337
|
+
|
|
708
1338
|
// src/plugin/entryDiscovery.ts
|
|
709
1339
|
var import_node_fs = __toESM(require("fs"), 1);
|
|
710
|
-
var
|
|
1340
|
+
var import_node_path2 = __toESM(require("path"), 1);
|
|
711
1341
|
var import_vite = require("vite");
|
|
712
1342
|
function discoverHtmlEntries(server) {
|
|
713
1343
|
const root = server.config.root;
|
|
@@ -720,7 +1350,7 @@ function walkHtmlEntries(root, dir, entries) {
|
|
|
720
1350
|
if (item.name === "node_modules" || item.name.startsWith(".")) {
|
|
721
1351
|
continue;
|
|
722
1352
|
}
|
|
723
|
-
const fullPath =
|
|
1353
|
+
const fullPath = import_node_path2.default.join(dir, item.name);
|
|
724
1354
|
if (item.isDirectory()) {
|
|
725
1355
|
walkHtmlEntries(root, fullPath, entries);
|
|
726
1356
|
continue;
|
|
@@ -728,10 +1358,10 @@ function walkHtmlEntries(root, dir, entries) {
|
|
|
728
1358
|
if (!item.isFile() || !item.name.endsWith(".html")) {
|
|
729
1359
|
continue;
|
|
730
1360
|
}
|
|
731
|
-
const
|
|
1361
|
+
const relative2 = (0, import_vite.normalizePath)(import_node_path2.default.relative(root, fullPath));
|
|
732
1362
|
entries.push({
|
|
733
|
-
file:
|
|
734
|
-
pathname:
|
|
1363
|
+
file: relative2,
|
|
1364
|
+
pathname: relative2 === "index.html" ? "/" : `/${relative2}`
|
|
735
1365
|
});
|
|
736
1366
|
}
|
|
737
1367
|
}
|
|
@@ -743,7 +1373,7 @@ function registerPageTools(server, ctx, vite) {
|
|
|
743
1373
|
{
|
|
744
1374
|
description: "List Vite page entries and connected runtime/CDP targets.",
|
|
745
1375
|
inputSchema: {
|
|
746
|
-
includeDisconnected:
|
|
1376
|
+
includeDisconnected: import_zod6.z.boolean().optional()
|
|
747
1377
|
}
|
|
748
1378
|
},
|
|
749
1379
|
async (input) => {
|
|
@@ -766,8 +1396,8 @@ function registerPageTools(server, ctx, vite) {
|
|
|
766
1396
|
{
|
|
767
1397
|
description: "Reload the selected page. CDP uses ignoreCache; Runtime Hook falls back to normal reload.",
|
|
768
1398
|
inputSchema: {
|
|
769
|
-
pageId:
|
|
770
|
-
ignoreCache:
|
|
1399
|
+
pageId: import_zod6.z.string().optional(),
|
|
1400
|
+
ignoreCache: import_zod6.z.boolean().optional()
|
|
771
1401
|
}
|
|
772
1402
|
},
|
|
773
1403
|
async (input) => {
|
|
@@ -826,16 +1456,16 @@ function resolveRuntimeReloadTarget(ctx, pageId) {
|
|
|
826
1456
|
function waitForRuntimePageReconnect(ctx) {
|
|
827
1457
|
let timeout;
|
|
828
1458
|
let cleanup;
|
|
829
|
-
const promise = new Promise((
|
|
1459
|
+
const promise = new Promise((resolve2) => {
|
|
830
1460
|
timeout = setTimeout(() => {
|
|
831
1461
|
cleanup?.();
|
|
832
|
-
|
|
1462
|
+
resolve2(null);
|
|
833
1463
|
}, 5e3);
|
|
834
1464
|
cleanup = ctx.hooks.hookOnce(RUNTIME_PAGE_RECONNECTED_EVENT, (payload) => {
|
|
835
1465
|
if (timeout) {
|
|
836
1466
|
clearTimeout(timeout);
|
|
837
1467
|
}
|
|
838
|
-
|
|
1468
|
+
resolve2(isPageTarget(payload) ? payload : null);
|
|
839
1469
|
});
|
|
840
1470
|
});
|
|
841
1471
|
return {
|
|
@@ -919,7 +1549,7 @@ function getPathname(url) {
|
|
|
919
1549
|
}
|
|
920
1550
|
|
|
921
1551
|
// src/mcp/tools/screenshot.ts
|
|
922
|
-
var
|
|
1552
|
+
var import_zod7 = require("zod");
|
|
923
1553
|
|
|
924
1554
|
// src/cdp/cdpScreenshot.ts
|
|
925
1555
|
async function cdpCaptureScreenshot(options) {
|
|
@@ -1025,16 +1655,16 @@ function isElementRect(value) {
|
|
|
1025
1655
|
|
|
1026
1656
|
// src/mcp/tools/screenshotOutput.ts
|
|
1027
1657
|
var import_node_crypto = require("crypto");
|
|
1028
|
-
var
|
|
1029
|
-
var
|
|
1658
|
+
var import_promises2 = require("fs/promises");
|
|
1659
|
+
var import_node_path3 = __toESM(require("path"), 1);
|
|
1030
1660
|
async function createScreenshotOutput(ctx, payload) {
|
|
1031
1661
|
if (ctx.options.screenshot.type === "base64") {
|
|
1032
1662
|
return payload;
|
|
1033
1663
|
}
|
|
1034
1664
|
const saveDir = resolveScreenshotSaveDir(ctx);
|
|
1035
|
-
await (0,
|
|
1036
|
-
const filePath =
|
|
1037
|
-
await (0,
|
|
1665
|
+
await (0, import_promises2.mkdir)(saveDir, { recursive: true });
|
|
1666
|
+
const filePath = import_node_path3.default.join(saveDir, createScreenshotFileName(payload));
|
|
1667
|
+
await (0, import_promises2.writeFile)(filePath, Buffer.from(payload.data, "base64"));
|
|
1038
1668
|
return {
|
|
1039
1669
|
source: payload.source,
|
|
1040
1670
|
target: payload.target,
|
|
@@ -1057,10 +1687,10 @@ function resolveScreenshotSaveDir(ctx) {
|
|
|
1057
1687
|
if (!root) {
|
|
1058
1688
|
throw new Error("Vite server root is required for screenshot path output");
|
|
1059
1689
|
}
|
|
1060
|
-
if (
|
|
1690
|
+
if (import_node_path3.default.isAbsolute(saveDir)) {
|
|
1061
1691
|
return saveDir;
|
|
1062
1692
|
}
|
|
1063
|
-
return
|
|
1693
|
+
return import_node_path3.default.resolve(root, saveDir);
|
|
1064
1694
|
}
|
|
1065
1695
|
function createScreenshotFileName(payload) {
|
|
1066
1696
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
@@ -1072,21 +1702,21 @@ function createProjectRelativePath(ctx, filePath) {
|
|
|
1072
1702
|
if (!root) {
|
|
1073
1703
|
throw new Error("Vite server root is required for screenshot path output");
|
|
1074
1704
|
}
|
|
1075
|
-
return
|
|
1705
|
+
return import_node_path3.default.relative(root, filePath).split(import_node_path3.default.sep).join("/");
|
|
1076
1706
|
}
|
|
1077
1707
|
|
|
1078
1708
|
// src/mcp/tools/screenshot.ts
|
|
1079
1709
|
var DEFAULT_SCREENSHOT_TARGET = "viewport";
|
|
1080
1710
|
var DEFAULT_SCREENSHOT_FORMAT = "png";
|
|
1081
1711
|
var screenshotInputSchema = {
|
|
1082
|
-
pageId:
|
|
1083
|
-
target:
|
|
1084
|
-
selector:
|
|
1085
|
-
format:
|
|
1086
|
-
prefer:
|
|
1087
|
-
quality:
|
|
1088
|
-
scale:
|
|
1089
|
-
snapdom:
|
|
1712
|
+
pageId: import_zod7.z.string().optional(),
|
|
1713
|
+
target: import_zod7.z.enum(["viewport", "fullPage", "element"]).optional(),
|
|
1714
|
+
selector: import_zod7.z.string().optional(),
|
|
1715
|
+
format: import_zod7.z.enum(["png", "jpeg", "webp"]).optional(),
|
|
1716
|
+
prefer: import_zod7.z.enum(["auto", "cdp", "runtime"]).optional(),
|
|
1717
|
+
quality: import_zod7.z.number().optional(),
|
|
1718
|
+
scale: import_zod7.z.number().optional(),
|
|
1719
|
+
snapdom: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.unknown()).optional()
|
|
1090
1720
|
};
|
|
1091
1721
|
function registerScreenshotTools(server, ctx) {
|
|
1092
1722
|
server.registerTool(
|
|
@@ -1159,7 +1789,7 @@ async function createRuntimeScreenshot(ctx, input, normalized) {
|
|
|
1159
1789
|
`screenshot is too large: ${String(result.byteLength)} bytes`
|
|
1160
1790
|
);
|
|
1161
1791
|
}
|
|
1162
|
-
if (!
|
|
1792
|
+
if (!isPlainRecord2(result)) {
|
|
1163
1793
|
return createToolError("runtime screenshot returned an invalid response");
|
|
1164
1794
|
}
|
|
1165
1795
|
if (result.ok === false) {
|
|
@@ -1188,9 +1818,9 @@ async function createScreenshotResponse(ctx, result) {
|
|
|
1188
1818
|
}
|
|
1189
1819
|
}
|
|
1190
1820
|
function isScreenshotTooLarge(ctx, result) {
|
|
1191
|
-
return
|
|
1821
|
+
return isPlainRecord2(result) && "byteLength" in result && typeof result.byteLength === "number" && result.byteLength > ctx.options.screenshot.maxBytes;
|
|
1192
1822
|
}
|
|
1193
|
-
function
|
|
1823
|
+
function isPlainRecord2(value) {
|
|
1194
1824
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
1195
1825
|
}
|
|
1196
1826
|
function createMimeType(format) {
|
|
@@ -1205,8 +1835,8 @@ function isScreenshotImagePayload(result) {
|
|
|
1205
1835
|
}
|
|
1206
1836
|
|
|
1207
1837
|
// src/mcp/tools/vue.ts
|
|
1208
|
-
var
|
|
1209
|
-
var
|
|
1838
|
+
var import_nanoid3 = require("nanoid");
|
|
1839
|
+
var import_zod8 = require("zod");
|
|
1210
1840
|
function registerVueTools(server, ctx) {
|
|
1211
1841
|
server.registerTool(
|
|
1212
1842
|
MCP_TOOL_NAMES.getComponentTree,
|
|
@@ -1219,7 +1849,7 @@ function registerVueTools(server, ctx) {
|
|
|
1219
1849
|
MCP_TOOL_NAMES.getComponentState,
|
|
1220
1850
|
{
|
|
1221
1851
|
description: "Get Vue component state.",
|
|
1222
|
-
inputSchema: { componentName:
|
|
1852
|
+
inputSchema: { componentName: import_zod8.z.string() }
|
|
1223
1853
|
},
|
|
1224
1854
|
async ({ componentName }) => requestVueData(ctx, (event) => {
|
|
1225
1855
|
void ctx.rpcServer?.getInspectorState({ event, componentName });
|
|
@@ -1230,10 +1860,10 @@ function registerVueTools(server, ctx) {
|
|
|
1230
1860
|
{
|
|
1231
1861
|
description: "Edit Vue component state.",
|
|
1232
1862
|
inputSchema: {
|
|
1233
|
-
componentName:
|
|
1234
|
-
path:
|
|
1235
|
-
value:
|
|
1236
|
-
valueType:
|
|
1863
|
+
componentName: import_zod8.z.string(),
|
|
1864
|
+
path: import_zod8.z.array(import_zod8.z.string()),
|
|
1865
|
+
value: import_zod8.z.string(),
|
|
1866
|
+
valueType: import_zod8.z.enum(["string", "number", "boolean", "object", "array"])
|
|
1237
1867
|
}
|
|
1238
1868
|
},
|
|
1239
1869
|
({ componentName, path: path8, value, valueType }) => {
|
|
@@ -1253,7 +1883,7 @@ function registerVueTools(server, ctx) {
|
|
|
1253
1883
|
MCP_TOOL_NAMES.highlightComponent,
|
|
1254
1884
|
{
|
|
1255
1885
|
description: "Highlight a Vue component.",
|
|
1256
|
-
inputSchema: { componentName:
|
|
1886
|
+
inputSchema: { componentName: import_zod8.z.string() }
|
|
1257
1887
|
},
|
|
1258
1888
|
({ componentName }) => {
|
|
1259
1889
|
if (!ctx.rpcServer) {
|
|
@@ -1281,7 +1911,7 @@ function registerVueTools(server, ctx) {
|
|
|
1281
1911
|
MCP_TOOL_NAMES.getPiniaState,
|
|
1282
1912
|
{
|
|
1283
1913
|
description: "Get Pinia store state.",
|
|
1284
|
-
inputSchema: { storeName:
|
|
1914
|
+
inputSchema: { storeName: import_zod8.z.string() }
|
|
1285
1915
|
},
|
|
1286
1916
|
async ({ storeName }) => requestVueData(ctx, (event) => {
|
|
1287
1917
|
void ctx.rpcServer?.getPiniaState({ event, storeName });
|
|
@@ -1292,7 +1922,7 @@ async function requestVueData(ctx, trigger) {
|
|
|
1292
1922
|
if (!ctx.rpcServer) {
|
|
1293
1923
|
return vueBridgeUnavailable();
|
|
1294
1924
|
}
|
|
1295
|
-
const event = (0,
|
|
1925
|
+
const event = (0, import_nanoid3.nanoid)();
|
|
1296
1926
|
const data = await waitForVueHook(ctx, event, () => {
|
|
1297
1927
|
trigger(event);
|
|
1298
1928
|
});
|
|
@@ -1301,13 +1931,13 @@ async function requestVueData(ctx, trigger) {
|
|
|
1301
1931
|
};
|
|
1302
1932
|
}
|
|
1303
1933
|
function waitForVueHook(ctx, event, trigger) {
|
|
1304
|
-
return new Promise((
|
|
1934
|
+
return new Promise((resolve2) => {
|
|
1305
1935
|
const timeout = setTimeout(() => {
|
|
1306
|
-
|
|
1936
|
+
resolve2({ ok: false, error: "Vue runtime bridge response timed out" });
|
|
1307
1937
|
}, 5e3);
|
|
1308
1938
|
ctx.hooks.hookOnce(event, (data) => {
|
|
1309
1939
|
clearTimeout(timeout);
|
|
1310
|
-
|
|
1940
|
+
resolve2(data);
|
|
1311
1941
|
});
|
|
1312
1942
|
trigger();
|
|
1313
1943
|
});
|
|
@@ -1328,6 +1958,7 @@ function createMcpServer(ctx, vite) {
|
|
|
1328
1958
|
registerConsoleTools(server, ctx);
|
|
1329
1959
|
registerEvaluateTools(server, ctx);
|
|
1330
1960
|
registerNetworkTools(server, ctx);
|
|
1961
|
+
registerPerformanceTools(server, ctx);
|
|
1331
1962
|
registerVueTools(server, ctx);
|
|
1332
1963
|
return server;
|
|
1333
1964
|
}
|
|
@@ -1415,6 +2046,18 @@ function createServerVueRuntimeRpc(ctx) {
|
|
|
1415
2046
|
onScreenshotTaken: (event, data) => {
|
|
1416
2047
|
void ctx.hooks.callHook(event, data);
|
|
1417
2048
|
},
|
|
2049
|
+
recordPerformance: () => void 0,
|
|
2050
|
+
onPerformanceRecorded: (event, data) => {
|
|
2051
|
+
void ctx.hooks.callHook(event, data);
|
|
2052
|
+
},
|
|
2053
|
+
startPerformanceRecording: () => void 0,
|
|
2054
|
+
onPerformanceRecordingStarted: (event, data) => {
|
|
2055
|
+
void ctx.hooks.callHook(event, data);
|
|
2056
|
+
},
|
|
2057
|
+
stopPerformanceRecording: () => void 0,
|
|
2058
|
+
onPerformanceRecordingStopped: (event, data) => {
|
|
2059
|
+
void ctx.hooks.callHook(event, data);
|
|
2060
|
+
},
|
|
1418
2061
|
getInspectorTree: () => void 0,
|
|
1419
2062
|
onInspectorTreeUpdated: (event, data) => {
|
|
1420
2063
|
void ctx.hooks.callHook(event, data);
|
|
@@ -1441,7 +2084,7 @@ function createServerVueRuntimeRpc(ctx) {
|
|
|
1441
2084
|
}
|
|
1442
2085
|
|
|
1443
2086
|
// src/cdp/cdpConsole.ts
|
|
1444
|
-
var
|
|
2087
|
+
var import_nanoid4 = require("nanoid");
|
|
1445
2088
|
|
|
1446
2089
|
// src/shared/serialization.ts
|
|
1447
2090
|
function safeStringify(value) {
|
|
@@ -1470,7 +2113,7 @@ async function startCdpConsole(options) {
|
|
|
1470
2113
|
await options.client.Runtime.enable();
|
|
1471
2114
|
options.client.Runtime.consoleAPICalled((event) => {
|
|
1472
2115
|
options.push({
|
|
1473
|
-
id: (0,
|
|
2116
|
+
id: (0, import_nanoid4.nanoid)(),
|
|
1474
2117
|
pageId: options.pageId,
|
|
1475
2118
|
source: "cdp",
|
|
1476
2119
|
level: normalizeConsoleLevel(event.type),
|
|
@@ -1490,7 +2133,7 @@ function normalizeConsoleLevel(level) {
|
|
|
1490
2133
|
}
|
|
1491
2134
|
|
|
1492
2135
|
// src/cdp/cdpNetwork.ts
|
|
1493
|
-
var
|
|
2136
|
+
var import_nanoid5 = require("nanoid");
|
|
1494
2137
|
|
|
1495
2138
|
// src/shared/sanitize.ts
|
|
1496
2139
|
function maskHeaders(headers = {}, maskNames = []) {
|
|
@@ -1530,7 +2173,7 @@ async function startCdpNetwork(options) {
|
|
|
1530
2173
|
await options.client.Network.enable();
|
|
1531
2174
|
options.client.Network.requestWillBeSent((event) => {
|
|
1532
2175
|
records.set(event.requestId, {
|
|
1533
|
-
id: (0,
|
|
2176
|
+
id: (0, import_nanoid5.nanoid)(),
|
|
1534
2177
|
pageId: options.pageId,
|
|
1535
2178
|
source: "cdp",
|
|
1536
2179
|
url: event.request.url,
|
|
@@ -1701,7 +2344,7 @@ async function startCdpObservers(ctx, target, client) {
|
|
|
1701
2344
|
|
|
1702
2345
|
// src/plugin/injectRuntime.ts
|
|
1703
2346
|
var import_node_module = require("module");
|
|
1704
|
-
var
|
|
2347
|
+
var import_node_path4 = require("path");
|
|
1705
2348
|
function createRuntimeInjectionController(options, getConfig) {
|
|
1706
2349
|
return {
|
|
1707
2350
|
resolveId(importee) {
|
|
@@ -1785,7 +2428,7 @@ function createSnapdomLoaderModule(root) {
|
|
|
1785
2428
|
}
|
|
1786
2429
|
function canResolveSnapdomFromProject(root) {
|
|
1787
2430
|
try {
|
|
1788
|
-
(0, import_node_module.createRequire)((0,
|
|
2431
|
+
(0, import_node_module.createRequire)((0, import_node_path4.join)(root ?? process.cwd(), "package.json")).resolve(
|
|
1789
2432
|
"@zumer/snapdom"
|
|
1790
2433
|
);
|
|
1791
2434
|
return true;
|
|
@@ -1824,18 +2467,18 @@ function getPluginPath(plugin) {
|
|
|
1824
2467
|
}
|
|
1825
2468
|
|
|
1826
2469
|
// src/plugin/mcpClientConfig/index.ts
|
|
1827
|
-
var
|
|
1828
|
-
var
|
|
2470
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
2471
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
1829
2472
|
|
|
1830
2473
|
// src/plugin/mcpClientConfig/codexConfig.ts
|
|
1831
|
-
var
|
|
1832
|
-
var
|
|
2474
|
+
var import_promises3 = __toESM(require("fs/promises"), 1);
|
|
2475
|
+
var import_node_path5 = __toESM(require("path"), 1);
|
|
1833
2476
|
async function updateCodexMcpClientConfig(options) {
|
|
1834
2477
|
try {
|
|
1835
2478
|
const current = await readOptionalTextFile(options.configPath);
|
|
1836
2479
|
const next = replaceOrAppendOwnedBlock(current, options);
|
|
1837
|
-
await
|
|
1838
|
-
await
|
|
2480
|
+
await import_promises3.default.mkdir(import_node_path5.default.dirname(options.configPath), { recursive: true });
|
|
2481
|
+
await import_promises3.default.writeFile(options.configPath, next);
|
|
1839
2482
|
} catch (error) {
|
|
1840
2483
|
console.warn(
|
|
1841
2484
|
`[vite-plugin-vue-mcp-next] Failed to update Codex MCP config at ${options.configPath}: ${formatError(error)}`
|
|
@@ -1907,7 +2550,7 @@ function renameServerTableHeader(line, fromServerName, toServerName) {
|
|
|
1907
2550
|
}
|
|
1908
2551
|
async function readOptionalTextFile(filePath) {
|
|
1909
2552
|
try {
|
|
1910
|
-
return await
|
|
2553
|
+
return await import_promises3.default.readFile(filePath, "utf-8");
|
|
1911
2554
|
} catch (error) {
|
|
1912
2555
|
if (isNodeError(error) && error.code === "ENOENT") {
|
|
1913
2556
|
return "";
|
|
@@ -1933,16 +2576,16 @@ function isNodeError(error) {
|
|
|
1933
2576
|
}
|
|
1934
2577
|
|
|
1935
2578
|
// src/plugin/mcpClientConfig/jsonConfig.ts
|
|
1936
|
-
var
|
|
1937
|
-
var
|
|
2579
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
2580
|
+
var import_node_path6 = __toESM(require("path"), 1);
|
|
1938
2581
|
async function updateJsonMcpClientConfig(options) {
|
|
1939
2582
|
try {
|
|
1940
2583
|
const config = await readJsonConfig(options.configPath);
|
|
1941
|
-
if (!
|
|
2584
|
+
if (!isPlainRecord3(config)) {
|
|
1942
2585
|
warnConfigFailure(options, "config root must be a JSON object");
|
|
1943
2586
|
return;
|
|
1944
2587
|
}
|
|
1945
|
-
const mcpServers =
|
|
2588
|
+
const mcpServers = isPlainRecord3(config.mcpServers) ? config.mcpServers : {};
|
|
1946
2589
|
if (Object.hasOwn(mcpServers, options.serverName)) {
|
|
1947
2590
|
return;
|
|
1948
2591
|
}
|
|
@@ -1974,8 +2617,8 @@ function renameLegacyServer(mcpServers, options) {
|
|
|
1974
2617
|
);
|
|
1975
2618
|
}
|
|
1976
2619
|
async function writeJsonConfig(configPath, config) {
|
|
1977
|
-
await
|
|
1978
|
-
await
|
|
2620
|
+
await import_promises4.default.mkdir(import_node_path6.default.dirname(configPath), { recursive: true });
|
|
2621
|
+
await import_promises4.default.writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
1979
2622
|
`);
|
|
1980
2623
|
}
|
|
1981
2624
|
async function readJsonConfig(configPath) {
|
|
@@ -1987,7 +2630,7 @@ async function readJsonConfig(configPath) {
|
|
|
1987
2630
|
}
|
|
1988
2631
|
async function readOptionalTextFile2(filePath) {
|
|
1989
2632
|
try {
|
|
1990
|
-
return await
|
|
2633
|
+
return await import_promises4.default.readFile(filePath, "utf-8");
|
|
1991
2634
|
} catch (error) {
|
|
1992
2635
|
if (isNodeError2(error) && error.code === "ENOENT") {
|
|
1993
2636
|
return "{}";
|
|
@@ -1995,7 +2638,7 @@ async function readOptionalTextFile2(filePath) {
|
|
|
1995
2638
|
throw error;
|
|
1996
2639
|
}
|
|
1997
2640
|
}
|
|
1998
|
-
function
|
|
2641
|
+
function isPlainRecord3(value) {
|
|
1999
2642
|
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
2000
2643
|
}
|
|
2001
2644
|
function warnConfigFailure(options, reason) {
|
|
@@ -2019,12 +2662,12 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
|
|
|
2019
2662
|
root,
|
|
2020
2663
|
clientName: "cursor",
|
|
2021
2664
|
enabled: options.cursor,
|
|
2022
|
-
entryPath:
|
|
2665
|
+
entryPath: import_node_path7.default.join(root, ".cursor"),
|
|
2023
2666
|
entryKind: "directory",
|
|
2024
2667
|
userOptions,
|
|
2025
2668
|
createJob: () => updateJsonMcpClientConfig({
|
|
2026
2669
|
clientName: "Cursor",
|
|
2027
|
-
configPath:
|
|
2670
|
+
configPath: import_node_path7.default.join(root, ".cursor", "mcp.json"),
|
|
2028
2671
|
mcpUrl: sseUrl,
|
|
2029
2672
|
serverName,
|
|
2030
2673
|
legacyServerNames
|
|
@@ -2034,11 +2677,11 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
|
|
|
2034
2677
|
root,
|
|
2035
2678
|
clientName: "codex",
|
|
2036
2679
|
enabled: options.codex,
|
|
2037
|
-
entryPath:
|
|
2680
|
+
entryPath: import_node_path7.default.join(root, ".codex"),
|
|
2038
2681
|
entryKind: "directory",
|
|
2039
2682
|
userOptions,
|
|
2040
2683
|
createJob: () => updateCodexMcpClientConfig({
|
|
2041
|
-
configPath:
|
|
2684
|
+
configPath: import_node_path7.default.join(root, ".codex", "config.toml"),
|
|
2042
2685
|
mcpUrl: streamableHttpUrl,
|
|
2043
2686
|
serverName,
|
|
2044
2687
|
legacyServerNames
|
|
@@ -2048,12 +2691,12 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
|
|
|
2048
2691
|
root,
|
|
2049
2692
|
clientName: "claudeCode",
|
|
2050
2693
|
enabled: options.claudeCode,
|
|
2051
|
-
entryPath:
|
|
2694
|
+
entryPath: import_node_path7.default.join(root, ".mcp.json"),
|
|
2052
2695
|
entryKind: "file",
|
|
2053
2696
|
userOptions,
|
|
2054
2697
|
createJob: () => updateJsonMcpClientConfig({
|
|
2055
2698
|
clientName: "Claude Code",
|
|
2056
|
-
configPath:
|
|
2699
|
+
configPath: import_node_path7.default.join(root, ".mcp.json"),
|
|
2057
2700
|
mcpUrl: sseUrl,
|
|
2058
2701
|
serverName,
|
|
2059
2702
|
legacyServerNames
|
|
@@ -2063,12 +2706,12 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options,
|
|
|
2063
2706
|
root,
|
|
2064
2707
|
clientName: "trae",
|
|
2065
2708
|
enabled: options.trae,
|
|
2066
|
-
entryPath:
|
|
2709
|
+
entryPath: import_node_path7.default.join(root, ".trae"),
|
|
2067
2710
|
entryKind: "directory",
|
|
2068
2711
|
userOptions,
|
|
2069
2712
|
createJob: () => updateJsonMcpClientConfig({
|
|
2070
2713
|
clientName: "Trae",
|
|
2071
|
-
configPath:
|
|
2714
|
+
configPath: import_node_path7.default.join(root, ".trae", "mcp.json"),
|
|
2072
2715
|
mcpUrl: sseUrl,
|
|
2073
2716
|
serverName,
|
|
2074
2717
|
legacyServerNames
|
|
@@ -2107,7 +2750,7 @@ function isClientExplicitlyConfigured(clientName, userOptions) {
|
|
|
2107
2750
|
}
|
|
2108
2751
|
async function hasExpectedEntry(entryPath, entryKind) {
|
|
2109
2752
|
try {
|
|
2110
|
-
const stat = await
|
|
2753
|
+
const stat = await import_promises5.default.stat(entryPath);
|
|
2111
2754
|
return entryKind === "directory" ? stat.isDirectory() : stat.isFile();
|
|
2112
2755
|
} catch (error) {
|
|
2113
2756
|
if (isNodeError3(error) && error.code === "ENOENT") {
|
|
@@ -2127,12 +2770,12 @@ function isNodeError3(error) {
|
|
|
2127
2770
|
}
|
|
2128
2771
|
|
|
2129
2772
|
// src/plugin/skillConfig/index.ts
|
|
2130
|
-
var
|
|
2131
|
-
var
|
|
2773
|
+
var import_promises7 = __toESM(require("fs/promises"), 1);
|
|
2774
|
+
var import_node_path9 = __toESM(require("path"), 1);
|
|
2132
2775
|
|
|
2133
2776
|
// src/plugin/skillConfig/writers.ts
|
|
2134
|
-
var
|
|
2135
|
-
var
|
|
2777
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
2778
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
2136
2779
|
var GENERATED_SKILL_CONFIG_MARKER = "<!-- Generated by vite-plugin-vue-mcp-next. Safe to edit, but automatic updates only apply while this marker remains. -->";
|
|
2137
2780
|
async function writeGeneratedTextFile(options) {
|
|
2138
2781
|
try {
|
|
@@ -2143,8 +2786,8 @@ async function writeGeneratedTextFile(options) {
|
|
|
2143
2786
|
);
|
|
2144
2787
|
return;
|
|
2145
2788
|
}
|
|
2146
|
-
await
|
|
2147
|
-
await
|
|
2789
|
+
await import_promises6.default.mkdir(import_node_path8.default.dirname(options.filePath), { recursive: true });
|
|
2790
|
+
await import_promises6.default.writeFile(options.filePath, options.content);
|
|
2148
2791
|
} catch (error) {
|
|
2149
2792
|
console.warn(
|
|
2150
2793
|
`[vite-plugin-vue-mcp-next] Failed to update ${options.targetName} at ${options.filePath}: ${formatError4(error)}`
|
|
@@ -2153,7 +2796,7 @@ async function writeGeneratedTextFile(options) {
|
|
|
2153
2796
|
}
|
|
2154
2797
|
async function readOptionalTextFile3(filePath) {
|
|
2155
2798
|
try {
|
|
2156
|
-
return await
|
|
2799
|
+
return await import_promises6.default.readFile(filePath, "utf-8");
|
|
2157
2800
|
} catch (error) {
|
|
2158
2801
|
if (isNodeError4(error) && error.code === "ENOENT") {
|
|
2159
2802
|
return "";
|
|
@@ -2170,7 +2813,7 @@ function isNodeError4(error) {
|
|
|
2170
2813
|
|
|
2171
2814
|
// src/plugin/skillConfig/index.ts
|
|
2172
2815
|
var PACKAGE_NAME = "@xiaou66/vite-plugin-vue-mcp-next";
|
|
2173
|
-
var PACKAGED_SKILL_PATH =
|
|
2816
|
+
var PACKAGED_SKILL_PATH = import_node_path9.default.join("skills", "vite-mcp-next", "SKILL.md");
|
|
2174
2817
|
async function updateSkillConfigs(root, options) {
|
|
2175
2818
|
if (!options.autoConfig) {
|
|
2176
2819
|
return;
|
|
@@ -2186,13 +2829,13 @@ async function updateSkillConfigs(root, options) {
|
|
|
2186
2829
|
function createSkillConfigDescriptors(root) {
|
|
2187
2830
|
return [
|
|
2188
2831
|
{
|
|
2189
|
-
entryPath:
|
|
2190
|
-
filePath:
|
|
2832
|
+
entryPath: import_node_path9.default.join(root, ".codex"),
|
|
2833
|
+
filePath: import_node_path9.default.join(root, ".codex", "skills", "vite-mcp-next", "SKILL.md"),
|
|
2191
2834
|
targetName: "Codex skill"
|
|
2192
2835
|
},
|
|
2193
2836
|
{
|
|
2194
|
-
entryPath:
|
|
2195
|
-
filePath:
|
|
2837
|
+
entryPath: import_node_path9.default.join(root, ".claude"),
|
|
2838
|
+
filePath: import_node_path9.default.join(
|
|
2196
2839
|
root,
|
|
2197
2840
|
".claude",
|
|
2198
2841
|
"skills",
|
|
@@ -2202,8 +2845,8 @@ function createSkillConfigDescriptors(root) {
|
|
|
2202
2845
|
targetName: "Claude Code skill"
|
|
2203
2846
|
},
|
|
2204
2847
|
{
|
|
2205
|
-
entryPath:
|
|
2206
|
-
filePath:
|
|
2848
|
+
entryPath: import_node_path9.default.join(root, ".cursor"),
|
|
2849
|
+
filePath: import_node_path9.default.join(root, ".cursor", "rules", "vite-mcp-next.mdc"),
|
|
2207
2850
|
targetName: "Cursor rule"
|
|
2208
2851
|
}
|
|
2209
2852
|
];
|
|
@@ -2227,7 +2870,7 @@ async function readPackagedSkillContent(root) {
|
|
|
2227
2870
|
const candidates = getPackagedSkillCandidates(root);
|
|
2228
2871
|
for (const candidate of candidates) {
|
|
2229
2872
|
try {
|
|
2230
|
-
return await
|
|
2873
|
+
return await import_promises7.default.readFile(candidate, "utf-8");
|
|
2231
2874
|
} catch (error) {
|
|
2232
2875
|
if (isNodeError5(error) && error.code === "ENOENT") {
|
|
2233
2876
|
continue;
|
|
@@ -2251,14 +2894,14 @@ async function safelyReadPackagedSkillContent(root) {
|
|
|
2251
2894
|
}
|
|
2252
2895
|
function getPackagedSkillCandidates(root) {
|
|
2253
2896
|
return [
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2897
|
+
import_node_path9.default.resolve(root, "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
|
|
2898
|
+
import_node_path9.default.resolve(process.cwd(), "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
|
|
2899
|
+
import_node_path9.default.resolve(process.cwd(), PACKAGED_SKILL_PATH)
|
|
2257
2900
|
];
|
|
2258
2901
|
}
|
|
2259
2902
|
async function hasDirectoryEntry(entryPath) {
|
|
2260
2903
|
try {
|
|
2261
|
-
const stat = await
|
|
2904
|
+
const stat = await import_promises7.default.stat(entryPath);
|
|
2262
2905
|
return stat.isDirectory();
|
|
2263
2906
|
} catch (error) {
|
|
2264
2907
|
if (isNodeError5(error) && error.code === "ENOENT") {
|
|
@@ -2311,29 +2954,58 @@ function vueMcpNext(userOptions = {}) {
|
|
|
2311
2954
|
() => createMcpServer(ctx, server),
|
|
2312
2955
|
server
|
|
2313
2956
|
);
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
(
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2957
|
+
const lastSeenAt = /* @__PURE__ */ new Map();
|
|
2958
|
+
const heartbeatTimer = setInterval(() => {
|
|
2959
|
+
const now = Date.now();
|
|
2960
|
+
for (const [pageId, seenAt] of lastSeenAt) {
|
|
2961
|
+
const target = ctx.pages.get(pageId);
|
|
2962
|
+
if (!target || target.source !== "runtime" || !target.connected) {
|
|
2963
|
+
lastSeenAt.delete(pageId);
|
|
2964
|
+
continue;
|
|
2965
|
+
}
|
|
2966
|
+
if (now - seenAt >= DEFAULT_RUNTIME_PAGE_HEARTBEAT_TIMEOUT_MS) {
|
|
2967
|
+
ctx.pages.disconnect(pageId, now);
|
|
2968
|
+
lastSeenAt.delete(pageId);
|
|
2321
2969
|
}
|
|
2322
2970
|
}
|
|
2323
|
-
);
|
|
2324
|
-
server.ws.on(
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2971
|
+
}, DEFAULT_RUNTIME_PAGE_HEARTBEAT_SCAN_INTERVAL_MS);
|
|
2972
|
+
server.ws.on(RUNTIME_PAGE_CONNECTED_EVENT, (payload) => {
|
|
2973
|
+
if (isRuntimePageTarget(payload)) {
|
|
2974
|
+
ctx.pages.upsert(payload);
|
|
2975
|
+
lastSeenAt.set(payload.pageId, Date.now());
|
|
2976
|
+
void ctx.hooks.callHook(RUNTIME_PAGE_RECONNECTED_EVENT, payload);
|
|
2977
|
+
void cdpLifecycle.connectPage(payload);
|
|
2978
|
+
}
|
|
2979
|
+
});
|
|
2980
|
+
server.ws.on(RUNTIME_PAGE_HEARTBEAT_EVENT, (payload) => {
|
|
2981
|
+
if (isRuntimeHeartbeatTarget(payload)) {
|
|
2982
|
+
const target = ctx.pages.get(payload.pageId);
|
|
2983
|
+
if (target?.source === "runtime" && target.connected) {
|
|
2984
|
+
lastSeenAt.set(payload.pageId, payload.timestamp);
|
|
2329
2985
|
}
|
|
2330
2986
|
}
|
|
2331
|
-
);
|
|
2987
|
+
});
|
|
2988
|
+
server.ws.on(RUNTIME_PAGE_DISCONNECTED_EVENT, (payload) => {
|
|
2989
|
+
if (isRuntimeDisconnectTarget(payload)) {
|
|
2990
|
+
ctx.pages.disconnect(payload.pageId);
|
|
2991
|
+
lastSeenAt.delete(payload.pageId);
|
|
2992
|
+
}
|
|
2993
|
+
});
|
|
2994
|
+
server.ws.on("vite-plugin-vue-mcp-next:console-record", (payload) => {
|
|
2995
|
+
if (isConsoleRecord(payload)) {
|
|
2996
|
+
ctx.consoleRecords.push(payload);
|
|
2997
|
+
}
|
|
2998
|
+
});
|
|
2999
|
+
server.ws.on("vite-plugin-vue-mcp-next:network-record", (payload) => {
|
|
3000
|
+
if (isNetworkRecord(payload)) {
|
|
3001
|
+
ctx.networkRecords.push(payload);
|
|
3002
|
+
}
|
|
3003
|
+
});
|
|
2332
3004
|
server.ws.on(
|
|
2333
|
-
"vite-plugin-vue-mcp-next:
|
|
3005
|
+
"vite-plugin-vue-mcp-next:performance-record",
|
|
2334
3006
|
(payload) => {
|
|
2335
|
-
if (
|
|
2336
|
-
ctx
|
|
3007
|
+
if (isPerformanceReport2(payload)) {
|
|
3008
|
+
appendPerformanceReport(ctx, payload);
|
|
2337
3009
|
}
|
|
2338
3010
|
}
|
|
2339
3011
|
);
|
|
@@ -2358,6 +3030,7 @@ function vueMcpNext(userOptions = {}) {
|
|
|
2358
3030
|
}, 300);
|
|
2359
3031
|
}
|
|
2360
3032
|
server.httpServer?.once("close", () => {
|
|
3033
|
+
clearInterval(heartbeatTimer);
|
|
2361
3034
|
void cdpLifecycle.closeAll();
|
|
2362
3035
|
});
|
|
2363
3036
|
},
|
|
@@ -2382,6 +3055,20 @@ function isRuntimePageTarget(payload) {
|
|
|
2382
3055
|
const target = payload;
|
|
2383
3056
|
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");
|
|
2384
3057
|
}
|
|
3058
|
+
function isRuntimeHeartbeatTarget(payload) {
|
|
3059
|
+
if (!payload || typeof payload !== "object") {
|
|
3060
|
+
return false;
|
|
3061
|
+
}
|
|
3062
|
+
const target = payload;
|
|
3063
|
+
return typeof target.pageId === "string" && typeof target.timestamp === "number";
|
|
3064
|
+
}
|
|
3065
|
+
function isRuntimeDisconnectTarget(payload) {
|
|
3066
|
+
if (!payload || typeof payload !== "object") {
|
|
3067
|
+
return false;
|
|
3068
|
+
}
|
|
3069
|
+
const target = payload;
|
|
3070
|
+
return typeof target.pageId === "string";
|
|
3071
|
+
}
|
|
2385
3072
|
function isConsoleRecord(payload) {
|
|
2386
3073
|
if (!payload || typeof payload !== "object") {
|
|
2387
3074
|
return false;
|
|
@@ -2399,6 +3086,13 @@ function isNetworkRecord(payload) {
|
|
|
2399
3086
|
const record = payload;
|
|
2400
3087
|
return typeof record.id === "string" && typeof record.pageId === "string" && record.source === "hook" && typeof record.url === "string" && typeof record.method === "string" && typeof record.startedAt === "number";
|
|
2401
3088
|
}
|
|
3089
|
+
function isPerformanceReport2(payload) {
|
|
3090
|
+
if (!payload || typeof payload !== "object") {
|
|
3091
|
+
return false;
|
|
3092
|
+
}
|
|
3093
|
+
const report = payload;
|
|
3094
|
+
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);
|
|
3095
|
+
}
|
|
2402
3096
|
// Annotate the CommonJS export names for ESM import in node:
|
|
2403
3097
|
0 && (module.exports = {
|
|
2404
3098
|
vueMcpNext
|