Skip to main content
Glama

TACIT:类型中追踪的代理能力

论文: 使用追踪能力保护代理安全(ACM)· arXiv:2603.00991 · 🏆 CAIS 26 最佳论文奖

TACIT(Tracked Agent Capabilities In Types,类型中追踪的代理能力)是一个面向 AI 代理的安全防护装置。 代理不直接调用工具,而是使用 Scala 3 编写代码,并启用捕获检查:这是一种类型系统,能够静态追踪能力,并强制保证代理代码无法伪造访问权限、无法超出其预算执行效果、无法从纯子计算中泄露信息。 它提供了 MCP 接口,因此所有兼容 MCP 的代理都可以轻松使用。

TACIT 框架概览

该框架包含三个主要组件:

  • Scala 3 编译器。 代理提交的代码会经过验证和类型检查,并在安全模式下启用捕获检查,从而强制实施一个能力安全的语言子集。

  • Scala REPL。 本地 REPL 实例执行编译后的代码,并在交互之间管理状态。支持无状态的一次性执行和有状态的会话。

  • 能力安全库。 一个类型化 API,是代理代码与现实世界交互的唯一通道:文件系统、进程执行、网络和子代理。该库是可扩展的:只需修改库代码即可添加新能力,无需更改 MCP 服务器本身。

快速开始

TACIT 提供一个标准的 MCP 服务器,通过 stdio 上的 JSON-RPC 进行通信。它适用于任何兼容 MCP 的代理,包括 Claude Code、OpenCode、GitHub Copilot 等。

需要 JDK 17 或更高版本。

安装 TACIT

从以下安装方式中选择一种。tacit CLI 包装器是推荐选项。

选项 1:安装 tacit(推荐)

tacit 是一个用于本地管理 TACIT 的小型包装命令。使用 tacit setup 一次性安装命令并获取最新版本,使用 tacit update 更新 JAR 文件,使用 tacit self update 刷新包装器本身,使用 tacit serve 启动 MCP 服务器。

# Download the wrapper directly (no git clone required)
curl -fsSL https://raw.githubusercontent.com/lampepfl/tacit/refs/heads/main/tacit -o tacit
chmod +x tacit

# Install it and download the latest TACIT release
./tacit setup

这会将 tacit 命令安装到 ~/.local/bin,确保 ~/.local/binPATH 中,并将最新版本下载到 ~/.cache/tacit/

常用命令:

# Refresh the cached release if a new version exists
tacit update

# Refresh the tacit wrapper itself
tacit self update

# Start the MCP server
tacit serve

# Remove the wrapper and cached release
tacit self uninstall

默认情况下,tacit 使用:

资产

默认路径

MCP 服务器

~/.cache/tacit/TACIT.jar

~/.cache/tacit/TACIT-library.jar

选项 2:直接下载预构建的发布 JAR

如果您不想使用包装器,请改用发布下载脚本。

# Download the script directly (no git clone required)
curl -fsSL https://raw.githubusercontent.com/lampepfl/tacit/refs/heads/main/download_release.sh -o download_release.sh
chmod +x download_release.sh

./download_release.sh

可选:

# Or use wget instead of curl
wget -q https://raw.githubusercontent.com/lampepfl/tacit/refs/heads/main/download_release.sh -O download_release.sh
chmod +x download_release.sh

# Download into a custom directory
./download_release.sh ./dist
./download_release.sh --pre-release ./dist

默认情况下,这会下载:

JAR

默认路径

MCP 服务器

./TACIT.jar

./TACIT-library.jar

包装器和脚本都会根据发布元数据中公布的 SHA-256 摘要验证下载的 JAR 文件,并拒绝安装摘要缺失或不匹配的 JAR。下载文件会先放入临时目录,只有在验证通过后才会移动到最终位置,因此下载失败永远不会替换已安装的 JAR。

要从当前源码树构建,请参阅下面的选项 3。

选项 3:从源码构建

需要 JDK 17+ 和 sbt 1.12+。

git clone https://github.com/lampepfl/tacit.git
cd tacit

./build.sh

可选:

# Build and copy JARs into a custom directory
./build.sh ./dist

# Show full sbt output while building
./build.sh --verbose

这会构建并复制两个 JAR:

JAR

路径

MCP 服务器

