关于 SQLite MCP Sidecar
SQLite MCP Sidecar 是通过 HTTP MCP 为 Agent 提供受控访问的服务,可与拥有数据库的应用并行使用一个现有 SQLite 数据库。默认只读;启用写入后,可通过结构化操作和限制控制修改范围。
主要功能
- 01提供架构、查询、备份和诊断工具
- 02结构化写入要求筛选条件并限制行数
- 03以 TOON 格式返回查询结果
- 04按部署配置令牌和权限
- 05请求与写入预算及幂等处理
适用场景
- 01让编码 Agent 检查应用运行中的数据库
- 02在行数上限内允许有限更新或删除
- 03应用持续写入时创建数据库备份
MCP 相关
通过 MCP Streamable HTTP 在 `/db/mcp` 暴露数据库工具。可用工具由配置的权限决定。
配置
本地服务的配置在各客户端中通用,复制后粘贴到客户端的 MCP 配置文件即可。
docker run -i --rm -e SQLITE_SIDECAR_DB -e SQLITE_SIDECAR_TOKEN -e SQLITE_SIDECAR_PERMISSIONS ghcr.io/xakpc/sqlite-mcp-sidecar:0.1.3SQLITE_SIDECAR_DBPath of the existing SQLite file inside the containerSQLITE_SIDECAR_TOKEN密钥Deployment bearer token (a long random string)SQLITE_SIDECAR_PERMISSIONSPermission set: schema, read, write, backup, diagnostics, danger-raw-write本地服务需要在你的电脑上安装运行环境(如 Node.js 18+)。各客户端的配置文件位置不同,请参考客户端文档。
ghcr.io/xakpc/sqlite-mcp-sidecar:0.1.3Streamable HTTP工具
本地服务需要在你的电脑上运行后才能读取工具,维护者暂未提供工具列表。
详细介绍
sqlite-sidecar-mcp
为单个正在使用的 SQLite 数据库提供安全供 Agent 使用的 MCP 侧车。
它让 AI Agent 受控地访问另一个应用已拥有的数据库, 并限制获得授权但操作失误的 Agent 可能造成的损害。
关于项目
应用拥有一个 SQLite 文件。你希望 Agent 能读取它,也许还可以修改几行数据。
把 SQL 连接交给 Agent,就等于把整个文件交给它;只要有一条 DELETE 语句使用了错误的谓词,
就会造成无法挽回的后果。sqlite-sidecar-mcp 作为另一个 SQLite 客户端运行在应用旁边,
并通过 HTTP 使用 MCP 通信。拥有该数据库的应用无需更改。
它在生产环境中与 MCP Canary API 一同运行,为 操作员的编码 Agent 提供对实时数据库的只读访问权限。
flowchart TD
agent[AI / MCP client] -->|HTTPS| proxy[Reverse proxy]
proxy -->|private HTTP| sidecar[sqlite-sidecar-mcp]
sidecar -->|SQLite client| db[(app.db)]
app[Owning application] -->|SQLite client| db
本产品可防范两类调用方。一类是未经授权的客户端,令牌和请求额度可以阻止它。另一类是已获授权但操作失误的客户端,这才是不同寻常之处:即使 Agent 获得了写入权限,也可能写错内容。结构化写入、强制谓词、统计级联操作的行数上限,以及每分钟写入额度,都是为此而设。
重要
**一个侧车进程只服务一个数据库。**请求额度、写入额度和幂等缓存都存放在进程内存中。第二个副本会带来两套独立的额度和两份独立的缓存,而且不会有任何提示。请勿扩展此服务。只有在服务于不同数据库或不同信任边界时,才运行第二个侧车。
构建技术
- .NET 10、ASP.NET Core
- 基于 Streamable HTTP 的 Model Context Protocol
Microsoft.Data.Sqlite,使用捆绑的原生 SQLite- 使用 TOON 输出行数据,比 JSON 消耗更少的令牌
(返回顶部)
开始使用
先决条件
- Docker
- 一个已存在的 SQLite 数据库。侧车绝不会创建数据库:创建数据库会掩盖路径错误,并在真实数据库旁留下一个空数据库。
运行
docker run -d --name sqlite-sidecar \
-p 127.0.0.1:8080:8080 \
-v /srv/myapp/data:/data \
-e SQLITE_SIDECAR_DB=/data/app.db \
-e SQLITE_SIDECAR_TOKEN='<a long random string>' \
-e SQLITE_SIDECAR_PERMISSIONS=schema,read \
--read-only --tmpfs /tmp \
--cap-drop=ALL --security-opt=no-new-privileges \
ghcr.io/xakpc/sqlite-mcp-sidecar:latest
即使使用上述只读权限设置,挂载也必须可写。对于 WAL 数据库的只读连接,SQLite 仍会以读写方式打开 -shm 文件;只读挂载会导致读取错误看起来像权限故障,而不是配置错误。
连接 Agent
端点为 /db/mcp,令牌需放在 Authorization 标头中:
{
"mcpServers": {
"app-database": {
"url": "https://example.com/db/mcp",
"headers": { "Authorization": "Bearer <the token>" }
}
}
}
这里没有 OAuth。坚持进行 OAuth 发现的客户端在收到 401 后找不到资源元数据,就会停止;预期的使用方是由操作员配置了静态标头的单个 Agent。
生成令牌和环境配置块
dotnet run scripts/token.cs
它会询问部署方式,从加密安全的随机源生成令牌,修正会导致启动失败的权限设置,并以 .env、shell 或 compose 格式打印配置块。
(返回顶部)
## 八种工具可用工具由权限集决定。部署未包含的工具不会出现在 tools/list 中——它不会先显示出来再报错。
| 工具 | 所需权限 | 功能 |
|---|---|---|
schema | schema | 返回用户表、视图和索引的 DDL。不包含行数据、内部 sqlite_% 对象或文件路径。 |
query | read | 运行一条调用方编写的只读语句,并以 TOON 格式返回行数据,同时受行数上限、字节数上限和超时限制。 |
insert | write | 根据表名和值映射插入一行。调用方无需发送 SQL。 |
update | write | 根据结构化筛选条件更新匹配的行。必须提供筛选条件和 maxRows。 |
delete | write | 根据结构化筛选条件删除匹配的行。必须提供筛选条件和 maxRows。 |
backup | backup | 在所属应用继续写入的同时复制正在使用的数据库。返回一个任务;客户端轮询以获取结果。 |
diagnostics | diagnostics | 报告一组固定的运行状况值,包括日志模式和 quick_check。 |
execute_write_sql | danger-raw-write | 运行一条调用方编写的 INSERT、UPDATE 或 DELETE。启用前请参阅安全性。 |
query 会以 TOON(一种紧凑的表格文本格式)返回结果:
rows[2]{id,status}:
54,done
41,failed
truncated: false
truncated: true 表示读取因达到上限而停止,并不表示查询失败。
结构化写入
update 和 delete 接受扁平筛选条件,绝不接受 SQL。每个条件都用 AND 连接;不支持 or,也不支持嵌套。析取操作需要按分支分别调用,而这也是限制更严格的做法:每次调用都会单独预先计数并设限;如果用一次 or 调用,就会把所有分支的影响范围合并到一个 maxRows 中。
maxRows 统计写入操作更改的所有行,包括因 ON DELETE CASCADE 被删除的行,以及触发器写入的行。maxRows: 1 的 delete 不能悄悄删除一个级联子树后只报告一行。匹配行数过多的筛选条件会在写入开始前被拒绝;如果写入最终超出上限,则会回滚。
每个写入工具都必须提供 requestId。相同的 requestId 会返回已提交写入所存储的响应,而不会再次应用写入。失败不会被缓存,因此失败后重试会真正重新执行。
(返回顶部)
## 权限一个部署只有一个令牌和一组权限。权限是部署的属性,而不是用户的属性——这里没有用户、角色或 ACL。不同的信任级别使用不同的部署,从而在部署配置中清晰呈现边界。
SQLITE_SIDECAR_PERMISSIONS=schema,read # the default
| 权限 | 风险 | 授予的能力 |
|---|---|---|
schema | 低 | 仅元数据。 |
read | 敏感 | 可以读取数据库中的所有数据。 |
write | 较高 | 可以对所有表进行受限的数据修改。 |
backup | 敏感 | 可以创建数据库副本。 |
diagnostics | 低至中等 | 元数据和运行状况值。 |
danger-raw-write | 高 | 可以在所有表中执行任意 INSERT、UPDATE 和 DELETE SQL。 |
danger-raw-write 这个名称刻意带有警示意味。中性的名称会掩盖其代价。
警告
某项权限适用于数据库中的每张表。 没有表允许列表,也没有列允许列表。拥有
write权限的 Agent 不会因为write而无法修改某张特定表。如果某张表必须保持不变,就应将它放在使用不同 sidecar 的独立数据库中。
sidecar 会在启动时强制执行以下两条规则,因此配置错误会导致启动失败,而不是日后带来意外:
- 读取权限下限。
write和danger-raw-write只有与schema和read一起配置时才有效。结构化写入会根据实时架构验证名称,因此无法调用schema的 Agent 只能靠猜测。sidecar 绝不会静默添加缺少的权限,因为那样会导致SQLITE_SIDECAR_PERMISSIONS无法如实反映部署配置。 - 不接受未知名称。
SQLITE_SIDECAR_PERMISSIONS=reed会导致启动失败,而不是静默授予少于操作者预期的权限。
任何权限都不会授予其他权限。read 不代表拥有 write 权限,danger-raw-write 也独立于 write。
(返回顶部)
配置
每个值都是带有 SQLITE_SIDECAR_ 前缀的环境变量。缺少值或值无效时,启动会失败,并一次性报告所有问题。sidecar 不会以降级状态启动。
必需:
| 变量 | 含义 |
|---|---|
SQLITE_SIDECAR_DB | SQLite 文件的路径。文件必须存在。 |
SQLITE_SIDECAR_TOKEN | 部署的持有者令牌。请将其作为机密提供。 |
可选,带默认值
| 变量 | 默认值 | 含义 |
|---|---|---|
SQLITE_SIDECAR_PERMISSIONS | schema,read | 权限集。 |
SQLITE_SIDECAR_BACKUP_DIR | none | 目标目录。启用 backup 时必需,且目录必须可写。 |
SQLITE_SIDECAR_MAX_ROWS | 1000 | 单个查询结果的行数上限。 |
SQLITE_SIDECAR_MAX_RESULT_BYTES | 4194304 | 单个查询结果的字节数上限。 |
SQLITE_SIDECAR_MAX_SQL_BYTES | 32768 | 可接受语句的最大长度。 |
SQLITE_SIDECAR_QUERY_TIMEOUT_SECONDS | 10 | 超时较长的查询会在 SQLite 内部中断。 |
SQLITE_SIDECAR_BUSY_TIMEOUT_SECONDS | 3 | 等待其他写入者释放锁的最长时间。 |
SQLITE_SIDECAR_MAX_CONCURRENCY | 4 | 并发数据库操作数。 |
SQLITE_SIDECAR_MAX_REQUESTS_PER_MINUTE | 120 | 请求额度,每个进程使用一个时间窗口。 |
SQLITE_SIDECAR_MAX_WRITE_ROWS | 100 | 服务端对每次写入可更改行数的上限。 |
SQLITE_SIDECAR_MAX_WRITE_ROWS_PER_MINUTE | 500 | 写入额度,每个进程使用一个时间窗口。 |
相关的 ASP.NET Core 设置:ASPNETCORE_URLS(镜像将其设为 http://0.0.0.0:8080)和
ASPNETCORE_ENVIRONMENT。在 Development 之外,sidecar 会输出 JSON 日志,这正是容器日志
管道所需要的格式。
实际写入上限为 min(调用方的 maxRows, SQLITE_SIDECAR_MAX_WRITE_ROWS),因此 Agent
可以请求更低的上限,但不能请求更高的上限。
| 路径 | 令牌 | 是否限流 |
|---|---|---|
/db/mcp | 必需 | 是 |
/health | 否 | 否 |
/health 只报告进程是否存活,不做其他检查。它绝不会访问数据库:探针每隔几秒就会运行一次,昂贵的探针会成为针对所属应用的拒绝服务攻击途径。确实需要访问文件的检查会在启动时运行一次。
(返回顶部)
部署
注意
代理不得移除
/db前缀。/db/mcp是完整的公开路径:边车会转发 Agent 调用的同一个 URL。Kamal 和 Coolify 默认都会移除匹配的前缀,而被移除的前缀会导致404,看起来就像服务出故障一样。下方的所有配置都会关闭前缀移除。
所有方案的共同要求:一个副本、即使是 schema,read,/data 也必须可写,并且容器端口绝不暴露到互联网——只能通过反向代理访问,而且也只有它会匹配 Host 请求头。边车自身不做主机过滤。
Docker Compose
services:
app:
image: my-app
volumes:
- sqlite-data:/data
sqlite-sidecar:
image: ghcr.io/xakpc/sqlite-mcp-sidecar:latest
deploy:
replicas: 1
environment:
SQLITE_SIDECAR_DB: /data/app.db
SQLITE_SIDECAR_TOKEN: ${SQLITE_SIDECAR_TOKEN}
SQLITE_SIDECAR_PERMISSIONS: schema,read,write,backup
SQLITE_SIDECAR_BACKUP_DIR: /backups
ASPNETCORE_URLS: http://0.0.0.0:8080
volumes:
- sqlite-data:/data
- sqlite-backups:/backups
read_only: true
tmpfs:
- /tmp
cap_drop:
- ALL
security_opt:
- no-new-privileges:true
volumes:
sqlite-data:
sqlite-backups:
两个容器都挂载同一个卷。数据库文件必须保存在本地文件系统上:NFS 和 SMB 不具备 SQLite 所需的锁定机制。
Kamal
一个数据库对应一个服务和一台主机。边车读取本地文件,因此无法适配多区域角色集:请指定存放数据库的主机。
service: sqlite-sidecar
image: ghcr.io/xakpc/sqlite-mcp-sidecar
servers:
app:
hosts: [<host that holds the database>]
proxy:
ssl: true
host: <the public host name>
path_prefix: /db
strip_path_prefix: false
app_port: 8080
healthcheck:
path: /health
volumes:
- "<host data directory>:/data"
env:
clear:
SQLITE_SIDECAR_DB: /data/app.db
SQLITE_SIDECAR_PERMISSIONS: schema,read
secret:
- SQLITE_SIDECAR_TOKEN
- 这里必须设置
healthcheck.path。kamal-proxy 默认会请求/up,部署会因该默认设置而失败。 - kamal-proxy 不具备限流功能。它提供缓冲、请求体大小限制和转发请求头。因此,进程内请求配额是此平台唯一的请求限制。
Coolify
请使用子域名,而不是路径。Coolify 会为域名路径生成 Traefik 标签,而路径与移除前缀的组合存在尚未修复的问题。使用子域名没有额外成本,也能消除风险。无论如何,应用路径都保持为 /db/mcp。
Domain https://mcp.<domain>
Strip Prefixes off
Health check /health
Replicas 1
Volume <host data directory>:/data, writable
Coolify 使用 Traefik,因此可以使用限流中间件。但并非必须:进程内配额在所有平台上都会启用。
普通 VPS,使用 systemd
完全不需要编排器。将 docker run 命令交给 systemd 管理,使其在重启后仍能运行:
# /etc/systemd/system/sqlite-sidecar.service
[Unit]
Description=sqlite-sidecar-mcp
After=docker.service
Requires=docker.service
[Service]
Restart=always
RestartSec=5
ExecStartPre=-/usr/bin/docker rm -f sqlite-sidecar
ExecStart=/usr/bin/docker run --rm --name sqlite-sidecar \
-p 127.0.0.1:8080:8080 \
-v /srv/myapp/data:/data \
--env-file /etc/sqlite-sidecar.env \
--read-only --tmpfs /tmp \
--cap-drop=ALL --security-opt=no-new-privileges \
ghcr.io/xakpc/sqlite-mcp-sidecar:latest
ExecStop=/usr/bin/docker stop sqlite-sidecar
[Install]
WantedBy=multi-user.target
-p 127.0.0.1:8080:8080 会绑定到回环地址,因此只有同一主机上的代理才能访问它。将令牌存入 /etc/sqlite-sidecar.env,并设置权限为 0600;再将 Caddy 或 nginx 指向 127.0.0.1:8080,并保持转发路径不变。
Fly.io
Fly Volume 只能挂载到一台机器上,这正适合此产品:一个文件、一个写入方、一个边车。
app = "myapp"
[build]
image = "ghcr.io/xakpc/sqlite-mcp-sidecar:latest"
[env]
SQLITE_SIDECAR_DB = "/data/app.db"
SQLITE_SIDECAR_PERMISSIONS = "schema,read"
ASPNETCORE_URLS = "http://0.0.0.0:8080"
[[mounts]]
source = "app_data"
destination = "/data"
[http_service]
internal_port = 8080
force_https = true
auto_stop_machines = false
auto_start_machines = false
min_machines_running = 1
[[http_service.checks]]
path = "/health"
fly secrets set SQLITE_SIDECAR_TOKEN='<the token>'
警告
边车和所属应用必须运行在同一台机器上。卷只能挂载到一台机器,因此两台机器意味着两个不同的数据库文件,而单边车规则会静默失效——每个进程都会提供自己的副本。请保持
auto_stop_machines = false,并且不要扩展应用规模。
令牌轮换
一次部署只有一个令牌。它永不过期,边车也不接受第二个令牌值。轮换需要更改密钥并重启,这会同时中断所有客户端。此处接受这一做法:要支持轮换窗口,就需要维护有效令牌列表,那将是另一种产品。
(返回顶部)
安全
每个请求都会依次经过所有层。没有任何一层是功能开关。
flowchart TD
req[MCP request] --> budget[Request budget]
budget --> auth[Bearer token check]
auth --> perm[Deployment permission check]
perm --> conc[Concurrency semaphore]
conc --> agent[Agent protections]
agent --> sandbox[SQLite sandbox]
sandbox --> sqlite[(app.db)]
budget -->|fail| e0[429 Too Many Requests]
auth -->|fail| e1[Unauthorized]
perm -->|fail| e2[PermissionDenied]
agent -->|fail| e3[WriteLimitExceeded / WriteBudgetExceeded]
sandbox -->|fail| e4[QueryRejected]
保证事项
使用默认配置时,以下说法均成立:
- 远程访问需要身份验证。
- 默认权限为只读。
read不代表write。- 没有
schema和read权限时,write权限无效。 write不接受调用方提供的写入 SQL。- 结构化的
update和delete需要谓词,且谓词之间只能用AND连接。 - 结构化的
update和delete有严格的行数上限;过滤条件过于宽泛时,会在写入开始前被拒绝。 - 该行数上限统计写入操作更改的每一行,因此,即使删除操作触发级联删除,也不能在只报告一行的情况下删除无界的级联子树。
- 使用相同
requestId重复执行写入,只会生效一次。 - 重复的结构化写入会触发速率限制。
- 原始写入需要显式授予危险权限。
- 原始写入不能执行 DDL、
ATTACH或加载本机扩展。 - 调用方不能提供备份文件系统路径。
- 完整备份文件始终是完整数据库。部分副本会保留
.partial后缀。 - 查询有时间限制、结果数量限制和并发限制。
- 本机 SQLite 防御机制已启用。
- 数据库文件绝不会通过网络传输。
这些都是具体的控制措施,但没有任何一项能让暴露在网络上的数据库绝对安全。
SQLite 沙箱
最内层的防护始终启用,任何权限都不能禁用其中任何部分——包括 danger-raw-write。它使用本机 SQLite 授权器,而不是通过字符串匹配来检查 SQL,因此以下操作在每条路径上都会在语句准备阶段被拒绝:
ATTACH DDL (CREATE / ALTER / DROP) PRAGMA
load_extension VACUUM transaction control
more than one statement in one request
防御模式已启用,可信架构已关闭;每个连接的运行时限制会约束 SQL 长度、表达式深度和 VDBE 操作数;运行中的语句超过超时时限后会被中断——是在 SQLite 内部中断,而不只是放弃等待。连接池已关闭,因为池化的句柄会保留上一次操作的授权器和限制。
不保证的事项
注意
无法撤销。 在配置的限制范围内执行的
delete会提交,而且没有还原工具。备份策略由运营者负责。这些限制旨在控制影响范围——一次错误会影响多少行。它们无法防范错误操作:看似正确但值有误的写入,会完全按照请求执行。
danger-raw-write 顾名思义就是危险的原始写入权限。 运营者启用此权限后,持有令牌的人可以执行:
DELETE FROM jobs;
这是预期行为,并非缺陷。该权限会有意移除六项保护:结构化写入、强制谓词、强制 maxRows、有界预计数、总更改数限制以及服务器生成 SQL。它只保留两项保护:幂等键和写入预算。上文所述的 SQLite 沙箱不会受到影响,因此原始写入仍然不能执行 DDL、附加数据库、加载扩展,或写入内部 sqlite_% 对象。
权限适用于整个数据库。 请参阅权限。
令牌一旦被盗,持有者就会获得其部署所授予的全部能力。 旁路服务会限制能力和影响范围,但无法让有意授予的破坏性权限变得无害。令牌没有有效期,也没有撤销列表——撤销令牌意味着更改密钥并重启。
预算只会限制请求速率,不会隔离调用方。 SQLITE_SIDECAR_MAX_REQUESTS_PER_MINUTE 是整个进程共用的固定时间窗口,不按调用方划分;它会在身份验证之前运行,因此错误令牌的洪泛请求也会受到限制,且被拒绝的请求仍会消耗一个名额。一个 Agent 耗尽预算后,其他 Agent 也会受到影响。写入预算的行为相同。两种预算都存储在进程内存中:重启后会重置,两个进程之间也不会共享。
容器端口绝不能直接暴露到互联网。 反向代理是唯一入口,负责终止 TLS,也是主机端的访问关卡;旁路服务本身不执行主机级过滤。
报告漏洞
请使用此仓库的 GitHub 私密安全公告。请勿公开提交安全问题。
(返回顶部)
参与贡献
欢迎贡献。请 Fork 此仓库、创建分支并提交拉取请求。
dotnet test --solution Xakpc.SQLiteMCPSidecar.slnx # the whole suite
pwsh ./scripts/seed-dev-db.ps1 # one time, before the first launch
pwsh ./scripts/dev-sidecar.ps1 # a live sidecar on port 8080
安全测试是强制要求,不是可选项。针对查询路径、写入路径或沙箱的更改,都需要有测试来证明边界仍然有效——此外,测试套件还会在 Linux 容器中再运行一次,因为沙箱依赖原生 SQLite 构建,而在其他平台上进行的开发者测试并不能证明发布版本的行为。
(返回顶部)
许可证
Apache-2.0。详见 LICENSE。
联系方式
Pavel Osadchuk — xakpc.dev · github.com/xakpc
项目:github.com/xakpc/sqlite-mcp-sidecar
致谢
- Model Context Protocol 和 C# SDK
- SQLite,感谢其提供本项目所依赖的授权器和在线备份 API
- TOON
- Best-README-Template
(返回顶部)
评论
一个开放协议,规定了 AI 应用如何以统一方式连接外部工具与数据源。客户端负责与模型对话,MCP 服务负责提供工具、资源和提示模板,两者通过标准消息通信,同一个服务可以被不同客户端复用。