clodexIDE
§ · Nested detail page

MCP Policy 深度解析

本页只拆一个模块面,用真实源码行号解释数据流、边界和失败模式。

§ 00 · DETAIL

配置 schema 是第一道防线

▸schema 收紧输入形状

MCP 子系统的契约从 schema 开始:packages/mcp-runtime/src/config.ts:4 与源码证据 packages/mcp-runtime/src/config.ts:5 与源码证据 packages/mcp-runtime/src/config.ts:6 与源码证据 packages/mcp-runtime/src/config.ts:7 定义全局上限,packages/mcp-runtime/src/config.ts:9 与源码证据 packages/mcp-runtime/src/config.ts:24 与源码证据 packages/mcp-runtime/src/config.ts:31 约束 id 和 credential reference,避免任意字符串直接进入 runtime。

src/packages/mcp-runtime/src/config.ts · L1-58TypeScript
1import path from 'node:path';
2import { z } from 'zod';
3
4export const MCP_REGISTRY_SCHEMA_VERSION = 1;
5export const MCP_MAX_SERVERS = 100;
6export const MCP_MAX_CONFIG_ENTRIES = 128;
7export const MCP_MAX_OAUTH_SCOPES = 64;
8
9const SAFE_ID_PATTERN = /^[a-z0-9]+(?:[-_.][a-z0-9]+)*$/;
10const SAFE_FIELD_PATTERN = /^[A-Za-z_][A-Za-z0-9_.-]*$/;
11const HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
12const SENSITIVE_KEY_PARTS = [
13  'AUTH',
14  'COOKIE',
15  'CREDENTIAL',
16  'KEY',
17  'PASSWORD',
18  'PRIVATE',
19  'SECRET',
20  'SESSION',
21  'TOKEN',
22] as const;
23
24export const mcpServerIdSchema = z
25  .string()
26  .trim()
27  .min(1)
28  .max(80)
29  .regex(SAFE_ID_PATTERN);
30
31export const mcpCredentialReferenceSchema = z
32  .object({
33    kind: z.literal('credential'),
34    credentialId: z.string().trim().min(1).max(120).regex(SAFE_ID_PATTERN),
35    field: z.string().trim().min(1).max(120).regex(SAFE_FIELD_PATTERN),
36  })
37  .strict();
38
39export const mcpLiteralValueSchema = z
40  .object({
41    kind: z.literal('literal'),
42    value: z
43      .string()
44      .max(16_384)
45      .refine((value) => !value.includes('\0'), 'Value may not contain NUL'),
46  })
47  .strict();
48
49export const mcpConfigValueSchema = z.discriminatedUnion('kind', [
50  mcpLiteralValueSchema,
51  mcpCredentialReferenceSchema,
52]);
53
54export type McpCredentialReference = z.infer<
55  typeof mcpCredentialReferenceSchema
56>;
57export type McpConfigValue = z.infer<typeof mcpConfigValueSchema>;
58

stdio 配置是本地 MCP 最危险的入口之一,因此 packages/mcp-runtime/src/config.ts:59 与源码证据 packages/mcp-runtime/src/config.ts:68 与源码证据 packages/mcp-runtime/src/config.ts:71 与源码证据 packages/mcp-runtime/src/config.ts:79 与源码证据 packages/mcp-runtime/src/config.ts:95 与源码证据 packages/mcp-runtime/src/config.ts:100 对 args 里的 secret、password、Bearer token 做拦截,并把敏感配置推向 credential reference。