./TACIT.jar(或 ./dist/TACIT.jar

./TACIT-library.jar(或 ./dist/TACIT-library.jar

通过上述任一选项安装 TACIT 后,请配置您的代理以启动 MCP 服务器。

配置您的代理

在代理的配置中将 TACIT 添加为 MCP 服务器。如果您安装了 tacit CLI,只需使用 tacit serve。如果您手动安装了 TACIT,请改用显式的 java -jar ... --library-jar ... 形式。

添加到项目的 .mcp.json(全局配置则添加到 ~/.claude.json)。

使用 tacit

{
  "mcpServers": {
    "tacit": {
      "command": "tacit",
      "args": ["serve"]
    }
  }
}

使用手动 JAR 路径:

{
  "mcpServers": {
    "tacit": {
      "command": "java",
      "args": [
        "-jar", "/path/to/TACIT.jar",
        "--library-jar", "/path/to/TACIT-library.jar"
      ]
    }
  }
}

添加到您的 opencode.json

使用 tacit

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "tacit": {
      "type": "local",
      "enabled": true,
      "command": ["tacit", "serve"]
    }
  }
}

使用手动 JAR 路径:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "tacit": {
      "type": "local",
      "enabled": true,
      "command": [
        "java",
        "-jar", "/path/to/TACIT.jar",
        "--library-jar", "/path/to/TACIT-library.jar"
      ]
    }
  }
}

添加到您的 .vscode/mcp.json

使用 tacit

{
  "servers": {
    "tacit": {
      "command": "tacit",
      "args": ["serve"]
    }
  }
}

使用手动 JAR 路径:

{
  "servers": {
    "tacit": {
      "command": "java",
      "args": [
        "-jar", "/path/to/TACIT.jar",
        "--library-jar", "/path/to/TACIT-library.jar"
      ]
    }
  }
}

您的代理现在可以使用 TACIT 的工具来执行沙箱化的 Scala 代码。

推荐:禁用内置工具

为了充分利用 TACIT 基于能力的安全性,请禁用代理内置的文件、Shell 和网络工具,以便所有操作都通过沙箱化的 REPL 进行。

使用 --disallowedTools 启动以阻止内置工具:

claude --disallowedTools "Bash,Read,Write,Edit,WebFetch"

或者添加到项目的 .claude/settings.json

{
  "permissions": {
    "disallowedTools": ["Bash", "Read", "Write", "Edit", "WebFetch"]
  }
}

opencode.json 中将内置工具权限设置为 "deny"

{
  "$schema": "https://opencode.ai/config.json",
  "permission": {
    "*": "ask",
    "bash": "deny",
    "read": "deny",
    "edit": "deny",
    "glob": "deny",
    "grep": "deny",
    "list": "deny",
    "tacit*": "allow"
  },
  "mcp": {
    "tacit": { "..." : "..." }
  }
}

在您的 VS Code settings.json 中,限制 Copilot 可用的工具:

{
  "github.copilot.chat.agent.tools": {
    "terminal": false,
    "fs_read": false,
    "fs_write": false
  }
}

Related MCP server: edict-lang

配置

服务器可以通过 CLI 标志或 JSON 配置文件进行配置。直接在代理的 MCP 参数中传递标志,或使用 --config 指向 JSON 文件。

配置分为服务器配置(传输、记录、会话)和库配置(沙箱行为、能力)。在 JSON 配置文件中,库设置位于 libraryConfig 键下,并直接传递给库进行处理。

CLI 标志

服务器标志:

标志

描述

--library-jar <path>

必需。 库 JAR(TACIT-library.jar)的路径

-r/--record <dir>

将每次执行记录到磁盘

-q/--quiet

抑制启动横幅和请求/响应日志

--no-session

禁用与会话相关的工具

--safe-mode / --no-safe-mode

