@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/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 z5 } from "zod";
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: z5.string().optional(),
820
- target: z5.enum(["viewport", "fullPage", "element"]).optional(),
821
- selector: z5.string().optional(),
822
- format: z5.enum(["png", "jpeg", "webp"]).optional(),
823
- prefer: z5.enum(["auto", "cdp", "runtime"]).optional(),
824
- quality: z5.number().optional(),
825
- scale: z5.number().optional(),
826
- snapdom: z5.record(z5.string(), z5.unknown()).optional()
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
- return createToolResponse(result);
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
- return createToolResponse(result);
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 z6 } from "zod";
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: z6.string() }
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: z6.string(),
953
- path: z6.array(z6.string()),
954
- value: z6.string(),
955
- valueType: z6.enum(["string", "number", "boolean", "object", "array"])
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: path5, value, valueType }) => {
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: path5,
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: z6.string() }
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: z6.string() }
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 path4 from "path";
1738
+ import path5 from "path";
1508
1739
 
1509
1740
  // src/plugin/mcpClientConfig/codexConfig.ts
1510
1741
  import fs2 from "fs/promises";
1511
- import path2 from "path";
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(path2.dirname(options.configPath), { recursive: true });
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 path3 from "path";
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(path3.dirname(options.configPath), { recursive: true });
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: path4.join(root, ".cursor", "mcp.json"),
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: path4.join(root, ".codex", "config.toml"),
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: path4.join(root, ".mcp.json"),
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: path4.join(root, ".trae", "mcp.json"),
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
  }