@xiaou66/vite-plugin-vue-mcp-next 1.1.1 → 1.2.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 CHANGED
@@ -15,6 +15,7 @@
15
15
  | Console 日志 | Runtime Hook | CDP 优先,Hook 兜底 | 采集 `log/info/warn/error/debug` 和运行时日志 |
16
16
  | Evaluate 控制台执行 | Runtime Hook | CDP 优先,Hook 兜底 | 默认关闭,必须显式开启 |
17
17
  | Network 请求 | Runtime Hook | CDP 优先,Hook 兜底 | 返回请求 URL、query、body、status、headers、response body |
18
+ | 浏览器存储 | Runtime Hook | CDP 优先,Hook 兜底 | 访问同源 Web Storage、IndexedDB;Cookie 在无 CDP 时回退到 `document.cookie` |
18
19
  | Vue 组件树 | Vue Runtime Bridge | Vue Runtime Bridge | Vue 专属语义不走 CDP |
19
20
  | Vue 组件状态 | Vue Runtime Bridge | Vue Runtime Bridge | 读取和编辑组件状态 |
20
21
  | Router 信息 | Vue Runtime Bridge | Vue Runtime Bridge | 返回当前路由和路由表 |
@@ -548,6 +549,25 @@ interface NetworkRecord {
548
549
 
549
550
  Vue 组件、Router、Pinia 是应用层语义,固定走 Vue Runtime Bridge,不用 CDP 替代。
550
551
 
552
+ ### 浏览器存储
553
+
554
+ 浏览器存储工具组面向调试当前页面的运行时数据。`localStorage`、`sessionStorage` 和 `IndexedDB` 只作用于当前选中页面同源,避免跨站误操作。Cookie 在无 CDP 时通过 `document.cookie` 访问当前页面可见的同源条目;配置 CDP 后可查询浏览器级 Cookie,并能读取 `HttpOnly` 条目,但删除和清空时会跳过 `HttpOnly` 并返回跳过数量。
555
+
556
+ | 资源 | Runtime Hook | CDP |
557
+ | --- | --- | --- |
558
+ | `localStorage` | 读 / 写 / 删 | 读 / 写 / 删 |
559
+ | `sessionStorage` | 读 / 写 / 删 | 读 / 写 / 删 |
560
+ | `IndexedDB` | 同源库和记录操作 | 同源库和记录操作 |
561
+ | `Cookie` | 查询 / 写入 / 删除当前页面可见条目 | 查询 / 写入 / 删除非 `HttpOnly` 条目,`HttpOnly` 仅可查询 |
562
+
563
+ 相关 MCP 工具:
564
+
565
+ - `list_storage`:列出当前页面同源存储和 Cookie;有 CDP 时补充浏览器级 Cookie
566
+ - `get_storage_item`:读取指定 key、IndexedDB 记录或 Cookie
567
+ - `set_storage_item`:写入 Web Storage、IndexedDB 记录或 Cookie
568
+ - `delete_storage_item`:删除 Web Storage、IndexedDB 记录或 Cookie;`HttpOnly` Cookie 仅在 CDP 下可见且删除时会跳过
569
+ - `clear_storage`:清空指定范围,Cookie 清空会跳过 `HttpOnly`
570
+
551
571
  ## 本地验证
552
572
 
553
573
  ### 自动化检查
package/dist/index.cjs CHANGED
@@ -72,6 +72,11 @@ var MCP_TOOL_NAMES = {
72
72
  getNetworkRequests: "get_network_requests",
73
73
  getNetworkRequestDetail: "get_network_request_detail",
74
74
  clearNetworkRequests: "clear_network_requests",
75
+ listStorage: "list_storage",
76
+ getStorageItem: "get_storage_item",
77
+ setStorageItem: "set_storage_item",
78
+ deleteStorageItem: "delete_storage_item",
79
+ clearStorage: "clear_storage",
75
80
  recordPerformance: "record_performance",
76
81
  startPerformanceRecording: "start_performance_recording",
77
82
  stopPerformanceRecording: "stop_performance_recording",
@@ -94,11 +99,6 @@ var RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID = `\0${VIRTUAL_SNAPDOM_LOADER_ID}`;
94
99
  var DEFAULT_MCP_CLIENT_SERVER_NAME = "vite-mcp-next";
95
100
  var LEGACY_MCP_CLIENT_SERVER_NAMES = ["vue-mcp-next"];
96
101
  var RUNTIME_PAGE_RECONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-reconnected";
97
- var RUNTIME_PAGE_CONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-connected";
98
- var RUNTIME_PAGE_DISCONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-disconnected";
99
- var RUNTIME_PAGE_HEARTBEAT_EVENT = "vite-plugin-vue-mcp-next:heartbeat";
100
- var DEFAULT_RUNTIME_PAGE_HEARTBEAT_TIMEOUT_MS = 45e3;
101
- var DEFAULT_RUNTIME_PAGE_HEARTBEAT_SCAN_INTERVAL_MS = 45e3;
102
102
  var DEFAULT_OPTIONS = {
103
103
  mcpPath: DEFAULT_MCP_PATH,
104
104
  host: "localhost",
@@ -1548,9 +1548,431 @@ function getPathname(url) {
1548
1548
  }
1549
1549
  }
1550
1550
 
1551
- // src/mcp/tools/screenshot.ts
1551
+ // src/mcp/tools/storage.ts
1552
1552
  var import_zod7 = require("zod");
1553
1553
 
1554
+ // src/cdp/cdpStorage.ts
1555
+ function createCdpStorageAdapter(client) {
1556
+ return {
1557
+ async manageStorage(request) {
1558
+ try {
1559
+ return await manageCdpStorage(client, request);
1560
+ } catch (error) {
1561
+ return createCdpStorageError(
1562
+ request,
1563
+ error instanceof Error ? error.message : String(error)
1564
+ );
1565
+ }
1566
+ }
1567
+ };
1568
+ }
1569
+ async function manageCdpStorage(client, request) {
1570
+ if (request.scope === "cookie") {
1571
+ return manageCdpCookies(client, request);
1572
+ }
1573
+ if (request.scope === "indexedDB") {
1574
+ return manageCdpIndexedDb(client, request);
1575
+ }
1576
+ return manageCdpDomStorage(client, request);
1577
+ }
1578
+ async function manageCdpCookies(client, request) {
1579
+ if (request.action === "list" || request.action === "get") {
1580
+ const result2 = await client.Storage.getCookies();
1581
+ const cookies2 = result2.cookies.filter(
1582
+ (cookie) => isCookieInOrigin(cookie, request.origin)
1583
+ );
1584
+ return createCdpStorageSuccess(request, {
1585
+ origin: request.origin,
1586
+ cookies: request.action === "get" && request.cookie?.name ? cookies2.filter((cookie) => cookie.name === request.cookie?.name) : cookies2
1587
+ });
1588
+ }
1589
+ if (request.action === "set") {
1590
+ if (!request.cookie?.name) {
1591
+ throw new Error("Cookie name is required for set operation");
1592
+ }
1593
+ await client.Storage.setCookies({
1594
+ cookies: [
1595
+ {
1596
+ name: request.cookie.name,
1597
+ value: request.cookie.value ?? request.value ?? "",
1598
+ url: request.cookie.url ?? request.origin,
1599
+ domain: request.cookie.domain,
1600
+ path: request.cookie.path,
1601
+ httpOnly: request.cookie.httpOnly,
1602
+ secure: request.cookie.secure,
1603
+ sameSite: normalizeCookieSameSite(request.cookie.sameSite),
1604
+ expires: request.cookie.expires
1605
+ }
1606
+ ]
1607
+ });
1608
+ return createCdpStorageSuccess(request, { ok: true });
1609
+ }
1610
+ const result = await client.Storage.getCookies();
1611
+ const cookies = result.cookies.filter(
1612
+ (cookie) => isCookieInOrigin(cookie, request.origin)
1613
+ );
1614
+ const candidates = request.action === "delete" && request.cookie?.name ? cookies.filter((cookie) => cookie.name === request.cookie?.name) : cookies;
1615
+ const deletable = candidates.filter((cookie) => !cookie.httpOnly);
1616
+ const skippedHttpOnlyCount = candidates.length - deletable.length;
1617
+ for (const cookie of deletable) {
1618
+ await client.Network.deleteCookies({
1619
+ name: cookie.name,
1620
+ domain: cookie.domain,
1621
+ path: cookie.path
1622
+ });
1623
+ }
1624
+ return createCdpStorageSuccess(request, {
1625
+ deletedCount: deletable.length,
1626
+ skippedHttpOnlyCount
1627
+ });
1628
+ }
1629
+ async function manageCdpDomStorage(client, request) {
1630
+ const storageId = {
1631
+ securityOrigin: request.origin,
1632
+ isLocalStorage: request.scope === "localStorage"
1633
+ };
1634
+ if (request.action === "list") {
1635
+ const result2 = await client.DOMStorage.getDOMStorageItems({ storageId });
1636
+ return createCdpStorageSuccess(request, {
1637
+ origin: request.origin,
1638
+ scope: request.scope,
1639
+ entries: result2.entries.map(([key, value]) => ({ key, value }))
1640
+ });
1641
+ }
1642
+ if (request.action === "get") {
1643
+ assertStorageKey(request);
1644
+ const result2 = await client.DOMStorage.getDOMStorageItems({ storageId });
1645
+ const entry = result2.entries.find(([key]) => key === request.key);
1646
+ return createCdpStorageSuccess(request, {
1647
+ key: request.key,
1648
+ value: entry?.[1] ?? null
1649
+ });
1650
+ }
1651
+ if (request.action === "set") {
1652
+ assertStorageKey(request);
1653
+ await client.DOMStorage.setDOMStorageItem({
1654
+ storageId,
1655
+ key: request.key,
1656
+ value: request.value ?? ""
1657
+ });
1658
+ return createCdpStorageSuccess(request, { ok: true });
1659
+ }
1660
+ if (request.action === "delete") {
1661
+ assertStorageKey(request);
1662
+ await client.DOMStorage.removeDOMStorageItem({
1663
+ storageId,
1664
+ key: request.key
1665
+ });
1666
+ return createCdpStorageSuccess(request, { ok: true });
1667
+ }
1668
+ const result = await client.DOMStorage.getDOMStorageItems({ storageId });
1669
+ for (const [key] of result.entries) {
1670
+ await client.DOMStorage.removeDOMStorageItem({ storageId, key });
1671
+ }
1672
+ return createCdpStorageSuccess(request, { deletedCount: result.entries.length });
1673
+ }
1674
+ async function manageCdpIndexedDb(client, request) {
1675
+ if (request.action === "list") {
1676
+ const result = await client.IndexedDB.requestDatabaseNames({
1677
+ securityOrigin: request.origin
1678
+ });
1679
+ return createCdpStorageSuccess(request, {
1680
+ origin: request.origin,
1681
+ databases: result.databaseNames
1682
+ });
1683
+ }
1684
+ assertIndexedDbTarget(request);
1685
+ if (request.action === "get") {
1686
+ const result = await client.IndexedDB.requestData({
1687
+ securityOrigin: request.origin,
1688
+ databaseName: request.databaseName,
1689
+ objectStoreName: request.objectStoreName,
1690
+ indexName: request.indexName ?? "",
1691
+ skipCount: 0,
1692
+ pageSize: 100
1693
+ });
1694
+ return createCdpStorageSuccess(request, {
1695
+ entries: result.objectStoreDataEntries,
1696
+ hasMore: result.hasMore
1697
+ });
1698
+ }
1699
+ if (request.action === "delete") {
1700
+ assertStorageKey(request);
1701
+ await client.IndexedDB.deleteObjectStoreEntries({
1702
+ securityOrigin: request.origin,
1703
+ databaseName: request.databaseName,
1704
+ objectStoreName: request.objectStoreName,
1705
+ keyRange: createExactCdpKeyRange(request.key)
1706
+ });
1707
+ return createCdpStorageSuccess(request, { ok: true });
1708
+ }
1709
+ if (request.action === "clear") {
1710
+ if (request.objectStoreName) {
1711
+ await client.IndexedDB.clearObjectStore({
1712
+ securityOrigin: request.origin,
1713
+ databaseName: request.databaseName,
1714
+ objectStoreName: request.objectStoreName
1715
+ });
1716
+ return createCdpStorageSuccess(request, { ok: true });
1717
+ }
1718
+ await client.IndexedDB.deleteDatabase({
1719
+ securityOrigin: request.origin,
1720
+ databaseName: request.databaseName
1721
+ });
1722
+ return createCdpStorageSuccess(request, { ok: true });
1723
+ }
1724
+ return createCdpStorageError(
1725
+ request,
1726
+ "IndexedDB set operation requires runtime bridge"
1727
+ );
1728
+ }
1729
+ function isCookieInOrigin(cookie, origin) {
1730
+ const hostname = new URL(origin).hostname;
1731
+ const domain = cookie.domain?.replace(/^\./, "");
1732
+ return Boolean(domain && (hostname === domain || hostname.endsWith(`.${domain}`)));
1733
+ }
1734
+ function normalizeCookieSameSite(sameSite) {
1735
+ if (!sameSite) {
1736
+ return void 0;
1737
+ }
1738
+ if (sameSite === "strict") {
1739
+ return "Strict";
1740
+ }
1741
+ if (sameSite === "lax") {
1742
+ return "Lax";
1743
+ }
1744
+ return "None";
1745
+ }
1746
+ function createExactCdpKeyRange(key) {
1747
+ const parsedKey = parseJsonValue(key);
1748
+ return {
1749
+ lower: parsedKey,
1750
+ upper: parsedKey,
1751
+ lowerOpen: false,
1752
+ upperOpen: false
1753
+ };
1754
+ }
1755
+ function parseJsonValue(value) {
1756
+ try {
1757
+ return JSON.parse(value);
1758
+ } catch {
1759
+ return value;
1760
+ }
1761
+ }
1762
+ function assertStorageKey(request) {
1763
+ if (!request.key) {
1764
+ throw new Error("Storage key is required for this operation");
1765
+ }
1766
+ }
1767
+ function assertIndexedDbTarget(request) {
1768
+ if (!request.databaseName || !request.objectStoreName) {
1769
+ throw new Error("IndexedDB databaseName and objectStoreName are required");
1770
+ }
1771
+ }
1772
+ function createCdpStorageSuccess(request, data) {
1773
+ return {
1774
+ ok: true,
1775
+ source: "cdp",
1776
+ action: request.action,
1777
+ scope: request.scope,
1778
+ data
1779
+ };
1780
+ }
1781
+ function createCdpStorageError(request, error) {
1782
+ return {
1783
+ ok: false,
1784
+ source: "cdp",
1785
+ action: request.action,
1786
+ scope: request.scope,
1787
+ error
1788
+ };
1789
+ }
1790
+
1791
+ // src/mcp/tools/storage.ts
1792
+ function registerStorageTools(server, ctx) {
1793
+ registerStorageTool(server, MCP_TOOL_NAMES.listStorage, {
1794
+ description: "List same-origin storage and CDP cookies when available.",
1795
+ inputSchema: {
1796
+ pageId: import_zod7.z.string().optional()
1797
+ },
1798
+ action: "list",
1799
+ handler: (input) => handleListStorage(ctx, input.pageId)
1800
+ });
1801
+ registerStorageTool(server, MCP_TOOL_NAMES.getStorageItem, {
1802
+ description: "Read one storage entry.",
1803
+ inputSchema: createStorageInputSchema(),
1804
+ action: "get",
1805
+ handler: (input) => handleStorageAction(ctx, { ...input, action: "get" })
1806
+ });
1807
+ registerStorageTool(server, MCP_TOOL_NAMES.setStorageItem, {
1808
+ description: "Write one storage entry.",
1809
+ inputSchema: createStorageInputSchema(),
1810
+ action: "set",
1811
+ handler: (input) => handleStorageAction(ctx, { ...input, action: "set" })
1812
+ });
1813
+ registerStorageTool(server, MCP_TOOL_NAMES.deleteStorageItem, {
1814
+ description: "Delete one storage entry.",
1815
+ inputSchema: createStorageInputSchema(),
1816
+ action: "delete",
1817
+ handler: (input) => handleStorageAction(ctx, { ...input, action: "delete" })
1818
+ });
1819
+ registerStorageTool(server, MCP_TOOL_NAMES.clearStorage, {
1820
+ description: "Clear one storage scope.",
1821
+ inputSchema: createStorageInputSchema(),
1822
+ action: "clear",
1823
+ handler: (input) => handleStorageAction(ctx, { ...input, action: "clear" })
1824
+ });
1825
+ }
1826
+ function registerStorageTool(server, name, options) {
1827
+ server.registerTool(
1828
+ name,
1829
+ {
1830
+ description: options.description,
1831
+ inputSchema: options.inputSchema
1832
+ },
1833
+ (async (input) => options.handler(
1834
+ input
1835
+ ))
1836
+ );
1837
+ }
1838
+ function createStorageInputSchema() {
1839
+ return {
1840
+ pageId: import_zod7.z.string().optional(),
1841
+ scope: import_zod7.z.enum(["localStorage", "sessionStorage", "indexedDB", "cookie"]).optional(),
1842
+ key: import_zod7.z.string().optional(),
1843
+ value: import_zod7.z.string().optional(),
1844
+ databaseName: import_zod7.z.string().optional(),
1845
+ objectStoreName: import_zod7.z.string().optional(),
1846
+ indexName: import_zod7.z.string().optional(),
1847
+ cookie: import_zod7.z.object({
1848
+ name: import_zod7.z.string(),
1849
+ value: import_zod7.z.string().optional(),
1850
+ domain: import_zod7.z.string().optional(),
1851
+ path: import_zod7.z.string().optional(),
1852
+ url: import_zod7.z.string().optional(),
1853
+ httpOnly: import_zod7.z.boolean().optional(),
1854
+ secure: import_zod7.z.boolean().optional(),
1855
+ sameSite: import_zod7.z.enum(["strict", "lax", "none"]).optional(),
1856
+ expires: import_zod7.z.number().optional()
1857
+ }).optional()
1858
+ };
1859
+ }
1860
+ async function handleListStorage(ctx, pageId) {
1861
+ let baseRequest;
1862
+ try {
1863
+ baseRequest = createStorageRequest(ctx, {
1864
+ pageId,
1865
+ action: "list",
1866
+ scope: "localStorage"
1867
+ });
1868
+ } catch (error) {
1869
+ return createToolError(error instanceof Error ? error.message : String(error));
1870
+ }
1871
+ const [localStorage, sessionStorage, indexedDB] = await Promise.all([
1872
+ requestRuntimeStorage(ctx, { ...baseRequest, scope: "localStorage" }),
1873
+ requestRuntimeStorage(ctx, { ...baseRequest, scope: "sessionStorage" }),
1874
+ requestRuntimeStorage(ctx, { ...baseRequest, scope: "indexedDB" })
1875
+ ]);
1876
+ const cookie = await listCookiesIfCdpAvailable(ctx, baseRequest, pageId);
1877
+ return createToolResponse({
1878
+ ok: true,
1879
+ origin: baseRequest.origin,
1880
+ localStorage: extractStorageData(localStorage),
1881
+ sessionStorage: extractStorageData(sessionStorage),
1882
+ indexedDB: extractStorageData(indexedDB),
1883
+ cookie
1884
+ });
1885
+ }
1886
+ async function handleStorageAction(ctx, input) {
1887
+ let request;
1888
+ try {
1889
+ request = createStorageRequest(ctx, input);
1890
+ } catch (error) {
1891
+ return createToolError(error instanceof Error ? error.message : String(error));
1892
+ }
1893
+ const cdp = await connectCdpForPage(ctx, input.pageId);
1894
+ if (cdp && shouldUseCdpStorage(request)) {
1895
+ try {
1896
+ const result2 = await createCdpStorageAdapter(cdp.client).manageStorage(
1897
+ request
1898
+ );
1899
+ return createToolResponse(result2);
1900
+ } finally {
1901
+ await closeCdpClient(cdp.client);
1902
+ }
1903
+ }
1904
+ const result = await requestRuntimeStorage(ctx, request);
1905
+ return createToolResponse(result);
1906
+ }
1907
+ async function requestRuntimeStorage(ctx, request) {
1908
+ return requestRuntimeData(ctx, (event) => {
1909
+ void ctx.rpcServer?.manageStorage({
1910
+ ...request,
1911
+ event
1912
+ });
1913
+ });
1914
+ }
1915
+ async function listCookiesIfCdpAvailable(ctx, request, pageId) {
1916
+ if (!hasCdpConfig2(ctx)) {
1917
+ return extractStorageData(
1918
+ await requestRuntimeStorage(ctx, { ...request, scope: "cookie" })
1919
+ );
1920
+ }
1921
+ const cdp = await connectCdpForPage(ctx, pageId);
1922
+ if (!cdp) {
1923
+ return extractStorageData(
1924
+ await requestRuntimeStorage(ctx, { ...request, scope: "cookie" })
1925
+ );
1926
+ }
1927
+ try {
1928
+ const result = await createCdpStorageAdapter(cdp.client).manageStorage({
1929
+ ...request,
1930
+ scope: "cookie"
1931
+ });
1932
+ return extractStorageData(result);
1933
+ } finally {
1934
+ await closeCdpClient(cdp.client);
1935
+ }
1936
+ }
1937
+ function extractStorageData(result) {
1938
+ if (!isStorageResultRecord(result)) {
1939
+ return result;
1940
+ }
1941
+ return result.data ?? result;
1942
+ }
1943
+ function isStorageResultRecord(value) {
1944
+ return typeof value === "object" && value !== null && "ok" in value;
1945
+ }
1946
+ function shouldUseCdpStorage(request) {
1947
+ return request.scope === "cookie";
1948
+ }
1949
+ function createStorageRequest(ctx, input) {
1950
+ const page = resolvePageTarget(ctx, input.pageId);
1951
+ const origin = new URL(page.url).origin;
1952
+ return {
1953
+ event: "",
1954
+ pageId: page.pageId,
1955
+ origin,
1956
+ action: input.action,
1957
+ scope: normalizeScope(input.scope),
1958
+ key: input.key,
1959
+ value: input.value,
1960
+ databaseName: input.databaseName,
1961
+ objectStoreName: input.objectStoreName,
1962
+ indexName: input.indexName,
1963
+ cookie: input.cookie
1964
+ };
1965
+ }
1966
+ function normalizeScope(scope) {
1967
+ return scope ?? "localStorage";
1968
+ }
1969
+ function hasCdpConfig2(ctx) {
1970
+ return Boolean(ctx.options.cdp.browserUrl || ctx.options.cdp.wsEndpoint);
1971
+ }
1972
+
1973
+ // src/mcp/tools/screenshot.ts
1974
+ var import_zod8 = require("zod");
1975
+
1554
1976
  // src/cdp/cdpScreenshot.ts
1555
1977
  async function cdpCaptureScreenshot(options) {
1556
1978
  if (options.target === "element") {
@@ -1709,14 +2131,14 @@ function createProjectRelativePath(ctx, filePath) {
1709
2131
  var DEFAULT_SCREENSHOT_TARGET = "viewport";
1710
2132
  var DEFAULT_SCREENSHOT_FORMAT = "png";
1711
2133
  var screenshotInputSchema = {
1712
- pageId: import_zod7.z.string().optional(),
1713
- target: import_zod7.z.enum(["viewport", "fullPage", "element"]).optional(),
1714
- selector: import_zod7.z.string().optional(),
1715
- format: import_zod7.z.enum(["png", "jpeg", "webp"]).optional(),
1716
- prefer: import_zod7.z.enum(["auto", "cdp", "runtime"]).optional(),
1717
- quality: import_zod7.z.number().optional(),
1718
- scale: import_zod7.z.number().optional(),
1719
- snapdom: import_zod7.z.record(import_zod7.z.string(), import_zod7.z.unknown()).optional()
2134
+ pageId: import_zod8.z.string().optional(),
2135
+ target: import_zod8.z.enum(["viewport", "fullPage", "element"]).optional(),
2136
+ selector: import_zod8.z.string().optional(),
2137
+ format: import_zod8.z.enum(["png", "jpeg", "webp"]).optional(),
2138
+ prefer: import_zod8.z.enum(["auto", "cdp", "runtime"]).optional(),
2139
+ quality: import_zod8.z.number().optional(),
2140
+ scale: import_zod8.z.number().optional(),
2141
+ snapdom: import_zod8.z.record(import_zod8.z.string(), import_zod8.z.unknown()).optional()
1720
2142
  };
1721
2143
  function registerScreenshotTools(server, ctx) {
1722
2144
  server.registerTool(
@@ -1836,7 +2258,7 @@ function isScreenshotImagePayload(result) {
1836
2258
 
1837
2259
  // src/mcp/tools/vue.ts
1838
2260
  var import_nanoid3 = require("nanoid");
1839
- var import_zod8 = require("zod");
2261
+ var import_zod9 = require("zod");
1840
2262
  function registerVueTools(server, ctx) {
1841
2263
  server.registerTool(
1842
2264
  MCP_TOOL_NAMES.getComponentTree,
@@ -1849,7 +2271,7 @@ function registerVueTools(server, ctx) {
1849
2271
  MCP_TOOL_NAMES.getComponentState,
1850
2272
  {
1851
2273
  description: "Get Vue component state.",
1852
- inputSchema: { componentName: import_zod8.z.string() }
2274
+ inputSchema: { componentName: import_zod9.z.string() }
1853
2275
  },
1854
2276
  async ({ componentName }) => requestVueData(ctx, (event) => {
1855
2277
  void ctx.rpcServer?.getInspectorState({ event, componentName });
@@ -1860,10 +2282,10 @@ function registerVueTools(server, ctx) {
1860
2282
  {
1861
2283
  description: "Edit Vue component state.",
1862
2284
  inputSchema: {
1863
- componentName: import_zod8.z.string(),
1864
- path: import_zod8.z.array(import_zod8.z.string()),
1865
- value: import_zod8.z.string(),
1866
- valueType: import_zod8.z.enum(["string", "number", "boolean", "object", "array"])
2285
+ componentName: import_zod9.z.string(),
2286
+ path: import_zod9.z.array(import_zod9.z.string()),
2287
+ value: import_zod9.z.string(),
2288
+ valueType: import_zod9.z.enum(["string", "number", "boolean", "object", "array"])
1867
2289
  }
1868
2290
  },
1869
2291
  ({ componentName, path: path8, value, valueType }) => {
@@ -1883,7 +2305,7 @@ function registerVueTools(server, ctx) {
1883
2305
  MCP_TOOL_NAMES.highlightComponent,
1884
2306
  {
1885
2307
  description: "Highlight a Vue component.",
1886
- inputSchema: { componentName: import_zod8.z.string() }
2308
+ inputSchema: { componentName: import_zod9.z.string() }
1887
2309
  },
1888
2310
  ({ componentName }) => {
1889
2311
  if (!ctx.rpcServer) {
@@ -1911,7 +2333,7 @@ function registerVueTools(server, ctx) {
1911
2333
  MCP_TOOL_NAMES.getPiniaState,
1912
2334
  {
1913
2335
  description: "Get Pinia store state.",
1914
- inputSchema: { storeName: import_zod8.z.string() }
2336
+ inputSchema: { storeName: import_zod9.z.string() }
1915
2337
  },
1916
2338
  async ({ storeName }) => requestVueData(ctx, (event) => {
1917
2339
  void ctx.rpcServer?.getPiniaState({ event, storeName });
@@ -1958,6 +2380,7 @@ function createMcpServer(ctx, vite) {
1958
2380
  registerConsoleTools(server, ctx);
1959
2381
  registerEvaluateTools(server, ctx);
1960
2382
  registerNetworkTools(server, ctx);
2383
+ registerStorageTools(server, ctx);
1961
2384
  registerPerformanceTools(server, ctx);
1962
2385
  registerVueTools(server, ctx);
1963
2386
  return server;
@@ -2046,6 +2469,10 @@ function createServerVueRuntimeRpc(ctx) {
2046
2469
  onScreenshotTaken: (event, data) => {
2047
2470
  void ctx.hooks.callHook(event, data);
2048
2471
  },
2472
+ manageStorage: () => void 0,
2473
+ onStorageUpdated: (event, data) => {
2474
+ void ctx.hooks.callHook(event, data);
2475
+ },
2049
2476
  recordPerformance: () => void 0,
2050
2477
  onPerformanceRecorded: (event, data) => {
2051
2478
  void ctx.hooks.callHook(event, data);
@@ -2954,53 +3381,32 @@ function vueMcpNext(userOptions = {}) {
2954
3381
  () => createMcpServer(ctx, server),
2955
3382
  server
2956
3383
  );
2957
- const lastSeenAt = /* @__PURE__ */ new Map();
2958
- const heartbeatTimer = setInterval(() => {
2959
- const now = Date.now();
2960
- for (const [pageId, seenAt] of lastSeenAt) {
2961
- const target = ctx.pages.get(pageId);
2962
- if (!target || target.source !== "runtime" || !target.connected) {
2963
- lastSeenAt.delete(pageId);
2964
- continue;
2965
- }
2966
- if (now - seenAt >= DEFAULT_RUNTIME_PAGE_HEARTBEAT_TIMEOUT_MS) {
2967
- ctx.pages.disconnect(pageId, now);
2968
- lastSeenAt.delete(pageId);
3384
+ server.ws.on(
3385
+ "vite-plugin-vue-mcp-next:page-connected",
3386
+ (payload) => {
3387
+ if (isRuntimePageTarget(payload)) {
3388
+ ctx.pages.upsert(payload);
3389
+ void ctx.hooks.callHook(RUNTIME_PAGE_RECONNECTED_EVENT, payload);
3390
+ void cdpLifecycle.connectPage(payload);
2969
3391
  }
2970
3392
  }
2971
- }, DEFAULT_RUNTIME_PAGE_HEARTBEAT_SCAN_INTERVAL_MS);
2972
- server.ws.on(RUNTIME_PAGE_CONNECTED_EVENT, (payload) => {
2973
- if (isRuntimePageTarget(payload)) {
2974
- ctx.pages.upsert(payload);
2975
- lastSeenAt.set(payload.pageId, Date.now());
2976
- void ctx.hooks.callHook(RUNTIME_PAGE_RECONNECTED_EVENT, payload);
2977
- void cdpLifecycle.connectPage(payload);
2978
- }
2979
- });
2980
- server.ws.on(RUNTIME_PAGE_HEARTBEAT_EVENT, (payload) => {
2981
- if (isRuntimeHeartbeatTarget(payload)) {
2982
- const target = ctx.pages.get(payload.pageId);
2983
- if (target?.source === "runtime" && target.connected) {
2984
- lastSeenAt.set(payload.pageId, payload.timestamp);
3393
+ );
3394
+ server.ws.on(
3395
+ "vite-plugin-vue-mcp-next:console-record",
3396
+ (payload) => {
3397
+ if (isConsoleRecord(payload)) {
3398
+ ctx.consoleRecords.push(payload);
2985
3399
  }
2986
3400
  }
2987
- });
2988
- server.ws.on(RUNTIME_PAGE_DISCONNECTED_EVENT, (payload) => {
2989
- if (isRuntimeDisconnectTarget(payload)) {
2990
- ctx.pages.disconnect(payload.pageId);
2991
- lastSeenAt.delete(payload.pageId);
2992
- }
2993
- });
2994
- server.ws.on("vite-plugin-vue-mcp-next:console-record", (payload) => {
2995
- if (isConsoleRecord(payload)) {
2996
- ctx.consoleRecords.push(payload);
2997
- }
2998
- });
2999
- server.ws.on("vite-plugin-vue-mcp-next:network-record", (payload) => {
3000
- if (isNetworkRecord(payload)) {
3001
- ctx.networkRecords.push(payload);
3401
+ );
3402
+ server.ws.on(
3403
+ "vite-plugin-vue-mcp-next:network-record",
3404
+ (payload) => {
3405
+ if (isNetworkRecord(payload)) {
3406
+ ctx.networkRecords.push(payload);
3407
+ }
3002
3408
  }
3003
- });
3409
+ );
3004
3410
  server.ws.on(
3005
3411
  "vite-plugin-vue-mcp-next:performance-record",
3006
3412
  (payload) => {
@@ -3030,7 +3436,6 @@ function vueMcpNext(userOptions = {}) {
3030
3436
  }, 300);
3031
3437
  }
3032
3438
  server.httpServer?.once("close", () => {
3033
- clearInterval(heartbeatTimer);
3034
3439
  void cdpLifecycle.closeAll();
3035
3440
  });
3036
3441
  },
@@ -3055,20 +3460,6 @@ function isRuntimePageTarget(payload) {
3055
3460
  const target = payload;
3056
3461
  return target.source === "runtime" && typeof target.pageId === "string" && typeof target.url === "string" && typeof target.pathname === "string" && typeof target.connected === "boolean" && (target.runtimeClientId === void 0 || typeof target.runtimeClientId === "string");
3057
3462
  }
3058
- function isRuntimeHeartbeatTarget(payload) {
3059
- if (!payload || typeof payload !== "object") {
3060
- return false;
3061
- }
3062
- const target = payload;
3063
- return typeof target.pageId === "string" && typeof target.timestamp === "number";
3064
- }
3065
- function isRuntimeDisconnectTarget(payload) {
3066
- if (!payload || typeof payload !== "object") {
3067
- return false;
3068
- }
3069
- const target = payload;
3070
- return typeof target.pageId === "string";
3071
- }
3072
3463
  function isConsoleRecord(payload) {
3073
3464
  if (!payload || typeof payload !== "object") {
3074
3465
  return false;