看本文之前,建议先看完前面的章节,了解什么是客户端能力
Roots
定义与用途
在初始化的时候,如果客户端声明了 roots 能力,说明服务器可以在 roots 限定的范围内,对客户端所在的文件系统执行一些操作,比如最基础的读写(至于怎么读写不知道,只是知道了能操作的范围),支持该能力的客户端必须能够响应 server 的 roots list 请求,并在该列表发生变更时发送通知。
roots 常被用来定义项目根目录,代码库位置,API 端点,配置位置(文档是这么说的),但我似乎只看到了他在 stdio 方式通信的用武之地,Remote MCP server 似乎不需要客户端提供的这项能力,因为提供了也没法通过文件系统操作访问,我搜了一下官方仓库的 discussion,也看到类似的问题
生命周期
上图是 roots 能力在使用时的信息流图
- 初始化阶段客户端声明 roots 能力
- 服务端发起
roots/list请求 - 客户端返回可用 roots 列表
- 当客户端可用 roots 发生变化时,向服务端发送
notifications/roots/list_changed通知 - 重复 2 和 3 步
实战例子
我们写一个简单的 server 的例子:
TypeScript
const server = new McpServer(
{
name: "simple-roots-server",
version: "1.0.0",
},
{ capabilities: {} },
);
let roots: string[] = [];
function extractRoots(response: ListRootsResult) {
if (response && "roots" in response) {
return response.roots.map((root) => root.uri);
}
return [];
}
server.server.oninitialized = async () => {
const clientCapabilities = server.server.getClientCapabilities();
// 由于 roots 是客户端能力,因此使用前需要检查
if (clientCapabilities?.roots) {
const response = await server.server.listRoots();
roots = extractRoots(response);
console.error("初始工作区的项目路径有: ", roots);
} else {
console.error("客户端不支持 roots 能力");
}
};
server.server.setNotificationHandler(
RootsListChangedNotificationSchema,
async () => {
const response = await server.server.listRoots();
roots = extractRoots(response);
console.error("工作区的项目路径更新为: ", roots);
},
);
// 注册一个使用 roots 功能的工具
server.registerTool(
"print-pwd",
{ description: "输出当前工作区的所有项目路径" },
async (): Promise<CallToolResult> => {
return {
content: [
{
type: "text",
text: `当前工作区的项目路径有: ${roots.join(", ")}`,
},
],
};
},
);
const transport = new StdioServerTransport();
server.connect(transport);