@xfe-repo/cli-plugin-ai-mcp 2.0.7 → 2.0.9

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/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # @xfe-repo/cli-plugin-ai-mcp
2
2
 
3
+ ## 2.0.9
4
+
5
+ ### Patch Changes
6
+
7
+ - 优化deploy
8
+
9
+ ## 2.0.8
10
+
11
+ ### Patch Changes
12
+
13
+ - 新增deploy自动部署支持
14
+ - Updated dependencies
15
+ - @xfe-repo/cli-core@2.1.0
16
+ - @xfe-repo/cli-plugin-ai-rules@2.0.8
17
+
3
18
  ## 2.0.7
4
19
 
5
20
  ### Patch Changes
package/README.md CHANGED
@@ -5,6 +5,7 @@ MCP(Model Context Protocol)服务器插件,将 XFE CLI 的命令暴露为
5
5
  ## 功能
6
6
 
7
7
  - 将 CLI 命令自动映射为 MCP Tools
8
+ - 同步命令完成后直接返回日志结果,异步或持续命令返回 sessionId 供 Agent 拉取日志
8
9
  - 支持动态端口分配,端口冲突时自动递增
9
10
  - 增量日志拉取,Agent 可持续监控任务输出
10
11
  - 通过 `mcp:enable` / `mcp:disable` 命令控制服务开关
