@xiaou66/vite-plugin-vue-mcp-next 0.0.6 → 0.0.7
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 +140 -92
- package/dist/index.cjs +271 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +265 -33
- package/dist/index.js.map +1 -1
- package/dist/runtime/client.cjs +31 -13
- package/dist/runtime/client.cjs.map +1 -1
- package/dist/runtime/client.d.cts +23 -1
- package/dist/runtime/client.d.ts +23 -1
- package/dist/runtime/client.js +30 -3
- package/dist/runtime/client.js.map +1 -1
- package/package.json +8 -2
package/dist/index.d.cts
CHANGED
|
@@ -177,6 +177,8 @@ type ScreenshotTarget = 'viewport' | 'fullPage' | 'element';
|
|
|
177
177
|
* MCP 返回 base64 数据,格式会直接影响体积;限制为常见浏览器截图格式可以简化大小控制和文档说明。
|
|
178
178
|
*/
|
|
179
179
|
type ScreenshotFormat = 'png' | 'jpeg' | 'webp';
|
|
180
|
+
/** 截图输出类型,项目级配置用于统一控制 MCP 返回路径还是直接返回图片数据。 */
|
|
181
|
+
type ScreenshotOutputType = 'path' | 'base64';
|
|
180
182
|
/**
|
|
181
183
|
* snapdom 插件路径对象。
|
|
182
184
|
*
|
|
@@ -247,6 +249,10 @@ interface SnapdomScreenshotOptions {
|
|
|
247
249
|
* 截图能力同时存在真截图和 DOM 降级截图;集中配置可以让用户明确选择准确度、体积和兼容性边界。
|
|
248
250
|
*/
|
|
249
251
|
interface ScreenshotOptions {
|
|
252
|
+
/** 截图输出类型,项目级 MCP 默认用路径减少 base64 对上下文的占用。 */
|
|
253
|
+
readonly type?: ScreenshotOutputType;
|
|
254
|
+
/** 截图保存目录,相对路径按 Vite 项目根目录解析。 */
|
|
255
|
+
readonly saveDir?: string;
|
|
250
256
|
/** 默认截图通道选择,适合项目按运行环境统一控制降级策略。 */
|
|
251
257
|
readonly prefer?: ScreenshotPrefer;
|
|
252
258
|
/** 单次 MCP 返回图片最大字节数,避免 base64 图片挤占上下文或拖慢客户端。 */
|
|
@@ -490,6 +496,12 @@ interface VueRuntimeRpc {
|
|
|
490
496
|
}): void | Promise<void>;
|
|
491
497
|
/** 回传 selector 查询结果。 */
|
|
492
498
|
onDomQueryUpdated(event: string, data: unknown): void;
|
|
499
|
+
/** 触发页面刷新,用于测试前消除上一轮运行状态对页面初始化的影响。 */
|
|
500
|
+
reloadPage(options: {
|
|
501
|
+
event: string;
|
|
502
|
+
}): void | Promise<void>;
|
|
503
|
+
/** 回传页面刷新触发结果;Runtime 路径只能普通刷新,不能承诺绕过 HTTP 缓存。 */
|
|
504
|
+
onPageReloaded(event: string, data: unknown): void;
|
|
493
505
|
/** 执行已授权的页面表达式,用于无 CDP 配置时提供控制台测试能力。 */
|
|
494
506
|
evaluateScript(options: {
|
|
495
507
|
event: string;
|
package/dist/index.d.ts
CHANGED
|
@@ -177,6 +177,8 @@ type ScreenshotTarget = 'viewport' | 'fullPage' | 'element';
|
|
|
177
177
|
* MCP 返回 base64 数据,格式会直接影响体积;限制为常见浏览器截图格式可以简化大小控制和文档说明。
|
|
178
178
|
*/
|
|
179
179
|
type ScreenshotFormat = 'png' | 'jpeg' | 'webp';
|
|
180
|
+
/** 截图输出类型,项目级配置用于统一控制 MCP 返回路径还是直接返回图片数据。 */
|
|
181
|
+
type ScreenshotOutputType = 'path' | 'base64';
|
|
180
182
|
/**
|
|
181
183
|
* snapdom 插件路径对象。
|
|
182
184
|
*
|
|
@@ -247,6 +249,10 @@ interface SnapdomScreenshotOptions {
|
|
|
247
249
|
* 截图能力同时存在真截图和 DOM 降级截图;集中配置可以让用户明确选择准确度、体积和兼容性边界。
|
|
248
250
|
*/
|
|
249
251
|
interface ScreenshotOptions {
|
|
252
|
+
/** 截图输出类型,项目级 MCP 默认用路径减少 base64 对上下文的占用。 */
|
|
253
|
+
readonly type?: ScreenshotOutputType;
|
|
254
|
+
/** 截图保存目录,相对路径按 Vite 项目根目录解析。 */
|
|
255
|
+
readonly saveDir?: string;
|
|
250
256
|
/** 默认截图通道选择,适合项目按运行环境统一控制降级策略。 */
|
|
251
257
|
readonly prefer?: ScreenshotPrefer;
|
|
252
258
|
/** 单次 MCP 返回图片最大字节数,避免 base64 图片挤占上下文或拖慢客户端。 */
|
|
@@ -490,6 +496,12 @@ interface VueRuntimeRpc {
|
|
|
490
496
|
}): void | Promise<void>;
|
|
491
497
|
/** 回传 selector 查询结果。 */
|
|
492
498
|
onDomQueryUpdated(event: string, data: unknown): void;
|
|
499
|
+
/** 触发页面刷新,用于测试前消除上一轮运行状态对页面初始化的影响。 */
|
|
500
|
+
reloadPage(options: {
|
|
501
|
+
event: string;
|
|
502
|
+
}): void | Promise<void>;
|
|
503
|
+
/** 回传页面刷新触发结果;Runtime 路径只能普通刷新,不能承诺绕过 HTTP 缓存。 */
|
|
504
|
+
onPageReloaded(event: string, data: unknown): void;
|
|
493
505
|
/** 执行已授权的页面表达式,用于无 CDP 配置时提供控制台测试能力。 */
|
|
494
506
|
evaluateScript(options: {
|
|
495
507
|
event: string;
|
package/dist/index.js
CHANGED
|
@@ -17,8 +17,10 @@ var DEFAULT_MASK_HEADERS = [
|
|
|
17
17
|
// src/constants.ts
|
|
18
18
|
var DEFAULT_MCP_PATH = "/__mcp";
|
|
19
19
|
var DEFAULT_SCREENSHOT_MAX_BYTES = 5 * 1024 * 1024;
|
|
20
|
+
var DEFAULT_SCREENSHOT_SAVE_DIR = ".vite-mcp/screenshot";
|
|
20
21
|
var MCP_TOOL_NAMES = {
|
|
21
22
|
listPages: "list_pages",
|
|
23
|
+
reloadPage: "reload_page",
|
|
22
24
|
getPageState: "get_page_state",
|
|
23
25
|
getDomTree: "get_dom_tree",
|
|
24
26
|
queryDom: "query_dom",
|
|
@@ -41,7 +43,10 @@ var VIRTUAL_RUNTIME_ID = "virtual:vite-plugin-vue-mcp-next/runtime";
|
|
|
41
43
|
var RESOLVED_VIRTUAL_RUNTIME_ID = `\0${VIRTUAL_RUNTIME_ID}`;
|
|
42
44
|
var VIRTUAL_SCREENSHOT_CONFIG_ID = "virtual:vite-plugin-vue-mcp-next/screenshot-config";
|
|
43
45
|
var RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID = `\0${VIRTUAL_SCREENSHOT_CONFIG_ID}`;
|
|
46
|
+
var VIRTUAL_SNAPDOM_LOADER_ID = "virtual:vite-plugin-vue-mcp-next/snapdom-loader";
|
|
47
|
+
var RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID = `\0${VIRTUAL_SNAPDOM_LOADER_ID}`;
|
|
44
48
|
var DEFAULT_MCP_CLIENT_SERVER_NAME = "vue-mcp-next";
|
|
49
|
+
var RUNTIME_PAGE_RECONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-reconnected";
|
|
45
50
|
var DEFAULT_OPTIONS = {
|
|
46
51
|
mcpPath: DEFAULT_MCP_PATH,
|
|
47
52
|
host: "localhost",
|
|
@@ -82,6 +87,8 @@ var DEFAULT_OPTIONS = {
|
|
|
82
87
|
maxRecords: DEFAULT_CONSOLE_MAX_RECORDS
|
|
83
88
|
},
|
|
84
89
|
screenshot: {
|
|
90
|
+
type: "path",
|
|
91
|
+
saveDir: DEFAULT_SCREENSHOT_SAVE_DIR,
|
|
85
92
|
prefer: "auto",
|
|
86
93
|
maxBytes: DEFAULT_SCREENSHOT_MAX_BYTES,
|
|
87
94
|
snapdom: {
|
|
@@ -611,6 +618,9 @@ function registerNetworkTools(server, ctx) {
|
|
|
611
618
|
);
|
|
612
619
|
}
|
|
613
620
|
|
|
621
|
+
// src/mcp/tools/pages.ts
|
|
622
|
+
import { z as z5 } from "zod";
|
|
623
|
+
|
|
614
624
|
// src/plugin/entryDiscovery.ts
|
|
615
625
|
import fs from "fs";
|
|
616
626
|
import path from "path";
|
|
@@ -662,6 +672,118 @@ function registerPageTools(server, ctx, vite) {
|
|
|
662
672
|
});
|
|
663
673
|
}
|
|
664
674
|
);
|
|
675
|
+
server.registerTool(
|
|
676
|
+
MCP_TOOL_NAMES.reloadPage,
|
|
677
|
+
{
|
|
678
|
+
description: "Reload the selected page. CDP uses ignoreCache; Runtime Hook falls back to normal reload.",
|
|
679
|
+
inputSchema: {
|
|
680
|
+
pageId: z5.string().optional(),
|
|
681
|
+
ignoreCache: z5.boolean().optional()
|
|
682
|
+
}
|
|
683
|
+
},
|
|
684
|
+
async (input) => {
|
|
685
|
+
if (hasCdpConfig(ctx)) {
|
|
686
|
+
return reloadPageWithCdp(ctx, input.pageId, input.ignoreCache ?? true);
|
|
687
|
+
}
|
|
688
|
+
const target = resolveRuntimeReloadTarget(ctx, input.pageId);
|
|
689
|
+
if (!target.ok) {
|
|
690
|
+
return createToolError(target.error);
|
|
691
|
+
}
|
|
692
|
+
const reconnect = waitForRuntimePageReconnect(ctx);
|
|
693
|
+
ctx.pages.disconnect(target.page.pageId);
|
|
694
|
+
const result = await requestRuntimeData(ctx, (event) => {
|
|
695
|
+
void ctx.rpcServer?.reloadPage({ event });
|
|
696
|
+
});
|
|
697
|
+
if (!isRecord(result) || result.ok === false) {
|
|
698
|
+
reconnect.cancel();
|
|
699
|
+
return createToolResponse(
|
|
700
|
+
isRecord(result) ? result : { ok: false, error: "Invalid runtime reload response" }
|
|
701
|
+
);
|
|
702
|
+
}
|
|
703
|
+
const page = await reconnect.promise;
|
|
704
|
+
return createToolResponse(
|
|
705
|
+
page ? { ...result, reconnected: true, pageId: page.pageId, page } : {
|
|
706
|
+
...result,
|
|
707
|
+
reconnected: false,
|
|
708
|
+
error: "runtime page reconnect timed out"
|
|
709
|
+
}
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
);
|
|
713
|
+
}
|
|
714
|
+
function hasCdpConfig(ctx) {
|
|
715
|
+
return Boolean(ctx.options.cdp.browserUrl || ctx.options.cdp.wsEndpoint);
|
|
716
|
+
}
|
|
717
|
+
function isRecord(value) {
|
|
718
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
719
|
+
}
|
|
720
|
+
function resolveRuntimeReloadTarget(ctx, pageId) {
|
|
721
|
+
try {
|
|
722
|
+
const page = resolvePageTarget(ctx, pageId);
|
|
723
|
+
if (page.source !== "runtime") {
|
|
724
|
+
return {
|
|
725
|
+
ok: false,
|
|
726
|
+
error: "Runtime reload requires a runtime page target"
|
|
727
|
+
};
|
|
728
|
+
}
|
|
729
|
+
return { ok: true, page };
|
|
730
|
+
} catch (error) {
|
|
731
|
+
return {
|
|
732
|
+
ok: false,
|
|
733
|
+
error: error instanceof Error ? error.message : String(error)
|
|
734
|
+
};
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function waitForRuntimePageReconnect(ctx) {
|
|
738
|
+
let timeout;
|
|
739
|
+
let cleanup;
|
|
740
|
+
const promise = new Promise((resolve) => {
|
|
741
|
+
timeout = setTimeout(() => {
|
|
742
|
+
cleanup?.();
|
|
743
|
+
resolve(null);
|
|
744
|
+
}, 5e3);
|
|
745
|
+
cleanup = ctx.hooks.hookOnce(RUNTIME_PAGE_RECONNECTED_EVENT, (payload) => {
|
|
746
|
+
if (timeout) {
|
|
747
|
+
clearTimeout(timeout);
|
|
748
|
+
}
|
|
749
|
+
resolve(isPageTarget(payload) ? payload : null);
|
|
750
|
+
});
|
|
751
|
+
});
|
|
752
|
+
return {
|
|
753
|
+
promise,
|
|
754
|
+
cancel() {
|
|
755
|
+
if (timeout) {
|
|
756
|
+
clearTimeout(timeout);
|
|
757
|
+
}
|
|
758
|
+
cleanup?.();
|
|
759
|
+
}
|
|
760
|
+
};
|
|
761
|
+
}
|
|
762
|
+
function isPageTarget(value) {
|
|
763
|
+
if (!isRecord(value)) {
|
|
764
|
+
return false;
|
|
765
|
+
}
|
|
766
|
+
return typeof value.pageId === "string" && (value.source === "runtime" || value.source === "cdp") && typeof value.url === "string" && typeof value.pathname === "string" && typeof value.connected === "boolean";
|
|
767
|
+
}
|
|
768
|
+
async function reloadPageWithCdp(ctx, pageId, ignoreCache) {
|
|
769
|
+
const cdp = await connectCdpForPage(ctx, pageId);
|
|
770
|
+
if (!cdp) {
|
|
771
|
+
return createToolError("CDP target is unavailable for page reload");
|
|
772
|
+
}
|
|
773
|
+
try {
|
|
774
|
+
await cdp.client.Page.enable();
|
|
775
|
+
const loaded = cdp.client.Page.loadEventFired();
|
|
776
|
+
await cdp.client.Page.reload({ ignoreCache });
|
|
777
|
+
await loaded;
|
|
778
|
+
return createToolResponse({
|
|
779
|
+
ok: true,
|
|
780
|
+
source: "cdp",
|
|
781
|
+
ignoreCache,
|
|
782
|
+
pageId
|
|
783
|
+
});
|
|
784
|
+
} finally {
|
|
785
|
+
await closeCdpClient(cdp.client);
|
|
786
|
+
}
|
|
665
787
|
}
|
|
666
788
|
async function listCdpPageTargets(ctx) {
|
|
667
789
|
if (ctx.options.cdp.wsEndpoint) {
|
|
@@ -708,7 +830,7 @@ function getPathname(url) {
|
|
|
708
830
|
}
|
|
709
831
|
|
|
710
832
|
// src/mcp/tools/screenshot.ts
|
|
711
|
-
import { z as
|
|
833
|
+
import { z as z6 } from "zod";
|
|
712
834
|
|
|
713
835
|
// src/cdp/cdpScreenshot.ts
|
|
714
836
|
async function cdpCaptureScreenshot(options) {
|
|
@@ -812,18 +934,70 @@ function isElementRect(value) {
|
|
|
812
934
|
return typeof rect.x === "number" && typeof rect.y === "number" && typeof rect.width === "number" && rect.width > 0 && typeof rect.height === "number" && rect.height > 0;
|
|
813
935
|
}
|
|
814
936
|
|
|
937
|
+
// src/mcp/tools/screenshotOutput.ts
|
|
938
|
+
import { randomUUID } from "crypto";
|
|
939
|
+
import { mkdir, writeFile } from "fs/promises";
|
|
940
|
+
import path2 from "path";
|
|
941
|
+
async function createScreenshotOutput(ctx, payload) {
|
|
942
|
+
if (ctx.options.screenshot.type === "base64") {
|
|
943
|
+
return payload;
|
|
944
|
+
}
|
|
945
|
+
const saveDir = resolveScreenshotSaveDir(ctx);
|
|
946
|
+
await mkdir(saveDir, { recursive: true });
|
|
947
|
+
const filePath = path2.join(saveDir, createScreenshotFileName(payload));
|
|
948
|
+
await writeFile(filePath, Buffer.from(payload.data, "base64"));
|
|
949
|
+
return {
|
|
950
|
+
source: payload.source,
|
|
951
|
+
target: payload.target,
|
|
952
|
+
format: payload.format,
|
|
953
|
+
width: payload.width,
|
|
954
|
+
height: payload.height,
|
|
955
|
+
mimeType: payload.mimeType,
|
|
956
|
+
byteLength: payload.byteLength,
|
|
957
|
+
limitations: payload.limitations,
|
|
958
|
+
path: filePath,
|
|
959
|
+
relativePath: createProjectRelativePath(ctx, filePath)
|
|
960
|
+
};
|
|
961
|
+
}
|
|
962
|
+
function resolveScreenshotSaveDir(ctx) {
|
|
963
|
+
const saveDir = ctx.options.screenshot.saveDir.trim();
|
|
964
|
+
if (!saveDir) {
|
|
965
|
+
throw new Error("screenshot.saveDir must be a non-empty string");
|
|
966
|
+
}
|
|
967
|
+
const root = ctx.server?.config.root;
|
|
968
|
+
if (!root) {
|
|
969
|
+
throw new Error("Vite server root is required for screenshot path output");
|
|
970
|
+
}
|
|
971
|
+
if (path2.isAbsolute(saveDir)) {
|
|
972
|
+
return saveDir;
|
|
973
|
+
}
|
|
974
|
+
return path2.resolve(root, saveDir);
|
|
975
|
+
}
|
|
976
|
+
function createScreenshotFileName(payload) {
|
|
977
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
|
|
978
|
+
const suffix = randomUUID().slice(0, 8);
|
|
979
|
+
return `${timestamp}-${payload.source}-${payload.target}-${suffix}.${payload.format}`;
|
|
980
|
+
}
|
|
981
|
+
function createProjectRelativePath(ctx, filePath) {
|
|
982
|
+
const root = ctx.server?.config.root;
|
|
983
|
+
if (!root) {
|
|
984
|
+
throw new Error("Vite server root is required for screenshot path output");
|
|
985
|
+
}
|
|
986
|
+
return path2.relative(root, filePath).split(path2.sep).join("/");
|
|
987
|
+
}
|
|
988
|
+
|
|
815
989
|
// src/mcp/tools/screenshot.ts
|
|
816
990
|
var DEFAULT_SCREENSHOT_TARGET = "viewport";
|
|
817
991
|
var DEFAULT_SCREENSHOT_FORMAT = "png";
|
|
818
992
|
var screenshotInputSchema = {
|
|
819
|
-
pageId:
|
|
820
|
-
target:
|
|
821
|
-
selector:
|
|
822
|
-
format:
|
|
823
|
-
prefer:
|
|
824
|
-
quality:
|
|
825
|
-
scale:
|
|
826
|
-
snapdom:
|
|
993
|
+
pageId: z6.string().optional(),
|
|
994
|
+
target: z6.enum(["viewport", "fullPage", "element"]).optional(),
|
|
995
|
+
selector: z6.string().optional(),
|
|
996
|
+
format: z6.enum(["png", "jpeg", "webp"]).optional(),
|
|
997
|
+
prefer: z6.enum(["auto", "cdp", "runtime"]).optional(),
|
|
998
|
+
quality: z6.number().optional(),
|
|
999
|
+
scale: z6.number().optional(),
|
|
1000
|
+
snapdom: z6.record(z6.string(), z6.unknown()).optional()
|
|
827
1001
|
};
|
|
828
1002
|
function registerScreenshotTools(server, ctx) {
|
|
829
1003
|
server.registerTool(
|
|
@@ -853,7 +1027,7 @@ async function handleTakeScreenshot(ctx, input) {
|
|
|
853
1027
|
format,
|
|
854
1028
|
quality: input.quality
|
|
855
1029
|
});
|
|
856
|
-
return createScreenshotResponse(ctx, {
|
|
1030
|
+
return await createScreenshotResponse(ctx, {
|
|
857
1031
|
source: "cdp",
|
|
858
1032
|
target,
|
|
859
1033
|
format,
|
|
@@ -899,15 +1073,30 @@ async function createRuntimeScreenshot(ctx, input, normalized) {
|
|
|
899
1073
|
if (!isPlainRecord(result)) {
|
|
900
1074
|
return createToolError("runtime screenshot returned an invalid response");
|
|
901
1075
|
}
|
|
902
|
-
|
|
1076
|
+
if (result.ok === false) {
|
|
1077
|
+
return createToolResponse(result);
|
|
1078
|
+
}
|
|
1079
|
+
if (!isScreenshotImagePayload(result)) {
|
|
1080
|
+
return createToolError("runtime screenshot returned an invalid response");
|
|
1081
|
+
}
|
|
1082
|
+
return createScreenshotResponse(ctx, {
|
|
1083
|
+
...result,
|
|
1084
|
+
target: normalized.target,
|
|
1085
|
+
format: normalized.format
|
|
1086
|
+
});
|
|
903
1087
|
}
|
|
904
|
-
function createScreenshotResponse(ctx, result) {
|
|
1088
|
+
async function createScreenshotResponse(ctx, result) {
|
|
905
1089
|
if (result.byteLength > ctx.options.screenshot.maxBytes) {
|
|
906
1090
|
return createToolError(
|
|
907
1091
|
`screenshot is too large: ${String(result.byteLength)} bytes`
|
|
908
1092
|
);
|
|
909
1093
|
}
|
|
910
|
-
|
|
1094
|
+
try {
|
|
1095
|
+
return createToolResponse(await createScreenshotOutput(ctx, result));
|
|
1096
|
+
} catch (error) {
|
|
1097
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1098
|
+
return createToolError(`failed to create screenshot output: ${message}`);
|
|
1099
|
+
}
|
|
911
1100
|
}
|
|
912
1101
|
function isScreenshotTooLarge(ctx, result) {
|
|
913
1102
|
return isPlainRecord(result) && "byteLength" in result && typeof result.byteLength === "number" && result.byteLength > ctx.options.screenshot.maxBytes;
|
|
@@ -922,10 +1111,13 @@ function getBase64ByteLength(data) {
|
|
|
922
1111
|
const padding = data.endsWith("==") ? 2 : data.endsWith("=") ? 1 : 0;
|
|
923
1112
|
return Math.ceil(data.length * 3 / 4) - padding;
|
|
924
1113
|
}
|
|
1114
|
+
function isScreenshotImagePayload(result) {
|
|
1115
|
+
return (result.source === "cdp" || result.source === "snapdom") && typeof result.data === "string" && typeof result.width === "number" && typeof result.height === "number" && typeof result.mimeType === "string" && typeof result.byteLength === "number";
|
|
1116
|
+
}
|
|
925
1117
|
|
|
926
1118
|
// src/mcp/tools/vue.ts
|
|
927
1119
|
import { nanoid as nanoid2 } from "nanoid";
|
|
928
|
-
import { z as
|
|
1120
|
+
import { z as z7 } from "zod";
|
|
929
1121
|
function registerVueTools(server, ctx) {
|
|
930
1122
|
server.registerTool(
|
|
931
1123
|
MCP_TOOL_NAMES.getComponentTree,
|
|
@@ -938,7 +1130,7 @@ function registerVueTools(server, ctx) {
|
|
|
938
1130
|
MCP_TOOL_NAMES.getComponentState,
|
|
939
1131
|
{
|
|
940
1132
|
description: "Get Vue component state.",
|
|
941
|
-
inputSchema: { componentName:
|
|
1133
|
+
inputSchema: { componentName: z7.string() }
|
|
942
1134
|
},
|
|
943
1135
|
async ({ componentName }) => requestVueData(ctx, (event) => {
|
|
944
1136
|
void ctx.rpcServer?.getInspectorState({ event, componentName });
|
|
@@ -949,19 +1141,19 @@ function registerVueTools(server, ctx) {
|
|
|
949
1141
|
{
|
|
950
1142
|
description: "Edit Vue component state.",
|
|
951
1143
|
inputSchema: {
|
|
952
|
-
componentName:
|
|
953
|
-
path:
|
|
954
|
-
value:
|
|
955
|
-
valueType:
|
|
1144
|
+
componentName: z7.string(),
|
|
1145
|
+
path: z7.array(z7.string()),
|
|
1146
|
+
value: z7.string(),
|
|
1147
|
+
valueType: z7.enum(["string", "number", "boolean", "object", "array"])
|
|
956
1148
|
}
|
|
957
1149
|
},
|
|
958
|
-
({ componentName, path:
|
|
1150
|
+
({ componentName, path: path6, value, valueType }) => {
|
|
959
1151
|
if (!ctx.rpcServer) {
|
|
960
1152
|
return vueBridgeUnavailable();
|
|
961
1153
|
}
|
|
962
1154
|
void ctx.rpcServer.editComponentState({
|
|
963
1155
|
componentName,
|
|
964
|
-
path:
|
|
1156
|
+
path: path6,
|
|
965
1157
|
value,
|
|
966
1158
|
valueType
|
|
967
1159
|
});
|
|
@@ -972,7 +1164,7 @@ function registerVueTools(server, ctx) {
|
|
|
972
1164
|
MCP_TOOL_NAMES.highlightComponent,
|
|
973
1165
|
{
|
|
974
1166
|
description: "Highlight a Vue component.",
|
|
975
|
-
inputSchema: { componentName:
|
|
1167
|
+
inputSchema: { componentName: z7.string() }
|
|
976
1168
|
},
|
|
977
1169
|
({ componentName }) => {
|
|
978
1170
|
if (!ctx.rpcServer) {
|
|
@@ -1000,7 +1192,7 @@ function registerVueTools(server, ctx) {
|
|
|
1000
1192
|
MCP_TOOL_NAMES.getPiniaState,
|
|
1001
1193
|
{
|
|
1002
1194
|
description: "Get Pinia store state.",
|
|
1003
|
-
inputSchema: { storeName:
|
|
1195
|
+
inputSchema: { storeName: z7.string() }
|
|
1004
1196
|
},
|
|
1005
1197
|
async ({ storeName }) => requestVueData(ctx, (event) => {
|
|
1006
1198
|
void ctx.rpcServer?.getPiniaState({ event, storeName });
|
|
@@ -1122,6 +1314,10 @@ function createServerVueRuntimeRpc(ctx) {
|
|
|
1122
1314
|
onDomQueryUpdated: (event, data) => {
|
|
1123
1315
|
void ctx.hooks.callHook(event, data);
|
|
1124
1316
|
},
|
|
1317
|
+
reloadPage: () => void 0,
|
|
1318
|
+
onPageReloaded: (event, data) => {
|
|
1319
|
+
void ctx.hooks.callHook(event, data);
|
|
1320
|
+
},
|
|
1125
1321
|
evaluateScript: () => void 0,
|
|
1126
1322
|
onEvaluateScriptUpdated: (event, data) => {
|
|
1127
1323
|
void ctx.hooks.callHook(event, data);
|
|
@@ -1415,6 +1611,8 @@ async function startCdpObservers(ctx, target, client) {
|
|
|
1415
1611
|
}
|
|
1416
1612
|
|
|
1417
1613
|
// src/plugin/injectRuntime.ts
|
|
1614
|
+
import { createRequire } from "module";
|
|
1615
|
+
import { join } from "path";
|
|
1418
1616
|
function createRuntimeInjectionController(options, getConfig) {
|
|
1419
1617
|
return {
|
|
1420
1618
|
resolveId(importee) {
|
|
@@ -1424,6 +1622,9 @@ function createRuntimeInjectionController(options, getConfig) {
|
|
|
1424
1622
|
if (importee === VIRTUAL_SCREENSHOT_CONFIG_ID) {
|
|
1425
1623
|
return RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID;
|
|
1426
1624
|
}
|
|
1625
|
+
if (importee === VIRTUAL_SNAPDOM_LOADER_ID) {
|
|
1626
|
+
return RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID;
|
|
1627
|
+
}
|
|
1427
1628
|
return void 0;
|
|
1428
1629
|
},
|
|
1429
1630
|
load(id) {
|
|
@@ -1433,6 +1634,9 @@ function createRuntimeInjectionController(options, getConfig) {
|
|
|
1433
1634
|
if (id === RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID) {
|
|
1434
1635
|
return createScreenshotConfigModule(options);
|
|
1435
1636
|
}
|
|
1637
|
+
if (id === RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID) {
|
|
1638
|
+
return createSnapdomLoaderModule(getConfig()?.root);
|
|
1639
|
+
}
|
|
1436
1640
|
return void 0;
|
|
1437
1641
|
},
|
|
1438
1642
|
transformIndexHtml(html) {
|
|
@@ -1470,12 +1674,39 @@ ${code}`;
|
|
|
1470
1674
|
}
|
|
1471
1675
|
function createRuntimeModule() {
|
|
1472
1676
|
return [
|
|
1473
|
-
"import { setScreenshotModuleRegistry, startRuntimeClient } from '@xiaou66/vite-plugin-vue-mcp-next/runtime/client';",
|
|
1677
|
+
"import { setScreenshotModuleRegistry, setSnapdomLoader, startRuntimeClient } from '@xiaou66/vite-plugin-vue-mcp-next/runtime/client';",
|
|
1474
1678
|
`import { screenshotModuleRegistry } from '${VIRTUAL_SCREENSHOT_CONFIG_ID}';`,
|
|
1679
|
+
`import { loadSnapdom } from '${VIRTUAL_SNAPDOM_LOADER_ID}';`,
|
|
1475
1680
|
"setScreenshotModuleRegistry(screenshotModuleRegistry);",
|
|
1681
|
+
"setSnapdomLoader(loadSnapdom);",
|
|
1476
1682
|
"void startRuntimeClient();"
|
|
1477
1683
|
].join("\n");
|
|
1478
1684
|
}
|
|
1685
|
+
function createSnapdomLoaderModule(root) {
|
|
1686
|
+
if (!canResolveSnapdomFromProject(root)) {
|
|
1687
|
+
return [
|
|
1688
|
+
"export const loadSnapdom = () =>",
|
|
1689
|
+
` Promise.reject(new Error(${JSON.stringify(createMissingSnapdomMessage())}));`
|
|
1690
|
+
].join("\n");
|
|
1691
|
+
}
|
|
1692
|
+
return [
|
|
1693
|
+
"import { snapdom } from '@zumer/snapdom';",
|
|
1694
|
+
"export const loadSnapdom = () => Promise.resolve({ snapdom });"
|
|
1695
|
+
].join("\n");
|
|
1696
|
+
}
|
|
1697
|
+
function canResolveSnapdomFromProject(root) {
|
|
1698
|
+
try {
|
|
1699
|
+
createRequire(join(root ?? process.cwd(), "package.json")).resolve(
|
|
1700
|
+
"@zumer/snapdom"
|
|
1701
|
+
);
|
|
1702
|
+
return true;
|
|
1703
|
+
} catch {
|
|
1704
|
+
return false;
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
function createMissingSnapdomMessage() {
|
|
1708
|
+
return "\u7F3A\u5C11\u53EF\u9009\u4F9D\u8D56 @zumer/snapdom\u3002DOM \u622A\u56FE\u964D\u7EA7\u9700\u8981\u8BE5\u4F9D\u8D56\uFF0C\u8BF7\u6267\u884C\uFF1Apnpm add -D @zumer/snapdom";
|
|
1709
|
+
}
|
|
1479
1710
|
function createScreenshotConfigModule(options) {
|
|
1480
1711
|
const paths = collectScreenshotImportPaths(options);
|
|
1481
1712
|
const imports = paths.map((item, index) => `import * as m${String(index)} from ${JSON.stringify(item)};`).join("\n");
|
|
@@ -1504,16 +1735,16 @@ function getPluginPath(plugin) {
|
|
|
1504
1735
|
}
|
|
1505
1736
|
|
|
1506
1737
|
// src/plugin/mcpClientConfig/index.ts
|
|
1507
|
-
import
|
|
1738
|
+
import path5 from "path";
|
|
1508
1739
|
|
|
1509
1740
|
// src/plugin/mcpClientConfig/codexConfig.ts
|
|
1510
1741
|
import fs2 from "fs/promises";
|
|
1511
|
-
import
|
|
1742
|
+
import path3 from "path";
|
|
1512
1743
|
async function updateCodexMcpClientConfig(options) {
|
|
1513
1744
|
try {
|
|
1514
1745
|
const current = await readOptionalTextFile(options.configPath);
|
|
1515
1746
|
const next = replaceOrAppendOwnedBlock(current, options);
|
|
1516
|
-
await fs2.mkdir(
|
|
1747
|
+
await fs2.mkdir(path3.dirname(options.configPath), { recursive: true });
|
|
1517
1748
|
await fs2.writeFile(options.configPath, next);
|
|
1518
1749
|
} catch (error) {
|
|
1519
1750
|
console.warn(
|
|
@@ -1583,7 +1814,7 @@ function isNodeError(error) {
|
|
|
1583
1814
|
|
|
1584
1815
|
// src/plugin/mcpClientConfig/jsonConfig.ts
|
|
1585
1816
|
import fs3 from "fs/promises";
|
|
1586
|
-
import
|
|
1817
|
+
import path4 from "path";
|
|
1587
1818
|
async function updateJsonMcpClientConfig(options) {
|
|
1588
1819
|
try {
|
|
1589
1820
|
const config = await readJsonConfig(options.configPath);
|
|
@@ -1597,7 +1828,7 @@ async function updateJsonMcpClientConfig(options) {
|
|
|
1597
1828
|
}
|
|
1598
1829
|
mcpServers[options.serverName] = { type: "sse", url: options.mcpUrl };
|
|
1599
1830
|
config.mcpServers = mcpServers;
|
|
1600
|
-
await fs3.mkdir(
|
|
1831
|
+
await fs3.mkdir(path4.dirname(options.configPath), { recursive: true });
|
|
1601
1832
|
await fs3.writeFile(
|
|
1602
1833
|
options.configPath,
|
|
1603
1834
|
`${JSON.stringify(config, null, 2)}
|
|
@@ -1647,7 +1878,7 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options)
|
|
|
1647
1878
|
jobs.push(
|
|
1648
1879
|
updateJsonMcpClientConfig({
|
|
1649
1880
|
clientName: "Cursor",
|
|
1650
|
-
configPath:
|
|
1881
|
+
configPath: path5.join(root, ".cursor", "mcp.json"),
|
|
1651
1882
|
mcpUrl: sseUrl,
|
|
1652
1883
|
serverName
|
|
1653
1884
|
})
|
|
@@ -1656,7 +1887,7 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options)
|
|
|
1656
1887
|
if (options.codex) {
|
|
1657
1888
|
jobs.push(
|
|
1658
1889
|
updateCodexMcpClientConfig({
|
|
1659
|
-
configPath:
|
|
1890
|
+
configPath: path5.join(root, ".codex", "config.toml"),
|
|
1660
1891
|
mcpUrl: streamableHttpUrl,
|
|
1661
1892
|
serverName
|
|
1662
1893
|
})
|
|
@@ -1666,7 +1897,7 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options)
|
|
|
1666
1897
|
jobs.push(
|
|
1667
1898
|
updateJsonMcpClientConfig({
|
|
1668
1899
|
clientName: "Claude Code",
|
|
1669
|
-
configPath:
|
|
1900
|
+
configPath: path5.join(root, ".mcp.json"),
|
|
1670
1901
|
mcpUrl: sseUrl,
|
|
1671
1902
|
serverName
|
|
1672
1903
|
})
|
|
@@ -1676,7 +1907,7 @@ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options)
|
|
|
1676
1907
|
jobs.push(
|
|
1677
1908
|
updateJsonMcpClientConfig({
|
|
1678
1909
|
clientName: "Trae",
|
|
1679
|
-
configPath:
|
|
1910
|
+
configPath: path5.join(root, ".trae", "mcp.json"),
|
|
1680
1911
|
mcpUrl: sseUrl,
|
|
1681
1912
|
serverName
|
|
1682
1913
|
})
|
|
@@ -1723,6 +1954,7 @@ function vueMcpNext(userOptions = {}) {
|
|
|
1723
1954
|
(payload) => {
|
|
1724
1955
|
if (isRuntimePageTarget(payload)) {
|
|
1725
1956
|
ctx.pages.upsert(payload);
|
|
1957
|
+
void ctx.hooks.callHook(RUNTIME_PAGE_RECONNECTED_EVENT, payload);
|
|
1726
1958
|
void cdpLifecycle.connectPage(payload);
|
|
1727
1959
|
}
|
|
1728
1960
|
}
|