在 REPL 中为每次执行启用/禁用 Scala 3 的 language.experimental.safe(默认:开启;请参阅安全模式

--exec-timeout-ms <ms>

单次 REPL 求值的墙钟超时(默认:无;请参阅执行超时

-c/--config <path>

JSON 配置文件(--config 之后的标志会覆盖文件中的值)

库标志(某些 libraryConfig 字段的简写):

标志

描述

-s/--strict

通过 exec 阻止内置的不安全命令黑名单(文件操作、Shell、解释器、网络工具、命令运行器等);匹配不区分大小写。适合快速实验;对于实际部署,请优先使用 --command-permissions

--command-permissions <patterns>

可执行命令的逗号分隔 glob 模式列表(例如 echo,py*,ls)。只有 * 被解释为通配符。设置后,--strict 将被忽略。

--network-permissions <patterns>

可达主机的逗号分隔 glob 模式列表(例如 *.example.com,api.github.com)。只有 * 被解释为通配符。

--allowed-roots <paths>

requestFileSystem 根目录的逗号分隔外部边界(例如 /home/me/project,/tmp)。请求的根目录必须解析为这些路径之一内的路径。未设置时默认为服务器的工作目录。

--classified-paths <patterns>

逗号分隔的分类路径模式(gitignore 风格,见下文)

--llm-base-url <url>

LLM API 基础 URL

--llm-api-key <key>

LLM API 密钥

--llm-model <name>

LLM 模型名称

JSON 配置文件

{
  "recordPath": "/tmp/recordings",
  "quiet": true,
  "sessionEnabled": true,
  "safeMode": true,
  "executionTimeoutMs": 60000,
  "libraryJarPath": "/path/to/TACIT-library.jar",
  "libraryConfig": {
    "commandPermissions": ["sbt", "scala", "javac", "java", "make"],
    "networkPermissions": ["*.scala-lang.org", "github.com", "docs.oracle.com"],
    "allowedRoots": ["/home/user/project", "/tmp"],
    "classifiedPaths": [".ssh", ".env", ".env.*", "secrets"],
    "secureOutput": "/tmp/secure.log",
    "classifiedWrite": false,
    "llm": {
      "baseUrl": "https://api.example.com",
      "apiKey": "sk-...",
      "model": "gpt-..."
    }
  }
}

commandPermissions(可选)。exec 允许列表:一个 glob 模式列表(只有 * 是通配符),传递给 exec 的每个命令都必须匹配。它叠加在由 requestExecPermission(...) 声明的每个作用域集合之上;命令必须同时匹配两者才能实际运行。设置后,strictMode 将被忽略。在实际部署中,您应始终显式配置此列表。

strictMode(可选,默认 true)。一个快速实验默认值,通过 exec 阻止内置的不安全命令黑名单:文件操作命令(catlsrmtarchmod 等)、Shell、解释器(pythonnodeperl 等)、网络工具(curlwgetssh 等)、命令运行器(xargsnohupenv 等)以及类似命令。匹配对命令的基本名称不区分大小写。当您只想快速尝试时很方便,但对于实际使用来说过于粗糙;请优先使用 commandPermissions

networkPermissions(可选)。网络允许列表:一个 glob 模式列表(只有 * 是通配符),通过 httpGet/httpPost/httpRequest 访问的每个主机都必须匹配。与 commandPermissions 类似,它叠加在由 requestNetwork(...) 声明的每个作用域集合之上;主机必须同时匹配两者。未设置时,仅应用每个作用域的 requestNetwork 允许列表。

allowedRoots(可选)。文件系统外边界:一个路径列表,用于限定 requestFileSystem(root) 可以操作的范围。请求的根目录必须解析(包括符号链接)为这些路径之一或位于其下嵌套的路径,否则访问将被拒绝。未设置时,默认使用服务器的当前工作目录,因此沙箱被限制在该子树内(失败即关闭)。显式设置它可以扩大或重新定位边界。在授予的任何根目录内,分类路径掩码仍然生效。

符号链接在包含性检查之前始终被解析,包括悬空链接(通过悬空链接写入会创建其目标,因此被检查的是目标)。由 children/walk(因此也包括 find/grepRecursive)找到的、解析到已授予根目录之外的条目(例如 .venv/bin/pythonnode_modules/.bin 链接)会被省略,而不会被跟随或报告为错误。

secureOutput(可选)。一个追加写入文件的路径,用于镜像隔离环境中的每次 println/print/printf 调用,但 Classified[_] 值会被解包。代理的主输出仍然显示掩码形式(Classified(***)),因此只有能读取该文件的人才能看到真实内容。父目录会自动创建,尚不存在的接收文件在 POSIX 系统上以仅所有者权限(rw-------)创建(原子操作,因此绝不会以更宽的权限存在)。已有文件则以不变的权限追加写入。未设置时,打印行为正常,不会向磁盘写入任何内容。

classifiedWrite(可选,默认 true;仅 JSON 配置)。设置为 false 时,对分类路径的所有写入都会被拒绝:writeClassified(path, content)access(path).writeClassified(content) 以及对分类路径的 mkdir()。 请注意,由于 classify 可以包装任何值,保持此选项启用意味着代理可以用任意内容覆盖分类文件(例如 .ssh/authorized_keys):Classified 机制保护的是机密性,而非完整性。在分类文件必须对代理只读的部署环境中,请将此选项设置为 false

分类路径模式

分类路径模式遵循 gitignore 风格的语法。如果路径匹配某个模式或是某个匹配项的后代,则该路径被分类。

模式

匹配内容

示例

.ssh

任何名为 .ssh 的路径组件

/home/user/.ssh/id_rsa

.env.*

匹配该通配符的任何组件

/project/.env.local

config/*/keys

相对于文件系统根目录,带通配符

<root>/config/prod/keys/secret.pem

**/secrets

任意深度的 secrets

<root>/a/b/secrets/key.txt

/home/user/.ssh

绝对路径(符号链接已解析)

/home/user/.ssh/id_rsa

规则:

  • 模式中无 / 匹配任意路径组件(基名匹配)

  • / 的相对模式: 锚定到文件系统根目录;支持 ***?[…]

  • 绝对模式: 针对完整路径匹配;非通配符前缀通过符号链接解析

  • 尾部 / 会被去除(不区分仅目录)

默认分类模式(未配置 classifiedPaths 时):.ssh.gnupg.env.env.*.netrc.npmrc.pypirc.docker.kube.aws.azure.gcloud

工具

工具

参数

描述

execute_scala

code

在全新的 REPL 中执行 Scala 代码片段(无状态)

create_repl_session

-

创建持久 REPL 会话,返回 session_id

execute_in_session

session_id, code

在现有会话中执行代码(有状态)

list_sessions

-

列出活动会话 ID

delete_repl_session

session_id

删除会话

show_interface

-

显示完整的能力 API 参考

会话上限为 100 个活动会话;达到上限时 create_repl_session 返回错误。执行输出上限为 10 MiB(截断会在结果中标记),exec 每次调用最多捕获 8 MiB 的 stdout 和 8 MiB 的 stderr。

示例:有状态会话

1. create_repl_session          → session_id: "abc-123"
2. execute_in_session(code: "val x = 42")   → x: Int = 42
3. execute_in_session(code: "x * 2")        → val res0: Int = 84
4. delete_repl_session(session_id: "abc-123")

安全特性

TACIT 的类型系统提供三项安全保证,无论代理是未对齐、产生幻觉还是遭受提示注入攻击,这些保证都成立:

属性

含义

能力安全

能力不能被伪造或遗忘。代理只能通过显式授予它的能力访问资源。

能力完备性

能力管控所有与安全相关的效应。代理只能通过其被授予的能力与世界交互。

局部纯性

特定计算可以被强制为无副作用。这可以防止代理处理分类数据时发生信息泄露。

能力 API

该库公开了三个能力请求方法,每个方法都将访问范围限定在一个代码块内。能力不能逃逸出其限定的代码块。这一点由捕获检查器在编译时强制执行。

// File system: scoped to a root directory
requestFileSystem("/tmp/work") {
  val f = access("data.txt")
  f.write("hello")
  val lines = f.readLines()
  grep("data.txt", "hello")
  find(".", "*.txt")
}

// Process execution: scoped to an allowlist of commands
requestExecPermission(Set("ls", "cat")) {
  val result = exec("ls", List("-la"))
  println(result.stdout)
}

// Network: scoped to an allowlist of hosts
requestNetwork(Set("api.example.com")) {
  val body = httpGet("https://api.example.com/data")
  httpPost("https://api.example.com/submit", """{"key":"value"}""")
  // Arbitrary verbs with a status code:
  val resp = httpRequest("DELETE", "https://api.example.com/item/42")  // resp.status, resp.body
}

网络方法还接受普通的 headers: Map[String, String]secretHeaders: Map[String, Classified[String]]secretHeaders 值(例如通过 readClassified 读取的 Authorization 令牌)会被发送到白名单主机,但代理代码永远无法观察到它,这使代理能够使用其无法读取的机密向允许的 API 进行身份验证。httpPostClassified 完善了这一设计:它 POST 一个 Classified[String] 请求体并返回一个 Classified[String] 响应,因此敏感数据可以在保持信息流控制的同时通过外部服务往返传输(见下文)。

通过 Classified 实现信息流控制

考虑一个在项目目录上工作的典型代码代理。有些文件是普通的(源代码、构建配置、README)。其他文件是敏感的:.env 中的 API 密钥、secrets/ 中的凭据、内部文档。该代理由云托管的 LLM(第三方服务)驱动。我们希望代理使用处理敏感数据(总结内部文档、轮换密钥、处理报告),但绝不将其泄露给云提供商。

TACIT 通过 Classified[T] 类型解决了这个问题。位于指定分类路径下的文件(通过 --classified-paths 配置,使用 gitignore 风格模式,例如 .ssh.env.*secrets**/keys)返回的内容被包装在 Classified[String] 中,而不是普通的 String。如果未另行配置,常见的机密路径(.ssh.gnupg.env.env.* 等)默认被分类。类型系统强制仅纯访问Classified.map 只接受纯函数(T -> U),意味着无副作用且不捕获能力。你可以转换数据,但不能将其发送到任何地方。任何试图窃取分类数据的行为都会在编译时被拒绝:

requestFileSystem("/project") {
  val secret = readClassified("secrets/api-key.txt")

  // Compile error: map captures the file capability, not a pure function
  secret.map: s =>
    access("exfil.txt").write(s) // error: capturing f is not allowed
    s

  // Compile error: print out the classified content to the cloud LLM
  secret.map: s =>
    println(s) // error: capturing IOCapability is not allowed
    s
}

那么代理如何利用分类数据做有用的事情呢?通过双 LLM 设计:一个独立的可信本地 LLM 处理分类内容。框架提供了一个接受 Classified[String] 并返回 Classified[String]chat 重载。可信 LLM 可以看到内容,但结果保持包装状态,永远无法流回不可信的云模型。

分类数据流

requestFileSystem("/project") {
  // OK: read classified content
  val doc = readClassified("secrets/contract-v2.txt")

  // OK: pure transformation
  val upper = doc.map(_.trim)

  // OK: send to trusted local LLM, result stays Classified
  val summary = chat(doc.map(s => s"Summarize the following document:\n$s"))
  // summary: Classified[String], content is still protected

  // OK: write back to a classified file
  writeClassified("secrets/summary.txt", summary)
}

除了可信 LLM 和分类文件之外,Classified 值还可以在不解密的情况下流向白名单网络主机,既可以作为机密请求头(例如向允许的 API 进行身份验证),也可以作为分类 POST 请求体,其响应保持包装状态:

requestNetwork(Set("api.example.com")) {
  requestFileSystem("/project") {
    val key = readClassified("secrets/api.key")

    // OK: the token reaches the allowlisted host as a header, but is never
    // observable to agent code (the value cannot be printed or inspected).
    val me = httpGet("https://api.example.com/me",
                     secretHeaders = Map("Authorization" -> key.map("Bearer " + _)))

    // OK: secret body in, Classified response out.
    val payload = readClassified("secrets/report.json")
    val reply = httpPostClassified("https://api.example.com/process", payload)
    // reply: Classified[String]
  }
}

安全模式

代理生成的代码在 Scala 3 的安全模式import language.experimental.safe)下编译,该模式强制一个能力安全的语言子集:

  1. 不允许未经检查的类型转换或模式匹配

  2. 不允许使用 caps.unsafe 模块中的特性

  3. 不允许 @unchecked 注解

  4. 不允许运行时反射

  5. 启用捕获检查和显式空值编译,跟踪所有变更效应

  6. 全局对象和函数只有在安全实现时才可访问

这些限制防止代理通过不安全的转换、反射或类型系统漏洞"遗忘"能力。未通过编译的代码永远不会被执行。

安全模式是一项仍在积极开发中的实验性功能。默认情况下,TACIT 使用静态代码验证器检查禁止模式以强制执行安全模式子集。--safe-mode 标志(或 JSON 配置中的 "safeMode": true)额外将 language.experimental.safe 导入每次 REPL 执行,选择 Scala 3 的编译器内强制机制。

执行超时

--exec-timeout-ms <ms>(或 JSON 配置中的 "executionTimeoutMs")限制单次 REPL 求值的墙钟时间。该值必须为正数;零或负值在启动时被拒绝。超时时客户端会收到明确的错误提示而不是挂起,对于有状态会话,会话保持其先前状态,因此被放弃的语句没有可观察的效应。

看门狗在工作者线程上运行每次求值,并且是尽力而为的:可中断响应的工作(阻塞 I/O、睡眠、大多数库调用)会被可靠地限制,但从不检查中断的纯 CPU 循环会在后台继续运行并持续持有 REPL 的输出锁。硬抢占需要进程级隔离;此旋钮是健壮性保护,而非沙箱边界。未设置时(默认),求值没有超时。

LLM 集成

通过 chat 方法可以使用辅助 LLM,无需能力作用域。安全性来自 Classified 类型系统:chat(String): String 用于常规数据,chat(Classified[String]): Classified[String] 用于敏感数据。

// Regular chat
val answer = chat("What is 2 + 2?")

// Classified chat: input and output stay wrapped
requestFileSystem("/secrets") {
  val secret = readClassified("/secrets/key.txt")
  val result = chat(secret.map(s => s"Summarize: $s"))
  // result is Classified[String], cannot be printed or leaked
}

通过 CLI 标志(--llm-base-url--llm-api-key--llm-model)或 JSON 配置文件(--config)进行配置。支持任何兼容 OpenAI 的 API。

实验结果

我们在安全性和表现力方面评估 TACIT(完整细节见论文第 4 节)。

安全性(RQ1)。分类模式(机密包装在 Classified[String] 中)下,Claude Sonnet 4.6 和 MiniMax M2.5 在所有 131 次试验中均实现100% 安全性。每次注入和恶意任务都被类型系统阻止。实用性保持较高(Sonnet 为 99.2%,MiniMax 为 90.0%)。

表现力(RQ2)。 在 τ2-bench 和 SWE-bench Lite 上,使用 TACIT 能力安全框架的代理在所有测试模型(gpt-oss-120b、MiniMax M2.5、DeepSeek V3.2)上达到或略微超过标准工具调用基线,证明编写类型安全的 Scala 不会降低代理性能。

扩展库:添加你自己的 API

该库(library/)定义了用户代码可以在 REPL 内调用的能力 API。要实现自定义权限和细粒度访问控制,你可以通过修改库并仅重新构建库 JAR 来添加新能力(例如数据库访问、消息队列、服务器管理)。

库结构

library/
├── Interface.scala          # Public API trait (what user code sees)
├── impl/
│   ├── InterfaceImpl.scala  # Wires everything together (exports Ops objects)
│   ├── BaseFileSystem.scala    # Shared path validation and gitignore-style classified-path matching
│   ├── FileOps.scala           # grep, grepRecursive, find
│   ├── ProcessOps.scala        # exec, execOutput
│   ├── WebOps.scala            # httpGet, httpPost, httpRequest, httpPostClassified
│   ├── LlmOps.scala            # chat
│   ├── RealFileSystem.scala    # FileSystem on real disk
│   ├── VirtualFileSystem.scala # In-memory FileSystem (for testing)
│   ├── ClassifiedImpl.scala    # Classified[T] wrapper implementation
│   ├── ProcessPermissionImpl.scala # Concrete ProcessPermission
│   ├── NetworkImpl.scala       # Concrete Network
│   ├── GlobMatcher.scala       # Shared `*`-glob to regex utility
│   ├── LibraryConfig.scala     # Library configuration with JSON parsing
│   └── LlmConfig.scala        # LLM configuration case class
└── test/                    # Library-level tests

分步指南:添加新 API

以下是一个添加假设的 requestDatabase 能力的示例。

1. 在 Interface.scala 中定义类型和能力

// Add a result type
case class QueryResult(columns: List[String], rows: List[List[String]])

// Add a capability class. Note the `private[library]` constructor: capability
// classes must not be constructible or extendable by agent code.
class DatabasePermission private[library] (val connectionString: String) extends caps.SharedCapability

// Add methods to the Interface trait
trait Interface:
  // ... existing methods ...

  def requestDatabase[T](connectionString: String)(op: DatabasePermission^ ?=> T)(using IOCapability): T

  def query(sql: String)(using DatabasePermission): QueryResult

要点:

  • 能力类必须继承 caps.SharedCapability。这是使 Scala 3 的捕获检查器能够防止能力逃逸出其作用域块的关键。

  • request* 方法接受一个块 op,该块通过上下文参数(?=>)接收能力。^ 标记表示该能力由捕获检查器跟踪。

  • 操作方法(如 query)将能力作为 using 参数,因此它们只能在相应的 request* 块内被调用。

2. 在 impl/ 中实现操作

创建 library/impl/DatabaseOps.scala

package tacit.library

import language.experimental.captureChecking

object DatabaseOps:
  def query(sql: String)(using perm: DatabasePermission): QueryResult =
    // Your implementation here
    // perm.connectionString has the connection info
    ???

3. 将其接入 InterfaceImpl

library/impl/InterfaceImpl.scala 中,导出您的新操作并实现 request* 方法:

abstract class InterfaceImpl private[library] (...) extends Interface:
  export FileOps.*
  export ProcessOps.*
  export WebOps.*
  export DatabaseOps.*   // ← add this

  // ... existing methods ...

  def requestDatabase[T](connectionString: String)(op: DatabasePermission^ ?=> T)(using IOCapability): T =
    val perm = new DatabasePermission(connectionString)
    op(using perm)

4. 在验证器中阻止直接访问(服务器端)

如果您的新 API 包装了一个用户不应直接调用的 Java/Scala 库,请在 src/main/scala/executor/CodeValidator.scala 中添加禁止模式:

ForbiddenPattern("db-jdbc", raw"java\.sql\b".r, "Direct JDBC access is forbidden; use requestDatabase"),
ForbiddenPattern("db-driver", raw"DriverManager".r, "DriverManager is forbidden; use requestDatabase"),

这确保用户代码通过能力 API 访问,而不是绕过它。

5. 添加依赖(如需要)

如果您的新 API 需要外部库,请在 build.sbt 中将其添加到 lib 项目中:

lazy val lib = project
  .in(file("library"))
  .settings(
    // ... existing settings ...
    libraryDependencies ++= Seq(
      "com.openai" % "openai-java" % "4.38.0",
      "org.postgresql" % "postgresql" % "42.7.3",  // ← add your dep
    ),
  )

6. 重新构建库 JAR

sbt "lib/assembly"

无需重新构建服务器 JAR,除非您更改了 CodeValidator(步骤 4)或其他服务器端代码。只需将服务器指向新的库 JAR:

java -jar server.jar --library-jar new-library.jar

7. 在开发 REPL 中尝试您的新 API

为了快速迭代而无需启动代理,请启动开发 REPL,这是一个交互式 Scala 提示符,预加载了能力 API 和 MCP 服务器使用的相同 CodeValidator

sbt devRepl                                  # default config
sbt "devRepl --strict --config my.json"      # with flags

需要记住的事项

  • 能力必须继承 caps.SharedCapability 这是使捕获检查生效的关键。没有它,编译器无法跟踪能力的作用域,用户可能会将其泄漏出 request* 块。

  • 能力类及其实现是密封的。 所有能力类型(FileSystemNetworkProcessPermissionIOCapabilityClassifiedFileEntry)和每个实现类(RealFileSystemNetworkImplLlmOps 等)都具有 private[library] 构造函数,因此代理代码既不能实例化也不能扩展它们:能力只能来自 request* 作用域。为您添加的任何能力保持这一不变性:给类一个 private[library] 构造函数,并使具体实现也是 private[library]

  • 接口本身也是密封的。 InterfaceImpl 的构造函数是 private[library],因此库外没有任何东西可以选择策略 JSON。服务器为每个沙箱注册一次库配置(InterfaceImpl.configure,在任何代码运行之前通过 REPL 的类加载器调用),而前导代码实例化无参数的 SandboxInterface,其策略就是该注册的配置。代理代码如果扩展 SandboxInterface 本身,只能获得一个相同的、受策略约束的接口,绝不会获得更宽的接口。

  • 捕获检查是实验性的。 该项目使用 -language:experimental.captureChecking。编译器行为可能随 Scala 3 夜间版本而变化。如果您遇到意外错误,请通过临时移除该标志来检查问题是否与捕获检查有关。

  • 该库使用 Scala 3 夜间版。 构建会自动获取最新的 Scala 3 夜间版。这意味着您的代码必须兼容前沿的 Scala。如果您需要稳定性,请在 build.sbt 中固定特定版本(val scala3Version = "3.x.y")。

  • Interface.scala 作为资源打包。 服务器在构建时将 Interface.scala 复制到其资源中,以便 show_interface 工具可以显示它。如果您添加了新 API,用户将通过 show_interface 自动看到它们,无需额外工作。

  • 禁止模式作用于用户代码,而非库代码。 CodeValidator.scala 中的验证器只检查用户提交的代码。库本身可以在其实现中自由使用 java.iojava.netProcessBuilder 等。但如果您的新 API 包装了一个 Java API,您应该添加相应的禁止模式,以便用户无法绕过您的能力包装器。

  • 库 JAR 是一个胖 JAR。 sbt "lib/assembly" 生成一个包含库所有依赖项(例如 openai-java)的 JAR。如果您添加依赖项,它将自动被打包。

  • 服务器在编译时依赖库类型。 服务器依赖接口类型来运行 REPL。请确保您的更改与服务器期望的接口兼容。

  • 先在库级别测试您的 API。 library/test/ 目录包含使用 MUnit 的库级测试,通过 scala-cli test library --server=false 运行(它们属于 sbt test--server=false 可避开当前 Scala 3 夜间版与 scala-cli 捆绑的 Bloop 服务器之间的 ASM 冲突)。在通过 MCP 服务器进行集成测试之前,请先在那里测试您的新操作。参见 LibrarySuite.test.scala 中的示例。

开发

要求:

  • JDK 17+

  • sbt 1.12+

sbt clean                      # Clean build artifacts
sbt compile                    # Compile
sbt test                       # Run the server test suites (src/test/scala)
sbt "testOnly *McpServerSuite" # Run a single server suite
scala-cli test library --server=false   # Run the library test suites (library/test)
sbt assembly                   # Build both JARs (server + library)
sbt "lib/assembly"             # Build library JAR only
sbt devRepl                    # Interactive REPL for testing the library

sbt test 只运行服务器测试套件。library/test/ 中的测试套件通过 scala-cli test library --server=false 单独运行。

# Basic
java -jar target/scala-*/TACIT-assembly-*.jar \
  --library-jar library/target/scala-*/TACIT-library.jar

# With logging
java -jar server.jar --library-jar library.jar --record ./log

# With JSON config
java -jar server.jar --library-jar library.jar --config config.json

引用

@inbook{10.1145/3786335.3813127,
author = {Odersky, Martin and Zhao, Yaoyu and Xu, Yichen and Bra\v{c}evac, Oliver and Pham, Cao Nguyen},
title = {Securing Agents With Tracked Capabilities},
year = {2026},
isbn = {9798400724152},
publisher = {Association for Computing Machinery},
address = {New York, NY, USA},
url = {https://doi.org/10.1145/3786335.3813127},
booktitle = {Proceedings of the ACM Conference on AI and Agentic Systems},
pages = {812–838},
numpages = {27}
}

许可证

Apache-2.0

A
license - permissive license
Not graded
quality - not tested
A
maintenance

Maintenance

UpdatingMaintainers
UpdatingResponse time
2wRelease cycle
10Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A unified MCP server providing observability, safety control, and behavior evolution for high-agency AI agents through tracing, replaying, and auditing. It features real-time firewall guardrails and ML-driven anomaly detection to monitor, block, or fork agent actions based on risk.
    7
  • A
    license
    C
    quality
    B
    maintenance
    Agent-first programming language: agents produce JSON AST, the compiler validates, type-checks, effect-checks, verifies contracts via Z3/SMT, and compiles to WASM. 19 MCP tools for the full compile-and-execute loop.
    22
    123
    11
    MIT

View all related MCP servers

Related MCP Connectors

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lampepfl/TACIT'

If you have feedback or need assistance with the MCP directory API, please join our Discord server