@xiaou66/vite-plugin-vue-mcp-next 0.0.1 → 0.0.3

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
@@ -28,33 +28,34 @@ pnpm add -D @xiaou66/vite-plugin-vue-mcp-next
28
28
  ```ts
29
29
  import vue from '@vitejs/plugin-vue'
30
30
  import { defineConfig } from 'vite'
31
- import vueMcpNext from 'vite-plugin-vue-mcp-next'
31
+ import vueMcpNext from '@xiaou66/vite-plugin-vue-mcp-next'
32
32
 
33
33
  export default defineConfig({
34
34
  plugins: [vue(), vueMcpNext()]
35
35
  })
36
36
  ```
37
37
 
38
- 启动 Vite 后,默认 MCP SSE 入口为:
38
+ 启动 Vite 后,默认会暴露两个 MCP 入口:
39
39
 
40
40
  ```text
41
- http://localhost:<vite-port>/__mcp/sse
41
+ SSE: http://localhost:<vite-port>/__mcp/sse
42
+ Streamable HTTP: http://localhost:<vite-port>/__mcp/mcp
42
43
  ```
43
44
 
44
- 启动 Vite dev server 后,插件会自动写入常见 AI 客户端的项目级 MCP 配置,服务名默认是 `vue-mcp-next`。自动配置只新增或更新本插件的 server 条目,不会删除已有 MCP Server。
45
+ 启动 Vite dev server 后,插件会自动写入常见 AI 客户端的项目级 MCP 配置,服务名默认是 `vue-mcp-next`。自动配置只会在缺少同名 server 条目时新增配置;如果用户已经配置了 `vue-mcp-next`,插件不会重复写入或覆盖原配置。
45
46
 
46
- | 客户端 | 自动配置文件 |
47
- |---|---|
48
- | Cursor | `.cursor/mcp.json` |
49
- | Codex | `.codex/config.toml` |
50
- | Claude Code | `.mcp.json` |
51
- | Trae | `.trae/mcp.json` |
47
+ | 客户端 | 自动配置文件 | 默认端点 |
48
+ |---|---|---|
49
+ | Cursor | `.cursor/mcp.json` | SSE |
50
+ | Codex | `.codex/config.toml` | Streamable HTTP |
51
+ | Claude Code | `.mcp.json` | SSE |
52
+ | Trae | `.trae/mcp.json` | SSE |
52
53
 
53
- 实际端口以启动日志中的 `MCP: Server is running at ...` 为准。
54
+ 实际端口以启动日志中的 `MCP: SSE server is running at ...` 和 `MCP: Streamable HTTP server is running at ...` 为准。
54
55
 
55
56
  ### 手动配置 MCP 客户端
56
57
 
57
- 如果你不想使用自动配置,或需要把地址复制到其他支持 HTTP MCP 的客户端,可以手动配置当前 Vite dev server 的 SSE 地址。
58
+ 如果你不想使用自动配置,或需要把地址复制到其他支持 HTTP MCP 的客户端,可以手动配置当前 Vite dev server 的 MCP 地址。
58
59
 
59
60
  Cursor、Claude Code、Trae 等 JSON 配置客户端可以使用:
60
61
 
