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

该框架包含三个主要组件:
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/bin 在 PATH 中,并将最新版本下载到 ~/.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 服务器 |
|
库 |
|
选项 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 服务器 |
|
库 |
|
包装器和脚本都会根据发布元数据中公布的 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 后,请配置您的代理以启动 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: repo-seatbelt
配置
服务器可以通过 CLI 标志或 JSON 配置文件进行配置。直接在代理的 MCP 参数中传递标志,或使用 --config 指向 JSON 文件。
配置分为服务器配置(传输、记录、会话)和库配置(沙箱行为、能力)。在 JSON 配置文件中,库设置位于 libraryConfig 键下,并直接传递给库进行处理。
CLI 标志
服务器标志:
标志 | 描述 |
| 必需。 库 JAR( |
| 将每次执行记录到磁盘 |
| 抑制启动横幅和请求/响应日志 |
| 禁用与会话相关的工具 |
| 在 REPL 中为每次执行启用/禁用 Scala 3 的 |
| 单次 REPL 求值的墙钟超时(默认:无;请参阅执行超时) |
| JSON 配置文件( |
库标志(某些 libraryConfig 字段的简写):
标志 | 描述 |
| 通过 exec 阻止内置的不安全命令黑名单(文件操作、Shell、解释器、网络工具、命令运行器等);匹配不区分大小写。适合快速实验;对于实际部署,请优先使用 |
| 可执行命令的逗号分隔 glob 模式列表(例如 |
| 可达主机的逗号分隔 glob 模式列表(例如 |
|
|
| 逗号分隔的分类路径模式(gitignore 风格,见下文) |
| LLM API 基础 URL |
| LLM API 密钥 |
| 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 阻止内置的不安全命令黑名单:文件操作命令(cat、ls、rm、tar、chmod 等)、Shell、解释器(python、node、perl 等)、网络工具(curl、wget、ssh 等)、命令运行器(xargs、nohup、env 等)以及类似命令。匹配对命令的基本名称不区分大小写。当您只想快速尝试时很方便,但对于实际使用来说过于粗糙;请优先使用 commandPermissions。
networkPermissions(可选)。网络允许列表:一个 glob 模式列表(只有 * 是通配符),通过 httpGet/httpPost/httpRequest 访问的每个主机都必须匹配。与 commandPermissions 类似,它叠加在由 requestNetwork(...) 声明的每个作用域集合之上;主机必须同时匹配两者。未设置时,仅应用每个作用域的 requestNetwork 允许列表。
allowedRoots(可选)。文件系统外边界:一个路径列表,用于限定 requestFileSystem(root) 可以操作的范围。请求的根目录必须解析(包括符号链接)为这些路径之一或位于其下嵌套的路径,否则访问将被拒绝。未设置时,默认使用服务器的当前工作目录,因此沙箱被限制在该子树内(失败即关闭)。显式设置它可以扩大或重新定位边界。在授予的任何根目录内,分类路径掩码仍然生效。
符号链接在包含性检查之前始终被解析,包括悬空链接(通过悬空链接写入会创建其目标,因此被检查的是目标)。由 children/walk(因此也包括 find/grepRecursive)找到的、解析到已授予根目录之外的条目(例如 .venv/bin/python 或 node_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 风格的语法。如果路径匹配某个模式或是某个匹配项的后代,则该路径被分类。
模式 | 匹配内容 | 示例 |
| 任何名为 |
|
| 匹配该通配符的任何组件 |
|
| 相对于文件系统根目录,带通配符 |
|
| 任意深度的 |
|
| 绝对路径(符号链接已解析) |
|
规则:
模式中无
/: 匹配任意路径组件(基名匹配)带
/的相对模式: 锚定到文件系统根目录;支持*、**、?、[…]绝对模式: 针对完整路径匹配;非通配符前缀通过符号链接解析
尾部
/会被去除(不区分仅目录)
默认分类模式(未配置 classifiedPaths 时):.ssh、.gnupg、.env、.env.*、.netrc、.npmrc、.pypirc、.docker、.kube、.aws、.azure、.gcloud。
工具
工具 | 参数 | 描述 |
|
| 在全新的 REPL 中执行 Scala 代码片段(无状态) |
| - | 创建持久 REPL 会话,返回 |
|
| 在现有会话中执行代码(有状态) |
| - | 列出活动会话 ID |
|
| 删除会话 |
| - | 显示完整的能力 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)下编译,该模式强制一个能力安全的语言子集:
不允许未经检查的类型转换或模式匹配
不允许使用
caps.unsafe模块中的特性不允许
@unchecked注解不允许运行时反射
启用捕获检查和显式空值编译,跟踪所有变更效应
全局对象和函数只有在安全实现时才可访问
这些限制防止代理通过不安全的转换、反射或类型系统漏洞"遗忘"能力。未通过编译的代码永远不会被执行。
安全模式是一项仍在积极开发中的实验性功能。默认情况下,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.jar7. 在开发 REPL 中尝试您的新 API
为了快速迭代而无需启动代理,请启动开发 REPL,这是一个交互式 Scala 提示符,预加载了能力 API 和 MCP 服务器使用的相同 CodeValidator:
sbt devRepl # default config
sbt "devRepl --strict --config my.json" # with flags需要记住的事项
能力必须继承
caps.SharedCapability。 这是使捕获检查生效的关键。没有它,编译器无法跟踪能力的作用域,用户可能会将其泄漏出request*块。能力类及其实现是密封的。 所有能力类型(
FileSystem、Network、ProcessPermission、IOCapability、Classified、FileEntry)和每个实现类(RealFileSystem、NetworkImpl、LlmOps等)都具有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.io、java.net、ProcessBuilder等。但如果您的新 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
This server cannot be deployed
Maintenance
Related MCP Connectors
Formally-verified injection/exfiltration detector for AI agents (MCP-02).
AgentGuard — 20-tool AI safety MCP: policy preflight, risk scoring, audit logging, rate limits.
Machine-verifiable authorization checks for autonomous AI agents.
Context and control for any AI. Agent identity, scoped access, and proof across tools.
Related MCP Servers
- AlicenseCqualityBmaintenanceAgent-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.2284 npm13MIT
- AlicenseNot gradedqualityDmaintenanceRuntime safety guardrails for AI coding agents. Checks file access, validates shell commands, and scores your repo's AI safety — all via MCP.6 npm8MIT
- AlicenseNot gradedqualityBmaintenanceEnables AI agents to operate through a local, signed boundary that blocks prompt injection and secret leakage, verifies outputs, preserves cross-session memory, and provides offline-verifiable receipts. It also exposes 900+ MCP tools for discoverable agent actions.7MIT
- AlicenseNot gradedqualityAmaintenanceLocal security layer for AI coding agents: MCP policy proxy, kernel sandbox, tamper-evident audit logMIT