From fd7b91ac29f5a72e2791958e16d474006a3fb6eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=A9=AC=E5=8D=83=E9=87=8C?= Date: Mon, 22 Jun 2026 21:42:45 +0800 Subject: [PATCH] fix: require trust for project MCP config --- USAGE.md | 10 +++++-- USAGE_ZH.md | 8 ++++-- src/cli-commands.ts | 2 +- src/config.ts | 26 +++++++++++++---- test/config.test.ts | 69 +++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 105 insertions(+), 10 deletions(-) create mode 100644 test/config.test.ts diff --git a/USAGE.md b/USAGE.md index 61ee59d..29ad26a 100644 --- a/USAGE.md +++ b/USAGE.md @@ -268,7 +268,13 @@ Example configuration: } ``` -Project-scoped MCP config is also supported through Claude Code compatible `.mcp.json`: +Project-scoped MCP config is also supported through Claude Code compatible `.mcp.json`, +but it is not loaded by default because it can spawn local commands from the +current repository. Use it only after you trust the project: + +```bash +MINI_CODE_TRUST_PROJECT_MCP=1 minicode +``` ```json { @@ -307,7 +313,7 @@ Configuration priority: 1. `~/.mini-code/settings.json` 2. `~/.mini-code/mcp.json` -3. project `.mcp.json` +3. project `.mcp.json` when `MINI_CODE_TRUST_PROJECT_MCP=1` 4. compatible existing local settings 5. process environment variables diff --git a/USAGE_ZH.md b/USAGE_ZH.md index b50419a..05f00ee 100644 --- a/USAGE_ZH.md +++ b/USAGE_ZH.md @@ -267,7 +267,11 @@ MiniCode 现在把长会话作为一等工作流处理: } ``` -也支持 Claude Code 风格的项目级 `.mcp.json`: +也支持 Claude Code 风格的项目级 `.mcp.json`,但默认不会加载它,因为它可以从当前仓库启动本地命令。确认信任该项目后再显式启用: + +```bash +MINI_CODE_TRUST_PROJECT_MCP=1 minicode +``` ```json { @@ -306,7 +310,7 @@ Skills 默认会从这些位置发现: 1. `~/.mini-code/settings.json` 2. `~/.mini-code/mcp.json` -3. 项目级 `.mcp.json` +3. 设置 `MINI_CODE_TRUST_PROJECT_MCP=1` 时的项目级 `.mcp.json` 4. 兼容的本地已有配置 5. 当前进程环境变量 diff --git a/src/cli-commands.ts b/src/cli-commands.ts index 3bf1dba..b50aead 100644 --- a/src/cli-commands.ts +++ b/src/cli-commands.ts @@ -226,7 +226,7 @@ export async function tryHandleLocalCommand( if (input === '/mcp') { const servers = context?.tools?.getMcpServers() ?? [] if (servers.length === 0) { - return 'No MCP servers configured. Add mcpServers to ~/.mini-code/settings.json, ~/.mini-code/mcp.json, or project .mcp.json.' + return 'No MCP servers configured. Add mcpServers to ~/.mini-code/settings.json or ~/.mini-code/mcp.json. Project .mcp.json is loaded only when MINI_CODE_TRUST_PROJECT_MCP=1.' } return servers diff --git a/src/config.ts b/src/config.ts index 618a679..9260c02 100644 --- a/src/config.ts +++ b/src/config.ts @@ -44,6 +44,14 @@ export const MINI_CODE_MCP_TOKENS_PATH = path.join(MINI_CODE_DIR, 'mcp-tokens.js export const MINI_CODE_PROJECTS_DIR = path.join(MINI_CODE_DIR, 'projects') export const CLAUDE_SETTINGS_PATH = path.join(os.homedir(), '.claude', 'settings.json') export const PROJECT_MCP_PATH = path.join(process.cwd(), '.mcp.json') +const TRUST_PROJECT_MCP_ENV = 'MINI_CODE_TRUST_PROJECT_MCP' + +export function shouldLoadProjectMcpConfig( + env: NodeJS.ProcessEnv = process.env, +): boolean { + const value = String(env[TRUST_PROJECT_MCP_ENV] ?? '').trim().toLowerCase() + return value === '1' || value === 'true' || value === 'yes' || value === 'on' +} export async function readMcpTokensFile( filePath = MINI_CODE_MCP_TOKENS_PATH, @@ -170,18 +178,24 @@ function mergeSettings( } } -export async function loadEffectiveSettings(): Promise { - const [claudeSettings, globalMcpConfig, projectMcpConfig, miniCodeSettings] = +export async function loadEffectiveSettings( + options: { cwd?: string; env?: NodeJS.ProcessEnv } = {}, +): Promise { + const cwd = options.cwd ?? process.cwd() + const projectMcpConfig = shouldLoadProjectMcpConfig(options.env) + ? readMcpConfigFile(getMcpConfigPath('project', cwd)) + : Promise.resolve({}) + const [claudeSettings, globalMcpConfig, trustedProjectMcpConfig, miniCodeSettings] = await Promise.all([ readSettingsFile(CLAUDE_SETTINGS_PATH), readMcpConfigFile(MINI_CODE_MCP_PATH), - readMcpConfigFile(PROJECT_MCP_PATH), + projectMcpConfig, readSettingsFile(MINI_CODE_SETTINGS_PATH), ]) return mergeSettings( mergeSettings( mergeSettings(claudeSettings, { mcpServers: globalMcpConfig }), - { mcpServers: projectMcpConfig }, + { mcpServers: trustedProjectMcpConfig }, ), miniCodeSettings, ) @@ -246,6 +260,8 @@ export async function loadRuntimeConfig(): Promise { apiKey, maxOutputTokens, mcpServers: effectiveSettings.mcpServers ?? {}, - sourceSummary: `config: ${MINI_CODE_SETTINGS_PATH} > ${CLAUDE_SETTINGS_PATH} > process.env`, + sourceSummary: `config: ${MINI_CODE_SETTINGS_PATH} > ${CLAUDE_SETTINGS_PATH}${ + shouldLoadProjectMcpConfig() ? ' > project .mcp.json' : '' + } > process.env`, } } diff --git a/test/config.test.ts b/test/config.test.ts new file mode 100644 index 0000000..61d0b09 --- /dev/null +++ b/test/config.test.ts @@ -0,0 +1,69 @@ +import { test } from 'node:test' +import assert from 'node:assert/strict' +import { mkdirSync, rmSync, writeFileSync } from 'node:fs' +import os from 'node:os' +import path from 'node:path' +import { loadEffectiveSettings, shouldLoadProjectMcpConfig } from '../src/config.js' + +function makeTempDir(): string { + const dir = path.join( + os.tmpdir(), + `minicode-config-test-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`, + ) + mkdirSync(dir, { recursive: true }) + return dir +} + +test('project .mcp.json is ignored unless explicitly trusted', async () => { + const dir = makeTempDir() + try { + writeFileSync(path.join(dir, '.mcp.json'), JSON.stringify({ + mcpServers: { + untrusted: { command: 'node', args: ['evil.js'] }, + }, + })) + + const settings = await loadEffectiveSettings({ cwd: dir, env: {} }) + + assert.equal(settings.mcpServers?.untrusted, undefined) + } finally { + rmSync(dir, { recursive: true, force: true }) + } +}) + +test('project .mcp.json loads when MINI_CODE_TRUST_PROJECT_MCP is set', async () => { + const dir = makeTempDir() + try { + writeFileSync(path.join(dir, '.mcp.json'), JSON.stringify({ + mcpServers: { + trusted: { command: 'node', args: ['server.js'] }, + }, + })) + + const settings = await loadEffectiveSettings({ + cwd: dir, + env: { MINI_CODE_TRUST_PROJECT_MCP: '1' }, + }) + + assert.equal(settings.mcpServers?.trusted?.command, 'node') + assert.deepEqual(settings.mcpServers?.trusted?.args, ['server.js']) + } finally { + rmSync(dir, { recursive: true, force: true }) + } +}) + +test('project MCP trust flag accepts explicit truthy values only', () => { + assert.equal( + shouldLoadProjectMcpConfig({ MINI_CODE_TRUST_PROJECT_MCP: 'true' }), + true, + ) + assert.equal( + shouldLoadProjectMcpConfig({ MINI_CODE_TRUST_PROJECT_MCP: 'yes' }), + true, + ) + assert.equal( + shouldLoadProjectMcpConfig({ MINI_CODE_TRUST_PROJECT_MCP: '0' }), + false, + ) + assert.equal(shouldLoadProjectMcpConfig({}), false) +})