Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions USAGE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand Down Expand Up @@ -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

Expand Down
8 changes: 6 additions & 2 deletions USAGE_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,11 @@ MiniCode 现在把长会话作为一等工作流处理:
}
```

也支持 Claude Code 风格的项目级 `.mcp.json`:
也支持 Claude Code 风格的项目级 `.mcp.json`,但默认不会加载它,因为它可以从当前仓库启动本地命令。确认信任该项目后再显式启用:

```bash
MINI_CODE_TRUST_PROJECT_MCP=1 minicode
```

```json
{
Expand Down Expand Up @@ -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. 当前进程环境变量

Expand Down
2 changes: 1 addition & 1 deletion src/cli-commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 21 additions & 5 deletions src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -170,18 +178,24 @@ function mergeSettings(
}
}

export async function loadEffectiveSettings(): Promise<MiniCodeSettings> {
const [claudeSettings, globalMcpConfig, projectMcpConfig, miniCodeSettings] =
export async function loadEffectiveSettings(
options: { cwd?: string; env?: NodeJS.ProcessEnv } = {},
): Promise<MiniCodeSettings> {
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,
)
Expand Down Expand Up @@ -246,6 +260,8 @@ export async function loadRuntimeConfig(): Promise<RuntimeConfig> {
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`,
}
}
69 changes: 69 additions & 0 deletions test/config.test.ts
Original file line number Diff line number Diff line change
@@ -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)
})