cloud-asset-mcp
把数据中心固定资产平台 cloud-asset 的查询能力,以 MCP 工具的形式提供给 AI 客户端。
只读 · 单工具 · stdio / Streamable HTTP 双传输 · 无状态
概述
AI 客户端接入本服务后,用户可以在对话里直接问「ABC123 这台机器在哪、谁在用、什么配置」,
模型调用唯一工具 get_asset(keyword),服务端完成「检索 → 定位 → 取详情 → 摘字段」,
只返回 LLM 真正需要的四类信息:当前位置、使用方、状态、配件配置。
服务端是纯只读的,不提供任何写操作,也不发起工单。
功能
| 能力 | 说明 |
|---|---|
| 多类型检索 | 同时覆盖服务器、网络设备、配件三类资产 |
| 多字段匹配 | 支持 SN、资产序列号(serialNo)、资产编码(assetCode)三种关键词;15 位以上纯数字自动按 assetId 直查 |
| 精简返回 | 从几十个字段的详情中只摘四类信息,规避凭据字段外泄,同时显著降低 token 消耗 |
| 结构化错误 | 业务失败返回 {ok: false, error: {code, message, hint}},模型可据 hint 自行修正参数,不会中断对话 |
| 状态歧义提示 | 命中「基础态/工单虚拟态」歧义状态码时明确标注不确定,不臆造结论 |
| 双传输 | 默认 stdio(客户端拉起子进程);--http 切换 Streamable HTTP(多客户端 / 远程托管) |
| 多用户托管 | HTTP 模式下按请求读取 X-Asset-Token / Authorization,各用户使用自己的凭据 |
| 凭据保护 | 日志强制走 stderr,并自动对 Bearer / JWT 打码;HTTP 客户端禁用系统代理与重定向 |
环境要求
- Python ≥ 3.11
- 可访问 cloud-asset 后端的网络
快速开始
1. 安装
# 从 PyPI 安装(发布后)
uvx cloud-asset-mcp --help
# 或从源码安装
git clone <仓库地址> cloud-asset-mcp
cd cloud-asset-mcp
uv sync
2. 配置
两个环境变量。本地调试可复制样例文件:
cp .env.example .env
ASSET_BASE_URL=https://asset.yourcompany.com
ASSET_TOKEN=<浏览器 Cookie 中的 Admin-Token 裸 JWT>
ASSET_TOKEN 获取方式:登录资产管理平台 → 开发者工具 → Application / Cookies → 复制 Admin-Token 的值。
Token 有 24 小时绝对有效期,且 30 分钟无操作即失效,过期需重新复制。
优先级:客户端传入的环境变量 > .env > 代码默认值。
3. 接入客户端
stdio 方式(Claude Desktop / Claude Code / Codex / Cursor / WorkBuddy 通用):
{
"mcpServers": {
"cloud-asset": {
"type": "stdio",
"command": "uvx",
"args": ["cloud-asset-mcp"],
"env": {
"ASSET_BASE_URL": "https://asset.yourcompany.com",
"ASSET_TOKEN": "你的JWT"
}
}
}
}
未发布到 PyPI 时,改用虚拟环境解释器的绝对路径:
{
"mcpServers": {
"cloud-asset": {
"command": "/绝对路径/cloud-asset-mcp/.venv/bin/python",
"args": ["/绝对路径/cloud-asset-mcp/server.py"],
"env": { "ASSET_BASE_URL": "https://asset.yourcompany.com", "ASSET_TOKEN": "你的JWT" }
}
}
}
配置完成后重启客户端,在对话中提问即可触发工具调用。各客户端配置文件位置与详细步骤见 部署指南。
配置项
| 变量 | 必填 | 默认值 | 说明 |
|---|---|---|---|
ASSET_BASE_URL |
是 | http://127.0.0.1:9039 |
cloud-asset 后端根地址,末尾斜杠自动去除 |
ASSET_TOKEN |
stdio 模式必填 | — | 裸 JWT。可带 Bearer 前缀,会自动剥离;HTTP 多用户模式下由请求头提供,可不配 |
命令行参数:
| 参数 | 默认值 | 说明 |
|---|---|---|
--http |
关闭 | 使用 Streamable HTTP 传输,默认 stdio |
--host |
127.0.0.1 |
HTTP 监听地址 |
--port |
8765 |
HTTP 监听端口 |
工具参考
get_asset(keyword)
按关键词查询单台资产,返回位置、使用方、状态与配件配置。
参数
| 名称 | 类型 | 必填 | 说明 |
|---|---|---|---|
keyword |
string | 是 | 设备 SN、资产序列号(serialNo)、资产编码(assetCode),或 15 位以上纯数字 assetId |
返回(成功)
{
"ok": true,
"asset": {
"assetId": "900000000000000001",
"assetType": "server",
"assetTypeText": "服务器",
"sn": "ABC123",
"serialNo": "FW2025010100001",
"assetCode": "ZC-2024-0001",
"brandName": "华为",
"status": { "code": 7, "text": "正常已交付", "note": null },
"deviceUse": "训练平台",
"location": {
"position": "A园区-1号楼-301机房-A01-U12",
"parkName": "A园区",
"buildingName": "1号楼",
"machineRoomName": "301机房",
"cabinetCode": "A01",
"cabinetColumnCode": "A",
"startUnit": 12,
"height": 2
},
"parts": {
"available": true,
"rowCount": 2,
"configured": 12,
"pending": 2,
"categories": {
"gpu": { "label": "GPU", "count": 2, "models": ["A100 80G"], "pending": 0 },
"power": { "label": "电源", "count": 1, "models": ["1600W"], "pending": 1 }
}
}
}
}
字段说明:
location.position是后端拼好的完整位置串,其余字段按后端返回的粒度可选出现。parts.rowCount是配件横表行数,不等于配件总数;configured才是已配置数量,pending为「待完善」槽位数。- 无配件明细时,
parts为{"available": false, "note": "..."}。
返回(失败)
{
"ok": false,
"error": {
"code": "ASSET_NOT_FOUND",
"message": "没有找到包含 'ABC999' 的资产。",
"hint": "已按 serialNo / sn / assetCode 在服务器、网络设备、配件三类里检索过。请确认关键词,或改用资产编码/序列号。"
}
}
错误码
| code | 含义 | 处理建议 |
|---|---|---|
INVALID_ARGUMENT |
keyword 为空 |
补全参数 |
ASSET_NOT_FOUND |
三类资产、三个字段均未命中 | 核对 SN / 资产编码 |
TOKEN_MISSING |
未配置 token,请求头也没带 | 补 ASSET_TOKEN 或加请求头 |
TOKEN_INVALID |
非三段式 JWT 或 payload 无法解析 | 重新复制完整 Admin-Token |
TOKEN_EXPIRED |
本地解析出 exp 已过期 |
重新登录复制新 token |
UNAUTHORIZED |
后端返回 401 | 同上,会话已失效 |
FORBIDDEN |
后端返回 403 | 当前账号无该数据权限 |
UPSTREAM_UNAVAILABLE |
连不上后端 | 检查 ASSET_BASE_URL 与网络 |
UPSTREAM_TIMEOUT |
后端 15s 超时 | 稍后重试 |
UPSTREAM_ERROR |
后端业务错误码非 200/401/403 | 看 message 中的后端原文 |
BAD_RESPONSE |
响应非 JSON 或缺 code 字段 |
确认地址指向资产管理后端 |
INTERNAL_ERROR |
未捕获异常(已记录堆栈) | 反馈维护者 |
后端的 HTTP 状态码恒为 200,成败判定依据响应体
code。服务端已统一处理该约定。
状态码语义(重要)
status 字段存在二义性:3 / 4 / 5 / 8 / 14 ~ 19 这些码既可能是基础库存态,也可能是被在途工单改写的「虚拟态」。
服务端策略:命中歧义码时照常返回 text,同时在 status.note 中明确说明不确定原因,建议用户到 Web 端核实。不查询工单,也不给出猜测结论。
部署
本地 / 单用户(stdio)
客户端负责拉起进程,无需常驻服务。日志输出到 stderr,不污染协议通道。
远程托管(Streamable HTTP)
ASSET_BASE_URL=https://asset.yourcompany.com uvx cloud-asset-mcp --http --host 0.0.0.0 --port 8765
服务端路径固定为 /mcp。客户端配置:
{
"mcpServers": {
"cloud-asset": {
"type": "http",
"url": "https://mcp.yourcompany.com/mcp",
"headers": { "X-Asset-Token": "你的JWT" }
}
}
}
多用户托管时不要在服务端配置 ASSET_TOKEN,每个用户通过 X-Asset-Token 或 Authorization: Bearer <JWT> 传递自己的凭据;服务端使用 ContextVar 做请求级隔离。
生产部署(systemd + Caddy HTTPS、Docker)所需的 Dockerfile、Caddyfile、cloud-asset-mcp.service 均在 deploy/ 目录,
完整步骤见 发布与用户接入指南。
安全设计
- 只读:不提供任何写接口,无工单能力。
- 最小返回:从详情中白名单摘字段,
bmcPassword一类凭据不会外泄,同时节省 token。 - 日志脱敏:
RedactingFormatter对Bearer xxx与eyJ...形式的 JWT 统一打码,日志可安全外发。 - stdout 纯净:日志 handler 固定在 stderr,避免破坏 stdio 协议帧。
- 不信任网络环境:
httpx设置trust_env=False与follow_redirects=False,防止 token 经代理或重定向外泄。 - 凭据不落库:服务端不存储、不缓存 token;HTTP 模式下 token 仅存活于单次请求上下文。
项目结构
cloud-asset-mcp/
├── server.py # 全部实现:配置 → 认证 → HTTP → 业务 → MCP → 启动
├── fake_backend.py # 零依赖假后端,无需真实环境即可联调
├── demo.py # 直接调用业务逻辑,便于调试
├── verify_mcp.py # 走真实 MCP 协议验证客户端视角
├── deploy/ # Dockerfile / Caddyfile / systemd unit
├── docs/ # 项目详解、部署指南、发布与用户接入指南
├── .env.example # 配置样例
└── pyproject.toml # 打包与命令行入口
server.py 按 6 节组织,节首有分隔注释:常量 / 配置 / 认证 / HTTP / 业务 / MCP / 启动。
文档
| 文档 | 适用读者 | 内容 |
|---|---|---|
| 项目详解 | 想读懂实现的人 | 从 Python、MCP 基础讲到 server.py 逐节精讲,附一次调用的完整时序 |
| 部署指南 | 要部署的人 | stdio / Streamable HTTP 两种形态、各客户端配置、排错手册 |
| 发布与用户接入指南 | 维护者 | PyPI 发版流程、TestPyPI 试发、systemd + Caddy、Docker 部署、接入验证 |
本地开发
无需真实后端和真实 token:
cp .env.example .env # 已指向假后端与演示 JWT
./.venv/bin/python fake_backend.py # 终端 1:假后端,会打印收到的请求
./.venv/bin/python demo.py ABC123 # 终端 2:直接验证业务逻辑
./.venv/bin/python verify_mcp.py # 终端 3:走 MCP 协议验证客户端视角
假后端内置样例数据:ABC123(服务器,缺失配件槽位)、ABC999(服务器,命中歧义状态码)、NET-001(网络设备,无配件)。
切换真实后端:修改 .env 中 ASSET_BASE_URL 与 ASSET_TOKEN 即可,代码无需改动。
发布新版本
# 1. 更新 pyproject.toml 的 version(语义化版本,不可重复上传同版本号)
# 2. 重新构建
rm -rf dist && uv build
# 3. 上传
uvx twine upload dist/*
建议先发 TestPyPI 验证,再发正式索引。完整流程与常见坑见 发布与用户接入指南。
排错
| 现象 | 原因 | 处理 |
|---|---|---|
| 客户端显示 server 已启动但无工具 | stdout 被 print 污染 |
确认日志走 stderr,移除调试输出 |
返回 TOKEN_EXPIRED |
token 超 24h 或 30 分钟未操作 | 重新复制 Admin-Token |
返回 UPSTREAM_UNAVAILABLE |
后端地址错误或未启动 | 校验 ASSET_BASE_URL;离线时先跑 fake_backend.py |
返回 BAD_RESPONSE |
地址指向了代理或静态站点 | 确认是 cloud-asset 后端根地址 |
uvx cloud-asset-mcp 找不到包 |
尚未发布到 PyPI | 改用虚拟环境绝对路径配置,或先发版 |
查询网络设备报 UPSTREAM_ERROR |
后端对不存在的 ID 返回 500 | 换用正确的 SN / 资产编码检索 |
License
内部项目,未声明开源许可。如需对外开源,请先补充 LICENSE 文件并确认代码中不含内部环境信息。
Metadata
Release files for cloud-asset-mcp 2.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| cloud_asset_mcp-2.1.0.tar.gz | 50.0 kB | Without provenance | Details for cloud_asset_mcp-2.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| cloud_asset_mcp-2.1.0-py3-none-any.whl | Python 3 | none | any | Details for cloud_asset_mcp-2.1.0-py3-none-any.whl |
Total release size: 68.1 kB
Release files / cloud_asset_mcp-2.1.0.tar.gz
| Download URL | cloud_asset_mcp-2.1.0.tar.gz |
|---|---|
| Size | 50.0 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
0a800561db8be8097e8ddf90d7786dcd6729204081068f8854c996431db4d875
|
|
BLAKE2b-256 checksum How to use checksums |
b377adb1fb845e2e31ec354fd2f1b150dd61fe00b5c3c004cab55ff949c79ac7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Release files / cloud_asset_mcp-2.1.0-py3-none-any.whl
| Download URL | cloud_asset_mcp-2.1.0-py3-none-any.whl |
|---|---|
| Size | 18.1 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
e844b3e488256a2ce1487cc7804d306eb847fd91695220e4a25042d2d7ade9f3
|
|
BLAKE2b-256 checksum How to use checksums |
981a410f801689183448e6487cdfef4a5bb89d4662d06affbef578a7d19f386d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.12.13
|
Security for cloud-asset-mcp 2.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.