@yeaft/webchat-agent 0.1.30 → 0.1.32

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.
@@ -22,6 +22,7 @@ import {
22
22
  } from '../crew.js';
23
23
  import { sendToServer, flushMessageBuffer } from './buffer.js';
24
24
  import { handleRestartAgent, handleUpgradeAgent } from './upgrade.js';
25
+ import { loadMcpServers, updateMcpConfig } from '../mcp.js';
25
26
 
26
27
  export async function handleMessage(msg) {
27
28
  switch (msg.type) {
@@ -54,6 +55,11 @@ export async function handleMessage(msg) {
54
55
 
55
56
  // ★ Phase 1: 通知 server 同步完成
56
57
  sendToServer({ type: 'agent_sync_complete' });
58
+
59
+ // ★ 发送 MCP servers 列表给 server(供前端 Settings > Tools tab 使用)
60
+ if (ctx.mcpServers.length > 0) {
61
+ sendToServer({ type: 'mcp_servers_list', servers: ctx.mcpServers });
62
+ }
57
63
  break;
58
64
 
59
65
  case 'create_conversation':
@@ -267,5 +273,16 @@ export async function handleMessage(msg) {
267
273
  case 'upgrade_agent':
268
274
  await handleUpgradeAgent();
269
275
  break;
276
+
277
+ // MCP configuration
278
+ case 'get_mcp_servers':
279
+ sendToServer({ type: 'mcp_servers_list', servers: ctx.mcpServers });
280
+ break;
281
+
282
+ case 'update_mcp_config': {
283
+ const updated = updateMcpConfig(msg.config || {});
284
+ sendToServer({ type: 'mcp_config_updated', servers: updated });
285
+ break;
286
+ }
270
287
  }
271
288
  }
package/context.js CHANGED
@@ -14,6 +14,8 @@ export default {
14
14
  agentCapabilities: [],
15
15
  // Agent 级别的 slash commands 缓存(所有 conversation 共用)
16
16
  slashCommands: [],
17
+ // MCP servers 列表 (从 ~/.claude.json 读取): [{ name, enabled, source }]
18
+ mcpServers: [],
17
19
  // 连接相关
18
20
  reconnectTimer: null,
19
21
  pendingAuthTempId: null,
@@ -110,6 +110,7 @@ export async function createRoleQuery(session, roleName) {
110
110
 
111
111
  const queryOptions = {
112
112
  cwd: roleCwd,
113
+ projectDir: session.projectDir,
113
114
  permissionMode: 'bypassPermissions',
114
115
  abort: abortController.signal,
115
116
  model: role.model || undefined,
package/index.js CHANGED
@@ -9,6 +9,7 @@ import ctx from './context.js';
9
9
  import { getConfigPath, loadServiceConfig } from './service.js';
10
10
  import { loadNodePty } from './terminal.js';
11
11
  import { connect } from './connection.js';
12
+ import { loadMcpServers } from './mcp.js';
12
13
 
13
14
  const execAsync = promisify(exec);
14
15
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -62,58 +63,27 @@ const CONFIG = {
62
63
  workDir: process.env.WORK_DIR || fileConfig.workDir || process.cwd(),
63
64
  reconnectInterval: fileConfig.reconnectInterval,
64
65
  agentSecret: process.env.AGENT_SECRET || fileConfig.agentSecret,
65
- // MCP 白名单:只允许这些 MCP 服务器的工具,其余自动禁用
66
- // 通过 ALLOWED_MCP_SERVERS 环境变量(逗号分隔)或配置文件 allowedMcpServers 指定
67
- // 默认只允许 playwright
68
- disallowedTools: (() => {
69
- // 解析显式禁用列表
66
+ // 显式禁用的工具(非 MCP 相关)
67
+ explicitDisallowedTools: (() => {
70
68
  const raw = process.env.DISALLOWED_TOOLS || fileConfig.disallowedTools || '';
71
- const explicit = raw === 'none' ? [] : raw.split(',').map(s => s.trim()).filter(Boolean);
72
-
73
- // 解析 MCP 白名单
74
- const allowedRaw = process.env.ALLOWED_MCP_SERVERS || fileConfig.allowedMcpServers || 'playwright';
75
- const allowedMcpServers = allowedRaw.split(',').map(s => s.trim()).filter(Boolean);
76
-
77
- // 读取 ~/.claude.json 中所有配置的 MCP 服务器名
78
- const claudeConfigPath = join(homedir(), '.claude.json');
79
- const mcpDisallowed = [];
80
- try {
81
- if (existsSync(claudeConfigPath)) {
82
- const claudeConfig = JSON.parse(readFileSync(claudeConfigPath, 'utf-8'));
83
- const allMcpNames = new Set();
84
- // 收集所有项目中配置的 MCP 服务器名
85
- for (const [, projCfg] of Object.entries(claudeConfig.projects || {})) {
86
- for (const name of Object.keys(projCfg.mcpServers || {})) {
87
- allMcpNames.add(name);
88
- }
89
- }
90
- // 顶层 mcpServers
91
- for (const name of Object.keys(claudeConfig.mcpServers || {})) {
92
- allMcpNames.add(name);
93
- }
94
- // 不在白名单中的 MCP 服务器 → 禁用
95
- for (const name of allMcpNames) {
96
- if (!allowedMcpServers.includes(name)) {
97
- mcpDisallowed.push(`mcp__${name}`);
98
- }
99
- }
100
- if (mcpDisallowed.length > 0) {
101
- console.log(`[MCP] Allowed: ${allowedMcpServers.join(', ')}`);
102
- console.log(`[MCP] Disallowed: ${mcpDisallowed.join(', ')}`);
103
- }
104
- }
105
- } catch (e) {
106
- console.warn('[MCP] Failed to read ~/.claude.json:', e.message);
107
- }
108
-
109
- return [...explicit, ...mcpDisallowed];
110
- })()
69
+ return raw === 'none' ? [] : raw.split(',').map(s => s.trim()).filter(Boolean);
70
+ })(),
71
+ // MCP 白名单初始值(环境变量或配置文件指定)
72
+ allowedMcpServers: (() => {
73
+ const raw = process.env.ALLOWED_MCP_SERVERS || fileConfig.allowedMcpServers || 'playwright';
74
+ return raw.split(',').map(s => s.trim()).filter(Boolean);
75
+ })(),
76
+ // disallowedTools 会在 loadMcpServers() 中计算
77
+ disallowedTools: []
111
78
  };
112
79
 
113
80
  // 初始化共享上下文
114
81
  ctx.CONFIG = CONFIG;
115
82
  ctx.saveConfig = saveConfig;
116
83
 
84
+ // 初始加载 MCP servers(必须在 ctx.CONFIG 赋值之后)
85
+ loadMcpServers();
86
+
117
87
  // Agent capabilities(启动时自动检测)
118
88
  async function detectCapabilities() {
119
89
  const capabilities = ['background_tasks', 'file_editor'];
package/mcp.js ADDED
@@ -0,0 +1,83 @@
1
+ import { homedir } from 'os';
2
+ import { existsSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import ctx from './context.js';
5
+
6
+ /**
7
+ * 读取 ~/.claude.json 中的 MCP servers 列表,
8
+ * 并根据 allowedMcpServers 白名单计算 disallowedTools。
9
+ * 返回 mcpServers 列表并同时存入 ctx.mcpServers。
10
+ */
11
+ export function loadMcpServers() {
12
+ const claudeConfigPath = join(homedir(), '.claude.json');
13
+ const allMcpNames = new Set();
14
+ try {
15
+ if (existsSync(claudeConfigPath)) {
16
+ const claudeConfig = JSON.parse(readFileSync(claudeConfigPath, 'utf-8'));
17
+ for (const [, projCfg] of Object.entries(claudeConfig.projects || {})) {
18
+ for (const name of Object.keys(projCfg.mcpServers || {})) {
19
+ allMcpNames.add(name);
20
+ }
21
+ }
22
+ for (const name of Object.keys(claudeConfig.mcpServers || {})) {
23
+ allMcpNames.add(name);
24
+ }
25
+ }
26
+ } catch (e) {
27
+ console.warn('[MCP] Failed to read ~/.claude.json:', e.message);
28
+ }
29
+
30
+ const allowed = ctx.CONFIG.allowedMcpServers;
31
+ const mcpServers = [];
32
+ for (const name of allMcpNames) {
33
+ mcpServers.push({
34
+ name,
35
+ enabled: allowed.includes(name),
36
+ source: name === 'playwright' ? 'Built-in' : 'MCP'
37
+ });
38
+ }
39
+
40
+ // 重新计算 disallowedTools
41
+ recalcDisallowedTools(mcpServers);
42
+ ctx.mcpServers = mcpServers;
43
+
44
+ if (mcpServers.length > 0) {
45
+ const enabledNames = mcpServers.filter(s => s.enabled).map(s => s.name);
46
+ console.log(`[MCP] Enabled: ${enabledNames.join(', ') || 'none'}`);
47
+ const mcpDisallowed = mcpServers.filter(s => !s.enabled).map(s => s.name);
48
+ if (mcpDisallowed.length > 0) {
49
+ console.log(`[MCP] Disabled: ${mcpDisallowed.join(', ')}`);
50
+ }
51
+ }
52
+
53
+ return mcpServers;
54
+ }
55
+
56
+ /**
57
+ * 更新 MCP servers 的 enabled 状态(从前端 toggle 触发)
58
+ * @param {Object<string, boolean>} config - { serverName: enabled }
59
+ */
60
+ export function updateMcpConfig(config) {
61
+ for (const server of ctx.mcpServers) {
62
+ if (config[server.name] !== undefined) {
63
+ server.enabled = config[server.name];
64
+ }
65
+ }
66
+
67
+ // 更新白名单
68
+ ctx.CONFIG.allowedMcpServers = ctx.mcpServers
69
+ .filter(s => s.enabled)
70
+ .map(s => s.name);
71
+
72
+ recalcDisallowedTools(ctx.mcpServers);
73
+
74
+ console.log(`[MCP] Config updated. Allowed: ${ctx.CONFIG.allowedMcpServers.join(', ') || 'none'}`);
75
+ return ctx.mcpServers;
76
+ }
77
+
78
+ function recalcDisallowedTools(mcpServers) {
79
+ const mcpDisallowed = mcpServers
80
+ .filter(s => !s.enabled)
81
+ .map(s => `mcp__${s.name}`);
82
+ ctx.CONFIG.disallowedTools = [...ctx.CONFIG.explicitDisallowedTools, ...mcpDisallowed];
83
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@yeaft/webchat-agent",
3
- "version": "0.1.30",
3
+ "version": "0.1.32",
4
4
  "description": "Remote agent for Yeaft WebChat — connects worker machines to the central server",
5
5
  "main": "index.js",
6
6
  "type": "module",
package/sdk/query.js CHANGED
@@ -272,6 +272,7 @@ export function query(config) {
272
272
  appendSystemPrompt,
273
273
  customSystemPrompt,
274
274
  cwd,
275
+ projectDir,
275
276
  disallowedTools = [],
276
277
  maxTurns,
277
278
  permissionMode = 'default',
@@ -301,6 +302,7 @@ export function query(config) {
301
302
  if (allowedTools.length > 0) args.push('--allowedTools', ...allowedTools);
302
303
  if (disallowedTools.length > 0) args.push('--disallowedTools', ...disallowedTools);
303
304
  if (permissionMode) args.push('--permission-mode', permissionMode);
305
+ if (projectDir) args.push('--project', projectDir);
304
306
 
305
307
  // Handle prompt input
306
308
  if (typeof prompt === 'string') {