@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/README.md
CHANGED
|
@@ -43,14 +43,18 @@ SSE: http://localhost:<vite-port>/__mcp/sse
|
|
|
43
43
|
Streamable HTTP: http://localhost:<vite-port>/__mcp/mcp
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
启动 Vite dev server
|
|
46
|
+
启动 Vite dev server 后,插件会按项目中已经存在的客户端入口自动写入项目级 MCP 配置,服务名默认是 `vite-mcp-next`。自动配置只会在缺少同名 server 条目时新增配置;如果用户已经配置了历史默认名 `vue-mcp-next`,插件会把它迁移为 `vite-mcp-next` 并保留原配置内容。
|
|
47
47
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
|
51
|
-
|
|
|
52
|
-
|
|
|
53
|
-
|
|
|
48
|
+
默认自动探测规则如下:
|
|
49
|
+
|
|
50
|
+
| 客户端 | 自动探测入口 | 自动配置文件 | 默认端点 |
|
|
51
|
+
| ----------- | ------------ | -------------------- | --------------- |
|
|
52
|
+
| Cursor | `.cursor/` | `.cursor/mcp.json` | SSE |
|
|
53
|
+
| Codex | `.codex/` | `.codex/config.toml` | Streamable HTTP |
|
|
54
|
+
| Claude Code | `.mcp.json` | `.mcp.json` | SSE |
|
|
55
|
+
| Trae | `.trae/` | `.trae/mcp.json` | SSE |
|
|
56
|
+
|
|
57
|
+
如果项目中没有对应入口,默认不会创建该客户端配置。需要强制创建时,可以在 `mcpClients` 中显式设置对应客户端为 `true`;需要禁用时显式设置为 `false`。
|
|
54
58
|
|
|
55
59
|
实际端口以启动日志中的 `MCP: SSE server is running at ...` 和 `MCP: Streamable HTTP server is running at ...` 为准。
|
|
56
60
|
|
|
@@ -63,7 +67,7 @@ Cursor、Claude Code、Trae 等 JSON 配置客户端可以使用:
|
|
|
63
67
|
```json
|
|
64
68
|
{
|
|
65
69
|
"mcpServers": {
|
|
66
|
-
"
|
|
70
|
+
"vite-mcp-next": {
|
|
67
71
|
"type": "sse",
|
|
68
72
|
"url": "http://localhost:5173/__mcp/sse"
|
|
69
73
|
}
|
|
@@ -74,14 +78,48 @@ Cursor、Claude Code、Trae 等 JSON 配置客户端可以使用:
|
|
|
74
78
|
Codex 使用 TOML 配置:
|
|
75
79
|
|
|
76
80
|
```toml
|
|
77
|
-
[mcp_servers.
|
|
81
|
+
[mcp_servers.vite-mcp-next]
|
|
78
82
|
url = "http://localhost:5173/__mcp/mcp"
|
|
79
83
|
```
|
|
80
84
|
|
|
81
85
|
`5173` 是示例端口。若 Vite 使用了其他端口,请替换为启动日志中打印的 MCP 地址。
|
|
82
86
|
|
|
87
|
+
### 自动复制 AI 使用指南
|
|
88
|
+
|
|
89
|
+
插件随 npm 包发布一份通用 Skill 文件:`skills/vite-mcp-next/SKILL.md`。Vite dev server 启动时,插件会在检测到项目中已经存在对应 AI 工具入口后,把这份静态文件复制到对应工具目录。插件不会在运行时拼接或生成 Skill 正文,包内 `skills/vite-mcp-next/SKILL.md` 是唯一内容来源。
|
|
90
|
+
|
|
91
|
+
| 客户端 | 探测入口 | 自动复制目标 | 说明 |
|
|
92
|
+
| ----------- | ---------- | ---------------------------------------------- | ------------------------------------- |
|
|
93
|
+
| Codex | `.codex/` | `.codex/skills/vite-mcp-next/SKILL.md` | 从包内 `skills/vite-mcp-next/SKILL.md` 复制 |
|
|
94
|
+
| Claude Code | `.claude/` | `.claude/skills/vite-mcp-next/SKILL.md` | 从包内 `skills/vite-mcp-next/SKILL.md` 复制 |
|
|
95
|
+
| Cursor | `.cursor/` | `.cursor/rules/vite-mcp-next.mdc` | 从同一份 Skill 文件复制,作为项目规则参考 |
|
|
96
|
+
| Trae | `.trae/` | 不自动复制 | 当前只自动写 MCP 配置,Rule 路径未作为首版稳定能力 |
|
|
97
|
+
|
|
98
|
+
使用指南覆盖以下工具顺序:
|
|
99
|
+
|
|
100
|
+
1. `list_pages`:先确认页面、runtime 和 CDP target
|
|
101
|
+
2. `get_dom_tree` / `query_dom`:检查 DOM 结构
|
|
102
|
+
3. `take_screenshot`:做视觉验证
|
|
103
|
+
4. `get_console_logs`:排查 Console 报错
|
|
104
|
+
5. `get_network_requests` / `get_network_request_detail`:排查接口请求
|
|
105
|
+
6. `get_component_tree` / `get_component_state` / `get_router_info` / `get_pinia_tree` / `get_pinia_state`:检查 Vue 语义状态
|
|
106
|
+
|
|
107
|
+
如果不希望插件复制任何 AI 使用指南,可以关闭:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
vueMcpNext({
|
|
111
|
+
skill: {
|
|
112
|
+
autoConfig: false
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
自动复制只会更新带有插件生成标记的文件;如果目标文件已经存在但不是插件生成的内容,插件会跳过并保留用户文件。没有使用某个 AI 工具时,对应入口目录不存在,插件不会创建该工具目录。
|
|
118
|
+
|
|
83
119
|
## 完整配置
|
|
84
120
|
|
|
121
|
+
`mcpClients` 中的布尔值保留为开关语义:默认解析后为 `true`,但写入阶段会先做项目入口自动探测;用户在配置中显式写出 `true` 时表示强制创建对应客户端配置。
|
|
122
|
+
|
|
85
123
|
```ts
|
|
86
124
|
vueMcpNext({
|
|
87
125
|
mcpPath: '/__mcp',
|
|
@@ -93,7 +131,10 @@ vueMcpNext({
|
|
|
93
131
|
codex: true,
|
|
94
132
|
claudeCode: true,
|
|
95
133
|
trae: true,
|
|
96
|
-
serverName: '
|
|
134
|
+
serverName: 'vite-mcp-next'
|
|
135
|
+
},
|
|
136
|
+
skill: {
|
|
137
|
+
autoConfig: true
|
|
97
138
|
},
|
|
98
139
|
appendTo: undefined,
|
|
99
140
|
runtime: {
|
|
@@ -147,8 +188,9 @@ vueMcpNext({
|
|
|
147
188
|
| `mcpPath` | `string` | `'/__mcp'` | MCP 服务挂载路径,实际 SSE 地址是 `${mcpPath}/sse`,Streamable HTTP 地址是 `${mcpPath}/mcp` |
|
|
148
189
|
| `host` | `string` | `'localhost'` | 打印 MCP 地址和写入 MCP 客户端配置时使用的 host |
|
|
149
190
|
| `printUrl` | `boolean` | `true` | 是否在 Vite 启动日志中打印 MCP SSE 地址 |
|
|
150
|
-
| `mcpClients` | `{ cursor?: boolean; codex?: boolean; claudeCode?: boolean; trae?: boolean; serverName?: string }` |
|
|
151
|
-
| `updateCursorMcpJson` | `boolean \| { enabled: boolean; serverName?: string }` |
|
|
191
|
+
| `mcpClients` | `{ cursor?: boolean; codex?: boolean; claudeCode?: boolean; trae?: boolean; serverName?: string }` | 自动探测 | 是否写入 Cursor、Codex、Claude Code、Trae 的项目级 MCP 配置;默认只处理项目中已有入口,显式 `true` 强制创建 |
|
|
192
|
+
| `updateCursorMcpJson` | `boolean \| { enabled: boolean; serverName?: string }` | 自动探测 | 兼容旧配置;默认只在 `.cursor` 已存在时写入,建议新项目使用 `mcpClients` |
|
|
193
|
+
| `skill` | `{ autoConfig?: boolean }` | `{ autoConfig: true }` | 是否在检测到 Codex、Claude Code、Cursor 项目入口时自动复制包内 AI 使用指南 |
|
|
152
194
|
| `appendTo` | `string \| RegExp` | `undefined` | 非 HTML 入口注入点。配置后会在匹配入口模块前追加 runtime import |
|
|
153
195
|
| `screenshot` | `ScreenshotOptions` | CDP 优先,snapdom 降级 | 页面截图配置,控制真截图、DOM 降级截图、体积上限和 snapdom 扩展 |
|
|
154
196
|
| `screenshot.type` | `'path' \| 'base64'` | `'path'` | 项目级控制截图返回文件路径还是 base64 数据 |
|
package/dist/index.cjs
CHANGED
|
@@ -81,7 +81,8 @@ var VIRTUAL_SCREENSHOT_CONFIG_ID = "virtual:vite-plugin-vue-mcp-next/screenshot-
|
|
|
81
81
|
var RESOLVED_VIRTUAL_SCREENSHOT_CONFIG_ID = `\0${VIRTUAL_SCREENSHOT_CONFIG_ID}`;
|
|
82
82
|
var VIRTUAL_SNAPDOM_LOADER_ID = "virtual:vite-plugin-vue-mcp-next/snapdom-loader";
|
|
83
83
|
var RESOLVED_VIRTUAL_SNAPDOM_LOADER_ID = `\0${VIRTUAL_SNAPDOM_LOADER_ID}`;
|
|
84
|
-
var DEFAULT_MCP_CLIENT_SERVER_NAME = "
|
|
84
|
+
var DEFAULT_MCP_CLIENT_SERVER_NAME = "vite-mcp-next";
|
|
85
|
+
var LEGACY_MCP_CLIENT_SERVER_NAMES = ["vue-mcp-next"];
|
|
85
86
|
var RUNTIME_PAGE_RECONNECTED_EVENT = "vite-plugin-vue-mcp-next:page-reconnected";
|
|
86
87
|
var DEFAULT_OPTIONS = {
|
|
87
88
|
mcpPath: DEFAULT_MCP_PATH,
|
|
@@ -98,6 +99,9 @@ var DEFAULT_OPTIONS = {
|
|
|
98
99
|
trae: true,
|
|
99
100
|
serverName: DEFAULT_MCP_CLIENT_SERVER_NAME
|
|
100
101
|
},
|
|
102
|
+
skill: {
|
|
103
|
+
autoConfig: true
|
|
104
|
+
},
|
|
101
105
|
runtime: {
|
|
102
106
|
mode: "auto",
|
|
103
107
|
evaluate: {
|
|
@@ -155,6 +159,10 @@ function mergeOptions(options = {}) {
|
|
|
155
159
|
...options,
|
|
156
160
|
updateCursorMcpJson: cursorConfig,
|
|
157
161
|
mcpClients,
|
|
162
|
+
skill: {
|
|
163
|
+
...DEFAULT_OPTIONS.skill,
|
|
164
|
+
...options.skill
|
|
165
|
+
},
|
|
158
166
|
runtime: {
|
|
159
167
|
...DEFAULT_OPTIONS.runtime,
|
|
160
168
|
...options.runtime,
|
|
@@ -1183,13 +1191,13 @@ function registerVueTools(server, ctx) {
|
|
|
1183
1191
|
valueType: import_zod7.z.enum(["string", "number", "boolean", "object", "array"])
|
|
1184
1192
|
}
|
|
1185
1193
|
},
|
|
1186
|
-
({ componentName, path:
|
|
1194
|
+
({ componentName, path: path8, value, valueType }) => {
|
|
1187
1195
|
if (!ctx.rpcServer) {
|
|
1188
1196
|
return vueBridgeUnavailable();
|
|
1189
1197
|
}
|
|
1190
1198
|
void ctx.rpcServer.editComponentState({
|
|
1191
1199
|
componentName,
|
|
1192
|
-
path:
|
|
1200
|
+
path: path8,
|
|
1193
1201
|
value,
|
|
1194
1202
|
valueType
|
|
1195
1203
|
});
|
|
@@ -1771,6 +1779,7 @@ function getPluginPath(plugin) {
|
|
|
1771
1779
|
}
|
|
1772
1780
|
|
|
1773
1781
|
// src/plugin/mcpClientConfig/index.ts
|
|
1782
|
+
var import_promises4 = __toESM(require("fs/promises"), 1);
|
|
1774
1783
|
var import_node_path6 = __toESM(require("path"), 1);
|
|
1775
1784
|
|
|
1776
1785
|
// src/plugin/mcpClientConfig/codexConfig.ts
|
|
@@ -1790,10 +1799,18 @@ async function updateCodexMcpClientConfig(options) {
|
|
|
1790
1799
|
}
|
|
1791
1800
|
function replaceOrAppendOwnedBlock(current, options) {
|
|
1792
1801
|
const block = createCodexServerBlock(options);
|
|
1793
|
-
const matcher =
|
|
1802
|
+
const matcher = createServerTableMatcher(options.serverName);
|
|
1794
1803
|
if (matcher.test(current)) {
|
|
1795
1804
|
return ensureTrailingNewline(current);
|
|
1796
1805
|
}
|
|
1806
|
+
const legacyServerName = options.legacyServerNames?.find(
|
|
1807
|
+
(serverName) => createServerTableMatcher(serverName).test(current)
|
|
1808
|
+
);
|
|
1809
|
+
if (legacyServerName) {
|
|
1810
|
+
return ensureTrailingNewline(
|
|
1811
|
+
renameServerTableHeaders(current, legacyServerName, options.serverName)
|
|
1812
|
+
);
|
|
1813
|
+
}
|
|
1797
1814
|
const separator = current.trim() ? "\n\n" : "";
|
|
1798
1815
|
return `${trimEndNewline(current)}${separator}${block}`;
|
|
1799
1816
|
}
|
|
@@ -1802,25 +1819,47 @@ function createCodexServerBlock(options) {
|
|
|
1802
1819
|
url = ${quoteTomlString(options.mcpUrl)}
|
|
1803
1820
|
`;
|
|
1804
1821
|
}
|
|
1805
|
-
function
|
|
1822
|
+
function createServerTableMatcher(serverName) {
|
|
1806
1823
|
const plainHeader = escapeRegExp(`[mcp_servers.${serverName}]`);
|
|
1824
|
+
const plainChildHeader = escapeRegExp(`[mcp_servers.${serverName}.`);
|
|
1807
1825
|
const quotedHeader = escapeRegExp(
|
|
1808
1826
|
`[mcp_servers.${quoteTomlKey(serverName)}]`
|
|
1809
1827
|
);
|
|
1828
|
+
const quotedChildHeader = escapeRegExp(
|
|
1829
|
+
`[mcp_servers.${quoteTomlKey(serverName)}.`
|
|
1830
|
+
);
|
|
1810
1831
|
return new RegExp(
|
|
1811
|
-
`(?:^|\\n)(?:${plainHeader}|${quotedHeader}
|
|
1832
|
+
`(?:^|\\n)(?:${plainHeader}|${plainChildHeader}|${quotedHeader}|${quotedChildHeader})`
|
|
1812
1833
|
);
|
|
1813
1834
|
}
|
|
1814
1835
|
function createTableHeader(serverName) {
|
|
1815
|
-
const key =
|
|
1836
|
+
const key = createTableKey(serverName);
|
|
1816
1837
|
return `[mcp_servers.${key}]`;
|
|
1817
1838
|
}
|
|
1839
|
+
function createTableKey(serverName) {
|
|
1840
|
+
return /^[A-Za-z0-9_-]+$/.test(serverName) ? serverName : quoteTomlKey(serverName);
|
|
1841
|
+
}
|
|
1818
1842
|
function quoteTomlKey(value) {
|
|
1819
1843
|
return quoteTomlString(value);
|
|
1820
1844
|
}
|
|
1821
1845
|
function quoteTomlString(value) {
|
|
1822
1846
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
1823
1847
|
}
|
|
1848
|
+
function renameServerTableHeaders(current, fromServerName, toServerName) {
|
|
1849
|
+
return current.split("\n").map((line) => renameServerTableHeader(line, fromServerName, toServerName)).join("\n");
|
|
1850
|
+
}
|
|
1851
|
+
function renameServerTableHeader(line, fromServerName, toServerName) {
|
|
1852
|
+
const toKey = createTableKey(toServerName);
|
|
1853
|
+
const fromKeys = [fromServerName, quoteTomlKey(fromServerName)];
|
|
1854
|
+
for (const fromKey of fromKeys) {
|
|
1855
|
+
const prefix = `[mcp_servers.${fromKey}`;
|
|
1856
|
+
const nextChar = line[prefix.length];
|
|
1857
|
+
if (line.startsWith(prefix) && (nextChar === "]" || nextChar === ".")) {
|
|
1858
|
+
return `[mcp_servers.${toKey}${line.slice(prefix.length)}`;
|
|
1859
|
+
}
|
|
1860
|
+
}
|
|
1861
|
+
return line;
|
|
1862
|
+
}
|
|
1824
1863
|
async function readOptionalTextFile(filePath) {
|
|
1825
1864
|
try {
|
|
1826
1865
|
return await import_promises2.default.readFile(filePath, "utf-8");
|
|
@@ -1862,18 +1901,38 @@ async function updateJsonMcpClientConfig(options) {
|
|
|
1862
1901
|
if (Object.hasOwn(mcpServers, options.serverName)) {
|
|
1863
1902
|
return;
|
|
1864
1903
|
}
|
|
1904
|
+
const migratedMcpServers = renameLegacyServer(mcpServers, options);
|
|
1905
|
+
if (migratedMcpServers) {
|
|
1906
|
+
config.mcpServers = migratedMcpServers;
|
|
1907
|
+
await writeJsonConfig(options.configPath, config);
|
|
1908
|
+
return;
|
|
1909
|
+
}
|
|
1865
1910
|
mcpServers[options.serverName] = { type: "sse", url: options.mcpUrl };
|
|
1866
1911
|
config.mcpServers = mcpServers;
|
|
1867
|
-
await
|
|
1868
|
-
await import_promises3.default.writeFile(
|
|
1869
|
-
options.configPath,
|
|
1870
|
-
`${JSON.stringify(config, null, 2)}
|
|
1871
|
-
`
|
|
1872
|
-
);
|
|
1912
|
+
await writeJsonConfig(options.configPath, config);
|
|
1873
1913
|
} catch (error) {
|
|
1874
1914
|
warnConfigFailure(options, formatError2(error));
|
|
1875
1915
|
}
|
|
1876
1916
|
}
|
|
1917
|
+
function renameLegacyServer(mcpServers, options) {
|
|
1918
|
+
const legacyServerName = options.legacyServerNames?.find(
|
|
1919
|
+
(name) => Object.hasOwn(mcpServers, name)
|
|
1920
|
+
);
|
|
1921
|
+
if (!legacyServerName) {
|
|
1922
|
+
return void 0;
|
|
1923
|
+
}
|
|
1924
|
+
return Object.fromEntries(
|
|
1925
|
+
Object.entries(mcpServers).map(([serverName, serverConfig]) => [
|
|
1926
|
+
serverName === legacyServerName ? options.serverName : serverName,
|
|
1927
|
+
serverConfig
|
|
1928
|
+
])
|
|
1929
|
+
);
|
|
1930
|
+
}
|
|
1931
|
+
async function writeJsonConfig(configPath, config) {
|
|
1932
|
+
await import_promises3.default.mkdir(import_node_path5.default.dirname(configPath), { recursive: true });
|
|
1933
|
+
await import_promises3.default.writeFile(configPath, `${JSON.stringify(config, null, 2)}
|
|
1934
|
+
`);
|
|
1935
|
+
}
|
|
1877
1936
|
async function readJsonConfig(configPath) {
|
|
1878
1937
|
const raw = await readOptionalTextFile2(configPath);
|
|
1879
1938
|
if (!raw.trim()) {
|
|
@@ -1907,50 +1966,271 @@ function isNodeError2(error) {
|
|
|
1907
1966
|
}
|
|
1908
1967
|
|
|
1909
1968
|
// src/plugin/mcpClientConfig/index.ts
|
|
1910
|
-
async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options) {
|
|
1969
|
+
async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options, userOptions = {}) {
|
|
1911
1970
|
const serverName = options.serverName;
|
|
1912
|
-
const
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1971
|
+
const legacyServerNames = getLegacyServerNames(serverName);
|
|
1972
|
+
const descriptors = [
|
|
1973
|
+
{
|
|
1974
|
+
root,
|
|
1975
|
+
clientName: "cursor",
|
|
1976
|
+
enabled: options.cursor,
|
|
1977
|
+
entryPath: import_node_path6.default.join(root, ".cursor"),
|
|
1978
|
+
entryKind: "directory",
|
|
1979
|
+
userOptions,
|
|
1980
|
+
createJob: () => updateJsonMcpClientConfig({
|
|
1916
1981
|
clientName: "Cursor",
|
|
1917
1982
|
configPath: import_node_path6.default.join(root, ".cursor", "mcp.json"),
|
|
1918
1983
|
mcpUrl: sseUrl,
|
|
1919
|
-
serverName
|
|
1984
|
+
serverName,
|
|
1985
|
+
legacyServerNames
|
|
1920
1986
|
})
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1987
|
+
},
|
|
1988
|
+
{
|
|
1989
|
+
root,
|
|
1990
|
+
clientName: "codex",
|
|
1991
|
+
enabled: options.codex,
|
|
1992
|
+
entryPath: import_node_path6.default.join(root, ".codex"),
|
|
1993
|
+
entryKind: "directory",
|
|
1994
|
+
userOptions,
|
|
1995
|
+
createJob: () => updateCodexMcpClientConfig({
|
|
1926
1996
|
configPath: import_node_path6.default.join(root, ".codex", "config.toml"),
|
|
1927
1997
|
mcpUrl: streamableHttpUrl,
|
|
1928
|
-
serverName
|
|
1998
|
+
serverName,
|
|
1999
|
+
legacyServerNames
|
|
1929
2000
|
})
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
2001
|
+
},
|
|
2002
|
+
{
|
|
2003
|
+
root,
|
|
2004
|
+
clientName: "claudeCode",
|
|
2005
|
+
enabled: options.claudeCode,
|
|
2006
|
+
entryPath: import_node_path6.default.join(root, ".mcp.json"),
|
|
2007
|
+
entryKind: "file",
|
|
2008
|
+
userOptions,
|
|
2009
|
+
createJob: () => updateJsonMcpClientConfig({
|
|
1935
2010
|
clientName: "Claude Code",
|
|
1936
2011
|
configPath: import_node_path6.default.join(root, ".mcp.json"),
|
|
1937
2012
|
mcpUrl: sseUrl,
|
|
1938
|
-
serverName
|
|
2013
|
+
serverName,
|
|
2014
|
+
legacyServerNames
|
|
1939
2015
|
})
|
|
1940
|
-
|
|
1941
|
-
|
|
1942
|
-
|
|
1943
|
-
|
|
1944
|
-
|
|
2016
|
+
},
|
|
2017
|
+
{
|
|
2018
|
+
root,
|
|
2019
|
+
clientName: "trae",
|
|
2020
|
+
enabled: options.trae,
|
|
2021
|
+
entryPath: import_node_path6.default.join(root, ".trae"),
|
|
2022
|
+
entryKind: "directory",
|
|
2023
|
+
userOptions,
|
|
2024
|
+
createJob: () => updateJsonMcpClientConfig({
|
|
1945
2025
|
clientName: "Trae",
|
|
1946
2026
|
configPath: import_node_path6.default.join(root, ".trae", "mcp.json"),
|
|
1947
2027
|
mcpUrl: sseUrl,
|
|
1948
|
-
serverName
|
|
2028
|
+
serverName,
|
|
2029
|
+
legacyServerNames
|
|
1949
2030
|
})
|
|
2031
|
+
}
|
|
2032
|
+
];
|
|
2033
|
+
const jobs = await createClientConfigJobs(descriptors);
|
|
2034
|
+
await Promise.all(jobs);
|
|
2035
|
+
}
|
|
2036
|
+
async function createClientConfigJobs(descriptors) {
|
|
2037
|
+
const jobs = [];
|
|
2038
|
+
for (const descriptor of descriptors) {
|
|
2039
|
+
if (await shouldUpdateClientConfig(descriptor)) {
|
|
2040
|
+
jobs.push(descriptor.createJob());
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
return jobs;
|
|
2044
|
+
}
|
|
2045
|
+
function getLegacyServerNames(serverName) {
|
|
2046
|
+
return serverName === DEFAULT_MCP_CLIENT_SERVER_NAME ? LEGACY_MCP_CLIENT_SERVER_NAMES : [];
|
|
2047
|
+
}
|
|
2048
|
+
async function shouldUpdateClientConfig(options) {
|
|
2049
|
+
if (!options.enabled) {
|
|
2050
|
+
return false;
|
|
2051
|
+
}
|
|
2052
|
+
if (isClientExplicitlyConfigured(options.clientName, options.userOptions)) {
|
|
2053
|
+
return true;
|
|
2054
|
+
}
|
|
2055
|
+
return hasExpectedEntry(options.entryPath, options.entryKind);
|
|
2056
|
+
}
|
|
2057
|
+
function isClientExplicitlyConfigured(clientName, userOptions) {
|
|
2058
|
+
if (Object.hasOwn(userOptions.mcpClients ?? {}, clientName)) {
|
|
2059
|
+
return true;
|
|
2060
|
+
}
|
|
2061
|
+
return clientName === "cursor" && userOptions.updateCursorMcpJson !== void 0;
|
|
2062
|
+
}
|
|
2063
|
+
async function hasExpectedEntry(entryPath, entryKind) {
|
|
2064
|
+
try {
|
|
2065
|
+
const stat = await import_promises4.default.stat(entryPath);
|
|
2066
|
+
return entryKind === "directory" ? stat.isDirectory() : stat.isFile();
|
|
2067
|
+
} catch (error) {
|
|
2068
|
+
if (isNodeError3(error) && error.code === "ENOENT") {
|
|
2069
|
+
return false;
|
|
2070
|
+
}
|
|
2071
|
+
console.warn(
|
|
2072
|
+
`[vite-plugin-vue-mcp-next] Failed to inspect MCP client entry at ${entryPath}: ${formatError3(error)}`
|
|
2073
|
+
);
|
|
2074
|
+
return false;
|
|
2075
|
+
}
|
|
2076
|
+
}
|
|
2077
|
+
function formatError3(error) {
|
|
2078
|
+
return error instanceof Error ? error.message : String(error);
|
|
2079
|
+
}
|
|
2080
|
+
function isNodeError3(error) {
|
|
2081
|
+
return error instanceof Error && "code" in error;
|
|
2082
|
+
}
|
|
2083
|
+
|
|
2084
|
+
// src/plugin/skillConfig/index.ts
|
|
2085
|
+
var import_promises6 = __toESM(require("fs/promises"), 1);
|
|
2086
|
+
var import_node_path8 = __toESM(require("path"), 1);
|
|
2087
|
+
|
|
2088
|
+
// src/plugin/skillConfig/writers.ts
|
|
2089
|
+
var import_promises5 = __toESM(require("fs/promises"), 1);
|
|
2090
|
+
var import_node_path7 = __toESM(require("path"), 1);
|
|
2091
|
+
var GENERATED_SKILL_CONFIG_MARKER = "<!-- Generated by vite-plugin-vue-mcp-next. Safe to edit, but automatic updates only apply while this marker remains. -->";
|
|
2092
|
+
async function writeGeneratedTextFile(options) {
|
|
2093
|
+
try {
|
|
2094
|
+
const current = await readOptionalTextFile3(options.filePath);
|
|
2095
|
+
if (current && !current.includes(GENERATED_SKILL_CONFIG_MARKER)) {
|
|
2096
|
+
console.warn(
|
|
2097
|
+
`[vite-plugin-vue-mcp-next] Skipped ${options.targetName} at ${options.filePath}: file is not generated by this plugin`
|
|
2098
|
+
);
|
|
2099
|
+
return;
|
|
2100
|
+
}
|
|
2101
|
+
await import_promises5.default.mkdir(import_node_path7.default.dirname(options.filePath), { recursive: true });
|
|
2102
|
+
await import_promises5.default.writeFile(options.filePath, options.content);
|
|
2103
|
+
} catch (error) {
|
|
2104
|
+
console.warn(
|
|
2105
|
+
`[vite-plugin-vue-mcp-next] Failed to update ${options.targetName} at ${options.filePath}: ${formatError4(error)}`
|
|
1950
2106
|
);
|
|
1951
2107
|
}
|
|
2108
|
+
}
|
|
2109
|
+
async function readOptionalTextFile3(filePath) {
|
|
2110
|
+
try {
|
|
2111
|
+
return await import_promises5.default.readFile(filePath, "utf-8");
|
|
2112
|
+
} catch (error) {
|
|
2113
|
+
if (isNodeError4(error) && error.code === "ENOENT") {
|
|
2114
|
+
return "";
|
|
2115
|
+
}
|
|
2116
|
+
throw error;
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
function formatError4(error) {
|
|
2120
|
+
return error instanceof Error ? error.message : String(error);
|
|
2121
|
+
}
|
|
2122
|
+
function isNodeError4(error) {
|
|
2123
|
+
return error instanceof Error && "code" in error;
|
|
2124
|
+
}
|
|
2125
|
+
|
|
2126
|
+
// src/plugin/skillConfig/index.ts
|
|
2127
|
+
var PACKAGE_NAME = "@xiaou66/vite-plugin-vue-mcp-next";
|
|
2128
|
+
var PACKAGED_SKILL_PATH = import_node_path8.default.join("skills", "vite-mcp-next", "SKILL.md");
|
|
2129
|
+
async function updateSkillConfigs(root, options) {
|
|
2130
|
+
if (!options.autoConfig) {
|
|
2131
|
+
return;
|
|
2132
|
+
}
|
|
2133
|
+
const descriptors = createSkillConfigDescriptors(root);
|
|
2134
|
+
const skillContent = await safelyReadPackagedSkillContent(root);
|
|
2135
|
+
if (!skillContent) {
|
|
2136
|
+
return;
|
|
2137
|
+
}
|
|
2138
|
+
const jobs = await createSkillConfigJobs(descriptors, skillContent);
|
|
1952
2139
|
await Promise.all(jobs);
|
|
1953
2140
|
}
|
|
2141
|
+
function createSkillConfigDescriptors(root) {
|
|
2142
|
+
return [
|
|
2143
|
+
{
|
|
2144
|
+
entryPath: import_node_path8.default.join(root, ".codex"),
|
|
2145
|
+
filePath: import_node_path8.default.join(root, ".codex", "skills", "vite-mcp-next", "SKILL.md"),
|
|
2146
|
+
targetName: "Codex skill"
|
|
2147
|
+
},
|
|
2148
|
+
{
|
|
2149
|
+
entryPath: import_node_path8.default.join(root, ".claude"),
|
|
2150
|
+
filePath: import_node_path8.default.join(
|
|
2151
|
+
root,
|
|
2152
|
+
".claude",
|
|
2153
|
+
"skills",
|
|
2154
|
+
"vite-mcp-next",
|
|
2155
|
+
"SKILL.md"
|
|
2156
|
+
),
|
|
2157
|
+
targetName: "Claude Code skill"
|
|
2158
|
+
},
|
|
2159
|
+
{
|
|
2160
|
+
entryPath: import_node_path8.default.join(root, ".cursor"),
|
|
2161
|
+
filePath: import_node_path8.default.join(root, ".cursor", "rules", "vite-mcp-next.mdc"),
|
|
2162
|
+
targetName: "Cursor rule"
|
|
2163
|
+
}
|
|
2164
|
+
];
|
|
2165
|
+
}
|
|
2166
|
+
async function createSkillConfigJobs(descriptors, content) {
|
|
2167
|
+
const jobs = [];
|
|
2168
|
+
for (const descriptor of descriptors) {
|
|
2169
|
+
if (await hasDirectoryEntry(descriptor.entryPath)) {
|
|
2170
|
+
jobs.push(
|
|
2171
|
+
writeGeneratedTextFile({
|
|
2172
|
+
filePath: descriptor.filePath,
|
|
2173
|
+
content,
|
|
2174
|
+
targetName: descriptor.targetName
|
|
2175
|
+
})
|
|
2176
|
+
);
|
|
2177
|
+
}
|
|
2178
|
+
}
|
|
2179
|
+
return jobs;
|
|
2180
|
+
}
|
|
2181
|
+
async function readPackagedSkillContent(root) {
|
|
2182
|
+
const candidates = getPackagedSkillCandidates(root);
|
|
2183
|
+
for (const candidate of candidates) {
|
|
2184
|
+
try {
|
|
2185
|
+
return await import_promises6.default.readFile(candidate, "utf-8");
|
|
2186
|
+
} catch (error) {
|
|
2187
|
+
if (isNodeError5(error) && error.code === "ENOENT") {
|
|
2188
|
+
continue;
|
|
2189
|
+
}
|
|
2190
|
+
throw error;
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
throw new Error(
|
|
2194
|
+
`Cannot find packaged vite-mcp-next skill at ${candidates.join(", ")}`
|
|
2195
|
+
);
|
|
2196
|
+
}
|
|
2197
|
+
async function safelyReadPackagedSkillContent(root) {
|
|
2198
|
+
try {
|
|
2199
|
+
return await readPackagedSkillContent(root);
|
|
2200
|
+
} catch (error) {
|
|
2201
|
+
console.warn(
|
|
2202
|
+
`[vite-plugin-vue-mcp-next] Failed to read packaged AI skill: ${formatError5(error)}`
|
|
2203
|
+
);
|
|
2204
|
+
return void 0;
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
function getPackagedSkillCandidates(root) {
|
|
2208
|
+
return [
|
|
2209
|
+
import_node_path8.default.resolve(root, "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
|
|
2210
|
+
import_node_path8.default.resolve(process.cwd(), "node_modules", PACKAGE_NAME, PACKAGED_SKILL_PATH),
|
|
2211
|
+
import_node_path8.default.resolve(process.cwd(), PACKAGED_SKILL_PATH)
|
|
2212
|
+
];
|
|
2213
|
+
}
|
|
2214
|
+
async function hasDirectoryEntry(entryPath) {
|
|
2215
|
+
try {
|
|
2216
|
+
const stat = await import_promises6.default.stat(entryPath);
|
|
2217
|
+
return stat.isDirectory();
|
|
2218
|
+
} catch (error) {
|
|
2219
|
+
if (isNodeError5(error) && error.code === "ENOENT") {
|
|
2220
|
+
return false;
|
|
2221
|
+
}
|
|
2222
|
+
console.warn(
|
|
2223
|
+
`[vite-plugin-vue-mcp-next] Failed to inspect skill config entry at ${entryPath}: ${formatError5(error)}`
|
|
2224
|
+
);
|
|
2225
|
+
return false;
|
|
2226
|
+
}
|
|
2227
|
+
}
|
|
2228
|
+
function formatError5(error) {
|
|
2229
|
+
return error instanceof Error ? error.message : String(error);
|
|
2230
|
+
}
|
|
2231
|
+
function isNodeError5(error) {
|
|
2232
|
+
return error instanceof Error && "code" in error;
|
|
2233
|
+
}
|
|
1954
2234
|
|
|
1955
2235
|
// src/plugin/createPlugin.ts
|
|
1956
2236
|
var import_vite_dev_rpc = require("vite-dev-rpc");
|
|
@@ -2020,8 +2300,10 @@ function vueMcpNext(userOptions = {}) {
|
|
|
2020
2300
|
root,
|
|
2021
2301
|
mcpSseUrl,
|
|
2022
2302
|
mcpStreamableHttpUrl,
|
|
2023
|
-
options.mcpClients
|
|
2303
|
+
options.mcpClients,
|
|
2304
|
+
userOptions
|
|
2024
2305
|
);
|
|
2306
|
+
await updateSkillConfigs(root, options.skill);
|
|
2025
2307
|
if (options.printUrl) {
|
|
2026
2308
|
setTimeout(() => {
|
|
2027
2309
|
console.log(` \u279C MCP: SSE server is running at ${mcpSseUrl}`);
|