Metadata-Version: 2.4
Name: canpoint-ui-mcp
Version: 0.1.0
Summary: UI 规范查询 MCP：AI 可查询研学小程序/研学后台的设计 token、组件规格与生成纪律，并对生成页面截图做色彩校验（stdio 薄客户端，代理远程 streamable-http 服务）。
Author: 若清风
License-Expression: Apache-2.0
Project-URL: Homepage, https://github.com/mjwyr/ui-knowledge
Project-URL: Repository, https://github.com/mjwyr/ui-knowledge.git
Project-URL: Issues, https://github.com/mjwyr/ui-knowledge/issues
Keywords: mcp,model-context-protocol,ui,design-tokens,design-system
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: mcp<2,>=1.6.0
Requires-Dist: httpx>=0.27.0

# canpoint-ui-mcp — UI 规范查询 MCP

「成果不出门、能力出门」：数据源是本仓库 `docs/` 下两套 Design DNA（研学小程序 /
研学后台），服务端用**显式 allowlist 投影**对外提供 token、组件规格、生成纪律与
截图色彩校验；蓝湖溯源、逐画板中间测量、裁决证据明细等内部数据**永不出服务端**。

## 0. 三层发布架构（对齐 canpoint-mcp）

```
使用方（Claude Code / Cursor / Codex）
   │  npx -y canpoint-ui-mcp                ← 第 1 层：npm 包装器（零依赖，按自身版本钉死 PyPI 版本）
   ▼
uvx --from canpoint-ui-mcp==X.Y.Z canpoint-ui-mcp
   │  stdio MCP（本仓库 ui_query_mcp.py）    ← 第 2 层：PyPI 薄客户端（只含客户端代码，wheel 白名单强制）
   │  Bearer usk_xxx（UI_SPEC_API_KEY）
   ▼
http://175.27.156.19:47393/mcp
      远程 FastAPI + FastMCP streamable-http ← 第 3 层：服务端（本仓库 app/，Docker 部署，不进 wheel）
```

- 工具（6 个，三层同名同签名）：`list_platforms` / `get_tokens` / `list_components` /
  `get_component_spec` / `get_generation_rules` / `verify_ui`
- 健康检查：`GET /health`（不鉴权，DNA 未全部就绪时返回 503 degraded）
- **env 前缀保持 `UI_SPEC_*` 不变**（兼容服务器已部署的 `.env`）：服务端用
  `UI_SPEC_HOST/PORT/KEY_FILE/DNA_*`，客户端用 `UI_SPEC_SERVER_URL/API_KEY/TIMEOUT/LOG_LEVEL`

## 1. 使用方接入（两种方式）

### A. npx 方式（推荐，stdio 薄客户端）

