什么是 MCP Roots?
Roots 是 MCP 中的一个概念,用于定义服务器可以操作的边界。它们为客户端提供了一种向服务器告知相关资源及其位置的方式。
简单来说,Root 是客户端建议服务器应该关注的 URI。当客户端连接到服务器时,它会声明服务器应该使用哪些根。虽然主要用于文件系统路径,但根可以是任何有效的 URI,包括 HTTP URL。
Roots 的典型示例
根可以是以下任何形式:
file:///home/user/project
- 本地项目目录https://api.example.com
- API 端点git://github.com/user/repo
- Git 仓库s3://bucket-name/path
- 云存储路径
为什么使用 Roots?
Roots 在 MCP 架构中发挥着重要作用:
- 指导性:告知服务器相关资源和位置
- 清晰性:明确哪些资源是工作空间的一部分
- 组织性:允许同时处理不同的资源集合
- 边界定义:为服务器操作设定明确的范围
Roots 的工作机制
当客户端支持 Roots 时,它会:
- 在连接期间声明
roots
能力 - 向服务器提供建议的根列表
- 在根发生变化时通知服务器(如果支持)
而服务器应该:
- 尊重提供的根
- 使用根 URI 来定位和访问资源
- 优先在根边界内进行操作
TypeScript SDK 实现示例
下面我们通过 TypeScript 代码来演示如何在 MCP 中实现和使用 Roots。
1. 定义 Root 接口
interface MCPRoot {
uri: string;
name?: string;
description?: string;
}
interface RootsCapability {
listChanged?: boolean; // 是否支持根列表变更通知
}
interface ClientCapabilities {
roots?: RootsCapability;
}
2. 客户端 Roots 管理器
class RootsManager {
private roots: Map<string, MCPRoot> = new Map();
private changeListeners: Array<(roots: MCPRoot[]) => void> = [];
constructor(private client: MCPClient) {}
/**
* 添加一个新的根
*/
addRoot(root: MCPRoot): void {
this.roots.set(root.uri, root);
this.notifyChange();
}
/**
* 移除指定的根
*/
removeRoot(uri: string): boolean {
const removed = this.roots.delete(uri);
if (removed) {
this.notifyChange();
}
return removed;
}
/**
* 获取所有根
*/
getAllRoots(): MCPRoot[] {
return Array.from(this.roots.values());
}
/**
* 根据 URI 查找根
*/
findRoot(uri: string): MCPRoot | undefined {
return this.roots.get(uri);
}
/**
* 检查 URI 是否在任何根的范围内
*/
isWithinRoots(uri: string): boolean {
return Array.from(this.roots.keys()).some(rootUri =>
uri.startsWith(rootUri)
);
}
/**
* 添加变更监听器
*/
onRootsChanged(listener: (roots: MCPRoot[]) => void): void {
this.changeListeners.push(listener);
}
/**
* 通知所有监听器根列表已变更
*/
private notifyChange(): void {
const currentRoots = this.getAllRoots();
this.changeListeners.forEach(listener => listener(currentRoots));
}
}
3. MCP 客户端实现
class MCPClient {
private rootsManager: RootsManager;
private serverConnection: ServerConnection;
constructor() {
this.rootsManager = new RootsManager(this);
this.setupRootsChangeHandler();
}
/**
* 初始化连接并发送根信息
*/
async initialize(): Promise<void> {
// 声明客户端能力
const capabilities: ClientCapabilities = {
roots: {
listChanged: true // 支持根列表变更通知
}
};
await this.serverConnection.initialize(capabilities);
// 发送初始根列表
await this.sendRootsToServer();
}
/**
* 向服务器发送根列表
*/
private async sendRootsToServer(): Promise<void> {
const roots = this.rootsManager.getAllRoots();
const message = {
jsonrpc: "2.0",
method: "notifications/roots/list_changed",
params: {
roots: roots.map(root => ({
uri: root.uri,
name: root.name
}))
}
};
await this.serverConnection.send(message);
}
/**
* 设置根变更处理器
*/
private setupRootsChangeHandler(): void {
this.rootsManager.onRootsChanged(async (roots) => {
if (this.serverConnection.isConnected()) {
await this.sendRootsToServer();
}
});
}
/**
* 获取根管理器
*/
getRootsManager(): RootsManager {
return this.rootsManager;
}
}
4. 服务器端 Roots 处理
class MCPServer {
private allowedRoots: Set<string> = new Set();
/**
* 处理根列表变更通知
*/
async handleRootsListChanged(params: { roots: MCPRoot[] }): Promise<void> {
this.allowedRoots.clear();
params.roots.forEach(root => {
this.allowedRoots.add(root.uri);
console.log(`Added root: ${root.uri} (${root.name || 'unnamed'})`);
});
// 验证当前操作是否在允许的根范围内
await this.validateCurrentOperations();
}
/**
* 检查资源是否在允许的根范围内
*/
isResourceAllowed(resourceUri: string): boolean {
if (this.allowedRoots.size === 0) {
return true; // 如果没有设置根,则允许所有资源
}
return Array.from(this.allowedRoots).some(rootUri =>
resourceUri.startsWith(rootUri)
);
}
/**
* 安全的资源访问方法
*/
async accessResource(resourceUri: string): Promise<any> {
if (!this.isResourceAllowed(resourceUri)) {
throw new Error(`Access denied: ${resourceUri} is outside allowed roots`);
}
// 执行实际的资源访问
return await this.performResourceAccess(resourceUri);
}
private async performResourceAccess(resourceUri: string): Promise<any> {
// 实际的资源访问逻辑
console.log(`Accessing resource: ${resourceUri}`);
}
private async validateCurrentOperations(): Promise<void> {
// 验证当前正在进行的操作是否仍然有效
console.log('Validating current operations against new roots...');
}
}
5. 完整使用示例
async function demonstrateMCPRoots() {
// 创建 MCP 客户端
const client = new MCPClient();
const rootsManager = client.getRootsManager();
// 添加项目根目录
rootsManager.addRoot({
uri: "file:///home/user/my-project",
name: "Main Project",
description: "主项目目录"
});
// 添加 API 端点根
rootsManager.addRoot({
uri: "https://api.myservice.com/v1",
name: "API Endpoint",
description: "主要 API 服务端点"
});
// 添加配置目录根
rootsManager.addRoot({
uri: "file:///etc/myapp",
name: "Configuration",
description: "应用配置目录"
});
// 初始化连接
await client.initialize();
// 检查资源是否在根范围内
const testUris = [
"file:///home/user/my-project/src/main.ts",
"https://api.myservice.com/v1/users",
"file:///tmp/temp-file.txt"
];
testUris.forEach(uri => {
const isWithin = rootsManager.isWithinRoots(uri);
console.log(`${uri}: ${isWithin ? '✓ 在根范围内' : '✗ 不在根范围内'}`);
});
// 动态更新根
setTimeout(() => {
rootsManager.addRoot({
uri: "file:///home/user/cache",
name: "Cache Directory",
description: "缓存目录"
});
console.log('添加了新的根目录');
}, 5000);
}
// 运行演示
demonstrateMCPRoots().catch(console.error);
最佳实践
在使用 MCP Roots 时,应遵循以下最佳实践:
1. 精确性原则
只建议必要的资源,避免过度暴露不相关的目录或端点。
// ✅ 好的做法:精确的根定义
rootsManager.addRoot({
uri: "file:///home/user/project/src",
name: "Source Code"
});
// ❌ 避免:过于宽泛的根定义
rootsManager.addRoot({
uri: "file:///",
name: "Entire Filesystem"
});
2. 清晰的命名
为根使用清晰、描述性的名称。
rootsManager.addRoot({
uri: "https://api.example.com/v2",
name: "User Management API",
description: "用户管理相关的 API 端点"
});
3. 优雅的错误处理
妥善处理根变更和访问错误。
class SafeRootsManager extends RootsManager {
async safeAddRoot(root: MCPRoot): Promise<boolean> {
try {
// 验证根的可访问性
await this.validateRootAccess(root.uri);
this.addRoot(root);
return true;
} catch (error) {
console.error(`Failed to add root ${root.uri}:`, error);
return false;
}
}
private async validateRootAccess(uri: string): Promise<void> {
// 根据 URI 类型进行相应的验证
if (uri.startsWith('file://')) {
// 验证文件系统访问
} else if (uri.startsWith('http')) {
// 验证 HTTP 端点访问
}
}
}
常见用例
1. 多项目工作空间
const workspaceRoots = [
{ uri: "file:///home/user/frontend", name: "Frontend Project" },
{ uri: "file:///home/user/backend", name: "Backend Project" },
{ uri: "file:///home/user/shared", name: "Shared Libraries" }
];
workspaceRoots.forEach(root => rootsManager.addRoot(root));
2. 混合资源访问
const mixedRoots = [
{ uri: "file:///project/local", name: "Local Files" },
{ uri: "https://api.service.com", name: "Remote API" },
{ uri: "s3://my-bucket/data", name: "Cloud Storage" }
];
mixedRoots.forEach(root => rootsManager.addRoot(root));
总结
MCP Roots 是一个强大的概念,它为 AI 应用程序提供了清晰的资源边界定义机制。通过合理使用 Roots,我们可以:
- 提高 AI 系统的安全性和可控性
- 优化资源访问的效率
- 简化复杂工作环境的管理
- 实现更好的权限控制
在实际项目中,建议根据具体需求灵活使用 Roots 概念,并始终遵循最佳实践来确保系统的稳定性和安全性。随着 MCP 生态系统的不断发展,Roots 概念将在 AI 应用程序的集成中发挥越来越重要的作用。