内容
# AsterixDB MCP 服务器
Apache AsterixDB 的 [模型上下文协议](https://modelcontextprotocol.io) 网关。它允许 LLM 代理发现数据集,检查模式(包括 ROW 与 COLUMNAR 存储),并对 AsterixDB 集群执行**只读** SQL++ 查询。
## 架构不变性
网关是一个**独立的 sidecar**。它从不解析 SQL++,从不持有集群控制器(CC)的状态,从不运行变异拒绝列表。CC 的 `readonly=true` 参数是变异拒绝的唯一权威 - 网关在每个查询中都硬编码了它。这使得数据库控制平面在 LLM 会话方面是无状态的。
```
LLM 客户端 ──MCP(stdio | HTTP)──▶ AsterixDB MCP 网关 ──HTTP──▶ AsterixDB CC
(此仓库) /query/service
/admin/*
```
## 功能
**26 种工具,12 种资源,6 种资源模板,6 种提示。** 工具执行操作;资源向客户端公开只读上下文,客户端可以将此上下文附加到会话中;资源模板通过 URI 模式公开每个数据仓库/数据集的上下文;提示是引导式多步骤工作流。
每种工具都宣传 MCP 行为注释(`readOnlyHint`、`destructiveHint`、`idempotentHint`、`openWorldHint`),以便客户端无需解析描述即可区分安全读取和状态更改调用 - 整个表面都是只读的,除了 `cancel_query`,而没有任何操作是破坏性的。提示和资源模板参数支持实时的 `completion/complete`:键入 `dataverse`、`dataset` 或分组/度量字段会根据集群的真实元数据完成,范围由已选择的任何参数限定。
每种工具还宣传一个 `outputSchema`,描述其成功结果的形状,以便客户端可以预期有效载荷并链接调用(例如,`submit_async_query` 产生 `clientContextID`,而 `fetch_query_result` 消耗)。模式仅描述成功结果;错误使用 `isError` 标记,并带有网关错误信封,因此广告永远不会导致失败调用被拒绝。
### 工具
| 分组 | 工具 | 目的 |
|-------|------|---------|
| 查询 | `execute_query` | 同步只读 SQL++,带有偏移/限制窗口。 |
| 查询 | `submit_async_query` | 提交一个长时间运行的查询;返回一个句柄。 |
| 查询 | `wait_on_async_query` | 长时间轮询异步句柄直到完成。 |
| 查询 | `fetch_query_result` | 分页浏览已完成的异步结果集。 |
| 查询 | `cancel_query` | 取消正在进行的异步查询。 |
| 分析 | `validate_syntax` | 仅编译语法检查,无执行。 |
| 分析 | `explain_query` | 语句的优化器计划。 |
| 分析 | `explain_physical_plan` | 物理 Hyracks 作业:操作员/连接器 DAG 和并行性。 |
| 分析 | `check_index_usage` | 查询的谓词是否命中索引。 |
| 分析 | `recommend_indexes` | 通过集群的本地 `ADVISE` 顾问提供 CREATE INDEX 建议。 |
| 分析 | `profile_query` | 运行查询并进行分析;每个操作员的运行时实际值(EXPLAIN ANALYZE)。 |
| 发现 | `list_dataverses` | 枚举集群上的数据仓库。 |
| 发现 | `list_datasets` | 分页数据集发现,可选地限定在数据仓库中。 |
| 发现 | `describe_dataverse` | 一个数据仓库中的数据集、类型、索引和函数。 |
| 发现 | `get_schema` | 单个数据集的模式,包括 `datasetFormatInfo`(ROW/COLUMNAR)。 |
| 发现 | `sample_dataset` | 数据集中的小有界行样本。 |
| 发现 | `search_metadata` | 跨元数据搜索数据集/类型/索引/函数。 |
| 发现 | `get_dataset_statistics` | 数据集的采样行数/大小估计和 ANALYZE 新鲜度。 |
| 函数 | `list_functions` | 内置/用户定义函数,按语言过滤。 |
| 函数 | `get_function` | 一个函数的签名,带有近似名称提示。 |
| 集群 | `get_cluster_status` | 实时集群状态和节点花名册。 |
| 集群 | `get_node_details` | 验证后的节点 ID 的每个节点诊断信息。 |
| 集群 | `list_running_queries` | 正在进行的集群请求;取消生命周期的读取侧。 |
| 健康 | `database_health_check` | 重复/冗余索引和 ROW-vs-COLUMNAR 候选的元数据扫描。 |
| 健康 | `get_query_history` | 最近的会话查询,带有结果和分类错误,用于自我调试。 |
| 文档 | `get_reference` | 按主题分类的 SQL++ 参考文档。 |
### 资源
| URI | 目的 |
|-----|---------|
| `asterixdb://version` | AsterixDB + 网关版本;活动探测。 |
| `asterixdb://cluster/status` | 来自 `/admin/cluster` 的实时集群状态。 |
| `asterixdb://cluster/diagnostics` | 聚合的每个节点健康诊断信息。 |
| `asterixdb://config-parameters` | 有效的网关出口/超时设置。 |
| `asterixdb://dataverses` | 数据仓库库存。 |
| `asterixdb://reference/sqlpp-syntax` | SQL++ 语法规则。 |
| `asterixdb://reference/builtin-functions` | 内置函数目录。 |
| `asterixdb://reference/index-types` | 支持的索引类型。 |
| `asterixdb://reference/type-system` | SQL++ / ADM 类型系统。 |
| `asterixdb://reference/error-codes` | 网关错误分类法。 |
| `asterixdb://reference/query-examples` | 有效的 SQL++ 示例。 |
| `asterixdb://reference/query-hints` | 内联 SQL++ 优化器提示。 |
### 资源模板
参数化 URI,客户端填充以附加数据仓库或数据集范围的上下文,无需工具调用。`{variables}` 解析为实时的 `Metadata`,因此稍后添加的任何数据仓库或数据集都无需更改代码,并且可以通过 `completion/complete` 自动完成。
| URI 模板 | 目的 |
|--------------|---------|
| `asterixdb://schema/{dataverse}/{dataset}` | 一个数据集的声明模式,包括存储格式。 |
| `asterixdb://dataverse/{dataverse}` | 一个数据仓库中所有数据集的完整模式。 |
| `asterixdb://sample/{dataverse}/{dataset}` | 真实文档的小有界样本。 |
| `asterixdb://datasets/{dataverse}` | 一个数据仓库内的数据集摘要。 |
| `asterixdb://indexes/{dataverse}/{dataset}` | 一个数据集上的详细二级索引。 |
| `asterixdb://indexes/{dataverse}` | 数据仓库的详细二级索引清单。 |
### 提示
| 提示 | 目的 |
|--------|---------|
| `analyze_dataverse` | 以库存 + 安全规则启动探索。 |
| `build_aggregation_query` | 指导构建 `GROUP BY` / 聚合查询。 |
| `analyze_query_performance` | 为慢速查询遍历计划 + 索引分析。 |
| `recommend_indexes` | 搭建索引建议工作流(`recommend_indexes` 工具计算它)。 |
| `explore_nested_data` | 导航嵌套/ROW 与 COLUMNAR 结构。 |
| `explain_error` | 将网关/CC 错误转换为修复。 |
每个查询都带有命名空间的 `client_context_id`(`{agentSessionId}::{userTag}::{uuid}`),用于端到端审计,并且由分层出口控制:时钟超时、缓冲响应字节上限和到达 LLM 的行/字节上限。
## 要求
- Python 3.10+
- 可达的 AsterixDB 集群(默认 `http://localhost:19002`)
## 安装
```bash
git clone https://github.com/<your-fork>/asterixdb-mcp-server.git
cd asterixdb-mcp-server
python -m venv .venv
source .venv/bin/activate
pip install -e ".[dev]"
```
## 配置
所有设置均来自环境变量(前缀 `ASTERIXDB_MCP_`):
| 变量 | 默认值 | 含义 |
|----------|---------|---------|
| `ASTERIXDB_MCP_CC_BASE_URL` | `http://localhost:19002` | CC REST 基 URL。 |
| `ASTERIXDB_MCP_CC_SHARED_SECRET` | _(未设置)_ | CC 跳转上的可选 `X-Gateway-Secret` 头。 |
| `ASTERIXDB_MCP_AGENT_SESSION_ID` | `local-session` | 命名空间上下文 ID 的前导段。 |
| `ASTERIXDB_MCP_MAX_TIME_MS` | `30000` | 出口层 1:每个查询的时钟上限。 |
| `ASTERIXDB_MCP_MAX_BYTES_PER_QUERY` | `10485760` | 出口层 2:最大缓冲响应字节。 |
| `ASTERIXDB_MCP_REQUEST_TIMEOUT_S` | `35.0` | CC 跳转的 httpx 传输超时。 |
### HTTP 传输(可选)
网关默认使用 **stdio**(本地 sidecar)。设置 `transport=http` 以公开 MCP **Streamable HTTP** 端点,用于远程/多客户端/网页访问。
| 变量 | 默认值 | 含义 |
|----------|---------|---------|
| `ASTERIXDB_MCP_TRANSPORT` | `stdio` | `stdio` 或 `http`。 |
| `ASTERIXDB_MCP_HTTP_HOST` | `127.0.0.1` | 绑定主机。保持回环地址,除非在代理后面。 |
| `ASTERIXDB_MCP_HTTP_PORT` | `19200` | 绑定端口(AsterixDB `19xxx` 系列,与集群自己的端口清除)。 |
| `ASTERIXDB_MCP_HTTP_PATH` | `/mcp` | Streamable HTTP 端点路径。 |
| `ASTERIXDB_MCP_AUTH_MODE` | `none` | `none`(仅限回环地址)、`bearer` 或 `oauth`。 |
| `ASTERIXDB_MCP_API_KEY` | _(未设置)_ | `auth_mode=bearer` 的承载令牌(≥ 16 个字符)。 |
| `ASTERIXDB_MCP_OAUTH_ISSUER` | _(未设置)_ | 授权服务器发行者 URL(`auth_mode=oauth`)。 |
| `ASTERIXDB_MCP_OAUTH_AUDIENCE` | _(未设置)_ | 此服务器的受众(令牌 `aud`,RFC 8707)。 |
| `ASTERIXDB_MCP_OAUTH_JWKS_URI` | _(未设置)_ | AS JWKS 端点,用于令牌签名验证。 |
| `ASTERIXDB_MCP_OAUTH_REQUIRED_SCOPES` | `[]` | 令牌必须携带的范围(JSON 列表)。 |
| `ASTERIXDB_MCP_OAUTH_ALGORITHMS` | `["RS256"]` | 接受的 JWT 签名算法(JSON 列表)。 |
| `ASTERIXDB_MCP_HTTP_ALLOWED_HOSTS` | `[]` | 额外允许的 `Host` 值(代理主机;包括 `:port`)。 |
| `ASTERIXDB_MCP_HTTP_ALLOWED_ORIGINS` | `[]` | 额外允许的 `Origin` 值(浏览器源,方案 + 主机[:端口])。 |
`GET /health` 存活探测服务是未经身份验证的,并返回 `{"status":"ok"}`(无集群调用,无版本披露)。
#### 安全模型
HTTP 监听器构建到防御基线:
- **DNS 重新绑定保护** 始终适用于 HTTP:仅允许网关自己的 `host:port`(以及回环地址和任何配置的额外地址)在 `Host` 和 `Origin` 头中被接受,因此浏览器页面无法驱动本地网关。
- **身份验证在回环地址外是必需的。** `auth_mode=none` 在非回环地址绑定时被拒绝 - 服务器快速失败而不是暴露数据库。
- **`bearer`** 是一个共享的静态令牌(恒定时间比较);最小 16 个字符。
- **`oauth`** 使网关成为 **OAuth 2.1 资源服务器**:它根据授权服务器的 JWKS 验证承载者 JWT,并检查发行者、受众、到期时间和所需范围。它从不颁发令牌 - 引入外部 AS(Auth0、Keycloak、WorkOS、Okta、...)。客户端通过 `/.well-known/oauth-protected-resource` 发现 AS。
- **在反向代理处终止 TLS。** 服务器使用明文 HTTP 通信;绝不通过未加密的公共跳跃发送承载令牌。为任何非本地部署绑定回环地址,并在前面加上终止 TLS 的代理。
只读保证不受影响:`readonly=true` 仍在每个 CC 查询中强制执行,无论传输或身份验证如何。
> Bearer 是网关后面带有受信任代理的务实层;`oauth` 是具有轮换、受众绑定和每个客户端身份的规范对齐模型。
## 运行
```bash
asterixdb-mcp-server # 在 stdio 上提供 MCP(默认)
# 在 127.0.0.1:19200 上使用 OAuth 2.1 资源服务器身份验证的 Streamable HTTP:
ASTERIXDB_MCP_TRANSPORT=http \
ASTERIXDB_MCP_AUTH_MODE=oauth \
ASTERIXDB_MCP_OAUTH_ISSUER=https://your-as.example.com \
ASTERIXDB_MCP_OAUTH_AUDIENCE=https://mcp.example.com/mcp \
ASTERIXDB_MCP_OAUTH_JWKS_URI=https://your-as.example.com/.well-known/jwks.json \
asterixdb-mcp-server
```
### 连接 Claude Desktop
添加到 `claude_desktop_config.json`:
```json
{
"mcpServers": {
"asterixdb": {
"command": "/absolute/path/to/.venv/bin/asterixdb-mcp-server",
"env": { "ASTERIXDB_MCP_CC_BASE_URL": "http://localhost:19002" }
}
}
}
```
### 任意 MCP 客户端
服务器通过 stdio 通信 MCP,因此任何支持 MCP 的客户端都有效:将 `asterixdb-mcp-server` 控制台脚本作为服务器命令启动,并将 `ASTERIXDB_MCP_CC_BASE_URL` 设置为您的集群。客户端背后的模型是您的选择 - 网关是模型无关的,不持有提供者密钥。
## 开发
```bash
ruff check src tests # 校验
ruff format src tests # 格式化
mypy # 严格类型检查
coverage run -m pytest # 单元 + 合同测试
coverage report # 强制 100% 行 + 分支覆盖率(fail_under=100)
```
覆盖率策略:**100%** 行和分支覆盖率是必需的。它由 `pyproject.toml` 中的 `fail_under = 100` 强制执行,因此 `coverage report` 在低于此值时退出非零。
## 项目布局
```
src/asterixdb_mcp/
config.py # 环境驱动的设置
context_id.py # {session}::{tag}::{uuid} 命名空间转换
errors.py # 错误分类法 + CC 错误分类
egress.py # 分层出口控制(超时,字节上限,行上限)
cc_client.py # 异步 CC REST 客户端(硬编码 readonly=true)
permits.py # 非阻塞并发许可池
statement_guard.py # 预飞行只读语句防护
plan_guard.py # 计划层变异反弹
server.py # FastMCP 绑定 + 传输选择(stdio | http)
http_app.py # Streamable HTTP ASGI 应用,/health 探测,承载中间件
http_security.py # DNS 重新绑定允许列表,启动检查,oauth 接线
auth.py # OAuth 2.1 资源服务器 JWT 验证(JWKS)
tools/ # 每个工具一个模块(SDK 无关的核心)
resources/ # 实时集群资源 + SQL++ 参考文档
prompts/ # 引导式多步骤工作流
tests/
unit/ # 每个模块的单元测试
contract/ # 公告的 MCP 接口
```
## 许可证
Apache-2.0。