src/packages/mcp-runtime/src/config.ts · L59-128TypeScript
59const stdioArgsSchema = z
60  .array(
61    z
62      .string()
63      .max(16_384)
64      .refine((value) => !value.includes('\0'), 'Argument may not contain NUL'),
65  )
66  .max(128)
67  .default([])
68  .superRefine((args, context) => {
69    for (const [index, argument] of args.entries()) {
70      if (
71        /^--?(?:api[-_]?key|auth|authorization|cookie|password|secret|token)=/i.test(
72          argument,
73        )
74      ) {
75        context.addIssue({
76          code: z.ZodIssueCode.custom,
77          path: [index],
78          message:
79            'Secrets may not be embedded in MCP command arguments; use an environment credential reference',
80        });
81      }
82      if (
83        /^--?(?:api[-_]?key|auth|authorization|cookie|password|secret|token)$/i.test(
84          argument,
85        ) &&
86        index + 1 < args.length
87      ) {
88        context.addIssue({
89          code: z.ZodIssueCode.custom,
90          path: [index + 1],
91          message:
92            'Secrets may not be embedded in MCP command arguments; use an environment credential reference',
93        });
94      }
95      if (/\bBearer\s+\S+/i.test(argument)) {
96        context.addIssue({
97          code: z.ZodIssueCode.custom,
98          path: [index],
99          message:
100            'Bearer credentials may not be embedded in MCP command arguments',
101        });
102      }
103    }
104  });
105
106const configValuesSchema = z
107  .record(z.string().min(1).max(256), mcpConfigValueSchema)
108  .superRefine((values, context) => {
109    if (Object.keys(values).length > MCP_MAX_CONFIG_ENTRIES) {
110      context.addIssue({
111        code: z.ZodIssueCode.custom,
112        message: `Configuration may contain at most ${MCP_MAX_CONFIG_ENTRIES} entries`,
113      });
114    }
115    for (const [key, value] of Object.entries(values)) {
116      if (
117        value.kind === 'literal' &&
118        (isSensitiveMcpConfigKey(key) || looksLikeMcpSecretValue(value.value))
119      ) {
120        context.addIssue({
121          code: z.ZodIssueCode.custom,
122          path: [key],
123          message:
124            'Sensitive configuration values must use a credential reference',
125        });
126      }
127    }
128  });

远程 MCP 走 URL,因此 packages/mcp-runtime/src/config.ts:154 与源码证据 packages/mcp-runtime/src/config.ts:165 与源码证据 packages/mcp-runtime/src/config.ts:166 与源码证据 packages/mcp-runtime/src/config.ts:171 与源码证据 packages/mcp-runtime/src/config.ts:174 与源码证据 packages/mcp-runtime/src/config.ts:177 与源码证据 packages/mcp-runtime/src/config.ts:186 与源码证据 packages/mcp-runtime/src/config.ts:190 强制非 loopback 使用 HTTPS、禁止 URL credentials、禁止敏感 query 参数持久化。

src/packages/mcp-runtime/src/config.ts · L154-194TypeScript
154const remoteUrlSchema = z
155  .string()
156  .url()
157  .max(4_096)
158  .superRefine((value, context) => {
159    const url = new URL(value);
160    const isLoopback =
161      url.hostname === 'localhost' ||
162      url.hostname === '127.0.0.1' ||
163      url.hostname === '[::1]' ||
164      url.hostname === '::1';
165    if (
166      url.protocol !== 'https:' &&
167      !(url.protocol === 'http:' && isLoopback)
168    ) {
169      context.addIssue({
170        code: z.ZodIssueCode.custom,
171        message: 'Remote MCP URLs must use HTTPS unless they target loopback',
172      });
173    }
174    if (url.username || url.password) {
175      context.addIssue({
176        code: z.ZodIssueCode.custom,
177        message: 'Credentials may not be embedded in an MCP URL',
178      });
179    }
180    if (url.hash) {
181      context.addIssue({
182        code: z.ZodIssueCode.custom,
183        message: 'MCP URLs may not contain fragments',
184      });
185    }
186    for (const key of url.searchParams.keys()) {
187      if (isSensitiveMcpConfigKey(key)) {
188        context.addIssue({
189          code: z.ZodIssueCode.custom,
190          message: `Sensitive URL parameter "${key}" must not be persisted`,
191        });
192      }
193    }
194  });