package/dist/index.js CHANGED
@@ -32,14 +32,14 @@ export function aiMcpPlugin() {
32
32
  label: 'MCP',
33
33
  icon: '⬡',
34
34
  color: 'blue',
35
- slot: 'footer',
35
+ slot: 'header',
36
36
  value: (state) => {
37
37
  const mcp = state['ai-mcp'];
38
38
  if (!mcp || mcp.status === 'off')
39
39
  return undefined;
40
40
  if (mcp.status === 'error')
41
41
  return '异常';
42
- return `MCP:${mcp.port}`;
42
+ return `${mcp.port}`;
43
43
  },
44
44
  url: (state) => {
45
45
  const mcp = state['ai-mcp'];
@@ -151,9 +151,10 @@ function buildMcpInjectionItems(config) {
151
151
  '## XFE 项目脚手架 (重要!!!)',
152
152
  '',
153
153
  '- 对于项目运行、构建、错误检查、环境切换,优先使用 `xfe-cli-mcp` 提供的工具,不要直接运行 pnpm、npm、yarn 等脚本命令。',
154
- '- 所有命令工具都会立即返回 sessionId。拿到 sessionId 后,必须使用 xfe-get-session-logs 轮询日志和状态,直到 status 变为 completed 或 error。',
154
+ '- MCP 命令可能直接返回 completed/error 结果;当返回 status 为 running 时,必须使用 xfe-get-session-logs 基于 sessionId 轮询日志和状态。',
155
155
  '- 除非用户明确要求停止任务,否则不要调用 xfe-kill-session 终止已有的开发任务。',
156
156
  '- 项目错误、编译日志获取优先顺序是:`xfe-cli-mcp` dev session log > IDE TypeScript/ESLint 报错 > 相关模块代码链路',
157
+ '- 不要轻易修改用户的环境、平台等配置,验证结果优先dev session log,不要频繁build验证',
157
158
  ].join('\n');
158
159
  const serverConfig = {
159
160
  type: 'http',
package/dist/server.js CHANGED
@@ -11,11 +11,16 @@ import { WebStandardStreamableHTTPServerTransport } from '@modelcontextprotocol/
11
11
  import { Hono } from 'hono';
12
12
  import { cors } from 'hono/cors';
13
13
  import { z } from 'zod';
14
+ import { CommandExecutionMode } from '@xfe-repo/cli-core';
14
15
  import { MemoryLogger, AutoPrompt, LogSpinner } from './adapters.js';
15
16
  const MCP_SERVER_INFO = { name: 'xfe-cli', version: '2.0.0' };
16
17
  const LIST_SESSIONS_TOOL_NAME = 'xfe-list-sessions';
17
18
  const GET_SESSION_LOGS_TOOL_NAME = 'xfe-get-session-logs';
18
19
  const KILL_SESSION_TOOL_NAME = 'xfe-kill-session';
20
+ const PERSISTENT_COMMAND_NAMES = new Set(['dev']);
21
+ const JSON_RPC_VERSION = '2.0';
22
+ const MCP_BAD_REQUEST_ERROR_CODE = -32000;
23
+ const MCP_SESSION_NOT_FOUND_ERROR_CODE = -32001;
19
24
  // ─── Public API ────────────────────────────────────────────
20
25
  export async function startMcpServer(ctx, port) {
21
26
  const sessions = new Map();
@@ -72,14 +77,14 @@ function createHttpApp(ctx, sessions) {
72
77
  app.get('/mcp', async (c) => {
73
78
  const sessionId = c.req.header('mcp-session-id');
74
79
  if (!sessionId || !sessions.has(sessionId)) {
75
- return c.text('No active session', 400);
80
+ return mcpSessionErrorResponse(c, sessionId);
76
81
  }
77
82
  return sessions.get(sessionId).transport.handleRequest(c.req.raw);
78
83
  });
79
84
  app.on('DELETE', '/mcp', async (c) => {
80
85
  const sessionId = c.req.header('mcp-session-id');
81
86
  if (!sessionId || !sessions.has(sessionId)) {
82
- return c.text('No active session', 400);
87
+ return mcpSessionErrorResponse(c, sessionId);
83
88
  }
84
89
  return sessions.get(sessionId).transport.handleRequest(c.req.raw);
85
90
  });
@@ -158,8 +163,9 @@ function registerSingleCommandTool(mcpServer, ctx, command, pluginName) {
158
163
  }
159
164
  mcpServer.registerTool(toolName, toolConfig, (params) => executeCommandAsTool(ctx, command, params));
160
165
  }
161
- function executeCommandAsTool(ctx, command, params) {
166
+ async function executeCommandAsTool(ctx, command, params) {
162
167
  const logger = new MemoryLogger();
168
+ const executionMode = resolveCommandExecutionMode(command, params);
163
169
  const sessionId = ctx.sessions.createSession({
164
170
  commandName: command.name,
165
171
  source: 'mcp',
@@ -168,15 +174,21 @@ function executeCommandAsTool(ctx, command, params) {
168
174
  createSpinner: () => new LogSpinner(logger),
169
175
  prefilled: params,
170
176
  });
177
+ if (executionMode === CommandExecutionMode.Sync) {
178
+ await waitForSessionSettled(ctx, sessionId);
179
+ const logResult = ctx.sessions.getSessionLogs(sessionId);
180
+ return mcpResult(buildCompletedCommandResult({ commandName: command.name, executionMode, logResult, sessionId }));
181
+ }
171
182
  return mcpResult({
172
183
  sessionId,
173
184
  command: command.name,
174
185
  status: 'running',
175
186
  source: 'mcp',
187
+ executionMode,
176
188
  logTool: GET_SESSION_LOGS_TOOL_NAME,
177
189
  listSessionsTool: LIST_SESSIONS_TOOL_NAME,
178
190
  killSessionTool: KILL_SESSION_TOOL_NAME,
179
- message: `命令 "${command.name}" 已启动。请使用 ${GET_SESSION_LOGS_TOOL_NAME} 基于 sessionId 拉取日志。`,
191
+ message: buildRunningCommandMessage(command.name, executionMode),
180
192
  });
181
193
  }
182
194
  function mcpResult(data) {
@@ -190,8 +202,8 @@ function buildCommandToolDescription(command, pluginName) {
190
202
  const lines = [
191
203
  `[来源插件: ${pluginName}]`,
192
204
  command.description ?? command.simpleDescription,
193
- '调用该工具会异步创建一个新的 xfe session 并立即返回 sessionId,不会等待命令执行完成。',
194
- `调用后请使用 ${GET_SESSION_LOGS_TOOL_NAME} 轮询日志;如需确认是否已有可复用任务,先使用 ${LIST_SESSIONS_TOOL_NAME}。`,
205
+ '调用该工具会创建一个新的 xfe session。同步任务会在命令完成后直接返回日志结果;异步或持续任务会立即返回 sessionId',
206
+ `当返回 status 为 running 时,请使用 ${GET_SESSION_LOGS_TOOL_NAME} 轮询日志;如需确认是否已有可复用任务,先使用 ${LIST_SESSIONS_TOOL_NAME}。`,
195
207
  ];
196
208
  if (command.parameters) {
197
209
  lines.push('若命令可能触发交互,请在本次调用中一次性传入所有相关参数。');
@@ -201,4 +213,77 @@ function buildCommandToolDescription(command, pluginName) {
201
213
  function resolveSession(ctx, sessionId) {
202
214
  return ctx.sessions.getSession(sessionId);
203
215
  }
216
+ function mcpSessionErrorResponse(c, sessionId) {
217
+ if (sessionId) {
218
+ return c.json(createJsonRpcError(MCP_SESSION_NOT_FOUND_ERROR_CODE, 'Session not found'), 404);
219
+ }
220
+ return c.json(createJsonRpcError(MCP_BAD_REQUEST_ERROR_CODE, 'Bad Request: Mcp-Session-Id header is required'), 400);
221
+ }
222
+ function createJsonRpcError(code, message) {
223
+ return {
224
+ jsonrpc: JSON_RPC_VERSION,
225
+ error: { code, message },
226
+ id: null,
227
+ };
228
+ }
229
+ function resolveCommandExecutionMode(command, params) {
230
+ if (typeof command.execMode === 'function') {
231
+ return command.execMode(params);
232
+ }
233
+ if (command.execMode)
234
+ return command.execMode;
235
+ if (PERSISTENT_COMMAND_NAMES.has(command.name))
236
+ return CommandExecutionMode.Persistent;
237
+ return CommandExecutionMode.Sync;
238
+ }
239
+ function waitForSessionSettled(ctx, sessionId) {
240
+ const session = ctx.sessions.getSession(sessionId);
241
+ if (!session || session.status !== 'running')
242
+ return Promise.resolve();
243
+ return new Promise((resolve) => {
244
+ let unsubscribe = () => { };
245
+ const completeIfSettled = () => {
246
+ const currentSession = ctx.sessions.getSession(sessionId);
247
+ if (currentSession?.status === 'running')
248
+ return;
249
+ unsubscribe();
250
+ resolve();
251
+ };
252
+ unsubscribe = ctx.sessions.subscribe(completeIfSettled);
253
+ completeIfSettled();
254
+ });
255
+ }
256
+ function buildCompletedCommandResult(options) {
257
+ const { commandName, executionMode, sessionId, logResult } = options;
258
+ const status = logResult?.status ?? 'completed';
259
+ return {
260
+ sessionId,
261
+ command: commandName,
262
+ status,
263
+ source: 'mcp',
264
+ executionMode,
265
+ logs: logResult?.logs ?? [],
266
+ totalLines: logResult?.totalLines ?? 0,
267
+ hasMore: logResult?.hasMore ?? false,
268
+ logTool: GET_SESSION_LOGS_TOOL_NAME,
269
+ listSessionsTool: LIST_SESSIONS_TOOL_NAME,
270
+ message: buildCompletedCommandMessage(commandName, status),
271
+ };
272
+ }
273
+ function buildCompletedCommandMessage(commandName, status) {
274
+ if (status === 'error')
275
+ return `命令 "${commandName}" 执行失败,错误日志已随本次响应返回。`;
276
+ return `命令 "${commandName}" 已完成,结果已随本次响应返回。`;
277
+ }
278
+ function buildRunningCommandMessage(commandName, mode) {
279
+ const modeName = formatExecutionMode(mode);
280
+ return `命令 "${commandName}" 已启动。该任务为${modeName},请使用 ${GET_SESSION_LOGS_TOOL_NAME} 基于 sessionId 拉取日志。`;
281
+ }
282
+ function formatExecutionMode(mode) {
283
+ if (mode === CommandExecutionMode.Persistent)
284
+ return '持续任务';
285
+ if (mode === CommandExecutionMode.Async)
286
+ return '异步任务';
287
+ return '同步任务';
288
+ }
204
289
  //# sourceMappingURL=server.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xfe-repo/cli-plugin-ai-mcp",
3
- "version": "2.0.7",
3
+ "version": "2.0.9",
4
4
  "description": "XFE CLI MCP Server 插件 —— 将 CLI 命令暴露为 MCP Tools",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -11,8 +11,8 @@
11
11
  "get-port": "^7.1.0",
12
12
  "hono": "^4.7.0",
13
13
  "zod": "^4.3.6",
14
- "@xfe-repo/cli-plugin-ai-rules": "2.0.7",
15
- "@xfe-repo/cli-core": "2.0.8"
14
+ "@xfe-repo/cli-core": "2.1.0",
15
+ "@xfe-repo/cli-plugin-ai-rules": "2.0.8"
16
16
  },
17
17
  "devDependencies": {
18
18
  "@types/node": "^24.3.0",