@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/README.md +54 -12
- package/dist/index.cjs +320 -38
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +22 -8
- package/dist/index.d.ts +22 -8
- package/dist/index.js +320 -38
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/skills/vite-mcp-next/SKILL.md +71 -0
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
|
|
32
|
+
/** 是否写入 Cursor MCP 配置;默认只在 `.cursor` 已存在时写入,显式 `true` 会创建配置。 */
|
|
33
33
|
readonly updateCursorMcpJson?: boolean | CursorMcpConfig;
|
|
34
|
-
/** 是否写入常见 AI 客户端的项目级 MCP
|
|
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
|
|
71
|
+
/** 是否写入 Cursor 的 `.cursor/mcp.json`;默认只在 `.cursor` 目录已存在时自动写入。 */
|
|
70
72
|
readonly cursor?: boolean;
|
|
71
|
-
/** 是否写入 Codex 的 `.codex/config.toml
|
|
73
|
+
/** 是否写入 Codex 的 `.codex/config.toml`;默认只在 `.codex` 目录已存在时自动写入。 */
|
|
72
74
|
readonly codex?: boolean;
|
|
73
|
-
/** 是否写入 Claude Code 的 `.mcp.json
|
|
75
|
+
/** 是否写入 Claude Code 的 `.mcp.json`;默认只在根目录 `.mcp.json` 已存在时自动写入。 */
|
|
74
76
|
readonly claudeCode?: boolean;
|
|
75
|
-
/** 是否写入 Trae 的 `.trae/mcp.json
|
|
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
|
|
32
|
+
/** 是否写入 Cursor MCP 配置;默认只在 `.cursor` 已存在时写入,显式 `true` 会创建配置。 */
|
|
33
33
|
readonly updateCursorMcpJson?: boolean | CursorMcpConfig;
|
|
34
|
-
/** 是否写入常见 AI 客户端的项目级 MCP
|
|
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
|
|
71
|
+
/** 是否写入 Cursor 的 `.cursor/mcp.json`;默认只在 `.cursor` 目录已存在时自动写入。 */
|
|
70
72
|
readonly cursor?: boolean;
|
|
71
|
-
/** 是否写入 Codex 的 `.codex/config.toml
|
|
73
|
+
/** 是否写入 Codex 的 `.codex/config.toml`;默认只在 `.codex` 目录已存在时自动写入。 */
|
|
72
74
|
readonly codex?: boolean;
|
|
73
|
-
/** 是否写入 Claude Code 的 `.mcp.json
|
|
75
|
+
/** 是否写入 Claude Code 的 `.mcp.json`;默认只在根目录 `.mcp.json` 已存在时自动写入。 */
|
|
74
76
|
readonly claudeCode?: boolean;
|
|
75
|
-
/** 是否写入 Trae 的 `.trae/mcp.json
|
|
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 = "
|
|
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:
|
|
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:
|
|
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 =
|
|
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
|
|
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}
|
|
1795
|
+
`(?:^|\\n)(?:${plainHeader}|${plainChildHeader}|${quotedHeader}|${quotedChildHeader})`
|
|
1775
1796
|
);
|
|
1776
1797
|
}
|
|
1777
1798
|
function createTableHeader(serverName) {
|
|
1778
|
-
const key =
|
|
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
|
|
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
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
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
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
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
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
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
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
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}`);
|