@xiaou66/vite-plugin-vue-mcp-next 1.0.0 → 1.0.2

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
@@ -29,10 +29,12 @@ interface VueMcpNextOptions {
29
29
  readonly host?: string;
30
30
  /** 是否在 Vite 启动日志中打印 MCP 地址,默认开启方便开发者复制到 MCP 客户端。 */
31
31
  readonly printUrl?: boolean;
32
- /** 是否写入 Cursor MCP 配置;只在 `.cursor` 已存在时生效,避免擅自创建编辑器配置目录。 */
32
+ /** 是否写入 Cursor MCP 配置;默认只在 `.cursor` 已存在时写入,显式 `true` 会创建配置。 */
33
33
  readonly updateCursorMcpJson?: boolean | CursorMcpConfig;
34
- /** 是否写入常见 AI 客户端的项目级 MCP 配置,默认启用 Cursor、Codex、Claude Code 和 Trae。 */
34
+ /** 是否写入常见 AI 客户端的项目级 MCP 配置;默认按项目已有客户端入口自动探测。 */
35
35
  readonly mcpClients?: McpClientConfigOptions;
36
+ /** AI 使用指南自动安装配置,用于让 AI 客户端知道何时以及如何使用本 MCP。 */
37
+ readonly skill?: SkillConfigOptions;
36
38
  /** 非 HTML 入口的运行时脚本注入点,用于兼容不直接使用 `index.html` 的项目。 */
37
39
  readonly appendTo?: string | RegExp;
38
40
  /** 通用 DevTools 能力配置;Vue 专属能力不受该配置影响,始终走 Vue Runtime Bridge。 */
@@ -54,7 +56,7 @@ interface VueMcpNextOptions {
54
56
  * 该配置只负责本地开发体验,不应强行修改未启用 Cursor 的项目。
55
57
  */
56
58
  interface CursorMcpConfig {
57
- /** 是否启用 Cursor 配置写入,适合团队项目显式关闭自动改配置。 */
59
+ /** 是否启用 Cursor 配置写入;显式启用会创建 `.cursor/mcp.json`,显式关闭会跳过。 */
58
60
  readonly enabled: boolean;
59
61
  /** Cursor 展示的 MCP 服务名,用于同一项目存在多个 MCP 服务时避免冲突。 */
60
62
  readonly serverName?: string;
@@ -66,17 +68,27 @@ interface CursorMcpConfig {
66
68
  * 集中配置可以避免为每个客户端暴露一组重复选项。
67
69
  */
68
70
  interface McpClientConfigOptions {
69
- /** 是否写入 Cursor 的 `.cursor/mcp.json`,适合 Cursor 用户开箱即用。 */
71
+ /** 是否写入 Cursor 的 `.cursor/mcp.json`;默认只在 `.cursor` 目录已存在时自动写入。 */
70
72
  readonly cursor?: boolean;
71
- /** 是否写入 Codex 的 `.codex/config.toml`,适合 Codex 项目级 MCP 配置。 */
73
+ /** 是否写入 Codex 的 `.codex/config.toml`;默认只在 `.codex` 目录已存在时自动写入。 */
72
74
  readonly codex?: boolean;
73
- /** 是否写入 Claude Code 的 `.mcp.json`,采用官方项目级 MCP 配置入口。 */
75
+ /** 是否写入 Claude Code 的 `.mcp.json`;默认只在根目录 `.mcp.json` 已存在时自动写入。 */
74
76
  readonly claudeCode?: boolean;
75
- /** 是否写入 Trae 的 `.trae/mcp.json`,与 Trae 项目级 MCP 文档保持一致。 */
77
+ /** 是否写入 Trae 的 `.trae/mcp.json`;默认只在 `.trae` 目录已存在时自动写入。 */
76
78
  readonly trae?: boolean;
77
79
  /** MCP 客户端中展示的服务名,同一项目存在多个 MCP 服务时用于避免冲突。 */
78
80
  readonly serverName?: string;
79
81
  }
82
+ /**
83
+ * AI 使用指南自动安装配置。
84
+ *
85
+ * 该配置只影响项目级 skill/rule 文件写入,不影响 MCP 服务是否启动;
86
+ * 独立开关可以让不希望插件修改 AI 客户端上下文的项目完全关闭自动安装。
87
+ */
88
+ interface SkillConfigOptions {
89
+ /** 是否在 Vite dev server 启动时自动安装 AI 使用指南,默认开启。 */
90
+ readonly autoConfig?: boolean;
91
+ }
80
92
  /**
81
93
  * 通用 DevTools 能力的运行模式。
82
94
  *
@@ -320,6 +332,8 @@ interface ResolvedVueMcpNextOptions {
320
332
  readonly updateCursorMcpJson: Required<CursorMcpConfig>;
321
333
  /** 已规范化的多客户端 MCP 配置写入策略,内部写入器只读取该对象。 */
322
334
  readonly mcpClients: Required<McpClientConfigOptions>;
335
+ /** 已补齐默认值的 AI 使用指南自动安装配置。 */
336
+ readonly skill: Required<SkillConfigOptions>;
323
337
  /** 非 HTML 入口注入点,未配置时 HTML 注入路径生效。 */
324
338
  readonly appendTo?: string | RegExp;
325
339
  /** 已补齐默认值的通用 DevTools 配置。 */
@@ -569,4 +583,4 @@ interface VueRuntimeRpc {
569
583
  */
570
584
  declare function vueMcpNext(userOptions?: VueMcpNextOptions): Plugin;
571
585
 
572
- export { type CdpOptions, type ConsoleOptions, type ConsoleRecord, type CursorMcpConfig, type DomOptions, type EvaluateOptions, type McpClientConfigOptions, type NetworkOptions, type NetworkRecord, type PageTarget, type ResolvedVueMcpNextOptions, type RuntimeMode, type RuntimeOptions, type VueMcpNextContext, type VueMcpNextOptions, vueMcpNext as default, vueMcpNext };
586
+ export { type CdpOptions, type ConsoleOptions, type ConsoleRecord, type CursorMcpConfig, type DomOptions, type EvaluateOptions, type McpClientConfigOptions, type NetworkOptions, type NetworkRecord, type PageTarget, type ResolvedVueMcpNextOptions, type RuntimeMode, type RuntimeOptions, type SkillConfigOptions, type VueMcpNextContext, type VueMcpNextOptions, vueMcpNext as default, vueMcpNext };
package/dist/index.d.ts CHANGED
@@ -29,10 +29,12 @@ interface VueMcpNextOptions {
29
29
  readonly host?: string;
30
30
  /** 是否在 Vite 启动日志中打印 MCP 地址,默认开启方便开发者复制到 MCP 客户端。 */
31
31
  readonly printUrl?: boolean;
32
- /** 是否写入 Cursor MCP 配置;只在 `.cursor` 已存在时生效,避免擅自创建编辑器配置目录。 */
32
+ /** 是否写入 Cursor MCP 配置;默认只在 `.cursor` 已存在时写入,显式 `true` 会创建配置。 */
33
33
  readonly updateCursorMcpJson?: boolean | CursorMcpConfig;
34
- /** 是否写入常见 AI 客户端的项目级 MCP 配置,默认启用 Cursor、Codex、Claude Code 和 Trae。 */
34
+ /** 是否写入常见 AI 客户端的项目级 MCP 配置;默认按项目已有客户端入口自动探测。 */
35
35
  readonly mcpClients?: McpClientConfigOptions;
36
+ /** AI 使用指南自动安装配置,用于让 AI 客户端知道何时以及如何使用本 MCP。 */
37
+ readonly skill?: SkillConfigOptions;
36
38
  /** 非 HTML 入口的运行时脚本注入点,用于兼容不直接使用 `index.html` 的项目。 */
37
39
  readonly appendTo?: string | RegExp;
38
40
  /** 通用 DevTools 能力配置;Vue 专属能力不受该配置影响,始终走 Vue Runtime Bridge。 */
@@ -54,7 +56,7 @@ interface VueMcpNextOptions {
54
56
  * 该配置只负责本地开发体验,不应强行修改未启用 Cursor 的项目。
55
57
  */
56
58
  interface CursorMcpConfig {
57
- /** 是否启用 Cursor 配置写入,适合团队项目显式关闭自动改配置。 */
59
+ /** 是否启用 Cursor 配置写入;显式启用会创建 `.cursor/mcp.json`,显式关闭会跳过。 */
58
60
  readonly enabled: boolean;
59
61
  /** Cursor 展示的 MCP 服务名,用于同一项目存在多个 MCP 服务时避免冲突。 */
60
62
  readonly serverName?: string;
@@ -66,17 +68,27 @@ interface CursorMcpConfig {
66
68
  * 集中配置可以避免为每个客户端暴露一组重复选项。
67
69
  */
68
70
  interface McpClientConfigOptions {
69
- /** 是否写入 Cursor 的 `.cursor/mcp.json`,适合 Cursor 用户开箱即用。 */
71
+ /** 是否写入 Cursor 的 `.cursor/mcp.json`;默认只在 `.cursor` 目录已存在时自动写入。 */
70
72
  readonly cursor?: boolean;
71
- /** 是否写入 Codex 的 `.codex/config.toml`,适合 Codex 项目级 MCP 配置。 */
73
+ /** 是否写入 Codex 的 `.codex/config.toml`;默认只在 `.codex` 目录已存在时自动写入。 */
72
74
  readonly codex?: boolean;
73
- /** 是否写入 Claude Code 的 `.mcp.json`,采用官方项目级 MCP 配置入口。 */
75
+ /** 是否写入 Claude Code 的 `.mcp.json`;默认只在根目录 `.mcp.json` 已存在时自动写入。 */
74
76
  readonly claudeCode?: boolean;
75
- /** 是否写入 Trae 的 `.trae/mcp.json`,与 Trae 项目级 MCP 文档保持一致。 */
77
+ /** 是否写入 Trae 的 `.trae/mcp.json`;默认只在 `.trae` 目录已存在时自动写入。 */
76
78
  readonly trae?: boolean;
77
79
  /** MCP 客户端中展示的服务名,同一项目存在多个 MCP 服务时用于避免冲突。 */
78
80
  readonly serverName?: string;
79
81
  }
82
+ /**
83
+ * AI 使用指南自动安装配置。
84
+ *
85
+ * 该配置只影响项目级 skill/rule 文件写入,不影响 MCP 服务是否启动;
86
+ * 独立开关可以让不希望插件修改 AI 客户端上下文的项目完全关闭自动安装。
87
+ */
88
+ interface SkillConfigOptions {
89
+ /** 是否在 Vite dev server 启动时自动安装 AI 使用指南,默认开启。 */
90
+ readonly autoConfig?: boolean;
91
+ }
80
92
  /**
81
93
  * 通用 DevTools 能力的运行模式。
82
94
  *
@@ -320,6 +332,8 @@ interface ResolvedVueMcpNextOptions {
320
332
  readonly updateCursorMcpJson: Required<CursorMcpConfig>;
321
333
  /** 已规范化的多客户端 MCP 配置写入策略,内部写入器只读取该对象。 */
322
334
  readonly mcpClients: Required<McpClientConfigOptions>;
335
+ /** 已补齐默认值的 AI 使用指南自动安装配置。 */
336
+ readonly skill: Required<SkillConfigOptions>;
323
337
  /** 非 HTML 入口注入点,未配置时 HTML 注入路径生效。 */
324
338
  readonly appendTo?: string | RegExp;
325
339
  /** 已补齐默认值的通用 DevTools 配置。 */
@@ -569,4 +583,4 @@ interface VueRuntimeRpc {
569
583
  */
570
584
  declare function vueMcpNext(userOptions?: VueMcpNextOptions): Plugin;
571
585
 
572
- export { type CdpOptions, type ConsoleOptions, type ConsoleRecord, type CursorMcpConfig, type DomOptions, type EvaluateOptions, type McpClientConfigOptions, type NetworkOptions, type NetworkRecord, type PageTarget, type ResolvedVueMcpNextOptions, type RuntimeMode, type RuntimeOptions, type VueMcpNextContext, type VueMcpNextOptions, vueMcpNext as default, vueMcpNext };
586
+ export { type CdpOptions, type ConsoleOptions, type ConsoleRecord, type CursorMcpConfig, type DomOptions, type EvaluateOptions, type McpClientConfigOptions, type NetworkOptions, type NetworkRecord, type PageTarget, type ResolvedVueMcpNextOptions, type RuntimeMode, type RuntimeOptions, type SkillConfigOptions, type VueMcpNextContext, type VueMcpNextOptions, vueMcpNext as default, vueMcpNext };
package/dist/index.js CHANGED
@@ -44,7 +44,8 @@ var VIRTUAL_SCREENSHOT_CONFIG_ID = "virtual:vite-plugin-vue-mcp-next/screenshot-
44
44
  var RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID = `\0${VIRTUAL_SCREENSHOT_CONFIG_ID}`;
45
45
  var VIRTUAL_SNAPDOM_LOADER_ID = "virtual:vite-plugin-vue-mcp-next/snapdom-loader";
46
46
  var RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID = `\0${VIRTUAL_SNAPDOM_LOADER_ID}`;
47
- var DEFAULT_MCP_CLIENT_SERVER_NAME = "vue-mcp-next";
47
+ var DEFAULT_MCP_CLIENT_SERVER_NAME = "vite-mcp-next";
48
+ var LEGACY_MCP_CLIENT_SERVER_NAMES = ["vue-mcp-next"];
48
49
  var RUNTIME_PAGE_RECONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-reconnected";
49
50
  var DEFAULT_OPTIONS = {
50
51
  mcpPath: DEFAULT_MCP_PATH,
@@ -61,6 +62,9 @@ var DEFAULT_OPTIONS = {
61
62
  trae: true,
62
63
  serverName: DEFAULT_MCP_CLIENT_SERVER_NAME
63
64
  },
65
+ skill: {
66
+ autoConfig: true
67
+ },
64
68
  runtime: {
65
69
  mode: "auto",
66
70
  evaluate: {
@@ -118,6 +122,10 @@ function mergeOptions(options = {}) {
118
122
  ...options,
119
123
  updateCursorMcpJson: cursorConfig,
120
124
  mcpClients,
125
+ skill: {
126
+ ...DEFAULT_OPTIONS.skill,
127
+ ...options.skill
128
+ },
121
129
  runtime: {
122
130
  ...DEFAULT_OPTIONS.runtime,
123
131
  ...options.runtime,
@@ -1146,13 +1154,13 @@ function registerVueTools(server, ctx) {
1146
1154
  valueType: z7.enum(["string", "number", "boolean", "object", "array"])
1147
1155
  }
1148
1156
  },
1149
- ({ componentName, path: path6, value, valueType }) => {
1157
+ ({ componentName, path: path8, value, valueType }) => {
1150
1158
  if (!ctx.rpcServer) {
1151
1159
  return vueBridgeUnavailable();
1152
1160
  }
1153
1161
  void ctx.rpcServer.editComponentState({
1154
1162
  componentName,
1155
- path: path6,
1163
+ path: path8,
1156
1164
  value,
1157
1165
  valueType
1158
1166
  });
@@ -1734,6 +1742,7 @@ function getPluginPath(plugin) {
1734
1742
  }
1735
1743
 
1736
1744
  // src/plugin/mcpClientConfig/index.ts
1745
+ import fs4 from "fs/promises";
1737
1746
  import path5 from "path";
1738
1747
 
1739
1748
  // src/plugin/mcpClientConfig/codexConfig.ts
@@ -1753,10 +1762,18 @@ async function updateCodexMcpClientConfig(options) {
1753
1762
  }
1754
1763
  function replaceOrAppendOwnedBlock(current, options) {
1755
1764
  const block = createCodexServerBlock(options);
1756
- const matcher = createOwnedBlockMatcher(options.serverName);
1765
+ const matcher = createServerTableMatcher(options.serverName);
1757
1766
  if (matcher.test(current)) {
1758
1767
  return ensureTrailingNewline(current);
1759
1768
  }
1769
+ const legacyServerName = options.legacyServerNames?.find(
1770
+ (serverName) => createServerTableMatcher(serverName).test(current)
1771
+ );
1772
+ if (legacyServerName) {
1773
+ return ensureTrailingNewline(
1774
+ renameServerTableHeaders(current, legacyServerName, options.serverName)
1775
+ );
1776
+ }
1760
1777
  const separator = current.trim() ? "\n\n" : "";
1761
1778
  return `${trimEndNewline(current)}${separator}${block}`;
1762
1779
  }
@@ -1765,25 +1782,47 @@ function createCodexServerBlock(options) {
1765
1782
  url = ${quoteTomlString(options.mcpUrl)}
1766
1783
  `;
1767
1784
  }
1768
- function createOwnedBlockMatcher(serverName) {
1785
+ function createServerTableMatcher(serverName) {
1769
1786
  const plainHeader = escapeRegExp(`[mcp_servers.${serverName}]`);
1787
+ const plainChildHeader = escapeRegExp(`[mcp_servers.${serverName}.`);
1770
1788
  const quotedHeader = escapeRegExp(
1771
1789
  `[mcp_servers.${quoteTomlKey(serverName)}]`
1772
1790
  );
1791
+ const quotedChildHeader = escapeRegExp(
1792
+ `[mcp_servers.${quoteTomlKey(serverName)}.`
1793
+ );
1773
1794
  return new RegExp(
1774
- `(?:^|\\n)(?:${plainHeader}|${quotedHeader})\\n[\\s\\S]*?(?=\\n\\[|$)`
1795
+ `(?:^|\\n)(?:${plainHeader}|${plainChildHeader}|${quotedHeader}|${quotedChildHeader})`
1775
1796
  );
1776
1797
  }
1777
1798
  function createTableHeader(serverName) {
1778
- const key = /^[A-Za-z0-9_-]+$/.test(serverName) ? serverName : quoteTomlKey(serverName);
1799
+ const key = createTableKey(serverName);
1779
1800
  return `[mcp_servers.${key}]`;
1780
1801
  }
1802
+ function createTableKey(serverName) {
1803
+ return /^[A-Za-z0-9_-]+$/.test(serverName) ? serverName : quoteTomlKey(serverName);
1804
+ }
1781
1805
  function quoteTomlKey(value) {
1782
1806
  return quoteTomlString(value);
1783
1807
  }
1784
1808
  function quoteTomlString(value) {
1785
1809
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
1786
1810
  }
1811
+ function renameServerTableHeaders(current, fromServerName, toServerName) {
1812
+ return current.split("\n").map((line) => renameServerTableHeader(line, fromServerName, toServerName)).join("\n");
1813
+ }
1814
+ function renameServerTableHeader(line, fromServerName, toServerName) {
1815
+ const toKey = createTableKey(toServerName);
1816
+ const fromKeys = [fromServerName, quoteTomlKey(fromServerName)];
1817
+ for (const fromKey of fromKeys) {
1818
+ const prefix = `[mcp_servers.${fromKey}`;
1819
+ const nextChar = line[prefix.length];
1820
+ if (line.startsWith(prefix) && (nextChar === "]" || nextChar === ".")) {
1821
+ return `[mcp_servers.${toKey}${line.slice(prefix.length)}`;
1822
+ }
1823
+ }
1824
+ return line;
1825
+ }
1787
1826
  async function readOptionalTextFile(filePath) {
1788
1827
  try {
1789
1828
  return await fs2.readFile(filePath, "utf-8");
@@ -1825,18 +1864,38 @@ async function updateJsonMcpClientConfig(options) {
1825
1864
  if (Object.hasOwn(mcpServers, options.serverName)) {
1826
1865
  return;
1827
1866
  }
1867
+ const migratedMcpServers = renameLegacyServer(mcpServers, options);
1868
+ if (migratedMcpServers) {
1869
+ config.mcpServers = migratedMcpServers;
1870
+ await writeJsonConfig(options.configPath, config);
1871
+ return;
1872
+ }
1828
1873
  mcpServers[options.serverName] = { type: "sse", url: options.mcpUrl };
1829
1874
  config.mcpServers = mcpServers;
1830
- await fs3.mkdir(path4.dirname(options.configPath), { recursive: true });
1831
- await fs3.writeFile(
1832
- options.configPath,
1833
- `${JSON.stringify(config, null, 2)}
1834
- `
1835
- );
1875
+ await writeJsonConfig(options.configPath, config);
1836
1876
  } catch (error) {
1837
1877
  warnConfigFailure(options, formatError2(error));
1838
1878
  }
1839
1879
  }
1880
+ function renameLegacyServer(mcpServers, options) {
1881
+ const legacyServerName = options.legacyServerNames?.find(
1882
+ (name) => Object.hasOwn(mcpServers, name)
1883
+ );
1884
+ if (!legacyServerName) {
1885
+ return void 0;
1886
+ }
1887
+ return Object.fromEntries(
1888
+ Object.entries(mcpServers).map(([serverName, serverConfig]) => [
1889
+ serverName === legacyServerName ? options.serverName : serverName,
1890
+ serverConfig
1891
+ ])
1892
+ );
1893
+ }
1894
+ async function writeJsonConfig(configPath, config) {
1895
+ await fs3.mkdir(path4.dirname(configPath), { recursive: true });
1896
+ await fs3.writeFile(configPath, `${JSON.stringify(config, null, 2)}
1897
+ `);
1898
+ }
1840
1899
  async function readJsonConfig(configPath) {
1841
1900
  const raw = await readOptionalTextFile2(configPath);
1842
1901
  if (!raw.trim()) {
@@ -1870,50 +1929,271 @@ function isNodeError2(error) {
1870
1929
  }
1871
1930
 
1872
1931
  // src/plugin/mcpClientConfig/index.ts
1873
- async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options) {
1932
+ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options, userOptions = {}) {
1874
1933
  const serverName = options.serverName;
1875
- const jobs = [];
1876
- if (options.cursor) {
1877
- jobs.push(
1878
- updateJsonMcpClientConfig({
1934
+ const legacyServerNames = getLegacyServerNames(serverName);
1935
+ const descriptors = [
1936
+ {
1937
+ root,
1938
+ clientName: "cursor",
1939
+ enabled: options.cursor,
1940
+ entryPath: path5.join(root, ".cursor"),
1941
+ entryKind: "directory",
1942
+ userOptions,
1943
+ createJob: () => updateJsonMcpClientConfig({
1879
1944
  clientName: "Cursor",
1880
1945
  configPath: path5.join(root, ".cursor", "mcp.json"),
1881
1946
  mcpUrl: sseUrl,
1882
- serverName
1947
+ serverName,
1948
+ legacyServerNames
1883
1949
  })
1884
- );
1885
- }
1886
- if (options.codex) {
1887
- jobs.push(
1888
- updateCodexMcpClientConfig({
1950
+ },
1951
+ {
1952
+ root,
1953
+ clientName: "codex",
1954
+ enabled: options.codex,
1955
+ entryPath: path5.join(root, ".codex"),
1956
+ entryKind: "directory",
1957
+ userOptions,
1958
+ createJob: () => updateCodexMcpClientConfig({
1889
1959
  configPath: path5.join(root, ".codex", "config.toml"),
1890
1960
  mcpUrl: streamableHttpUrl,
1891
- serverName
1961
+ serverName,
1962
+ legacyServerNames
1892
1963
  })
1893
- );
1894
- }
1895
- if (options.claudeCode) {
1896
- jobs.push(
1897
- updateJsonMcpClientConfig({
1964
+ },
1965
+ {
1966
+ root,
1967
+ clientName: "claudeCode",
1968
+ enabled: options.claudeCode,
1969
+ entryPath: path5.join(root, ".mcp.json"),
1970
+ entryKind: "file",
1971
+ userOptions,
1972
+ createJob: () => updateJsonMcpClientConfig({
1898
1973
  clientName: "Claude Code",
1899
1974
  configPath: path5.join(root, ".mcp.json"),
1900
1975
  mcpUrl: sseUrl,
1901
- serverName
1976
+ serverName,
1977
+ legacyServerNames
1902
1978
  })
1903
- );
1904
- }
1905
- if (options.trae) {
1906
- jobs.push(
1907
- updateJsonMcpClientConfig({
1979
+ },
1980
+ {
1981
+ root,
1982
+ clientName: "trae",
1983
+ enabled: options.trae,
1984
+ entryPath: path5.join(root, ".trae"),
1985
+ entryKind: "directory",
1986
+ userOptions,
1987
+ createJob: () => updateJsonMcpClientConfig({
1908
1988
  clientName: "Trae",
1909
1989
  configPath: path5.join(root, ".trae", "mcp.json"),
1910
1990
  mcpUrl: sseUrl,
1911
- serverName
1991
+ serverName,
1992
+ legacyServerNames
1912
1993
  })
1994
+ }
1995
+ ];
1996
+ const jobs = await createClientConfigJobs(descriptors);
1997
+ await Promise.all(jobs);
1998
+ }
1999
+ async function createClientConfigJobs(descriptors) {
2000
+ const jobs = [];
2001
+ for (const descriptor of descriptors) {
2002
+ if (await shouldUpdateClientConfig(descriptor)) {
2003
+ jobs.push(descriptor.createJob());
2004
+ }
2005
+ }
2006
+ return jobs;
2007
+ }
2008
+ function getLegacyServerNames(serverName) {
2009
+ return serverName === DEFAULT_MCP_CLIENT_SERVER_NAME ? LEGACY_MCP_CLIENT_SERVER_NAMES : [];
2010
+ }
2011
+ async function shouldUpdateClientConfig(options) {
2012
+ if (!options.enabled) {
2013
+ return false;
2014
+ }
2015
+ if (isClientExplicitlyConfigured(options.clientName, options.userOptions)) {
2016
+ return true;
2017
+ }
2018
+ return hasExpectedEntry(options.entryPath, options.entryKind);
2019
+ }
2020
+ function isClientExplicitlyConfigured(clientName, userOptions) {
2021
+ if (Object.hasOwn(userOptions.mcpClients ?? {}, clientName)) {
2022
+ return true;
2023
+ }
2024
+ return clientName === "cursor" && userOptions.updateCursorMcpJson !== void 0;
2025
+ }
2026
+ async function hasExpectedEntry(entryPath, entryKind) {
2027
+ try {
2028
+ const stat = await fs4.stat(entryPath);
2029
+ return entryKind === "directory" ? stat.isDirectory() : stat.isFile();
2030
+ } catch (error) {
2031
+ if (isNodeError3(error) && error.code === "ENOENT") {
2032
+ return false;
2033
+ }
2034
+ console.warn(
2035
+ `[vite-plugin-vue-mcp-next] Failed to inspect MCP client entry at ${entryPath}: ${formatError3(error)}`
2036
+ );
2037
+ return false;
2038
+ }
2039
+ }
2040
+ function formatError3(error) {
2041
+ return error instanceof Error ? error.message : String(error);
2042
+ }
2043
+ function isNodeError3(error) {
2044
+ return error instanceof Error && "code" in error;
2045
+ }
2046
+
2047
+ // src/plugin/skillConfig/index.ts
2048
+ import fs6 from "fs/promises";
2049
+ import path7 from "path";
2050
+
2051
+ // src/plugin/skillConfig/writers.ts
2052
+ import fs5 from "fs/promises";
2053
+ import path6 from "path";
2054
+ var GENERATED_SKILL_CONFIG_MARKER = "<!-- Generated by vite-plugin-vue-mcp-next. Safe to edit, but automatic updates only apply while this marker remains. -->";
2055
+ async function writeGeneratedTextFile(options) {
2056
+ try {
2057
+ const current = await readOptionalTextFile3(options.filePath);
2058
+ if (current && !current.includes(GENERATED_SKILL_CONFIG_MARKER)) {
2059
+ console.warn(
2060
+ `[vite-plugin-vue-mcp-next] Skipped ${options.targetName} at ${options.filePath}: file is not generated by this plugin`
2061
+ );
2062
+ return;
2063
+ }
2064
+ await fs5.mkdir(path6.dirname(options.filePath), { recursive: true });
2065
+ await fs5.writeFile(options.filePath, options.content);
2066
+ } catch (error) {
2067
+ console.warn(
2068
+ `[vite-plugin-vue-mcp-next] Failed to update ${options.targetName} at ${options.filePath}: ${formatError4(error)}`
1913
2069
  );
1914
2070
  }
2071
+ }
2072
+ async function readOptionalTextFile3(filePath) {
2073
+ try {
2074
+ return await fs5.readFile(filePath, "utf-8");
2075
+ } catch (error) {
2076
+ if (isNodeError4(error) && error.code === "ENOENT") {
2077
+ return "";
2078
+ }
2079
+ throw error;
2080
+ }
2081
+ }
2082
+ function formatError4(error) {
2083
+ return error instanceof Error ? error.message : String(error);
2084
+ }
2085
+ function isNodeError4(error) {
2086
+ return error instanceof Error && "code" in error;
2087
+ }
2088
+
2089
+ // src/plugin/skillConfig/index.ts
2090
+ var PACKAGE_NAME = "@xiaou66/vite-plugin-vue-mcp-next";
2091
+ var PACKAGED_SKILL_PATH = path7.join("skills", "vite-mcp-next", "SKILL.md");
2092
+ async function updateSkillConfigs(root, options) {
2093
+ if (!options.autoConfig) {
2094
+ return;
2095
+ }
2096
+ const descriptors = createSkillConfigDescriptors(root);
2097
+ const skillContent = await safelyReadPackagedSkillContent(root);
2098
+ if (!skillContent) {
2099
+ return;
2100
+ }
2101
+ const jobs = await createSkillConfigJobs(descriptors, skillContent);
1915
2102
  await Promise.all(jobs);
1916
2103
  }
2104
+ function createSkillConfigDescriptors(root) {
2105
+ return [
2106
+ {
2107
+ entryPath: path7.join(root, ".codex"),
2108
+ filePath: path7.join(root, ".codex", "skills", "vite-mcp-next", "SKILL.md"),
2109
+ targetName: "Codex skill"
2110
+ },
2111
+ {
2112
+ entryPath: path7.join(root, ".claude"),
2113
+ filePath: path7.join(
2114
+ root,
2115
+ ".claude",
2116
+ "skills",
2117
+ "vite-mcp-next",
2118
+ "SKILL.md"
2119
+ ),
2120
+ targetName: "Claude Code skill"
2121
+ },
2122
+ {
2123
+ entryPath: path7.join(root, ".cursor"),
2124
+ filePath: path7.join(root, ".cursor", "rules", "vite-mcp-next.mdc"),
2125
+ targetName: "Cursor rule"
2126
+ }
2127
+ ];
2128
+ }
2129
+ async function createSkillConfigJobs(descriptors, content) {
2130
+ const jobs = [];
2131
+ for (const descriptor of descriptors) {
2132
+ if (await hasDirectoryEntry(descriptor.entryPath)) {
2133
+ jobs.push(
2134
+ writeGeneratedTextFile({
2135
+ filePath: descriptor.filePath,
2136
+ content,
2137
+ targetName: descriptor.targetName
2138
+ })
2139
+ );
2140
+ }
2141
+ }
2142
+ return jobs;
2143
+ }
2144
+ async function readPackagedSkillContent(root) {
2145
+ const candidates = getPackagedSkillCandidates(root);
2146
+ for (const candidate of candidates) {
2147
+ try {
2148
+ return await fs6.readFile(candidate, "utf-8");
2149
+ } catch (error) {
2150
+ if (isNodeError5(error) && error.code === "ENOENT") {
2151
+ continue;
2152
+ }
2153
+ throw error;
2154
+ }
2155
+ }
2156
+ throw new Error(
2157
+ `Cannot find packaged vite-mcp-next skill at ${candidates.join(", ")}`
2158
+ );
2159
+ }
2160
+ async function safelyReadPackagedSkillContent(root) {
2161
+ try {
2162
+ return await readPackagedSkillContent(root);
2163
+ } catch (error) {
2164
+ console.warn(
2165
+ `[vite-plugin-vue-mcp-next] Failed to read packaged AI skill: ${formatError5(error)}`
2166
+ );
2167
+ return void 0;
2168
+ }
2169
+ }
2170
+ function getPackagedSkillCandidates(root) {
2171
+ return [
2172
+ path7.resolve(root, "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
2173
+ path7.resolve(process.cwd(), "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
2174
+ path7.resolve(process.cwd(), PACKAGED_SKILL_PATH)
2175
+ ];
2176
+ }
2177
+ async function hasDirectoryEntry(entryPath) {
2178
+ try {
2179
+ const stat = await fs6.stat(entryPath);
2180
+ return stat.isDirectory();
2181
+ } catch (error) {
2182
+ if (isNodeError5(error) && error.code === "ENOENT") {
2183
+ return false;
2184
+ }
2185
+ console.warn(
2186
+ `[vite-plugin-vue-mcp-next] Failed to inspect skill config entry at ${entryPath}: ${formatError5(error)}`
2187
+ );
2188
+ return false;
2189
+ }
2190
+ }
2191
+ function formatError5(error) {
2192
+ return error instanceof Error ? error.message : String(error);
2193
+ }
2194
+ function isNodeError5(error) {
2195
+ return error instanceof Error && "code" in error;
2196
+ }
1917
2197
 
1918
2198
  // src/plugin/createPlugin.ts
1919
2199
  import { createRPCServer } from "vite-dev-rpc";
@@ -1983,8 +2263,10 @@ function vueMcpNext(userOptions = {}) {
1983
2263
  root,
1984
2264
  mcpSseUrl,
1985
2265
  mcpStreamableHttpUrl,
1986
- options.mcpClients
2266
+ options.mcpClients,
2267
+ userOptions
1987
2268
  );
2269
+ await updateSkillConfigs(root, options.skill);
1988
2270
  if (options.printUrl) {
1989
2271
  setTimeout(() => {
1990
2272
  console.log(` \u279C MCP: SSE server is running at ${mcpSseUrl}`);