@xiaou66/vite-plugin-vue-mcp-next 1.1.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +61 -0
- package/dist/index.cjs +791 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +5 -849
- package/dist/index.d.ts +5 -849
- package/dist/index.js +763 -54
- package/dist/index.js.map +1 -1
- package/dist/runtime/client.cjs +934 -81
- package/dist/runtime/client.cjs.map +1 -1
- package/dist/runtime/client.d.cts +5 -1
- package/dist/runtime/client.d.ts +5 -1
- package/dist/runtime/client.js +934 -81
- package/dist/runtime/client.js.map +1 -1
- package/dist/types-BKXdHkwk.d.cts +1019 -0
- package/dist/types-BKXdHkwk.d.ts +1019 -0
- package/package.json +3 -1
- package/skills/vite-mcp-next/SKILL.md +14 -6
package/dist/index.js
CHANGED
|
@@ -35,6 +35,11 @@ var MCP_TOOL_NAMES = {
|
|
|
35
35
|
getNetworkRequests: "get_network_requests",
|
|
36
36
|
getNetworkRequestDetail: "get_network_request_detail",
|
|
37
37
|
clearNetworkRequests: "clear_network_requests",
|
|
38
|
+
listStorage: "list_storage",
|
|
39
|
+
getStorageItem: "get_storage_item",
|
|
40
|
+
setStorageItem: "set_storage_item",
|
|
41
|
+
deleteStorageItem: "delete_storage_item",
|
|
42
|
+
clearStorage: "clear_storage",
|
|
38
43
|
recordPerformance: "record_performance",
|
|
39
44
|
startPerformanceRecording: "start_performance_recording",
|
|
40
45
|
stopPerformanceRecording: "stop_performance_recording",
|
|
@@ -46,7 +51,8 @@ var MCP_TOOL_NAMES = {
|
|
|
46
51
|
highlightComponent: "highlight_component",
|
|
47
52
|
getRouterInfo: "get_router_info",
|
|
48
53
|
getPiniaTree: "get_pinia_tree",
|
|
49
|
-
getPiniaState: "get_pinia_state"
|
|
54
|
+
getPiniaState: "get_pinia_state",
|
|
55
|
+
getElementContext: "get_element_context"
|
|
50
56
|
};
|
|
51
57
|
var VIRTUAL_RUNTIME_ID = "virtual:vite-plugin-vue-mcp-next/runtime";
|
|
52
58
|
var RESOLVED_VIRTUAL_RUNTIME_ID = `\0${VIRTUAL_RUNTIME_ID}`;
|
|
@@ -62,6 +68,7 @@ var RUNTIME_PAGE_DISCONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-disconnecte
|
|
|
62
68
|
var RUNTIME_PAGE_HEARTBEAT_EVENT = "vite-plugin-vue-mcp-next:heartbeat";
|
|
63
69
|
var DEFAULT_RUNTIME_PAGE_HEARTBEAT_TIMEOUT_MS = 45e3;
|
|
64
70
|
var DEFAULT_RUNTIME_PAGE_HEARTBEAT_SCAN_INTERVAL_MS = 45e3;
|
|
71
|
+
var DEFAULT_ELEMENT_PICKER_TOAST_DURATION_MS = 2200;
|
|
65
72
|
var DEFAULT_OPTIONS = {
|
|
66
73
|
mcpPath: DEFAULT_MCP_PATH,
|
|
67
74
|
host: "localhost",
|
|
@@ -80,6 +87,16 @@ var DEFAULT_OPTIONS = {
|
|
|
80
87
|
skill: {
|
|
81
88
|
autoConfig: true
|
|
82
89
|
},
|
|
90
|
+
elementPicker: {
|
|
91
|
+
enabled: true,
|
|
92
|
+
shortcut: {
|
|
93
|
+
altKey: true,
|
|
94
|
+
shiftKey: true,
|
|
95
|
+
metaKey: false,
|
|
96
|
+
ctrlKey: false
|
|
97
|
+
},
|
|
98
|
+
toastDurationMs: DEFAULT_ELEMENT_PICKER_TOAST_DURATION_MS
|
|
99
|
+
},
|
|
83
100
|
runtime: {
|
|
84
101
|
mode: "auto",
|
|
85
102
|
evaluate: {
|
|
@@ -154,6 +171,14 @@ function mergeOptions(options = {}) {
|
|
|
154
171
|
...DEFAULT_OPTIONS.skill,
|
|
155
172
|
...options.skill
|
|
156
173
|
},
|
|
174
|
+
elementPicker: {
|
|
175
|
+
...DEFAULT_OPTIONS.elementPicker,
|
|
176
|
+
...options.elementPicker,
|
|
177
|
+
shortcut: {
|
|
178
|
+
...DEFAULT_OPTIONS.elementPicker.shortcut,
|
|
179
|
+
...options.elementPicker?.shortcut
|
|
180
|
+
}
|
|
181
|
+
},
|
|
157
182
|
runtime: {
|
|
158
183
|
...DEFAULT_OPTIONS.runtime,
|
|
159
184
|
...options.runtime,
|
|
@@ -574,9 +599,164 @@ function registerDomTools(server, ctx) {
|
|
|
574
599
|
);
|
|
575
600
|
}
|
|
576
601
|
|
|
577
|
-
// src/mcp/tools/
|
|
602
|
+
// src/mcp/tools/elementContext.ts
|
|
578
603
|
import { z as z3 } from "zod";
|
|
579
604
|
|
|
605
|
+
// src/shared/elementId.ts
|
|
606
|
+
var PROJECT_SOURCE_ID_PATTERN = /^(.+\.(?:vue|tsx|jsx|ts|js)):(\d+):(\d+)$/;
|
|
607
|
+
var RUNTIME_ID_PATTERN = /^runtime:([A-Za-z0-9_-]+)$/;
|
|
608
|
+
var PACKAGE_ID_PREFIX = "pkg:";
|
|
609
|
+
function parseElementId(elementId) {
|
|
610
|
+
const sourceMatch = PROJECT_SOURCE_ID_PATTERN.exec(elementId);
|
|
611
|
+
if (sourceMatch) {
|
|
612
|
+
return {
|
|
613
|
+
kind: "project-source",
|
|
614
|
+
elementId,
|
|
615
|
+
file: sourceMatch[1],
|
|
616
|
+
line: Number(sourceMatch[2]),
|
|
617
|
+
column: Number(sourceMatch[3])
|
|
618
|
+
};
|
|
619
|
+
}
|
|
620
|
+
if (elementId.startsWith(PACKAGE_ID_PREFIX)) {
|
|
621
|
+
return parsePackageElementId(elementId);
|
|
622
|
+
}
|
|
623
|
+
const runtimeMatch = RUNTIME_ID_PATTERN.exec(elementId);
|
|
624
|
+
if (runtimeMatch) {
|
|
625
|
+
return {
|
|
626
|
+
kind: "runtime",
|
|
627
|
+
elementId,
|
|
628
|
+
runtimeId: runtimeMatch[1]
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
kind: "invalid",
|
|
633
|
+
elementId,
|
|
634
|
+
reason: "unsupported elementId format"
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
function parsePackageElementId(elementId) {
|
|
638
|
+
const value = elementId.slice(PACKAGE_ID_PREFIX.length);
|
|
639
|
+
const parts = value.split("/").filter(Boolean);
|
|
640
|
+
if (parts.length < 2) {
|
|
641
|
+
return {
|
|
642
|
+
kind: "invalid",
|
|
643
|
+
elementId,
|
|
644
|
+
reason: "package elementId must include packageName and entryFile"
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
const scoped = parts[0]?.startsWith("@");
|
|
648
|
+
const packageName = scoped ? parts.slice(0, 2).join("/") : parts[0];
|
|
649
|
+
const entryFile = parts.slice(scoped ? 2 : 1).join("/");
|
|
650
|
+
if (!entryFile) {
|
|
651
|
+
return {
|
|
652
|
+
kind: "invalid",
|
|
653
|
+
elementId,
|
|
654
|
+
reason: "package elementId must include entryFile"
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
return {
|
|
658
|
+
kind: "package",
|
|
659
|
+
elementId,
|
|
660
|
+
packageName,
|
|
661
|
+
entryFile
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// src/mcp/tools/elementContext.ts
|
|
666
|
+
function registerElementContextTools(server, ctx) {
|
|
667
|
+
server.registerTool(
|
|
668
|
+
MCP_TOOL_NAMES.getElementContext,
|
|
669
|
+
{
|
|
670
|
+
description: "Get editable source, Vue component, and DOM context for a copied elementId.",
|
|
671
|
+
inputSchema: {
|
|
672
|
+
elementId: z3.string(),
|
|
673
|
+
pageId: z3.string().optional()
|
|
674
|
+
}
|
|
675
|
+
},
|
|
676
|
+
async (input) => {
|
|
677
|
+
const runtimeResult = await tryRuntimeElementContext(
|
|
678
|
+
ctx,
|
|
679
|
+
input.elementId,
|
|
680
|
+
input.pageId
|
|
681
|
+
);
|
|
682
|
+
if (runtimeResult) {
|
|
683
|
+
return createToolResponse(runtimeResult);
|
|
684
|
+
}
|
|
685
|
+
return createToolResponse(createStaticElementContext(input.elementId));
|
|
686
|
+
}
|
|
687
|
+
);
|
|
688
|
+
}
|
|
689
|
+
async function tryRuntimeElementContext(ctx, elementId, pageId) {
|
|
690
|
+
if (!ctx.rpcServer) {
|
|
691
|
+
return parseElementId(elementId).kind === "runtime" ? createRuntimeUnavailableContext(elementId) : void 0;
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
resolvePageTarget(ctx, pageId);
|
|
695
|
+
} catch (error) {
|
|
696
|
+
const parsed = parseElementId(elementId);
|
|
697
|
+
if (parsed.kind === "project-source" || parsed.kind === "package") {
|
|
698
|
+
return void 0;
|
|
699
|
+
}
|
|
700
|
+
return {
|
|
701
|
+
ok: false,
|
|
702
|
+
elementId,
|
|
703
|
+
error: error instanceof Error ? error.message : String(error),
|
|
704
|
+
limitations: ["call list_pages and pass pageId when multiple pages exist"]
|
|
705
|
+
};
|
|
706
|
+
}
|
|
707
|
+
return await requestRuntimeData(ctx, (event) => {
|
|
708
|
+
void ctx.rpcServer?.getElementContext({ event, elementId });
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
function createStaticElementContext(elementId) {
|
|
712
|
+
const parsed = parseElementId(elementId);
|
|
713
|
+
if (parsed.kind === "project-source") {
|
|
714
|
+
return {
|
|
715
|
+
ok: true,
|
|
716
|
+
elementId,
|
|
717
|
+
editable: true,
|
|
718
|
+
codeLocation: {
|
|
719
|
+
file: parsed.file,
|
|
720
|
+
line: parsed.line,
|
|
721
|
+
column: parsed.column
|
|
722
|
+
},
|
|
723
|
+
limitations: ["runtime unavailable, DOM and component context omitted"]
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
if (parsed.kind === "package") {
|
|
727
|
+
return {
|
|
728
|
+
ok: true,
|
|
729
|
+
elementId,
|
|
730
|
+
editable: false,
|
|
731
|
+
packageLocation: {
|
|
732
|
+
packageName: parsed.packageName,
|
|
733
|
+
entryFile: parsed.entryFile
|
|
734
|
+
},
|
|
735
|
+
limitations: ["third-party package source is not editable from this project"]
|
|
736
|
+
};
|
|
737
|
+
}
|
|
738
|
+
if (parsed.kind === "runtime") {
|
|
739
|
+
return createRuntimeUnavailableContext(elementId);
|
|
740
|
+
}
|
|
741
|
+
return {
|
|
742
|
+
ok: false,
|
|
743
|
+
elementId,
|
|
744
|
+
error: parsed.reason,
|
|
745
|
+
limitations: ["please provide a copied elementId from the element picker"]
|
|
746
|
+
};
|
|
747
|
+
}
|
|
748
|
+
function createRuntimeUnavailableContext(elementId) {
|
|
749
|
+
return {
|
|
750
|
+
ok: false,
|
|
751
|
+
elementId,
|
|
752
|
+
error: "runtime bridge is not connected",
|
|
753
|
+
limitations: ["runtime ids are only valid while the page is connected"]
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
// src/mcp/tools/evaluate.ts
|
|
758
|
+
import { z as z4 } from "zod";
|
|
759
|
+
|
|
580
760
|
// src/cdp/cdpEvaluate.ts
|
|
581
761
|
async function cdpEvaluate(options) {
|
|
582
762
|
const result = await options.client.Runtime.evaluate({
|
|
@@ -597,9 +777,9 @@ function registerEvaluateTools(server, ctx) {
|
|
|
597
777
|
{
|
|
598
778
|
description: "Evaluate a script in the selected page when explicitly enabled.",
|
|
599
779
|
inputSchema: {
|
|
600
|
-
pageId:
|
|
601
|
-
expression:
|
|
602
|
-
awaitPromise:
|
|
780
|
+
pageId: z4.string().optional(),
|
|
781
|
+
expression: z4.string(),
|
|
782
|
+
awaitPromise: z4.boolean().optional()
|
|
603
783
|
}
|
|
604
784
|
},
|
|
605
785
|
async (input) => {
|
|
@@ -648,18 +828,18 @@ function assertEvaluateEnabled(options) {
|
|
|
648
828
|
}
|
|
649
829
|
|
|
650
830
|
// src/mcp/tools/network.ts
|
|
651
|
-
import { z as
|
|
831
|
+
import { z as z5 } from "zod";
|
|
652
832
|
function registerNetworkTools(server, ctx) {
|
|
653
833
|
server.registerTool(
|
|
654
834
|
MCP_TOOL_NAMES.getNetworkRequests,
|
|
655
835
|
{
|
|
656
836
|
description: "Get captured network request summaries.",
|
|
657
837
|
inputSchema: {
|
|
658
|
-
pageId:
|
|
659
|
-
urlContains:
|
|
660
|
-
method:
|
|
661
|
-
status:
|
|
662
|
-
limit:
|
|
838
|
+
pageId: z5.string().optional(),
|
|
839
|
+
urlContains: z5.string().optional(),
|
|
840
|
+
method: z5.string().optional(),
|
|
841
|
+
status: z5.number().optional(),
|
|
842
|
+
limit: z5.number().optional()
|
|
663
843
|
}
|
|
664
844
|
},
|
|
665
845
|
(input) => {
|
|
@@ -682,7 +862,7 @@ function registerNetworkTools(server, ctx) {
|
|
|
682
862
|
MCP_TOOL_NAMES.getNetworkRequestDetail,
|
|
683
863
|
{
|
|
684
864
|
description: "Get captured network request detail by id.",
|
|
685
|
-
inputSchema: { id:
|
|
865
|
+
inputSchema: { id: z5.string() }
|
|
686
866
|
},
|
|
687
867
|
(input) => {
|
|
688
868
|
if (ctx.options.network.mode === "off") {
|
|
@@ -699,7 +879,7 @@ function registerNetworkTools(server, ctx) {
|
|
|
699
879
|
{
|
|
700
880
|
description: "Clear cached network requests.",
|
|
701
881
|
inputSchema: {
|
|
702
|
-
pageId:
|
|
882
|
+
pageId: z5.string().optional()
|
|
703
883
|
}
|
|
704
884
|
},
|
|
705
885
|
() => {
|
|
@@ -710,7 +890,7 @@ function registerNetworkTools(server, ctx) {
|
|
|
710
890
|
}
|
|
711
891
|
|
|
712
892
|
// src/mcp/tools/performance.ts
|
|
713
|
-
import { z as
|
|
893
|
+
import { z as z6 } from "zod";
|
|
714
894
|
|
|
715
895
|
// src/performance/summary.ts
|
|
716
896
|
function buildPerformanceSummary(input) {
|
|
@@ -992,10 +1172,10 @@ function registerPerformanceTools(server, ctx) {
|
|
|
992
1172
|
{
|
|
993
1173
|
description: "Record a performance sample for the selected page.",
|
|
994
1174
|
inputSchema: {
|
|
995
|
-
pageId:
|
|
996
|
-
durationMs:
|
|
997
|
-
includeMemory:
|
|
998
|
-
includeStacks:
|
|
1175
|
+
pageId: z6.string().optional(),
|
|
1176
|
+
durationMs: z6.number().optional(),
|
|
1177
|
+
includeMemory: z6.boolean().optional(),
|
|
1178
|
+
includeStacks: z6.boolean().optional()
|
|
999
1179
|
}
|
|
1000
1180
|
},
|
|
1001
1181
|
async (input) => handleRecordPerformance(ctx, input)
|
|
@@ -1005,9 +1185,9 @@ function registerPerformanceTools(server, ctx) {
|
|
|
1005
1185
|
{
|
|
1006
1186
|
description: "Start a performance recording session.",
|
|
1007
1187
|
inputSchema: {
|
|
1008
|
-
pageId:
|
|
1009
|
-
includeMemory:
|
|
1010
|
-
includeStacks:
|
|
1188
|
+
pageId: z6.string().optional(),
|
|
1189
|
+
includeMemory: z6.boolean().optional(),
|
|
1190
|
+
includeStacks: z6.boolean().optional()
|
|
1011
1191
|
}
|
|
1012
1192
|
},
|
|
1013
1193
|
async (input) => handleStartPerformanceRecording(ctx, input)
|
|
@@ -1017,7 +1197,7 @@ function registerPerformanceTools(server, ctx) {
|
|
|
1017
1197
|
{
|
|
1018
1198
|
description: "Stop a performance recording session.",
|
|
1019
1199
|
inputSchema: {
|
|
1020
|
-
recordingId:
|
|
1200
|
+
recordingId: z6.string()
|
|
1021
1201
|
}
|
|
1022
1202
|
},
|
|
1023
1203
|
async (input) => handleStopPerformanceRecording(ctx, input)
|
|
@@ -1027,9 +1207,9 @@ function registerPerformanceTools(server, ctx) {
|
|
|
1027
1207
|
{
|
|
1028
1208
|
description: "Get cached performance reports and active sessions.",
|
|
1029
1209
|
inputSchema: {
|
|
1030
|
-
pageId:
|
|
1031
|
-
recordingId:
|
|
1032
|
-
limit:
|
|
1210
|
+
pageId: z6.string().optional(),
|
|
1211
|
+
recordingId: z6.string().optional(),
|
|
1212
|
+
limit: z6.number().optional()
|
|
1033
1213
|
}
|
|
1034
1214
|
},
|
|
1035
1215
|
(input) => handleGetPerformanceReport(ctx, input)
|
|
@@ -1039,7 +1219,7 @@ function registerPerformanceTools(server, ctx) {
|
|
|
1039
1219
|
{
|
|
1040
1220
|
description: "Take a heap snapshot with CDP.",
|
|
1041
1221
|
inputSchema: {
|
|
1042
|
-
pageId:
|
|
1222
|
+
pageId: z6.string().optional()
|
|
1043
1223
|
}
|
|
1044
1224
|
},
|
|
1045
1225
|
async (input) => handleTakeHeapSnapshot(ctx, input)
|
|
@@ -1296,7 +1476,7 @@ function toStructuredRecord(value) {
|
|
|
1296
1476
|
}
|
|
1297
1477
|
|
|
1298
1478
|
// src/mcp/tools/pages.ts
|
|
1299
|
-
import { z as
|
|
1479
|
+
import { z as z7 } from "zod";
|
|
1300
1480
|
|
|
1301
1481
|
// src/plugin/entryDiscovery.ts
|
|
1302
1482
|
import fs from "fs";
|
|
@@ -1321,10 +1501,10 @@ function walkHtmlEntries(root, dir, entries) {
|
|
|
1321
1501
|
if (!item.isFile() || !item.name.endsWith(".html")) {
|
|
1322
1502
|
continue;
|
|
1323
1503
|
}
|
|
1324
|
-
const
|
|
1504
|
+
const relative3 = normalizePath(path.relative(root, fullPath));
|
|
1325
1505
|
entries.push({
|
|
1326
|
-
file:
|
|
1327
|
-
pathname:
|
|
1506
|
+
file: relative3,
|
|
1507
|
+
pathname: relative3 === "index.html" ? "/" : `/${relative3}`
|
|
1328
1508
|
});
|
|
1329
1509
|
}
|
|
1330
1510
|
}
|
|
@@ -1336,7 +1516,7 @@ function registerPageTools(server, ctx, vite) {
|
|
|
1336
1516
|
{
|
|
1337
1517
|
description: "List Vite page entries and connected runtime/CDP targets.",
|
|
1338
1518
|
inputSchema: {
|
|
1339
|
-
includeDisconnected:
|
|
1519
|
+
includeDisconnected: z7.boolean().optional()
|
|
1340
1520
|
}
|
|
1341
1521
|
},
|
|
1342
1522
|
async (input) => {
|
|
@@ -1359,8 +1539,8 @@ function registerPageTools(server, ctx, vite) {
|
|
|
1359
1539
|
{
|
|
1360
1540
|
description: "Reload the selected page. CDP uses ignoreCache; Runtime Hook falls back to normal reload.",
|
|
1361
1541
|
inputSchema: {
|
|
1362
|
-
pageId:
|
|
1363
|
-
ignoreCache:
|
|
1542
|
+
pageId: z7.string().optional(),
|
|
1543
|
+
ignoreCache: z7.boolean().optional()
|
|
1364
1544
|
}
|
|
1365
1545
|
},
|
|
1366
1546
|
async (input) => {
|
|
@@ -1511,8 +1691,430 @@ function getPathname(url) {
|
|
|
1511
1691
|
}
|
|
1512
1692
|
}
|
|
1513
1693
|
|
|
1694
|
+
// src/mcp/tools/storage.ts
|
|
1695
|
+
import { z as z8 } from "zod";
|
|
1696
|
+
|
|
1697
|
+
// src/cdp/cdpStorage.ts
|
|
1698
|
+
function createCdpStorageAdapter(client) {
|
|
1699
|
+
return {
|
|
1700
|
+
async manageStorage(request) {
|
|
1701
|
+
try {
|
|
1702
|
+
return await manageCdpStorage(client, request);
|
|
1703
|
+
} catch (error) {
|
|
1704
|
+
return createCdpStorageError(
|
|
1705
|
+
request,
|
|
1706
|
+
error instanceof Error ? error.message : String(error)
|
|
1707
|
+
);
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
};
|
|
1711
|
+
}
|
|
1712
|
+
async function manageCdpStorage(client, request) {
|
|
1713
|
+
if (request.scope === "cookie") {
|
|
1714
|
+
return manageCdpCookies(client, request);
|
|
1715
|
+
}
|
|
1716
|
+
if (request.scope === "indexedDB") {
|
|
1717
|
+
return manageCdpIndexedDb(client, request);
|
|
1718
|
+
}
|
|
1719
|
+
return manageCdpDomStorage(client, request);
|
|
1720
|
+
}
|
|
1721
|
+
async function manageCdpCookies(client, request) {
|
|
1722
|
+
if (request.action === "list" || request.action === "get") {
|
|
1723
|
+
const result2 = await client.Storage.getCookies();
|
|
1724
|
+
const cookies2 = result2.cookies.filter(
|
|
1725
|
+
(cookie) => isCookieInOrigin(cookie, request.origin)
|
|
1726
|
+
);
|
|
1727
|
+
return createCdpStorageSuccess(request, {
|
|
1728
|
+
origin: request.origin,
|
|
1729
|
+
cookies: request.action === "get" && request.cookie?.name ? cookies2.filter((cookie) => cookie.name === request.cookie?.name) : cookies2
|
|
1730
|
+
});
|
|
1731
|
+
}
|
|
1732
|
+
if (request.action === "set") {
|
|
1733
|
+
if (!request.cookie?.name) {
|
|
1734
|
+
throw new Error("Cookie name is required for set operation");
|
|
1735
|
+
}
|
|
1736
|
+
await client.Storage.setCookies({
|
|
1737
|
+
cookies: [
|
|
1738
|
+
{
|
|
1739
|
+
name: request.cookie.name,
|
|
1740
|
+
value: request.cookie.value ?? request.value ?? "",
|
|
1741
|
+
url: request.cookie.url ?? request.origin,
|
|
1742
|
+
domain: request.cookie.domain,
|
|
1743
|
+
path: request.cookie.path,
|
|
1744
|
+
httpOnly: request.cookie.httpOnly,
|
|
1745
|
+
secure: request.cookie.secure,
|
|
1746
|
+
sameSite: normalizeCookieSameSite(request.cookie.sameSite),
|
|
1747
|
+
expires: request.cookie.expires
|
|
1748
|
+
}
|
|
1749
|
+
]
|
|
1750
|
+
});
|
|
1751
|
+
return createCdpStorageSuccess(request, { ok: true });
|
|
1752
|
+
}
|
|
1753
|
+
const result = await client.Storage.getCookies();
|
|
1754
|
+
const cookies = result.cookies.filter(
|
|
1755
|
+
(cookie) => isCookieInOrigin(cookie, request.origin)
|
|
1756
|
+
);
|
|
1757
|
+
const candidates = request.action === "delete" && request.cookie?.name ? cookies.filter((cookie) => cookie.name === request.cookie?.name) : cookies;
|
|
1758
|
+
const deletable = candidates.filter((cookie) => !cookie.httpOnly);
|
|
1759
|
+
const skippedHttpOnlyCount = candidates.length - deletable.length;
|
|
1760
|
+
for (const cookie of deletable) {
|
|
1761
|
+
await client.Network.deleteCookies({
|
|
1762
|
+
name: cookie.name,
|
|
1763
|
+
domain: cookie.domain,
|
|
1764
|
+
path: cookie.path
|
|
1765
|
+
});
|
|
1766
|
+
}
|
|
1767
|
+
return createCdpStorageSuccess(request, {
|
|
1768
|
+
deletedCount: deletable.length,
|
|
1769
|
+
skippedHttpOnlyCount
|
|
1770
|
+
});
|
|
1771
|
+
}
|
|
1772
|
+
async function manageCdpDomStorage(client, request) {
|
|
1773
|
+
const storageId = {
|
|
1774
|
+
securityOrigin: request.origin,
|
|
1775
|
+
isLocalStorage: request.scope === "localStorage"
|
|
1776
|
+
};
|
|
1777
|
+
if (request.action === "list") {
|
|
1778
|
+
const result2 = await client.DOMStorage.getDOMStorageItems({ storageId });
|
|
1779
|
+
return createCdpStorageSuccess(request, {
|
|
1780
|
+
origin: request.origin,
|
|
1781
|
+
scope: request.scope,
|
|
1782
|
+
entries: result2.entries.map(([key, value]) => ({ key, value }))
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
if (request.action === "get") {
|
|
1786
|
+
assertStorageKey(request);
|
|
1787
|
+
const result2 = await client.DOMStorage.getDOMStorageItems({ storageId });
|
|
1788
|
+
const entry = result2.entries.find(([key]) => key === request.key);
|
|
1789
|
+
return createCdpStorageSuccess(request, {
|
|
1790
|
+
key: request.key,
|
|
1791
|
+
value: entry?.[1] ?? null
|
|
1792
|
+
});
|
|
1793
|
+
}
|
|
1794
|
+
if (request.action === "set") {
|
|
1795
|
+
assertStorageKey(request);
|
|
1796
|
+
await client.DOMStorage.setDOMStorageItem({
|
|
1797
|
+
storageId,
|
|
1798
|
+
key: request.key,
|
|
1799
|
+
value: request.value ?? ""
|
|
1800
|
+
});
|
|
1801
|
+
return createCdpStorageSuccess(request, { ok: true });
|
|
1802
|
+
}
|
|
1803
|
+
if (request.action === "delete") {
|
|
1804
|
+
assertStorageKey(request);
|
|
1805
|
+
await client.DOMStorage.removeDOMStorageItem({
|
|
1806
|
+
storageId,
|
|
1807
|
+
key: request.key
|
|
1808
|
+
});
|
|
1809
|
+
return createCdpStorageSuccess(request, { ok: true });
|
|
1810
|
+
}
|
|
1811
|
+
const result = await client.DOMStorage.getDOMStorageItems({ storageId });
|
|
1812
|
+
for (const [key] of result.entries) {
|
|
1813
|
+
await client.DOMStorage.removeDOMStorageItem({ storageId, key });
|
|
1814
|
+
}
|
|
1815
|
+
return createCdpStorageSuccess(request, { deletedCount: result.entries.length });
|
|
1816
|
+
}
|
|
1817
|
+
async function manageCdpIndexedDb(client, request) {
|
|
1818
|
+
if (request.action === "list") {
|
|
1819
|
+
const result = await client.IndexedDB.requestDatabaseNames({
|
|
1820
|
+
securityOrigin: request.origin
|
|
1821
|
+
});
|
|
1822
|
+
return createCdpStorageSuccess(request, {
|
|
1823
|
+
origin: request.origin,
|
|
1824
|
+
databases: result.databaseNames
|
|
1825
|
+
});
|
|
1826
|
+
}
|
|
1827
|
+
assertIndexedDbTarget(request);
|
|
1828
|
+
if (request.action === "get") {
|
|
1829
|
+
const result = await client.IndexedDB.requestData({
|
|
1830
|
+
securityOrigin: request.origin,
|
|
1831
|
+
databaseName: request.databaseName,
|
|
1832
|
+
objectStoreName: request.objectStoreName,
|
|
1833
|
+
indexName: request.indexName ?? "",
|
|
1834
|
+
skipCount: 0,
|
|
1835
|
+
pageSize: 100
|
|
1836
|
+
});
|
|
1837
|
+
return createCdpStorageSuccess(request, {
|
|
1838
|
+
entries: result.objectStoreDataEntries,
|
|
1839
|
+
hasMore: result.hasMore
|
|
1840
|
+
});
|
|
1841
|
+
}
|
|
1842
|
+
if (request.action === "delete") {
|
|
1843
|
+
assertStorageKey(request);
|
|
1844
|
+
await client.IndexedDB.deleteObjectStoreEntries({
|
|
1845
|
+
securityOrigin: request.origin,
|
|
1846
|
+
databaseName: request.databaseName,
|
|
1847
|
+
objectStoreName: request.objectStoreName,
|
|
1848
|
+
keyRange: createExactCdpKeyRange(request.key)
|
|
1849
|
+
});
|
|
1850
|
+
return createCdpStorageSuccess(request, { ok: true });
|
|
1851
|
+
}
|
|
1852
|
+
if (request.action === "clear") {
|
|
1853
|
+
if (request.objectStoreName) {
|
|
1854
|
+
await client.IndexedDB.clearObjectStore({
|
|
1855
|
+
securityOrigin: request.origin,
|
|
1856
|
+
databaseName: request.databaseName,
|
|
1857
|
+
objectStoreName: request.objectStoreName
|
|
1858
|
+
});
|
|
1859
|
+
return createCdpStorageSuccess(request, { ok: true });
|
|
1860
|
+
}
|
|
1861
|
+
await client.IndexedDB.deleteDatabase({
|
|
1862
|
+
securityOrigin: request.origin,
|
|
1863
|
+
databaseName: request.databaseName
|
|
1864
|
+
});
|
|
1865
|
+
return createCdpStorageSuccess(request, { ok: true });
|
|
1866
|
+
}
|
|
1867
|
+
return createCdpStorageError(
|
|
1868
|
+
request,
|
|
1869
|
+
"IndexedDB set operation requires runtime bridge"
|
|
1870
|
+
);
|
|
1871
|
+
}
|
|
1872
|
+
function isCookieInOrigin(cookie, origin) {
|
|
1873
|
+
const hostname = new URL(origin).hostname;
|
|
1874
|
+
const domain = cookie.domain?.replace(/^\./, "");
|
|
1875
|
+
return Boolean(domain && (hostname === domain || hostname.endsWith(`.${domain}`)));
|
|
1876
|
+
}
|
|
1877
|
+
function normalizeCookieSameSite(sameSite) {
|
|
1878
|
+
if (!sameSite) {
|
|
1879
|
+
return void 0;
|
|
1880
|
+
}
|
|
1881
|
+
if (sameSite === "strict") {
|
|
1882
|
+
return "Strict";
|
|
1883
|
+
}
|
|
1884
|
+
if (sameSite === "lax") {
|
|
1885
|
+
return "Lax";
|
|
1886
|
+
}
|
|
1887
|
+
return "None";
|
|
1888
|
+
}
|
|
1889
|
+
function createExactCdpKeyRange(key) {
|
|
1890
|
+
const parsedKey = parseJsonValue(key);
|
|
1891
|
+
return {
|
|
1892
|
+
lower: parsedKey,
|
|
1893
|
+
upper: parsedKey,
|
|
1894
|
+
lowerOpen: false,
|
|
1895
|
+
upperOpen: false
|
|
1896
|
+
};
|
|
1897
|
+
}
|
|
1898
|
+
function parseJsonValue(value) {
|
|
1899
|
+
try {
|
|
1900
|
+
return JSON.parse(value);
|
|
1901
|
+
} catch {
|
|
1902
|
+
return value;
|
|
1903
|
+
}
|
|
1904
|
+
}
|
|
1905
|
+
function assertStorageKey(request) {
|
|
1906
|
+
if (!request.key) {
|
|
1907
|
+
throw new Error("Storage key is required for this operation");
|
|
1908
|
+
}
|
|
1909
|
+
}
|
|
1910
|
+
function assertIndexedDbTarget(request) {
|
|
1911
|
+
if (!request.databaseName || !request.objectStoreName) {
|
|
1912
|
+
throw new Error("IndexedDB databaseName and objectStoreName are required");
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
function createCdpStorageSuccess(request, data) {
|
|
1916
|
+
return {
|
|
1917
|
+
ok: true,
|
|
1918
|
+
source: "cdp",
|
|
1919
|
+
action: request.action,
|
|
1920
|
+
scope: request.scope,
|
|
1921
|
+
data
|
|
1922
|
+
};
|
|
1923
|
+
}
|
|
1924
|
+
function createCdpStorageError(request, error) {
|
|
1925
|
+
return {
|
|
1926
|
+
ok: false,
|
|
1927
|
+
source: "cdp",
|
|
1928
|
+
action: request.action,
|
|
1929
|
+
scope: request.scope,
|
|
1930
|
+
error
|
|
1931
|
+
};
|
|
1932
|
+
}
|
|
1933
|
+
|
|
1934
|
+
// src/mcp/tools/storage.ts
|
|
1935
|
+
function registerStorageTools(server, ctx) {
|
|
1936
|
+
registerStorageTool(server, MCP_TOOL_NAMES.listStorage, {
|
|
1937
|
+
description: "List same-origin storage and CDP cookies when available.",
|
|
1938
|
+
inputSchema: {
|
|
1939
|
+
pageId: z8.string().optional()
|
|
1940
|
+
},
|
|
1941
|
+
action: "list",
|
|
1942
|
+
handler: (input) => handleListStorage(ctx, input.pageId)
|
|
1943
|
+
});
|
|
1944
|
+
registerStorageTool(server, MCP_TOOL_NAMES.getStorageItem, {
|
|
1945
|
+
description: "Read one storage entry.",
|
|
1946
|
+
inputSchema: createStorageInputSchema(),
|
|
1947
|
+
action: "get",
|
|
1948
|
+
handler: (input) => handleStorageAction(ctx, { ...input, action: "get" })
|
|
1949
|
+
});
|
|
1950
|
+
registerStorageTool(server, MCP_TOOL_NAMES.setStorageItem, {
|
|
1951
|
+
description: "Write one storage entry.",
|
|
1952
|
+
inputSchema: createStorageInputSchema(),
|
|
1953
|
+
action: "set",
|
|
1954
|
+
handler: (input) => handleStorageAction(ctx, { ...input, action: "set" })
|
|
1955
|
+
});
|
|
1956
|
+
registerStorageTool(server, MCP_TOOL_NAMES.deleteStorageItem, {
|
|
1957
|
+
description: "Delete one storage entry.",
|
|
1958
|
+
inputSchema: createStorageInputSchema(),
|
|
1959
|
+
action: "delete",
|
|
1960
|
+
handler: (input) => handleStorageAction(ctx, { ...input, action: "delete" })
|
|
1961
|
+
});
|
|
1962
|
+
registerStorageTool(server, MCP_TOOL_NAMES.clearStorage, {
|
|
1963
|
+
description: "Clear one storage scope.",
|
|
1964
|
+
inputSchema: createStorageInputSchema(),
|
|
1965
|
+
action: "clear",
|
|
1966
|
+
handler: (input) => handleStorageAction(ctx, { ...input, action: "clear" })
|
|
1967
|
+
});
|
|
1968
|
+
}
|
|
1969
|
+
function registerStorageTool(server, name, options) {
|
|
1970
|
+
server.registerTool(
|
|
1971
|
+
name,
|
|
1972
|
+
{
|
|
1973
|
+
description: options.description,
|
|
1974
|
+
inputSchema: options.inputSchema
|
|
1975
|
+
},
|
|
1976
|
+
(async (input) => options.handler(
|
|
1977
|
+
input
|
|
1978
|
+
))
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
function createStorageInputSchema() {
|
|
1982
|
+
return {
|
|
1983
|
+
pageId: z8.string().optional(),
|
|
1984
|
+
scope: z8.enum(["localStorage", "sessionStorage", "indexedDB", "cookie"]).optional(),
|
|
1985
|
+
key: z8.string().optional(),
|
|
1986
|
+
value: z8.string().optional(),
|
|
1987
|
+
databaseName: z8.string().optional(),
|
|
1988
|
+
objectStoreName: z8.string().optional(),
|
|
1989
|
+
indexName: z8.string().optional(),
|
|
1990
|
+
cookie: z8.object({
|
|
1991
|
+
name: z8.string(),
|
|
1992
|
+
value: z8.string().optional(),
|
|
1993
|
+
domain: z8.string().optional(),
|
|
1994
|
+
path: z8.string().optional(),
|
|
1995
|
+
url: z8.string().optional(),
|
|
1996
|
+
httpOnly: z8.boolean().optional(),
|
|
1997
|
+
secure: z8.boolean().optional(),
|
|
1998
|
+
sameSite: z8.enum(["strict", "lax", "none"]).optional(),
|
|
1999
|
+
expires: z8.number().optional()
|
|
2000
|
+
}).optional()
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
2003
|
+
async function handleListStorage(ctx, pageId) {
|
|
2004
|
+
let baseRequest;
|
|
2005
|
+
try {
|
|
2006
|
+
baseRequest = createStorageRequest(ctx, {
|
|
2007
|
+
pageId,
|
|
2008
|
+
action: "list",
|
|
2009
|
+
scope: "localStorage"
|
|
2010
|
+
});
|
|
2011
|
+
} catch (error) {
|
|
2012
|
+
return createToolError(error instanceof Error ? error.message : String(error));
|
|
2013
|
+
}
|
|
2014
|
+
const [localStorage, sessionStorage, indexedDB] = await Promise.all([
|
|
2015
|
+
requestRuntimeStorage(ctx, { ...baseRequest, scope: "localStorage" }),
|
|
2016
|
+
requestRuntimeStorage(ctx, { ...baseRequest, scope: "sessionStorage" }),
|
|
2017
|
+
requestRuntimeStorage(ctx, { ...baseRequest, scope: "indexedDB" })
|
|
2018
|
+
]);
|
|
2019
|
+
const cookie = await listCookiesIfCdpAvailable(ctx, baseRequest, pageId);
|
|
2020
|
+
return createToolResponse({
|
|
2021
|
+
ok: true,
|
|
2022
|
+
origin: baseRequest.origin,
|
|
2023
|
+
localStorage: extractStorageData(localStorage),
|
|
2024
|
+
sessionStorage: extractStorageData(sessionStorage),
|
|
2025
|
+
indexedDB: extractStorageData(indexedDB),
|
|
2026
|
+
cookie
|
|
2027
|
+
});
|
|
2028
|
+
}
|
|
2029
|
+
async function handleStorageAction(ctx, input) {
|
|
2030
|
+
let request;
|
|
2031
|
+
try {
|
|
2032
|
+
request = createStorageRequest(ctx, input);
|
|
2033
|
+
} catch (error) {
|
|
2034
|
+
return createToolError(error instanceof Error ? error.message : String(error));
|
|
2035
|
+
}
|
|
2036
|
+
const cdp = await connectCdpForPage(ctx, input.pageId);
|
|
2037
|
+
if (cdp && shouldUseCdpStorage(request)) {
|
|
2038
|
+
try {
|
|
2039
|
+
const result2 = await createCdpStorageAdapter(cdp.client).manageStorage(
|
|
2040
|
+
request
|
|
2041
|
+
);
|
|
2042
|
+
return createToolResponse(result2);
|
|
2043
|
+
} finally {
|
|
2044
|
+
await closeCdpClient(cdp.client);
|
|
2045
|
+
}
|
|
2046
|
+
}
|
|
2047
|
+
const result = await requestRuntimeStorage(ctx, request);
|
|
2048
|
+
return createToolResponse(result);
|
|
2049
|
+
}
|
|
2050
|
+
async function requestRuntimeStorage(ctx, request) {
|
|
2051
|
+
return requestRuntimeData(ctx, (event) => {
|
|
2052
|
+
void ctx.rpcServer?.manageStorage({
|
|
2053
|
+
...request,
|
|
2054
|
+
event
|
|
2055
|
+
});
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
async function listCookiesIfCdpAvailable(ctx, request, pageId) {
|
|
2059
|
+
if (!hasCdpConfig2(ctx)) {
|
|
2060
|
+
return extractStorageData(
|
|
2061
|
+
await requestRuntimeStorage(ctx, { ...request, scope: "cookie" })
|
|
2062
|
+
);
|
|
2063
|
+
}
|
|
2064
|
+
const cdp = await connectCdpForPage(ctx, pageId);
|
|
2065
|
+
if (!cdp) {
|
|
2066
|
+
return extractStorageData(
|
|
2067
|
+
await requestRuntimeStorage(ctx, { ...request, scope: "cookie" })
|
|
2068
|
+
);
|
|
2069
|
+
}
|
|
2070
|
+
try {
|
|
2071
|
+
const result = await createCdpStorageAdapter(cdp.client).manageStorage({
|
|
2072
|
+
...request,
|
|
2073
|
+
scope: "cookie"
|
|
2074
|
+
});
|
|
2075
|
+
return extractStorageData(result);
|
|
2076
|
+
} finally {
|
|
2077
|
+
await closeCdpClient(cdp.client);
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
function extractStorageData(result) {
|
|
2081
|
+
if (!isStorageResultRecord(result)) {
|
|
2082
|
+
return result;
|
|
2083
|
+
}
|
|
2084
|
+
return result.data ?? result;
|
|
2085
|
+
}
|
|
2086
|
+
function isStorageResultRecord(value) {
|
|
2087
|
+
return typeof value === "object" && value !== null && "ok" in value;
|
|
2088
|
+
}
|
|
2089
|
+
function shouldUseCdpStorage(request) {
|
|
2090
|
+
return request.scope === "cookie";
|
|
2091
|
+
}
|
|
2092
|
+
function createStorageRequest(ctx, input) {
|
|
2093
|
+
const page = resolvePageTarget(ctx, input.pageId);
|
|
2094
|
+
const origin = new URL(page.url).origin;
|
|
2095
|
+
return {
|
|
2096
|
+
event: "",
|
|
2097
|
+
pageId: page.pageId,
|
|
2098
|
+
origin,
|
|
2099
|
+
action: input.action,
|
|
2100
|
+
scope: normalizeScope(input.scope),
|
|
2101
|
+
key: input.key,
|
|
2102
|
+
value: input.value,
|
|
2103
|
+
databaseName: input.databaseName,
|
|
2104
|
+
objectStoreName: input.objectStoreName,
|
|
2105
|
+
indexName: input.indexName,
|
|
2106
|
+
cookie: input.cookie
|
|
2107
|
+
};
|
|
2108
|
+
}
|
|
2109
|
+
function normalizeScope(scope) {
|
|
2110
|
+
return scope ?? "localStorage";
|
|
2111
|
+
}
|
|
2112
|
+
function hasCdpConfig2(ctx) {
|
|
2113
|
+
return Boolean(ctx.options.cdp.browserUrl || ctx.options.cdp.wsEndpoint);
|
|
2114
|
+
}
|
|
2115
|
+
|
|
1514
2116
|
// src/mcp/tools/screenshot.ts
|
|
1515
|
-
import { z as
|
|
2117
|
+
import { z as z9 } from "zod";
|
|
1516
2118
|
|
|
1517
2119
|
// src/cdp/cdpScreenshot.ts
|
|
1518
2120
|
async function cdpCaptureScreenshot(options) {
|
|
@@ -1672,14 +2274,14 @@ function createProjectRelativePath(ctx, filePath) {
|
|
|
1672
2274
|
var DEFAULT_SCREENSHOT_TARGET = "viewport";
|
|
1673
2275
|
var DEFAULT_SCREENSHOT_FORMAT = "png";
|
|
1674
2276
|
var screenshotInputSchema = {
|
|
1675
|
-
pageId:
|
|
1676
|
-
target:
|
|
1677
|
-
selector:
|
|
1678
|
-
format:
|
|
1679
|
-
prefer:
|
|
1680
|
-
quality:
|
|
1681
|
-
scale:
|
|
1682
|
-
snapdom:
|
|
2277
|
+
pageId: z9.string().optional(),
|
|
2278
|
+
target: z9.enum(["viewport", "fullPage", "element"]).optional(),
|
|
2279
|
+
selector: z9.string().optional(),
|
|
2280
|
+
format: z9.enum(["png", "jpeg", "webp"]).optional(),
|
|
2281
|
+
prefer: z9.enum(["auto", "cdp", "runtime"]).optional(),
|
|
2282
|
+
quality: z9.number().optional(),
|
|
2283
|
+
scale: z9.number().optional(),
|
|
2284
|
+
snapdom: z9.record(z9.string(), z9.unknown()).optional()
|
|
1683
2285
|
};
|
|
1684
2286
|
function registerScreenshotTools(server, ctx) {
|
|
1685
2287
|
server.registerTool(
|
|
@@ -1799,7 +2401,7 @@ function isScreenshotImagePayload(result) {
|
|
|
1799
2401
|
|
|
1800
2402
|
// src/mcp/tools/vue.ts
|
|
1801
2403
|
import { nanoid as nanoid3 } from "nanoid";
|
|
1802
|
-
import { z as
|
|
2404
|
+
import { z as z10 } from "zod";
|
|
1803
2405
|
function registerVueTools(server, ctx) {
|
|
1804
2406
|
server.registerTool(
|
|
1805
2407
|
MCP_TOOL_NAMES.getComponentTree,
|
|
@@ -1812,7 +2414,7 @@ function registerVueTools(server, ctx) {
|
|
|
1812
2414
|
MCP_TOOL_NAMES.getComponentState,
|
|
1813
2415
|
{
|
|
1814
2416
|
description: "Get Vue component state.",
|
|
1815
|
-
inputSchema: { componentName:
|
|
2417
|
+
inputSchema: { componentName: z10.string() }
|
|
1816
2418
|
},
|
|
1817
2419
|
async ({ componentName }) => requestVueData(ctx, (event) => {
|
|
1818
2420
|
void ctx.rpcServer?.getInspectorState({ event, componentName });
|
|
@@ -1823,10 +2425,10 @@ function registerVueTools(server, ctx) {
|
|
|
1823
2425
|
{
|
|
1824
2426
|
description: "Edit Vue component state.",
|
|
1825
2427
|
inputSchema: {
|
|
1826
|
-
componentName:
|
|
1827
|
-
path:
|
|
1828
|
-
value:
|
|
1829
|
-
valueType:
|
|
2428
|
+
componentName: z10.string(),
|
|
2429
|
+
path: z10.array(z10.string()),
|
|
2430
|
+
value: z10.string(),
|
|
2431
|
+
valueType: z10.enum(["string", "number", "boolean", "object", "array"])
|
|
1830
2432
|
}
|
|
1831
2433
|
},
|
|
1832
2434
|
({ componentName, path: path8, value, valueType }) => {
|
|
@@ -1846,7 +2448,7 @@ function registerVueTools(server, ctx) {
|
|
|
1846
2448
|
MCP_TOOL_NAMES.highlightComponent,
|
|
1847
2449
|
{
|
|
1848
2450
|
description: "Highlight a Vue component.",
|
|
1849
|
-
inputSchema: { componentName:
|
|
2451
|
+
inputSchema: { componentName: z10.string() }
|
|
1850
2452
|
},
|
|
1851
2453
|
({ componentName }) => {
|
|
1852
2454
|
if (!ctx.rpcServer) {
|
|
@@ -1874,7 +2476,7 @@ function registerVueTools(server, ctx) {
|
|
|
1874
2476
|
MCP_TOOL_NAMES.getPiniaState,
|
|
1875
2477
|
{
|
|
1876
2478
|
description: "Get Pinia store state.",
|
|
1877
|
-
inputSchema: { storeName:
|
|
2479
|
+
inputSchema: { storeName: z10.string() }
|
|
1878
2480
|
},
|
|
1879
2481
|
async ({ storeName }) => requestVueData(ctx, (event) => {
|
|
1880
2482
|
void ctx.rpcServer?.getPiniaState({ event, storeName });
|
|
@@ -1917,10 +2519,12 @@ function createMcpServer(ctx, vite) {
|
|
|
1917
2519
|
});
|
|
1918
2520
|
registerPageTools(server, ctx, vite);
|
|
1919
2521
|
registerDomTools(server, ctx);
|
|
2522
|
+
registerElementContextTools(server, ctx);
|
|
1920
2523
|
registerScreenshotTools(server, ctx);
|
|
1921
2524
|
registerConsoleTools(server, ctx);
|
|
1922
2525
|
registerEvaluateTools(server, ctx);
|
|
1923
2526
|
registerNetworkTools(server, ctx);
|
|
2527
|
+
registerStorageTools(server, ctx);
|
|
1924
2528
|
registerPerformanceTools(server, ctx);
|
|
1925
2529
|
registerVueTools(server, ctx);
|
|
1926
2530
|
return server;
|
|
@@ -1997,6 +2601,10 @@ function createServerVueRuntimeRpc(ctx) {
|
|
|
1997
2601
|
onDomQueryUpdated: (event, data) => {
|
|
1998
2602
|
void ctx.hooks.callHook(event, data);
|
|
1999
2603
|
},
|
|
2604
|
+
getElementContext: () => void 0,
|
|
2605
|
+
onElementContextUpdated: (event, data) => {
|
|
2606
|
+
void ctx.hooks.callHook(event, data);
|
|
2607
|
+
},
|
|
2000
2608
|
reloadPage: () => void 0,
|
|
2001
2609
|
onPageReloaded: (event, data) => {
|
|
2002
2610
|
void ctx.hooks.callHook(event, data);
|
|
@@ -2009,6 +2617,10 @@ function createServerVueRuntimeRpc(ctx) {
|
|
|
2009
2617
|
onScreenshotTaken: (event, data) => {
|
|
2010
2618
|
void ctx.hooks.callHook(event, data);
|
|
2011
2619
|
},
|
|
2620
|
+
manageStorage: () => void 0,
|
|
2621
|
+
onStorageUpdated: (event, data) => {
|
|
2622
|
+
void ctx.hooks.callHook(event, data);
|
|
2623
|
+
},
|
|
2012
2624
|
recordPerformance: () => void 0,
|
|
2013
2625
|
onPerformanceRecorded: (event, data) => {
|
|
2014
2626
|
void ctx.hooks.callHook(event, data);
|
|
@@ -2305,6 +2917,82 @@ async function startCdpObservers(ctx, target, client) {
|
|
|
2305
2917
|
}
|
|
2306
2918
|
}
|
|
2307
2919
|
|
|
2920
|
+
// src/plugin/elementInstrumentation.ts
|
|
2921
|
+
import { parse } from "@vue/compiler-sfc";
|
|
2922
|
+
import MagicString from "magic-string";
|
|
2923
|
+
import { relative as relative2 } from "path";
|
|
2924
|
+
var VUE_FILE_SUFFIX = ".vue";
|
|
2925
|
+
var ELEMENT_NODE_TYPE = 1;
|
|
2926
|
+
var SKIPPED_TAGS = /* @__PURE__ */ new Set(["template", "slot", "script", "style"]);
|
|
2927
|
+
var MCP_ID_ATTR = "data-v-mcp-id";
|
|
2928
|
+
function createElementInstrumentationController(options) {
|
|
2929
|
+
return {
|
|
2930
|
+
transform(code, id, ssr) {
|
|
2931
|
+
if (ssr || shouldSkipInstrumentation(id)) {
|
|
2932
|
+
return void 0;
|
|
2933
|
+
}
|
|
2934
|
+
const filename = id.split("?", 1)[0];
|
|
2935
|
+
if (!filename.endsWith(VUE_FILE_SUFFIX)) {
|
|
2936
|
+
return void 0;
|
|
2937
|
+
}
|
|
2938
|
+
const parsed = parse(code, { filename });
|
|
2939
|
+
const template = parsed.descriptor.template;
|
|
2940
|
+
if (!template?.ast) {
|
|
2941
|
+
return void 0;
|
|
2942
|
+
}
|
|
2943
|
+
const s = new MagicString(code);
|
|
2944
|
+
const relativeFile = normalizePath2(relative2(options.root, filename));
|
|
2945
|
+
for (const node of template.ast.children) {
|
|
2946
|
+
injectNodeId(s, node, relativeFile);
|
|
2947
|
+
}
|
|
2948
|
+
if (!s.hasChanged()) {
|
|
2949
|
+
return void 0;
|
|
2950
|
+
}
|
|
2951
|
+
return {
|
|
2952
|
+
code: s.toString(),
|
|
2953
|
+
map: s.generateMap({ hires: true })
|
|
2954
|
+
};
|
|
2955
|
+
}
|
|
2956
|
+
};
|
|
2957
|
+
}
|
|
2958
|
+
function shouldSkipInstrumentation(id) {
|
|
2959
|
+
if (id.startsWith("\0")) {
|
|
2960
|
+
return true;
|
|
2961
|
+
}
|
|
2962
|
+
const normalized = normalizePath2(id);
|
|
2963
|
+
if (normalized.includes("/node_modules/")) {
|
|
2964
|
+
return true;
|
|
2965
|
+
}
|
|
2966
|
+
return !normalized.startsWith("/") && !/^[A-Za-z]:\//.test(normalized);
|
|
2967
|
+
}
|
|
2968
|
+
function normalizePath2(path8) {
|
|
2969
|
+
return path8.replace(/\\/g, "/");
|
|
2970
|
+
}
|
|
2971
|
+
function injectNodeId(s, node, relativeFile) {
|
|
2972
|
+
if (node.type !== ELEMENT_NODE_TYPE || !node.tag || !node.loc) {
|
|
2973
|
+
return;
|
|
2974
|
+
}
|
|
2975
|
+
if (!SKIPPED_TAGS.has(node.tag) && !hasMcpIdAttr(node)) {
|
|
2976
|
+
const id = `${relativeFile}:${String(node.loc.start.line)}:${String(node.loc.start.column)}`;
|
|
2977
|
+
const insertAt = node.loc.start.offset + node.tag.length + 1;
|
|
2978
|
+
s.appendLeft(insertAt, ` ${MCP_ID_ATTR}="${id}"`);
|
|
2979
|
+
}
|
|
2980
|
+
for (const child of node.children ?? []) {
|
|
2981
|
+
injectNodeId(s, child, relativeFile);
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
function hasMcpIdAttr(node) {
|
|
2985
|
+
return (node.props ?? []).some((prop) => {
|
|
2986
|
+
if (!isTemplateProp(prop)) {
|
|
2987
|
+
return false;
|
|
2988
|
+
}
|
|
2989
|
+
return prop.name === MCP_ID_ATTR;
|
|
2990
|
+
});
|
|
2991
|
+
}
|
|
2992
|
+
function isTemplateProp(value) {
|
|
2993
|
+
return Boolean(value && typeof value === "object" && "name" in value);
|
|
2994
|
+
}
|
|
2995
|
+
|
|
2308
2996
|
// src/plugin/injectRuntime.ts
|
|
2309
2997
|
import { createRequire } from "module";
|
|
2310
2998
|
import { join } from "path";
|
|
@@ -2324,7 +3012,7 @@ function createRuntimeInjectionController(options, getConfig) {
|
|
|
2324
3012
|
},
|
|
2325
3013
|
load(id) {
|
|
2326
3014
|
if (id === RESOLVED_VIRTUAL_RUNTIME_ID) {
|
|
2327
|
-
return createRuntimeModule();
|
|
3015
|
+
return createRuntimeModule(options, getConfig()?.root);
|
|
2328
3016
|
}
|
|
2329
3017
|
if (id === RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID) {
|
|
2330
3018
|
return createScreenshotConfigModule(options);
|
|
@@ -2367,14 +3055,17 @@ ${code}`;
|
|
|
2367
3055
|
}
|
|
2368
3056
|
};
|
|
2369
3057
|
}
|
|
2370
|
-
function createRuntimeModule() {
|
|
3058
|
+
function createRuntimeModule(options, root) {
|
|
2371
3059
|
return [
|
|
2372
3060
|
"import { setScreenshotModuleRegistry, setSnapdomLoader, startRuntimeClient } from '@xiaou66/vite-plugin-vue-mcp-next/runtime/client';",
|
|
2373
3061
|
`import { screenshotModuleRegistry } from '${VIRTUAL_SCREENSHOT_CONFIG_ID}';`,
|
|
2374
3062
|
`import { loadSnapdom } from '${VIRTUAL_SNAPDOM_LOADER_ID}';`,
|
|
2375
3063
|
"setScreenshotModuleRegistry(screenshotModuleRegistry);",
|
|
2376
3064
|
"setSnapdomLoader(loadSnapdom);",
|
|
2377
|
-
|
|
3065
|
+
`void startRuntimeClient(${JSON.stringify({
|
|
3066
|
+
elementPicker: options.elementPicker,
|
|
3067
|
+
projectRoot: root
|
|
3068
|
+
})});`
|
|
2378
3069
|
].join("\n");
|
|
2379
3070
|
}
|
|
2380
3071
|
function createSnapdomLoaderModule(root) {
|
|
@@ -2893,6 +3584,7 @@ function vueMcpNext(userOptions = {}) {
|
|
|
2893
3584
|
options,
|
|
2894
3585
|
() => config
|
|
2895
3586
|
);
|
|
3587
|
+
let elementInstrumentation;
|
|
2896
3588
|
const cdpLifecycle = createCdpLifecycleController(ctx);
|
|
2897
3589
|
ctx.cdpLifecycle = cdpLifecycle;
|
|
2898
3590
|
return {
|
|
@@ -2901,6 +3593,9 @@ function vueMcpNext(userOptions = {}) {
|
|
|
2901
3593
|
apply: "serve",
|
|
2902
3594
|
configResolved(resolvedConfig) {
|
|
2903
3595
|
config = resolvedConfig;
|
|
3596
|
+
elementInstrumentation = createElementInstrumentationController({
|
|
3597
|
+
root: resolvedConfig.root
|
|
3598
|
+
});
|
|
2904
3599
|
},
|
|
2905
3600
|
async configureServer(server) {
|
|
2906
3601
|
ctx.server = server;
|
|
@@ -3004,7 +3699,21 @@ function vueMcpNext(userOptions = {}) {
|
|
|
3004
3699
|
return runtimeInjection.load(id);
|
|
3005
3700
|
},
|
|
3006
3701
|
transform(code, id, transformOptions) {
|
|
3007
|
-
|
|
3702
|
+
const instrumented = elementInstrumentation?.transform(
|
|
3703
|
+
code,
|
|
3704
|
+
id,
|
|
3705
|
+
transformOptions?.ssr
|
|
3706
|
+
);
|
|
3707
|
+
const nextCode = instrumented && typeof instrumented === "object" && "code" in instrumented && typeof instrumented.code === "string" ? instrumented.code : code;
|
|
3708
|
+
const runtimeInjected = runtimeInjection.transform(
|
|
3709
|
+
nextCode,
|
|
3710
|
+
id,
|
|
3711
|
+
transformOptions?.ssr
|
|
3712
|
+
);
|
|
3713
|
+
if (runtimeInjected) {
|
|
3714
|
+
return runtimeInjected;
|
|
3715
|
+
}
|
|
3716
|
+
return instrumented;
|
|
3008
3717
|
},
|
|
3009
3718
|
transformIndexHtml(html) {
|
|
3010
3719
|
return runtimeInjection.transformIndexHtml(html);
|