跳转到主要内容
根路径是由 Client 声明的文件系统边界。服务器可以使用根路径来识别 Client 文件系统中哪些部分被视为在作用范围内(例如,“这个项目文件夹”),并在读写文件时 强制执行这些安全边界 在模型上下文协议 (MCP) 中,根路径是一种 Client 功能roots/list)。Dedalus MCP 提供了一个服务端的 RootsService,用于:
  • 从 Client 获取根路径,
  • 为每个会话缓存一份快照,以及
  • 提供用于路径校验的 RootGuard 辅助工具。

基本用法

获取当前会话的最新根节点,然后使用它们:
from dedalus_mcp import get_context, tool

@tool(description="列出 Client 根目录")
async def list_roots() -> list[str]:
    ctx = get_context()
    server = ctx.server
    if server is None:
        raise RuntimeError("上下文中没有活动服务器")

    roots = await server.roots.refresh(ctx.session)  # 从 Client 获取
    return [f"{r.name}: {r.uri}" for r in roots]
你不需要每次都重新拉取,可以读取缓存的快照:
roots = ctx.server.roots.snapshot(ctx.session)

根结构

每个根包含:
字段类型描述
uristr根 URI(通常为 file:// URI)
namestr人类可读的名称

示例:安全文件操作(RootGuard)

使用 RootGuard 检查路径是否位于允许的根目录之一内:
from pathlib import Path
from dedalus_mcp import get_context, tool

@tool(description="读取文件,仅限根目录范围内")
async def safe_read(filepath: str) -> str:
    ctx = get_context()
    server = ctx.server
    if server is None:
        raise RuntimeError("上下文中无活动服务器")

    # 确保获取最新快照
    await server.roots.refresh(ctx.session)

    guard = server.roots.guard(ctx.session)
    target = Path(filepath).expanduser().resolve()

    if not guard.within(target):
        raise ValueError("路径超出允许的根目录范围")

    return target.read_text(encoding="utf-8")

示例:项目发现(file:// 根路径)

如果你的 Client 根路径是 file://... URI,你可以遍历这些路径来发现项目。
from pathlib import Path
from urllib.parse import urlparse, unquote

from dedalus_mcp import get_context, tool

def file_uri_to_path(uri: str) -> Path:
    parsed = urlparse(uri)
    if parsed.scheme != "file":
        raise ValueError(f"Unsupported root scheme: {parsed.scheme!r}")
    return Path(unquote(parsed.path)).expanduser().resolve()

@tool(description="根据标记文件查找项目根目录")
async def find_projects() -> list[dict]:
    ctx = get_context()
    server = ctx.server
    if server is None:
        raise RuntimeError("No active server in context")

    roots = await server.roots.refresh(ctx.session)
    projects: list[dict] = []

    for root in roots:
        root_path = file_uri_to_path(str(root.uri))

        for marker in ["package.json", "pyproject.toml", "Cargo.toml"]:
            if (root_path / marker).exists():
                projects.append(
                    {
                        "root": root.name,
                        "path": str(root_path),
                        "type": marker,
                    }
                )

    return projects

仅在根路径中搜索(并记录当前操作):
from pathlib import Path
from urllib.parse import urlparse, unquote

from dedalus_mcp import get_context, tool

def file_uri_to_path(uri: str) -> Path:
    parsed = urlparse(uri)
    if parsed.scheme != "file":
        raise ValueError(f"Unsupported root scheme: {parsed.scheme!r}")
    return Path(unquote(parsed.path)).expanduser().resolve()

@tool(description="在根目录内搜索文件")
async def search_files(pattern: str) -> list[str]:
    ctx = get_context()
    server = ctx.server
    if server is None:
        raise RuntimeError("No active server in context")

    roots = await server.roots.refresh(ctx.session)
    await ctx.info("Searching roots", data={"roots": len(roots), "pattern": pattern})

    matches: list[str] = []
    for root in roots:
        await ctx.debug("Searching root", data={"root": root.name, "uri": str(root.uri)})
        root_path = file_uri_to_path(str(root.uri))

        for match in root_path.rglob(pattern):
            matches.append(str(match))

    await ctx.info("Search complete", data={"matches": len(matches)})
    return matches

注意事项

  • 缓存server.roots.snapshot(session) 返回已缓存的 roots。await server.roots.refresh(session) 通过调用 Client 来刷新缓存。
  • 由 Client 驱动的更新:如果 Client 发送 roots/list_changed,Dedalus 模型上下文协议 (MCP) 会自动(带防抖)更新该会话的快照。
  • 安全性:roots 既是指导信息,也是你自行检查的边界。如果你在执行文件 I/O 操作,读写前务必先强制使用防护(RootGuard.within(...))进行边界校验。