@@ -62,6 +63,7 @@ Cursor、Claude Code、Trae 等 JSON 配置客户端可以使用:
62
63
  {
63
64
  "mcpServers": {
64
65
  "vue-mcp-next": {
66
+ "type": "sse",
65
67
  "url": "http://localhost:5173/__mcp/sse"
66
68
  }
67
69
  }
@@ -72,7 +74,7 @@ Codex 使用 TOML 配置:
72
74
 
73
75
  ```toml
74
76
  [mcp_servers.vue-mcp-next]
75
- url = "http://localhost:5173/__mcp/sse"
77
+ url = "http://localhost:5173/__mcp/mcp"
76
78
  ```
77
79
 
78
80
  `5173` 是示例端口。若 Vite 使用了其他端口,请替换为启动日志中打印的 MCP 地址。
@@ -128,7 +130,7 @@ vueMcpNext({
128
130
 
129
131
  | 配置 | 类型 | 默认值 | 说明 |
130
132
  |---|---|---|---|
131
- | `mcpPath` | `string` | `'/__mcp'` | MCP 服务挂载路径,实际 SSE 地址是 `${mcpPath}/sse` |
133
+ | `mcpPath` | `string` | `'/__mcp'` | MCP 服务挂载路径,实际 SSE 地址是 `${mcpPath}/sse`,Streamable HTTP 地址是 `${mcpPath}/mcp` |
132
134
  | `host` | `string` | `'localhost'` | 打印 MCP 地址和写入 MCP 客户端配置时使用的 host |
133
135
  | `printUrl` | `boolean` | `true` | 是否在 Vite 启动日志中打印 MCP SSE 地址 |
134
136
  | `mcpClients` | `{ cursor?: boolean; codex?: boolean; claudeCode?: boolean; trae?: boolean; serverName?: string }` | 全部启用 | 是否自动写入 Cursor、Codex、Claude Code、Trae 的项目级 MCP 配置 |
@@ -366,7 +368,8 @@ pnpm run play
366
368
 
367
369
  ```text
368
370
  Local: http://localhost:3456/
369
- MCP: Server is running at http://localhost:3456/__mcp/sse
371
+ MCP: SSE server is running at http://localhost:3456/__mcp/sse
372
+ MCP: Streamable HTTP server is running at http://localhost:3456/__mcp/mcp
370
373
  ```
371
374
 
372
375
  当前 playground 页面入口为:
package/dist/index.cjs CHANGED
@@ -846,18 +846,39 @@ function createMcpServer(ctx, vite) {
846
846
 
847
847
  // src/mcp/transport.ts
848
848
  var import_sse = require("@modelcontextprotocol/sdk/server/sse.js");
849
- function setupMcpTransport(base, server, vite) {
849
+ var import_streamableHttp = require("@modelcontextprotocol/sdk/server/streamableHttp.js");
850
+ function setupMcpTransport(base, createServer, vite) {
850
851
  const transports = /* @__PURE__ */ new Map();
851
852
  vite.middlewares.use(`${base}/sse`, (_req, res) => {
852
853
  const transport = new import_sse.SSEServerTransport(`${base}/messages`, res);
853
- transports.set(transport.sessionId, transport);
854
+ const server = createServer();
855
+ transports.set(transport.sessionId, { server, transport });
854
856
  res.on("close", () => {
855
857
  transports.delete(transport.sessionId);
858
+ void server.close();
856
859
  });
857
860
  void server.connect(transport).catch((error) => {
858
861
  res.destroy(error instanceof Error ? error : new Error(String(error)));
859
862
  });
860
863
  });
864
+ vite.middlewares.use(`${base}/mcp`, (req, res) => {
865
+ if (req.method !== "POST") {
866
+ res.statusCode = 405;
867
+ res.end("Method Not Allowed");
868
+ return;
869
+ }
870
+ const transport = new import_streamableHttp.StreamableHTTPServerTransport({
871
+ sessionIdGenerator: void 0
872
+ });
873
+ const server = createServer();
874
+ res.on("close", () => {
875
+ void transport.close();
876
+ void server.close();
877
+ });
878
+ void server.connect(transport).then(() => transport.handleRequest(req, res)).catch((error) => {
879
+ res.destroy(error instanceof Error ? error : new Error(String(error)));
880
+ });
881
+ });
861
882
  vite.middlewares.use(`${base}/messages`, (req, res) => {
862
883
  if (req.method !== "POST") {
863
884
  res.statusCode = 405;
@@ -871,13 +892,13 @@ function setupMcpTransport(base, server, vite) {
871
892
  res.end("Bad Request");
872
893
  return;
873
894
  }
874
- const transport = transports.get(sessionId);
875
- if (!transport) {
895
+ const entry = transports.get(sessionId);
896
+ if (!entry) {
876
897
  res.statusCode = 404;
877
898
  res.end("Not Found");
878
899
  return;
879
900
  }
880
- void transport.handlePostMessage(req, res).catch((error) => {
901
+ void entry.transport.handlePostMessage(req, res).catch((error) => {
881
902
  res.destroy(error instanceof Error ? error : new Error(String(error)));
882
903
  });
883
904
  });
@@ -1195,7 +1216,7 @@ function createRuntimeInjectionController(options, getConfig) {
1195
1216
  if (id !== RESOLVED_VIRTUAL_RUNTIME_ID) {
1196
1217
  return void 0;
1197
1218
  }
1198
- return "import { startRuntimeClient } from 'vite-plugin-vue-mcp-next/runtime/client';\nvoid startRuntimeClient();";
1219
+ return "import { startRuntimeClient } from '@xiaou66/vite-plugin-vue-mcp-next/runtime/client';\nvoid startRuntimeClient();";
1199
1220
  },
1200
1221
  transformIndexHtml(html) {
1201
1222
  if (options.appendTo) {
@@ -1253,7 +1274,7 @@ function replaceOrAppendOwnedBlock(current, options) {
1253
1274
  const block = createCodexServerBlock(options);
1254
1275
  const matcher = createOwnedBlockMatcher(options.serverName);
1255
1276
  if (matcher.test(current)) {
1256
- return current.replace(matcher, block);
1277
+ return ensureTrailingNewline(current);
1257
1278
  }
1258
1279
  const separator = current.trim() ? "\n\n" : "";
1259
1280
  return `${trimEndNewline(current)}${separator}${block}`;
@@ -1295,6 +1316,10 @@ async function readOptionalTextFile(filePath) {
1295
1316
  function trimEndNewline(value) {
1296
1317
  return value.replace(/\n+$/u, "");
1297
1318
  }
1319
+ function ensureTrailingNewline(value) {
1320
+ return value.endsWith("\n") ? value : `${value}
1321
+ `;
1322
+ }
1298
1323
  function escapeRegExp(value) {
1299
1324
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1300
1325
  }
@@ -1316,7 +1341,10 @@ async function updateJsonMcpClientConfig(options) {
1316
1341
  return;
1317
1342
  }
1318
1343
  const mcpServers = isPlainRecord(config.mcpServers) ? config.mcpServers : {};
1319
- mcpServers[options.serverName] = { url: options.mcpUrl };
1344
+ if (Object.hasOwn(mcpServers, options.serverName)) {
1345
+ return;
1346
+ }
1347
+ mcpServers[options.serverName] = { type: "sse", url: options.mcpUrl };
1320
1348
  config.mcpServers = mcpServers;
1321
1349
  await import_promises2.default.mkdir(import_node_path3.default.dirname(options.configPath), { recursive: true });
1322
1350
  await import_promises2.default.writeFile(
@@ -1361,7 +1389,7 @@ function isNodeError2(error) {
1361
1389
  }
1362
1390
 
1363
1391
  // src/plugin/mcpClientConfig/index.ts
1364
- async function updateMcpClientConfigs(root, mcpUrl, options) {
1392
+ async function updateMcpClientConfigs(root, sseUrl, streamableHttpUrl, options) {
1365
1393
  const serverName = options.serverName;
1366
1394
  const jobs = [];
1367
1395
  if (options.cursor) {
@@ -1369,7 +1397,7 @@ async function updateMcpClientConfigs(root, mcpUrl, options) {
1369
1397
  updateJsonMcpClientConfig({
1370
1398
  clientName: "Cursor",
1371
1399
  configPath: import_node_path4.default.join(root, ".cursor", "mcp.json"),
1372
- mcpUrl,
1400
+ mcpUrl: sseUrl,
1373
1401
  serverName
1374
1402
  })
1375
1403
  );
@@ -1378,7 +1406,7 @@ async function updateMcpClientConfigs(root, mcpUrl, options) {
1378
1406
  jobs.push(
1379
1407
  updateCodexMcpClientConfig({
1380
1408
  configPath: import_node_path4.default.join(root, ".codex", "config.toml"),
1381
- mcpUrl,
1409
+ mcpUrl: streamableHttpUrl,
1382
1410
  serverName
1383
1411
  })
1384
1412
  );
@@ -1388,7 +1416,7 @@ async function updateMcpClientConfigs(root, mcpUrl, options) {
1388
1416
  updateJsonMcpClientConfig({
1389
1417
  clientName: "Claude Code",
1390
1418
  configPath: import_node_path4.default.join(root, ".mcp.json"),
1391
- mcpUrl,
1419
+ mcpUrl: sseUrl,
1392
1420
  serverName
1393
1421
  })
1394
1422
  );
@@ -1398,7 +1426,7 @@ async function updateMcpClientConfigs(root, mcpUrl, options) {
1398
1426
  updateJsonMcpClientConfig({
1399
1427
  clientName: "Trae",
1400
1428
  configPath: import_node_path4.default.join(root, ".trae", "mcp.json"),
1401
- mcpUrl,
1429
+ mcpUrl: sseUrl,
1402
1430
  serverName
1403
1431
  })
1404
1432
  );
@@ -1434,8 +1462,11 @@ function vueMcpNext(userOptions = {}) {
1434
1462
  timeout: -1
1435
1463
  }
1436
1464
  );
1437
- const mcpServer = createMcpServer(ctx, server);
1438
- setupMcpTransport(options.mcpPath, mcpServer, server);
1465
+ setupMcpTransport(
1466
+ options.mcpPath,
1467
+ () => createMcpServer(ctx, server),
1468
+ server
1469
+ );
1439
1470
  server.ws.on(
1440
1471
  "vite-plugin-vue-mcp-next:page-connected",
1441
1472
  (payload) => {
@@ -1462,12 +1493,21 @@ function vueMcpNext(userOptions = {}) {
1462
1493
  }
1463
1494
  );
1464
1495
  const port = String(server.config.server.port || 5173);
1465
- const mcpUrl = `http://${options.host}:${port}${options.mcpPath}/sse`;
1496
+ const mcpSseUrl = `http://${options.host}:${port}${options.mcpPath}/sse`;
1497
+ const mcpStreamableHttpUrl = `http://${options.host}:${port}${options.mcpPath}/mcp`;
1466
1498
  const root = (0, import_vite2.searchForWorkspaceRoot)(server.config.root);
1467
- await updateMcpClientConfigs(root, mcpUrl, options.mcpClients);
1499
+ await updateMcpClientConfigs(
1500
+ root,
1501
+ mcpSseUrl,
1502
+ mcpStreamableHttpUrl,
1503
+ options.mcpClients
1504
+ );
1468
1505
  if (options.printUrl) {
1469
1506
  setTimeout(() => {
1470
- console.log(` \u279C MCP: Server is running at ${mcpUrl}`);
1507
+ console.log(` \u279C MCP: SSE server is running at ${mcpSseUrl}`);
1508
+ console.log(
1509
+ ` \u279C MCP: Streamable HTTP server is running at ${mcpStreamableHttpUrl}`
1510
+ );
1471
1511
  }, 300);
1472
1512
  }
1473
1513
  server.httpServer?.once("close", () => {