前置：已安装 [uv](https://docs.astral.sh/uv/getting-started/installation/)（提供 `uvx`）。

Claude Code（项目 `.mcp.json`）：

```json
{
  "mcpServers": {
    "canpoint-ui": {
      "command": "npx",
      "args": ["-y", "canpoint-ui-mcp"],
      "env": {
        "UI_SPEC_SERVER_URL": "http://175.27.156.19:47393",
        "UI_SPEC_API_KEY": "usk_<服务管理员发放的key>"
      }
    }
  }
}
```

Cursor（`mcp.json`）同款。Windows 下若 `npx` 启动失败，把 `command` 改为
`cmd`、`args` 改为 `["/c", "npx", "-y", "canpoint-ui-mcp"]`（env 表不变）。

不经 npm 直接用 PyPI 包：`uvx canpoint-ui-mcp`（同样注入两个 env）。

### B. 直连 http 方式（streamable-http）

```json
{
  "mcpServers": {
    "canpoint-ui": {
      "type": "http",
      "url": "http://175.27.156.19:47393/mcp",
      "headers": { "Authorization": "Bearer usk_<key>" }
    }
  }
}
```

## 2. 175 服务器部署流程（服务端）

> 构建上下文是**仓库根**（镜像需要 COPY docs/），Dockerfile 位于 mcp-server/ 下。

```bash
# 首次部署
git clone git@github.com:mjwyr/ui-knowledge.git /software/ui-knowledge
cd /software/ui-knowledge/mcp-server
cp .env.example .env            # 按需编辑（密钥路径等；env 前缀 UI_SPEC_* 不变）
cp keys.example.json keys.json  # 编辑发放真实 key（usk_ 前缀随机串）
cd /software/ui-knowledge
docker build -f mcp-server/Dockerfile -t canpoint-ui-mcp:latest .

# 启动（.env 与 keys.json 只读挂载，logs 落宿主机）
docker rm -f canpoint-ui-mcp 2>/dev/null
docker run -d --name canpoint-ui-mcp --restart unless-stopped \
  -p 47393:8002 -e TZ=Asia/Shanghai \
  -v /software/ui-knowledge/mcp-server/.env:/app/mcp-server/.env:ro \
  -v /software/ui-knowledge/mcp-server/keys.json:/app/mcp-server/keys.json:ro \
  -v /software/ui-knowledge/mcp-server/logs:/app/mcp-server/logs \
  canpoint-ui-mcp:latest

# 验证
curl -s http://127.0.0.1:47393/health   # 期望 {"status":"ok","data":{"mini":true,"admin":true}}
```

**更新**（数据或代码变更后）：

```bash
cd /software/ui-knowledge && git pull
docker tag canpoint-ui-mcp:latest canpoint-ui-mcp:prev-$(date +%Y%m%d)   # 回滚锚点
docker build -f mcp-server/Dockerfile -t canpoint-ui-mcp:latest .
docker rm -f canpoint-ui-mcp && docker run -d ...（同上启动命令）
```

**回滚**：`docker tag canpoint-ui-mcp:prev-YYYYMMDD canpoint-ui-mcp:latest` 后重跑
`docker rm -f` + `docker run`。

## 3. 客户端包发布流程（PyPI + npm）

发布用一键脚本，版本号以 `pyproject.toml` 为唯一事实来源（自动同步到
`npm/package.json`）：

```bash
cd mcp-server
python scripts/publish.py --dry-run                             # 试跑（构建+白名单校验，不真实发布）
python scripts/publish.py --set-version <版本> --platform all   # 正式发布
python scripts/publish.py --bump patch                          # 升补丁版本后双平台发布
```

脚本内置防线：

- **版本重复探测**：发布前 HTTP 探测 PyPI/npm，该版本已存在则拒发；
- **wheel 内容白名单**：构建后逐条检查 zip 内容，`app/`、`tests/`、`scripts/`、
  `npm/`、`Dockerfile`、`requirements*.txt`、`keys*.json`、`.env` 等任何一条命中
  即拒发（服务端与密钥绝不进包）；
- **npm 官方源钉死**：全局 registry 是 npmmirror 只读镜像，脚本显式
  `--registry https://registry.npmjs.org/`；
- **镜像同步**：npm 发布成功后自动
  `PUT https://registry.npmmirror.com/-/package/canpoint-ui-mcp/syncs`
  触发 npmmirror 同步（同步完成前镜像侧 npx 仍拿旧版本）。

凭据位置（**均不进 git**）：

- PyPI：`PYPI_TOKEN` 环境变量，或本机已登录的 `uv`（`uv publish` 直接发）；
- npm：`NPM_TOKEN` 环境变量（脚本写临时 `npm/.npmrc`，用后即删），或
  `~/.npmrc` 已登录态（`npm login`；Automation 类型 token 免 OTP）。

npm 包装器按自身版本号钉死 PyPI 包版本（`uvx --from canpoint-ui-mcp==<npm版本>`），
**两平台必须同版本发布**。

## 4. key 发放 / 吊销流程

1. 编辑服务器上的 `/software/ui-knowledge/mcp-server/keys.json`：
   - 发放：追加 `{"id":"姓名","key":"usk_<随机串>","enabled":true,"note":"用途"}`
     （随机串生成：`python -c "import secrets;print('usk_'+secrets.token_urlsafe(32))"`）；
   - 吊销：把对应条目 `enabled` 改为 `false` 或直接删除。
2. `docker restart canpoint-ui-mcp`（key 表在启动时加载）。
3. key 表缺失/为空/解析失败时服务 **fail-closed**：进程照常启动、`/health`
   可访问，但 `/mcp` 拒绝一切请求。

## 5. 审计日志

- 服务端：`mcp-server/logs/ui_audit.log`（容器内 `/app/mcp-server/logs/`，按天轮转、
  保留 30 天；服务端运行日志为同目录 `ui_spec_server.log`）。每次工具调用一行：

  ```
  2026-09-08 12:00:00,123 key_id=张三 tool=get_tokens args=platform=mini category=color status=ok took=5ms
  ```

- 客户端：`logs/ui_client.log`（相对客户端进程工作目录，按天轮转、保留 30 天），
  每次工具调用记 tool + 参数摘要（截断 80 字符）+ 结果状态 + 耗时；只写文件与
  stderr，**绝不写 stdout**（stdio 协议流）。
- 安全红线：绝不记录 key 值本身；绝不记录 base64 图片数据（`verify_ui` 只记
  platform 与图片字节数）；args 摘要单行化截断 80 字符。

## 6. 本地开发与测试

```bash
cd mcp-server
python --version            # 需要 3.10+
pip install -r requirements.txt   # 服务端依赖（客户端只需 mcp/httpx）
python -m unittest discover -s tests -v
# 本地起服务端
python -m app.main          # 或 uvicorn app.main:app --port 8002
# 本地起 stdio 客户端（连远程或本地服务端）
UI_SPEC_SERVER_URL=http://127.0.0.1:8002 UI_SPEC_API_KEY=usk_xxx python ui_query_mcp.py
```

环境变量见 `.env.example`（复制为 `.env`，不进 git）；本地 `keys.json` 可参照
`keys.example.json` 制作（不进 git）。

## 7. 安全设计（投影 allowlist + wheel 白名单）

**数据投影**——出门的：色彩 token、typography 阶梯、spacing、shape、elevation、
layout/density（admin_chrome 几何、表格密度 token、breakpoints_fact、小程序
chrome）、组件规格、生成纪律（version_policy 结论、missing_states、裁决结论、
设计事实）、design_style 定性字段、visual_effects 结论字段。

永不出门的（`app/views.py` 结构性排除 + 字符串消毒双保险，测试断言）：
`meta.source_references`、`measured_palette` 聚合、`measurement` 配置、
meta 其余溯源字段（lanhu_project / original_assets / sampling 说明等）、
任何含 `lanhuapp` / `alipic` / `image_id` / intermediate 路径的字符串、
adjudications 的 values/source 证据明细。

**发布物收敛**——PyPI wheel 只含 `ui_query_mcp.py`、`logging_config.py`、
`clients/`（见 pyproject `[tool.setuptools]` 白名单），客户端依赖收敛为
`mcp>=1.6,<2` + `httpx`；`scripts/publish.py` 构建后强制校验，违规拒发。
API key 只从环境变量读取，绝不嵌入可分发包；`UI_SPEC_SERVER_URL` 拒绝内嵌
basic auth（key 是唯一客户端凭据）。
