Skip to content
5 changes: 5 additions & 0 deletions .changeset/inherit-workspace-trust.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@moonshot-ai/kimi-code": patch
---

Allow a trusted workspace folder to cover its subdirectories.
2 changes: 1 addition & 1 deletion docs/en/customization/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ Run `/mcp-config` in the TUI to interactively add, edit, or delete servers witho

Deleting a server from the configuration does not interrupt open sessions: the server stays listed in `/mcp` as `removed`, its tools remain visible there, and calls to them fail with a removal notice, while new sessions do not register the tools at all. Conversely, a server added mid-session — by editing `mcp.json` or installing a plugin — is not registered in already-open sessions; it only joins sessions created later.

When Kimi Code finds project-level MCP servers in an untrusted folder, it shows each server's transport and launch target in the workspace trust prompt. The prompt defaults to `Don't trust`; move to `Trust this folder` and confirm only after reviewing the listed command and arguments or remote URL. Trusting the folder enables the project-level MCP servers for that workspace.
When Kimi Code finds project-level MCP servers in an untrusted folder, it shows each server's transport and launch target in the workspace trust prompt. The prompt defaults to `Don't trust`; move to `Trust this folder` and confirm only after reviewing the listed command and arguments or remote URL. Trusting a folder also trusts its subfolders, enabling their project-level MCP servers without another prompt.

Structure of `mcp.json`:

Expand Down
2 changes: 1 addition & 1 deletion docs/zh/customization/mcp.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ MCP server 配置写在 `mcp.json` 中,分两层:

从配置中删除某个 server 不会打断进行中的会话:该 server 在 `/mcp` 中仍显示为 `removed`,其工具在这些会话中保持可见,但调用会失败并返回移除提示;新会话则完全不会注册这些工具。反过来,会话进行中新增的 server——无论是编辑 `mcp.json` 还是安装 plugin——都不会注册到已打开的会话中,只会加入之后创建的会话。

当 Kimi Code 在不受信任的文件夹中发现项目级 MCP server 时,工作区信任提示会显示每个 server 的传输方式和启动目标。提示默认选中 `Don't trust`;请先移动到 `Trust this folder`,核对列出的命令与参数或远程 URL 后,再确认信任。信任文件夹后,该工作区的项目级 MCP server 才会启用
当 Kimi Code 在不受信任的文件夹中发现项目级 MCP server 时,工作区信任提示会显示每个 server 的传输方式和启动目标。提示默认选中 `Don't trust`;请先移动到 `Trust this folder`,核对列出的命令与参数或远程 URL 后,再确认信任。信任一个文件夹也会信任其子文件夹,因此这些子文件夹中的项目级 MCP server 无需再次提示即可启用

`mcp.json` 的结构:

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@
* Persists the trust marker through the `persistence` domain's
* `IAtomicDocumentStore` under the `workspace-trust` scope, one document per
* workspace keyed by `encodeWorkDirKey(root)`, with the raw root kept in the
* value for inspection. The document's presence IS the trusted state: `trust()`
* writes it, `untrust()` deletes it. The record lives under the kimi home,
* value for inspection. A document for this root or an ancestor grants trust:
* `trust()` writes this root's document, `untrust()` deletes it. The record
* lives under the kimi home,
* never inside the workspace, so a checked-out tree cannot pre-trust
* itself. The flag is read once through `ready` and every later mutation
* goes through this service, so the view is in-process: another process
Expand All @@ -16,6 +17,8 @@
* Bound at Workspace scope.
*/

import { dirname, normalize } from 'pathe';

import { Disposable } from '#/_base/di/lifecycle';
import { Emitter } from '#/_base/event';
import { defineState } from '#/_base/state/stateRegistry';
Expand Down Expand Up @@ -96,7 +99,22 @@ export class WorkspaceTrustService extends Disposable implements IWorkspaceTrust

private async initialize(): Promise<void> {
try {
this.trusted = (await this.docs.get<TrustRecord>(TRUST_SCOPE, this.storeKey)) !== undefined;
if ((await this.docs.get<TrustRecord>(TRUST_SCOPE, this.storeKey)) !== undefined) {
this.trusted = true;
return;
}

let current = dirname(normalize(this.root));
while (true) {
if ((await this.docs.get<TrustRecord>(TRUST_SCOPE, encodeWorkDirKey(current))) !== undefined) {
this.trusted = true;
Comment thread
atirna marked this conversation as resolved.
Outdated
Comment thread
atirna marked this conversation as resolved.
Outdated
return;
}
const parent = dirname(current);
if (parent === current) break;
current = parent;
}
this.trusted = false;
} catch {
this.trusted = false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
*/

import { mkdtempSync } from 'node:fs';
import { rm } from 'node:fs/promises';
import { mkdir, rm } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'pathe';

Expand Down Expand Up @@ -123,6 +123,31 @@ describe('WorkspaceTrustService', () => {
expect(second.isTrusted()).toBe(true);
});

it('inherits trust from an ancestor workspace', async () => {
const nested = join(cwd, 'test', 'workspace');
const prefixSibling = `${cwd}-other`;
const siblingNested = join(prefixSibling, 'test', 'workspace');
await mkdir(nested, { recursive: true });
await mkdir(siblingNested, { recursive: true });

try {
const { service: parent } = createService(cwd);
await parent.ready;
await parent.trust();

const { service: child } = createService(nested);
await child.ready;

const { service: sibling } = createService(siblingNested);
await sibling.ready;

expect(child.isTrusted()).toBe(true);
expect(sibling.isTrusted()).toBe(false);
} finally {
await rm(prefixSibling, { recursive: true, force: true });
}
});

it('tracks different roots independently', async () => {
const other = mkdtempSync(join(tmpdir(), 'kimi-workspace-trust-other-'));
try {
Expand Down