源码证据 packages/mcp-runtime/src/config.ts:12 与源码证据 packages/mcp-runtime/src/config.ts:24 与源码证据 packages/mcp-runtime/src/config.ts:31 与源码证据 packages/mcp-runtime/src/config.ts:49 与源码证据 packages/mcp-runtime/src/config.ts:59 与源码证据 packages/mcp-runtime/src/config.ts:68 与源码证据 packages/mcp-runtime/src/config.ts:95 与源码证据 packages/mcp-runtime/src/config.ts:106 显示,Clodex 不是把 AI 聊天窗口塞进 IDE,而是把任务状态、工具权限、运行时隔离和可审计执行拆成独立层。这里每个引用都对应真实实现:管理器负责状态与生命周期,isolated runtime 负责模型/工具循环,MCP 配置负责边界校验,shell 层负责可取消、可回放的命令执行。

源码证据 packages/mcp-runtime/src/config.ts:130 与源码证据 packages/mcp-runtime/src/config.ts:154 与源码证据 packages/mcp-runtime/src/config.ts:165 与源码证据 packages/mcp-runtime/src/config.ts:174 与源码证据 packages/mcp-runtime/src/config.ts:186 与源码证据 packages/mcp-runtime/src/config.ts:210 与源码证据 packages/mcp-runtime/src/config.ts:266 与源码证据 packages/mcp-runtime/src/config.ts:377 显示,Clodex 不是把 AI 聊天窗口塞进 IDE,而是把任务状态、工具权限、运行时隔离和可审计执行拆成独立层。这里每个引用都对应真实实现:管理器负责状态与生命周期,isolated runtime 负责模型/工具循环,MCP 配置负责边界校验,shell 层负责可取消、可回放的命令执行。

源码证据 packages/mcp-runtime/src/policy.ts:7 与源码证据 packages/mcp-runtime/src/policy.ts:27 与源码证据 packages/mcp-runtime/src/policy.ts:36 与源码证据 packages/mcp-runtime/src/policy.ts:44 与源码证据 packages/mcp-runtime/src/policy.ts:52 与源码证据 packages/mcp-runtime/src/policy.ts:60 显示,Clodex 不是把 AI 聊天窗口塞进 IDE,而是把任务状态、工具权限、运行时隔离和可审计执行拆成独立层。这里每个引用都对应真实实现:管理器负责状态与生命周期,isolated runtime 负责模型/工具循环,MCP 配置负责边界校验,shell 层负责可取消、可回放的命令执行。

§ 01 · DETAIL

从配置到决策

▸policy 把工具信号归一成决策

策略层把默认决策收束到 allow/ask/deny。packages/mcp-runtime/src/config.ts:377 与源码证据 packages/mcp-runtime/src/config.ts:380 与源码证据 packages/mcp-runtime/src/config.ts:398 与源码证据 packages/mcp-runtime/src/config.ts:409 与源码证据 packages/mcp-runtime/src/config.ts:414 与源码证据 packages/mcp-runtime/src/config.ts:434 描述 registry 和 policy schema,packages/mcp-runtime/src/policy.ts:27 与源码证据 packages/mcp-runtime/src/policy.ts:36 与源码证据 packages/mcp-runtime/src/policy.ts:38 与源码证据 packages/mcp-runtime/src/policy.ts:44 与源码证据 packages/mcp-runtime/src/policy.ts:52 与源码证据 packages/mcp-runtime/src/policy.ts:60 则负责基于 tool signals 评估实际决策。

§ 02 · DETAIL

边界与失败

▸失败先进入配置错误

失败模式包括 server 数量过多、配置项过多、OAuth header 冲突和明文 secret。packages/mcp-runtime/src/config.ts:109 与源码证据 packages/mcp-runtime/src/config.ts:112 与源码证据 packages/mcp-runtime/src/config.ts:414 与源码证据 packages/mcp-runtime/src/config.ts:418 与源码证据 packages/mcp-runtime/src/config.ts:481 与源码证据 packages/mcp-runtime/src/config.ts:504 把这些情况变成 schema error。

Sub-page · 2026-07-13↑ clodex-ide 主页面