From 68740319f04cc2b7e3f4bb40facecb026a4592d3 Mon Sep 17 00:00:00 2001 From: luoye520ww <100058663+luoye520ww@users.noreply.github.com> Date: Tue, 23 Jun 2026 18:35:21 +0800 Subject: [PATCH 01/39] feat(mcp): support stdio server cwd --- kun/README.md | 4 +++- kun/README.zh-CN.md | 3 ++- kun/src/adapters/tool/mcp-tool-provider.ts | 13 +++++++++++- kun/src/contracts/capabilities.ts | 1 + kun/tests/mcp-config.test.ts | 2 ++ kun/tests/mcp-tool-provider.test.ts | 21 +++++++++++++++++++ src/main/kun-process.test.ts | 2 ++ src/main/kun-process.ts | 2 ++ .../src/components/mcp/McpServersEditor.tsx | 11 ++++++++++ .../components/mcp/mcp-config-form.test.ts | 5 +++++ .../src/components/mcp/mcp-config-form.ts | 5 +++++ src/renderer/src/locales/en/settings.json | 3 +++ src/renderer/src/locales/zh/settings.json | 3 +++ 13 files changed, 72 insertions(+), 3 deletions(-) diff --git a/kun/README.md b/kun/README.md index c1c2748cf..f63d06110 100644 --- a/kun/README.md +++ b/kun/README.md @@ -187,6 +187,7 @@ Shape: "enabled": true, "transport": "stdio", "command": "npx", + "cwd": "/path/to/workspace", "args": ["-y", "@modelcontextprotocol/server-github"], "env": { "GITHUB_TOKEN": "" }, "trustScope": "workspace", @@ -453,7 +454,8 @@ stay local to one thread, leave it as a pinned constraint. server `enabled` flag, transport-specific fields (`command` for `stdio`, `url` for HTTP/SSE), `trustedWorkspaceRoots` for workspace-scoped servers, and `/v1/runtime/tools` for redacted - `lastError` diagnostics. + `lastError` diagnostics. Stdio servers can set `cwd`; if omitted, + workspace-scoped servers start in the first trusted workspace root. - Web tools are missing: `capabilities.web.enabled` must be true and at least one of `fetchEnabled` / `searchEnabled` must be true. Built-in fetch handles HTTP(S) pages; search may still be diff --git a/kun/README.zh-CN.md b/kun/README.zh-CN.md index 12e296e59..7458fd6a3 100644 --- a/kun/README.zh-CN.md +++ b/kun/README.zh-CN.md @@ -176,6 +176,7 @@ Kun 使用 JSON 配置文件管理运行时行为,避免重建后重配或硬 "enabled": true, "transport": "stdio", "command": "npx", + "cwd": "/path/to/workspace", "args": ["-y", "@modelcontextprotocol/server-github"], "env": { "GITHUB_TOKEN": "" }, "trustScope": "workspace", @@ -391,7 +392,7 @@ SSE 使用 `id: `、`event: ` 与 `data:`。新连接可通过 `since ## 故障排查 -- MCP 不出现:检查 `capabilities.mcp.enabled`、服务器的 `enabled` 开关、`transport` 字段(`stdio` 需检查 `command`,HTTP/SSE 需检查 `url`)、workspace 级服务器的 `trustedWorkspaceRoots`,以及 `/v1/runtime/tools` 的 `lastError`。 +- MCP 不出现:检查 `capabilities.mcp.enabled`、服务器的 `enabled` 开关、`transport` 字段(`stdio` 需检查 `command`,HTTP/SSE 需检查 `url`)、workspace 级服务器的 `trustedWorkspaceRoots`,以及 `/v1/runtime/tools` 的 `lastError`。stdio 服务器可配置 `cwd`;未配置时,workspace 级服务器会在第一个受信任工作区根目录中启动。 - Web 工具不可用:检查 `capabilities.web.enabled`,并确保 `fetchEnabled` / `searchEnabled` 至少一项为 true。内置 provider 负责抓取 HTTP(S) 页面,搜索可能因未实现 provider 而不可用。 - 图片上传失败:检查 `maxImageBytes`、`maxImageDimension`、`allowedMimeTypes` 与文本 fallback 的大小限制。纯文本模型需要足够小的 base64 文本 fallback。 - 记忆未注入:确认 `capabilities.memory` 为 true,`/v1/memory/diagnostics` 显示正常,作用域与工作区匹配且未被禁用;再看 `lastInjectedIds`。 diff --git a/kun/src/adapters/tool/mcp-tool-provider.ts b/kun/src/adapters/tool/mcp-tool-provider.ts index d02a2a32e..99220f734 100644 --- a/kun/src/adapters/tool/mcp-tool-provider.ts +++ b/kun/src/adapters/tool/mcp-tool-provider.ts @@ -463,13 +463,16 @@ async function createSdkMcpClient(serverId: string, server: McpServerConfig): Pr function createTransport(server: McpServerConfig): Transport { switch (server.transport) { - case 'stdio': + case 'stdio': { + const cwd = resolveMcpServerCwd(server) return new StdioClientTransport({ command: server.command ?? '', args: server.args, env: buildMcpStdioEnvironment(server.env), + ...(cwd ? { cwd } : {}), stderr: 'pipe' }) + } case 'streamable-http': return new StreamableHTTPClientTransport(new URL(server.url ?? ''), { requestInit: { headers: server.headers } @@ -482,6 +485,14 @@ function createTransport(server: McpServerConfig): Transport { } } +export function resolveMcpServerCwd(server: McpServerConfig): string | undefined { + if (server.transport !== 'stdio') return undefined + const configured = server.cwd?.trim() + if (configured) return configured + if (server.trustScope !== 'workspace') return undefined + return server.trustedWorkspaceRoots.map((root) => root.trim()).find(Boolean) +} + function fetchWithHeaders(headers: Record): typeof fetch { return (input, init) => { const mergedHeaders = new Headers(init?.headers) diff --git a/kun/src/contracts/capabilities.ts b/kun/src/contracts/capabilities.ts index 062ba3e00..2754d8d26 100644 --- a/kun/src/contracts/capabilities.ts +++ b/kun/src/contracts/capabilities.ts @@ -112,6 +112,7 @@ export const McpServerConfig = z transport: McpTransportKind, command: z.string().min(1).optional(), args: z.array(z.string()).default([]), + cwd: z.string().min(1).optional(), url: z.string().min(1).optional(), headers: StringRecord.default({}), env: StringRecord.default({}), diff --git a/kun/tests/mcp-config.test.ts b/kun/tests/mcp-config.test.ts index e73958aff..4d7e35780 100644 --- a/kun/tests/mcp-config.test.ts +++ b/kun/tests/mcp-config.test.ts @@ -10,6 +10,7 @@ describe('MCP config', () => { const server = McpServerConfig.parse({ transport: 'stdio', command: 'node', + cwd: '/tmp/project', args: ['server.js'], env: { API_KEY: 'secret' }, trustScope: 'workspace', @@ -18,6 +19,7 @@ describe('MCP config', () => { expect(server.enabled).toBe(true) expect(server.transport).toBe('stdio') + expect(server.cwd).toBe('/tmp/project') expect(server.timeoutMs).toBe(30_000) }) diff --git a/kun/tests/mcp-tool-provider.test.ts b/kun/tests/mcp-tool-provider.test.ts index 3fe4ad33c..c0b647171 100644 --- a/kun/tests/mcp-tool-provider.test.ts +++ b/kun/tests/mcp-tool-provider.test.ts @@ -7,6 +7,7 @@ import { formatMcpConnectionError, isMcpServerTrusted, normalizeMcpToolName, + resolveMcpServerCwd, type McpClientLike } from '../src/adapters/tool/mcp-tool-provider.js' import { REDACTED_SECRET } from '../src/config/secret-redaction.js' @@ -138,6 +139,26 @@ describe('MCP tool provider', () => { expect(isMcpServerTrusted(server, '/tmp/other')).toBe(false) }) + it('resolves stdio MCP working directories from explicit config or trusted workspace fallback', () => { + const base = { + enabled: true, + transport: 'stdio', + command: 'node', + args: [], + url: undefined, + headers: {}, + env: {}, + trustScope: 'workspace', + trustedWorkspaceRoots: ['/tmp/project'], + timeoutMs: 30_000 + } satisfies McpServerConfig + + expect(resolveMcpServerCwd({ ...base, cwd: '/tmp/explicit' })).toBe('/tmp/explicit') + expect(resolveMcpServerCwd(base)).toBe('/tmp/project') + expect(resolveMcpServerCwd({ ...base, trustScope: 'user', trustedWorkspaceRoots: [] })).toBeUndefined() + expect(resolveMcpServerCwd({ ...base, transport: 'streamable-http', url: 'https://mcp.example.test' })).toBeUndefined() + }) + it('builds registry providers from connected MCP clients and executes tools', async () => { const config = KunCapabilitiesConfig.parse({ mcp: { diff --git a/src/main/kun-process.test.ts b/src/main/kun-process.test.ts index a7677ac18..533fe632a 100644 --- a/src/main/kun-process.test.ts +++ b/src/main/kun-process.test.ts @@ -875,6 +875,7 @@ describe('syncGuiManagedKunConfig', () => { servers: { 'stata-mcp': { command: 'uvx', + cwd: 'D:\\Workspace\\stata-project', args: ['stata-mcp'], env: { STATA_CLI: 'D:\\stata\\StataMP-64.exe' @@ -902,6 +903,7 @@ describe('syncGuiManagedKunConfig', () => { enabled: true, transport: 'stdio', command: 'uvx', + cwd: 'D:\\Workspace\\stata-project', args: ['stata-mcp'], env: { STATA_CLI: 'D:\\stata\\StataMP-64.exe' diff --git a/src/main/kun-process.ts b/src/main/kun-process.ts index b5f66718e..809cd08a8 100644 --- a/src/main/kun-process.ts +++ b/src/main/kun-process.ts @@ -610,6 +610,7 @@ function mcpServersFromGuiConfig(config: Record): Record | null { const raw = objectValue(server) const command = scalarStringValue(raw.command) + const cwd = scalarStringValue(raw.cwd)?.trim() const url = scalarStringValue(raw.url) const args = stringArrayValue(raw.args) const headers = stringRecordValue(raw.headers) @@ -626,6 +627,7 @@ function normalizeGuiManagedMcpServer(server: unknown): Record enabled: raw.enabled === false || raw.disabled === true ? false : true, transport, ...(command ? { command } : {}), + ...(transport === 'stdio' && cwd ? { cwd } : {}), ...(args.length > 0 ? { args } : {}), ...(url ? { url } : {}), ...(Object.keys(headers).length > 0 ? { headers } : {}), diff --git a/src/renderer/src/components/mcp/McpServersEditor.tsx b/src/renderer/src/components/mcp/McpServersEditor.tsx index 4af14f1d8..a29c2b401 100644 --- a/src/renderer/src/components/mcp/McpServersEditor.tsx +++ b/src/renderer/src/components/mcp/McpServersEditor.tsx @@ -269,6 +269,17 @@ function McpServerCard({ /> {commandError ? : null} +