mocode-ai 1.3.7 → 1.3.8

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.
@@ -529,6 +529,7 @@ export const config = {
529
529
  subAgentEnabled: process.env.MOCODE_SUBAGENT_ENABLED === 'true',
530
530
  subAgentMaxSteps: Number(process.env.SUB_AGENT_MAX_STEPS) || Number(process.env.MAX_STEPS) || 1000,
531
531
  frontendToolsEnabled: process.env.MOCODE_FRONTEND_TOOLS_ENABLED === 'true',
532
+ mcpEnabled: process.env.MOCODE_MCP_ENABLED !== 'false',
532
533
  sessionDir: path.join(process.cwd(), '.mocode', 'sessions'),
533
534
  searchApiKey: process.env.ANYSEARCH_API_KEY,
534
535
  sandboxRoot: process.env.SANDBOX_ROOT || undefined,
@@ -613,6 +614,15 @@ export function updateFrontendToolsConfig(enabled) {
613
614
  config.frontendToolsEnabled = enabled;
614
615
  process.env.MOCODE_FRONTEND_TOOLS_ENABLED = enabled ? 'true' : 'false';
615
616
  }
617
+ /** MCP 总开关;关闭时下次启动跳过 MCP 配置读取与服务连接。 */
618
+ export function isMcpEnabled() {
619
+ return config.mcpEnabled;
620
+ }
621
+ /** 运行时更新 MCP 开关;连接建立或释放、工具表刷新由 REPL 调用方执行。 */
622
+ export function updateMcpConfig(enabled) {
623
+ config.mcpEnabled = enabled;
624
+ process.env.MOCODE_MCP_ENABLED = enabled ? 'true' : 'false';
625
+ }
616
626
  /**
617
627
  * 记忆子系统总开关:单一来源。/memory_switch、/memory_status、buildSystemPrompt、
618
628
  * tools/builtins/index.ts、tools/constants.ts 的 plan-mode 列表都从这里查。
@@ -27,6 +27,10 @@ const zhCN = {
27
27
  'commands.feOn': '开启前端工具簇',
28
28
  'commands.feOff': '关闭前端工具簇',
29
29
  'commands.feStatus': '查看前端工具簇状态',
30
+ 'commands.mcp': 'MCP 服务开关(重启后生效)',
31
+ 'commands.mcpOn': '开启 MCP 服务',
32
+ 'commands.mcpOff': '关闭 MCP 服务',
33
+ 'commands.mcpStatus': '查看 MCP 服务状态',
30
34
  'commands.theme': '切换颜色主题(↑↓·Enter)',
31
35
  'commands.model': '模型配置与预设管理',
32
36
  'commands.modelConfigure': '配置新模型(向导)',
@@ -175,7 +179,6 @@ const zhCN = {
175
179
  'banner.disabled': '关闭',
176
180
  'status.modeSwitch': 'Shift+Tab 切换',
177
181
  'status.history': '历史 ↑{count}(PgDn 回底)',
178
- 'status.interruptHint': 'Esc / Ctrl+C 中断',
179
182
  'status.measured': '实测',
180
183
  'status.estimated': '估算',
181
184
  'status.messages': '{count} 条消息',
@@ -260,6 +263,14 @@ const zhCN = {
260
263
  'fe.changedOn': '已开启前端工具簇;browser / dev_server / screenshot / view_image 将从下一次模型请求起可用。',
261
264
  'fe.changedOff': '已关闭前端工具簇;browser / dev_server / screenshot / view_image 已从模型工具表移除。',
262
265
  'fe.usage': '用法:/fe on|off|status',
266
+ 'mcp.status': 'MCP 服务:{state}',
267
+ 'mcp.stateOn': '开启',
268
+ 'mcp.stateOff': '关闭',
269
+ 'mcp.changedOn': '已开启 MCP;已按当前配置连接服务并刷新工具表。',
270
+ 'mcp.changedOff': '已关闭 MCP;已断开连接并从工具表移除 MCP 工具。',
271
+ 'mcp.usage': '用法:/mcp on|off|status',
272
+ 'mcp.unchanged': 'MCP 已是{state},未变更。',
273
+ 'mcp.restartHint': '该设置已写入配置文件;下次启动将按此状态决定是否连接 MCP。',
263
274
  'plan.ready': '计划已就绪',
264
275
  'plan.approvalDetail': '切换到 auto 模式按上述计划执行?(plan 模式只读探查,执行需切 auto)',
265
276
  'plan.execute': '切 auto 执行',
@@ -318,6 +329,10 @@ const en = {
318
329
  'commands.feOn': 'Enable frontend tools',
319
330
  'commands.feOff': 'Disable frontend tools',
320
331
  'commands.feStatus': 'Show frontend tools status',
332
+ 'commands.mcp': 'MCP service controls (applies immediately)',
333
+ 'commands.mcpOn': 'Enable MCP services',
334
+ 'commands.mcpOff': 'Disable MCP services',
335
+ 'commands.mcpStatus': 'Show MCP service status',
321
336
  'commands.theme': 'Switch color theme (↑↓·Enter)',
322
337
  'commands.model': 'Model configuration and presets',
323
338
  'commands.modelConfigure': 'Configure a new model (wizard)',
@@ -466,7 +481,6 @@ const en = {
466
481
  'banner.disabled': 'Disabled',
467
482
  'status.modeSwitch': 'Shift+Tab Switch',
468
483
  'status.history': 'History ↑{count} (PgDn to bottom)',
469
- 'status.interruptHint': 'Esc / Ctrl+C to interrupt',
470
484
  'status.measured': 'measured',
471
485
  'status.estimated': 'estimated',
472
486
  'status.messages': '{count} messages',
@@ -551,6 +565,14 @@ const en = {
551
565
  'fe.changedOn': 'Frontend tools enabled; browser / dev_server / screenshot / view_image will be available from the next model request.',
552
566
  'fe.changedOff': 'Frontend tools disabled; browser / dev_server / screenshot / view_image have been removed from the model tool list.',
553
567
  'fe.usage': 'Usage: /fe on|off|status',
568
+ 'mcp.status': 'MCP services: {state}',
569
+ 'mcp.stateOn': 'enabled',
570
+ 'mcp.stateOff': 'disabled',
571
+ 'mcp.changedOn': 'MCP enabled; services have been connected from the current configuration and the tool list refreshed.',
572
+ 'mcp.changedOff': 'MCP disabled; connections have been closed and MCP tools removed from the tool list.',
573
+ 'mcp.usage': 'Usage: /mcp on|off|status',
574
+ 'mcp.unchanged': 'MCP is already {state}; no change made.',
575
+ 'mcp.restartHint': 'This setting is saved to the configuration file; future startups use it to decide whether to connect MCP.',
554
576
  'plan.ready': 'Plan ready',
555
577
  'plan.approvalDetail': 'Switch to auto mode and execute the plan above? (Plan mode is read-only; execution requires auto mode.)',
556
578
  'plan.execute': 'Switch to auto and execute',
@@ -7,6 +7,9 @@ import path from 'node:path';
7
7
  * JSON 格式兼容常见的 { "mcpServers": { name: { command/url/... } } },也允许直接 server map。
8
8
  */
9
9
  export function readMcpServers() {
10
+ // 总开关必须早于任何配置读取,以便即使用户级 MCP 配置存在也能快速启动。
11
+ if (process.env.MOCODE_MCP_ENABLED === 'false')
12
+ return { servers: [], warnings: [] };
10
13
  const warnings = [];
11
14
  const servers = new Map();
12
15
  const fromFile = process.env.MCP_CONFIG_PATH;
@@ -1,7 +1,7 @@
1
1
  import readline from 'node:readline/promises';
2
2
  import { emitKeypressEvents } from 'node:readline';
3
3
  import { stdin, stdout } from 'node:process';
4
- import { config, updateModelConfig, isModelConfigured, updateMemoryConfig, isMemoryEnabled, isSubAgentEnabled, updateSubAgentConfig, isFrontendToolsEnabled, updateFrontendToolsConfig, updateLanguageConfig, languageFromShell, buildBasePrompt, getPlanModeSuffix, hasCodegraphIndex, DEFAULT_CONTEXT_WINDOW_TOKENS, pinSessionModel, } from '../config/index.js';
4
+ import { config, updateModelConfig, isModelConfigured, updateMemoryConfig, isMemoryEnabled, isSubAgentEnabled, updateSubAgentConfig, isFrontendToolsEnabled, updateFrontendToolsConfig, isMcpEnabled, updateMcpConfig, updateLanguageConfig, languageFromShell, buildBasePrompt, getPlanModeSuffix, hasCodegraphIndex, DEFAULT_CONTEXT_WINDOW_TOKENS, pinSessionModel, } from '../config/index.js';
5
5
  import { getLanguage, normalizeLanguage, t, } from '../i18n/index.js';
6
6
  import { DEFAULT_BUDGET_POLICY } from '../context/budget.js';
7
7
  import { updateConfigKey, writeConfigKeys, CONFIG_PATH } from '../config/file.js';
@@ -84,6 +84,13 @@ function buildSlashCommands() {
84
84
  { name: 'status', value: '/fe status', desc: d('commands.feStatus') },
85
85
  ],
86
86
  },
87
+ {
88
+ name: '/mcp', desc: d('commands.mcp'), children: [
89
+ { name: 'on', value: '/mcp on', desc: d('commands.mcpOn') },
90
+ { name: 'off', value: '/mcp off', desc: d('commands.mcpOff') },
91
+ { name: 'status', value: '/mcp status', desc: d('commands.mcpStatus') },
92
+ ],
93
+ },
87
94
  { name: '/theme', desc: d('commands.theme') },
88
95
  {
89
96
  name: '/model', desc: d('commands.model'), children: [
@@ -157,7 +164,7 @@ const LLM_ERROR_HINT_KEYS = {
157
164
  const HELP_GROUPS = [
158
165
  { key: 'help.groupFrequent', names: ['/help', '/clear', '/context', '/compact', '/resume', '/model'] },
159
166
  { key: 'help.groupSession', names: ['/sessions', '/rollback', '/memory', '/skills', '/skill', '/init'] },
160
- { key: 'help.groupConfig', names: ['/mode', '/subagent', '/fe', '/theme', '/pet', '/image', '/language', '/upgrade'] },
167
+ { key: 'help.groupConfig', names: ['/mode', '/subagent', '/fe', '/mcp', '/theme', '/pet', '/image', '/language', '/upgrade'] },
161
168
  ];
162
169
  /**
163
170
  * 菜单树里全部可用命令名(含分支路径与叶子的真实命令),供未知命令纠错建议用。
@@ -2401,6 +2408,43 @@ export async function startRepl(initialHistory, sessionId, sandboxRootOverride,
2401
2408
  layout.contentWrite(`${enabled ? ui.green : ui.yellow}${t(enabled ? 'fe.changedOn' : 'fe.changedOff')}${ui.reset}\n`);
2402
2409
  continue;
2403
2410
  }
2411
+ if (line === '/mcp' || line.startsWith('/mcp ')) {
2412
+ const arg = line.startsWith('/mcp ')
2413
+ ? line.slice('/mcp '.length).trim().toLowerCase()
2414
+ : 'status';
2415
+ if (arg === '' || arg === 'status') {
2416
+ const enabled = isMcpEnabled();
2417
+ const state = t(enabled ? 'mcp.stateOn' : 'mcp.stateOff');
2418
+ layout.contentWrite(`${ui.accent}${t('mcp.status', { state })}${ui.reset}\n` +
2419
+ `${ui.dim}MOCODE_MCP_ENABLED=${enabled ? 'true' : 'false'} · ${CONFIG_PATH}${ui.reset}\n` +
2420
+ `${ui.dim}${t('mcp.restartHint')}${ui.reset}\n`);
2421
+ continue;
2422
+ }
2423
+ if (arg !== 'on' && arg !== 'off') {
2424
+ layout.contentWrite(`${ui.yellow}${t('mcp.usage')}${ui.reset}\n`);
2425
+ continue;
2426
+ }
2427
+ const enabled = arg === 'on';
2428
+ if (enabled === isMcpEnabled()) {
2429
+ layout.contentWrite(`${ui.dim}${t('mcp.unchanged', { state: t(enabled ? 'mcp.stateOn' : 'mcp.stateOff') })}${ui.reset}\n`);
2430
+ continue;
2431
+ }
2432
+ updateMcpConfig(enabled);
2433
+ updateConfigKey('MOCODE_MCP_ENABLED', enabled ? 'true' : 'false');
2434
+ if (enabled) {
2435
+ await initializeAllMcp();
2436
+ registerToolsExtension('mcp', getMcpTools());
2437
+ }
2438
+ else {
2439
+ await closeAllMcp();
2440
+ registerToolsExtension('mcp', []);
2441
+ }
2442
+ refreshChatTools();
2443
+ history[0] = { role: 'system', content: buildSystemMessage(getAgentMode() === 'plan') };
2444
+ layout.rewriteBanner(bannerLines(banner()));
2445
+ layout.contentWrite(`${enabled ? ui.green : ui.yellow}${t(enabled ? 'mcp.changedOn' : 'mcp.changedOff')}${ui.reset}\n`);
2446
+ continue;
2447
+ }
2404
2448
  if (line === '/memory_switch' ||
2405
2449
  line.startsWith('/memory_switch ') ||
2406
2450
  line === '/memory_status' ||
package/dist/ui/layout.js CHANGED
@@ -1406,14 +1406,6 @@ function composeSpinnerLine(status, cols) {
1406
1406
  const elapsed = (mode === 'running' && turnStart != null)
1407
1407
  ? fmtElapsed(Date.now() - turnStart)
1408
1408
  : '';
1409
- // 新手中断提示:每轮开始后前 6 秒,spinner 行尾随一次 dim「(Esc / Ctrl+C 中断)」,
1410
- // 6 秒后自然消失(不常驻,不打扰老用户)。只在非滚动回看时画,回看态右段让给历史指示。
1411
- const INTERRUPT_HINT_WINDOW_MS = 6000;
1412
- const hintActive = !scrolled && mode === 'running' && turnStart != null
1413
- && Date.now() - turnStart < INTERRUPT_HINT_WINDOW_MS;
1414
- const hintText = t('status.interruptHint');
1415
- const hint = hintActive ? ` ${ui.dim}(${hintText})${ui.reset}` : '';
1416
- const hintW = hintActive ? 4 + displayWidth(hintText) : 0; // 2 缩进 + 左右括号
1417
1409
  // ── 左段:帧 + 状态 + 走时,全部紧跟 ──
1418
1410
  let lead;
1419
1411
  let leadW;
@@ -1432,17 +1424,17 @@ function composeSpinnerLine(status, cols) {
1432
1424
  leadW = 1 + 1 + displayWidth(label) + (elapsed ? 1 + displayWidth(elapsed) : 0);
1433
1425
  }
1434
1426
  else if (hasSpinner) {
1435
- // spinner 激活(思考中/执行工具…):帧 + 状态 + 走时 (+ 新手中断提示)
1427
+ // spinner 激活(思考中/执行工具…):帧 + 状态 + 走时
1436
1428
  const ePart = elapsed ? ` ${ui.dim}${elapsed}${ui.reset}` : '';
1437
- lead = `${ui.bold}${ui.accent}${status.spinnerFrame}${ui.reset} ${ui.dim}${status.status}${ui.reset}${ePart}${hint}`;
1438
- leadW = 1 + 1 + displayWidth(status.status) + (elapsed ? 1 + displayWidth(elapsed) : 0) + hintW;
1429
+ lead = `${ui.bold}${ui.accent}${status.spinnerFrame}${ui.reset} ${ui.dim}${status.status}${ui.reset}${ePart}`;
1430
+ leadW = 1 + 1 + displayWidth(status.status) + (elapsed ? 1 + displayWidth(elapsed) : 0);
1439
1431
  }
1440
1432
  else if (spinning) {
1441
- // 运行态心跳帧(流式输出中 / 命令态如 /rollback /compact /resume):帧 + 状态文字(优先)或生成中(兜底) + 走时 (+ 新手中断提示)
1433
+ // 运行态心跳帧(流式输出中 / 命令态如 /rollback /compact /resume):帧 + 状态文字(优先)或生成中(兜底) + 走时
1442
1434
  const label = status.status || '生成中';
1443
1435
  const ePart = elapsed ? ` ${ui.dim}${elapsed}${ui.reset}` : '';
1444
- lead = `${ui.bold}${ui.accent}${RUNNING_FRAMES[runningFrame]}${ui.reset} ${ui.dim}${label}${ui.reset}${ePart}${hint}`;
1445
- leadW = 1 + 1 + displayWidth(label) + (elapsed ? 1 + displayWidth(elapsed) : 0) + hintW;
1436
+ lead = `${ui.bold}${ui.accent}${RUNNING_FRAMES[runningFrame]}${ui.reset} ${ui.dim}${label}${ui.reset}${ePart}`;
1437
+ leadW = 1 + 1 + displayWidth(label) + (elapsed ? 1 + displayWidth(elapsed) : 0);
1446
1438
  }
1447
1439
  else {
1448
1440
  // INPUT 态:● + 状态文字(无走时)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mocode-ai",
3
- "version": "1.3.7",
3
+ "version": "1.3.8",
4
4
  "description": "终端编码 agent:LLM + tool-call 循环 + 流式输出(含思考)+ 16 个工具,接任意 OpenAI 兼容后端。",
5
5
  "type": "module",
6
6
  "bin": {