feat: 添加 Docker 部署支持和 MCP 原生集成

- 添加 Dockerfile、docker-compose.yml 和 Jenkins 流水线配置
- 新增 MCP 原生集成模块 (mcp_native.py)
- 移除旧的 mcp_bridge.py,更新依赖和文档
- 添加部署文档 (Docker 和服务器部署指南)

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
qihongkun 2026-05-25 14:21:43 +08:00
parent a41f61bcf6
commit f5143aee09
20 changed files with 1722 additions and 403 deletions

35
.dockerignore Normal file
View File

@ -0,0 +1,35 @@
.git
.gitignore
.venv
venv
__pycache__
*.py[cod]
.pytest_cache
.mypy_cache
.ruff_cache
ai_test.db
*.db
*.db-journal
*.db-wal
*.db-shm
frontend-admin/node_modules
frontend-admin/dist
frontend-admin/.vite
.env
.env.*
!.env.example
.cursor
.serena
.idea
.vscode
.run_logs
*.log
docs/
*.md
!README.md
start_project.command

26
.env.example Normal file
View File

@ -0,0 +1,26 @@
# 复制为 .env 后修改cp .env.example .env
# 映射到宿主机的端口
QIP_HOST_PORT=8000
# 持久化数据目录SQLite、SSH 脚本等),挂载到容器 /data
QIP_DATA_HOST_DIR=/docker_data
# 生产务必修改
AUTH_TOKEN_SECRET=change-me-to-a-long-random-string
DEFAULT_SUPERADMIN_USERNAME=admin
DEFAULT_SUPERADMIN_PASSWORD=change-me-strong-password
DEFAULT_SUPERADMIN_DISPLAY_NAME=系统超管
# MCP 鉴权(建议 true
MCP_REQUIRE_API_KEY=true
# 从宿主机 / 其它机器用 Cursor 连 MCP 时建议 true
QIP_MCP_DISABLE_DNS_PROTECTION=true
# QIP_MCP_ALLOWED_HOSTS=10.0.0.5:8000,qip.internal:8000
# QIP_MCP_ALLOWED_ORIGINS=http://10.0.0.5:8000
# 可选Loki
# GENERAL_LOKI_EXPLORE_URL=https://grafana.example/explore
# LOKI_DATASOURCE=Loki
# LOKI_ORG_ID=1

View File

@ -10,7 +10,7 @@
## 快速规则
- MCP 服务名:`quality-inspection-platform`stdio 桥 `mcp_bridge.py`
- MCP 服务名:`quality-inspection-platform`Streamable HTTP`http://<主机>:8000/mcp`,需 `sto-` API Key
- 写操作与执行类工具须 `sto-` API Key
- 任务开始:`catalog_snapshot`
- 跑批:`workflow_batch_create` → `workflow_batch_update``workflow_batch_run`
@ -18,5 +18,5 @@
## 其它文档
- 安装桥接`docs/mcp_install.md`
- 人类调用示例:`docs/mcp_quickstart.md`
- MCP 安装与 Cursor 配置`docs/mcp_install.md`
- 人类调用示例curl`docs/mcp_quickstart.md`

41
Dockerfile Normal file
View File

@ -0,0 +1,41 @@
FROM node:20-bookworm-slim AS frontend-build
WORKDIR /build/frontend-admin
COPY frontend-admin/package.json frontend-admin/package-lock.json* ./
RUN npm install
COPY frontend-admin/ ./
RUN npm run build
FROM python:3.11-slim-bookworm
WORKDIR /app
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& rm -rf /var/lib/apt/lists/*
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY app ./app
COPY --from=frontend-build /build/frontend-admin/dist ./frontend-admin/dist
COPY docker/entrypoint.sh /entrypoint.sh
RUN chmod +x /entrypoint.sh
ENV PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
QIP_DATA_DIR=/data \
DATABASE_URL=sqlite:////data/ai_test.db
EXPOSE 8000
VOLUME ["/data"]
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')" || exit 1
ENTRYPOINT ["/entrypoint.sh"]
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

180
Jenkinsfile vendored Normal file
View File

@ -0,0 +1,180 @@
pipeline {
agent any
environment {
DEPLOY_DIR = '/opt/quality-inspection-platform'
DATA_DIR = '/docker_data'
}
parameters {
string(name: 'DEPLOY_HOST', defaultValue: '', description: '部署目标服务器 SSH 地址,如 root@10.0.0.5')
string(name: 'SSH_PORT', defaultValue: '22', description: 'SSH 端口')
string(name: 'SSH_CREDENTIALS', defaultValue: 'deploy-ssh-key', description: 'Jenkins 凭据 IDSSH 私钥或用户名密码)')
choice(name: 'ACTION', choices: ['deploy', 'restart', 'stop', 'logs'], description: '执行动作')
}
options {
timeout(time: 30, unit: 'MINUTES')
disableConcurrentBuilds()
buildDiscarder(logRotator(numToKeepStr: '20'))
}
stages {
// ──────────────────────────────────────
// 1. 拉取代码
// ──────────────────────────────────────
stage('Checkout') {
steps {
checkout scm
script {
env.GIT_COMMIT_SHORT = sh(script: 'git rev-parse --short HEAD', returnStdout: true).trim()
env.GIT_BRANCH_NAME = sh(script: 'git rev-parse --abbrev-ref HEAD', returnStdout: true).trim()
}
echo "分支: ${env.GIT_BRANCH_NAME} 提交: ${env.GIT_COMMIT_SHORT}"
}
}
// ──────────────────────────────────────
// 2. 推送源码到目标服务器
// ──────────────────────────────────────
stage('Sync Source') {
when { expression { params.ACTION == 'deploy' } }
steps {
sshagent(credentials: [params.SSH_CREDENTIALS]) {
sh """
ssh -p ${params.SSH_PORT} -o StrictHostKeyChecking=no ${params.DEPLOY_HOST} \
'mkdir -p ${DEPLOY_DIR}'
rsync -avz --delete \
--exclude '.git' \
--exclude '.venv' \
--exclude '__pycache__' \
--exclude 'node_modules' \
--exclude '*.pyc' \
--exclude '.env' \
-e "ssh -p ${params.SSH_PORT} -o StrictHostKeyChecking=no" \
./ ${params.DEPLOY_HOST}:${DEPLOY_DIR}/
"""
}
echo '源码同步完成'
}
}
// ──────────────────────────────────────
// 3. 远程服务器 build + 启动
// ──────────────────────────────────────
stage('Remote Build & Start') {
when { expression { params.ACTION == 'deploy' } }
steps {
sshagent(credentials: [params.SSH_CREDENTIALS]) {
sh """
ssh -p ${params.SSH_PORT} -o StrictHostKeyChecking=no ${params.DEPLOY_HOST} '
set -eu
cd ${DEPLOY_DIR}
# 确保 .env 存在(首次部署从 example 复制)
if [ ! -f .env ]; then
cp .env.example .env
echo "首次部署:已从 .env.example 创建 .env请稍后修改密码和密钥"
fi
# 确保数据目录存在
DATA_HOST_DIR=\$(grep "^QIP_DATA_HOST_DIR=" .env | cut -d= -f2 || echo "/docker_data")
[ -z "\$DATA_HOST_DIR" ] && DATA_HOST_DIR="/docker_data"
mkdir -p "\$DATA_HOST_DIR"
# 备份数据库
if [ -f "\$DATA_HOST_DIR/ai_test.db" ]; then
cp "\$DATA_HOST_DIR/ai_test.db" \
"\$DATA_HOST_DIR/ai_test.db.bak.\$(date +%Y%m%d%H%M%S)"
echo "数据库已备份"
fi
# 构建镜像(使用 BuildKit 加速)
DOCKER_BUILDKIT=1 docker compose build --pull
# 重启容器
docker compose down --remove-orphans
docker compose up -d
echo "等待容器启动..."
sleep 20
# 健康检查
PORT=\$(grep "^QIP_HOST_PORT=" .env | cut -d= -f2 || echo "8000")
[ -z "\$PORT" ] && PORT="8000"
STATUS=\$(curl -s -o /dev/null -w "%{http_code}" http://127.0.0.1:\$PORT/health || true)
if [ "\$STATUS" = "200" ]; then
echo "✓ 部署成功,健康检查通过 (HTTP 200)"
else
echo "✗ 健康检查未通过 (HTTP \$STATUS),查看日志:"
docker compose logs --tail 30
exit 1
fi
'
"""
}
}
}
// ──────────────────────────────────────
// 4. 仅重启(不重新 build
// ──────────────────────────────────────
stage('Restart Only') {
when { expression { params.ACTION == 'restart' } }
steps {
sshagent(credentials: [params.SSH_CREDENTIALS]) {
sh """
ssh -p ${params.SSH_PORT} -o StrictHostKeyChecking=no ${params.DEPLOY_HOST} '
cd ${DEPLOY_DIR}
docker compose restart
sleep 10
curl -sf http://127.0.0.1:\$(grep "^QIP_HOST_PORT=" .env | cut -d= -f2 || echo 8000)/health \
&& echo "重启成功" || echo "重启后健康检查失败"
'
"""
}
}
}
// ──────────────────────────────────────
// 5. 停止
// ──────────────────────────────────────
stage('Stop') {
when { expression { params.ACTION == 'stop' } }
steps {
sshagent(credentials: [params.SSH_CREDENTIALS]) {
sh """
ssh -p ${params.SSH_PORT} -o StrictHostKeyChecking=no ${params.DEPLOY_HOST} '
cd ${DEPLOY_DIR} && docker compose down
echo "容器已停止"
'
"""
}
}
}
// ──────────────────────────────────────
// 6. 查看日志
// ──────────────────────────────────────
stage('View Logs') {
when { expression { params.ACTION == 'logs' } }
steps {
sshagent(credentials: [params.SSH_CREDENTIALS]) {
sh """
ssh -p ${params.SSH_PORT} -o StrictHostKeyChecking=no ${params.DEPLOY_HOST} '
cd ${DEPLOY_DIR} && docker compose logs --tail 100
'
"""
}
}
}
}
post {
success { echo "✓ 流水线执行成功: ${params.ACTION}" }
failure { echo "✗ 流水线执行失败,请检查日志" }
always { cleanWs() }
}
}

View File

@ -6,7 +6,7 @@
- Mock 数据管理
- Workflow 流程编排与执行
此外,项目还带了一个 `stdio MCP bridge`,可以把平台里暴露的工具注册成真正的 MCP Server供 Cursor、Claude Desktop、Codex 等客户端调用
此外,平台内置 **Streamable HTTP MCP Server**`/mcp`),供 Cursor、Claude Desktop、Codex 等客户端直接连接,无需单独桥接进程
## 技术栈
@ -32,13 +32,29 @@ app/
templates/ Jinja2 页面模板
docs/
mcp_tools_for_ai.md MCP 能力说明AI Agent 首选)
mcp_install.md MCP bridge 安装说明
mcp_install.md MCP Server 安装说明Cursor / HTTP
mcp_quickstart.md MCP 调用说明(人类 / curl
AGENTS.md Codex 等自动加载的项目 Agent 约定
mcp_bridge.py stdio MCP Server 桥接入口
app/mcp_native.py 内置 Streamable HTTP MCP挂载于 /mcp
start_project.command macOS 一键启动前后端(双击或终端执行)
requirements.txt Python 依赖
```
## 服务器部署
| 方式 | 文档 |
|------|------|
| **Docker Compose推荐尤其 CentOS 7** | [`docs/deploy_docker.md`](./docs/deploy_docker.md) |
| 裸机 systemd + Nginx | [`docs/deploy_server.md`](./docs/deploy_server.md) |
快速启动:
```bash
cp .env.example .env # 修改密码与密钥
docker compose build
docker compose up -d
```
## 安装依赖
建议先进入项目目录,再安装依赖:
@ -61,7 +77,8 @@ python -m uvicorn app.main:app --host 127.0.0.1 --port 8000 --reload
- 首页:`http://127.0.0.1:8000/`
- API 文档:`http://127.0.0.1:8000/docs`
- MCP 工具列表:`http://127.0.0.1:8000/mcp/tools`
- MCP 协议端点:`http://127.0.0.1:8000/mcp`
- MCP 工具列表HTTP 兼容):`http://127.0.0.1:8000/mcp/tools`
### 方式二:在 PyCharm 里启动
@ -73,20 +90,37 @@ python -m uvicorn app.main:app --host 127.0.0.1 --port 8000 --reload
注意:`app.main:app` 必须放在 `uvicorn` 后面作为位置参数传入,不能写到最后。
## 启动 MCP Bridge
### 方式三macOS 快速启动(推荐)
如果你要把本平台作为 MCP Server 暴露给其他客户端,再额外启动:
项目根目录有 `start_project.command`,会分别打开两个 Terminal 窗口启动后端8000与前端5173就绪后自动打开浏览器。
```bash
python3 mcp_bridge.py
# 在项目根目录执行(脚本会自动 pip install前端需已 npm install
./start_project.command
```
桥接默认读取环境变量:
首次使用若前端未安装依赖:`cd frontend-admin && npm install`
- `QIP_BASE_URL`,默认值是 `http://127.0.0.1:8000`(兼容旧名 `AI_TEST_BASE_URL`
- `QIP_API_KEY``sto-` 开头的 API Key兼容 `AI_TEST_API_KEY`
也可在 Finder 中双击该文件。脚本使用相对路径,不依赖旧目录名;若你曾在桌面/程序坞放了快捷方式,请把目标路径改为:
完整配置见:
`/Users/qihongkun/work/My_app/quality-inspection-platform/start_project.command`
## 配置 MCP 客户端Cursor 等)
后端启动后,在 `~/.cursor/mcp.json` 中配置 URL 与 API Key无需额外进程
```json
{
"mcpServers": {
"quality-inspection-platform": {
"url": "http://127.0.0.1:8000/mcp",
"headers": { "Authorization": "Bearer sto-你的API密钥" }
}
}
}
```
完整说明含内网部署、团队协作、Remote SSH、排障
- [`docs/mcp_install.md`](./docs/mcp_install.md)
@ -101,7 +135,7 @@ AI 使用 MCP 前请阅读:
rg --files
rg "workflow_run" app docs
python -m uvicorn app.main:app --host 127.0.0.1 --port 8000 --reload
python3 -m py_compile app/main.py app/services/engine.py mcp_bridge.py
python3 -m py_compile app/main.py app/services/engine.py app/mcp_native.py
```
## 当前已知情况

View File

@ -1,7 +1,9 @@
import os
from sqlalchemy import create_engine
from sqlalchemy.orm import declarative_base, sessionmaker
DATABASE_URL = "sqlite:///./ai_test.db"
DATABASE_URL = os.getenv("DATABASE_URL", "sqlite:///./ai_test.db")
engine = create_engine(DATABASE_URL, connect_args={"check_same_thread": False})
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)

View File

@ -391,7 +391,10 @@ def _ensure_default_superadmin():
_ensure_sqlite_columns()
_ensure_default_superadmin()
app = FastAPI(title="质量检测平台", version="0.1.0")
from .mcp_native import mcp_lifespan, mount_mcp_streamable_http
app = FastAPI(title="质量检测平台", version="0.1.0", lifespan=mcp_lifespan)
mount_mcp_streamable_http(app)
FRONTEND_DIST_DIR = Path(__file__).resolve().parent.parent / "frontend-admin" / "dist"
FRONTEND_ASSETS_DIR = FRONTEND_DIST_DIR / "assets"
FRONTEND_INDEX_FILE = FRONTEND_DIST_DIR / "index.html"
@ -1725,16 +1728,12 @@ def mcp_tools(request: Request, db: Session = Depends(get_db)):
}
@app.post("/mcp/invoke", response_model=McpInvokeResponse)
async def mcp_invoke(payload: McpInvokeRequest, request: Request, db: Session = Depends(get_db)):
tool = payload.tool
args = payload.arguments or {}
actor = _resolve_mcp_actor(
request,
db,
api_key=payload.api_key,
require_credential=_mcp_tool_requires_credential(tool),
)
async def _dispatch_mcp_tool(
tool: str,
args: dict[str, Any],
db: Session,
actor: User,
) -> McpInvokeResponse:
try:
if tool == "folder_ensure":
target = str(args["target"])
@ -2063,6 +2062,19 @@ async def mcp_invoke(payload: McpInvokeRequest, request: Request, db: Session =
return McpInvokeResponse(ok=False, tool=tool, error=str(exc))
@app.post("/mcp/invoke", response_model=McpInvokeResponse)
async def mcp_invoke(payload: McpInvokeRequest, request: Request, db: Session = Depends(get_db)):
tool = payload.tool
args = payload.arguments or {}
actor = _resolve_mcp_actor(
request,
db,
api_key=payload.api_key,
require_credential=_mcp_tool_requires_credential(tool),
)
return await _dispatch_mcp_tool(tool, args, db, actor)
@app.post("/mcp/invoke-batch")
async def mcp_invoke_batch(payload: McpInvokeBatchRequest, request: Request, db: Session = Depends(get_db)):
outputs: list[dict[str, Any]] = []

158
app/mcp_native.py Normal file
View File

@ -0,0 +1,158 @@
"""Native MCP Server (Streamable HTTP) mounted on the FastAPI app."""
from __future__ import annotations
import json
import logging
import os
from contextlib import asynccontextmanager
from typing import Any
from fastapi import FastAPI, HTTPException, Request
from mcp import types
from mcp.server.lowlevel.server import Server
from mcp.server.streamable_http_manager import StreamableHTTPSessionManager
from mcp.server.transport_security import TransportSecuritySettings
from sqlalchemy.orm import Session
from starlette.requests import Request as StarletteRequest
from .database import SessionLocal
logger = logging.getLogger(__name__)
SERVER_NAME = "quality-inspection-platform"
SERVER_VERSION = "0.2.0"
MCP_INSTRUCTIONS = (
"Quality inspection platform (质量检测平台). Before complex tasks, read project file "
"docs/mcp_tools_for_ai.md (tool picker, auth, recipes). "
"Typical start: catalog_snapshot. "
"Batch runs: workflow_batch_create → workflow_batch_update → workflow_batch_run. "
"Writes and execution require sto- API Key (Authorization: Bearer or X-API-Key)."
)
_mcp_server = Server(SERVER_NAME, version=SERVER_VERSION, instructions=MCP_INSTRUCTIONS)
_session_manager: StreamableHTTPSessionManager | None = None
def _mcp_transport_security() -> TransportSecuritySettings | None:
if os.getenv("QIP_MCP_DISABLE_DNS_PROTECTION", "").strip().lower() in {"1", "true", "yes", "on"}:
return TransportSecuritySettings(enable_dns_rebinding_protection=False)
hosts = [item.strip() for item in os.getenv("QIP_MCP_ALLOWED_HOSTS", "").split(",") if item.strip()]
origins = [item.strip() for item in os.getenv("QIP_MCP_ALLOWED_ORIGINS", "").split(",") if item.strip()]
if not hosts and not origins:
return None
return TransportSecuritySettings(
enable_dns_rebinding_protection=True,
allowed_hosts=hosts,
allowed_origins=origins,
)
def _tool_specs() -> list[dict[str, Any]]:
from .main import MCP_TOOL_SPECS
return MCP_TOOL_SPECS
def _specs_to_mcp_tools() -> list[types.Tool]:
tools: list[types.Tool] = []
for spec in _tool_specs():
schema = spec.get("input_schema") or {"type": "object"}
tools.append(
types.Tool(
name=str(spec.get("name", "")),
description=str(spec.get("description", "")),
inputSchema=schema,
)
)
return tools
def _starlette_request() -> StarletteRequest | None:
ctx = _mcp_server.request_context
request = ctx.request
return request if isinstance(request, StarletteRequest) else None
def _resolve_actor_for_tool(tool: str, request: StarletteRequest, db: Session):
from .main import _mcp_global_require_api_key, _mcp_tool_requires_credential, _resolve_mcp_actor
require = _mcp_global_require_api_key() or _mcp_tool_requires_credential(tool)
return _resolve_mcp_actor(request, db, require_credential=require)
@_mcp_server.list_tools()
async def _handle_list_tools() -> list[types.Tool]:
request = _starlette_request()
if request is None:
return _specs_to_mcp_tools()
db = SessionLocal()
try:
from .main import _mcp_global_require_api_key
if _mcp_global_require_api_key():
_resolve_actor_for_tool("catalog_snapshot", request, db)
return _specs_to_mcp_tools()
finally:
db.close()
@_mcp_server.call_tool()
async def _handle_call_tool(
name: str,
arguments: dict[str, Any] | None,
) -> types.CallToolResult:
request = _starlette_request()
if request is None:
raise ValueError("MCP request context missing HTTP request")
db = SessionLocal()
try:
try:
actor = _resolve_actor_for_tool(name, request, db)
from .main import _dispatch_mcp_tool
result = await _dispatch_mcp_tool(name, arguments or {}, db, actor)
except HTTPException as exc:
payload = {"ok": False, "tool": name, "data": {}, "error": str(exc.detail)}
return types.CallToolResult(
content=[types.TextContent(type="text", text=json.dumps(payload, ensure_ascii=False, indent=2))],
isError=True,
)
text = json.dumps(result.model_dump(), ensure_ascii=False, indent=2)
return types.CallToolResult(
content=[types.TextContent(type="text", text=text)],
isError=not result.ok,
)
finally:
db.close()
def _get_session_manager() -> StreamableHTTPSessionManager:
global _session_manager
if _session_manager is None:
_session_manager = StreamableHTTPSessionManager(
app=_mcp_server,
json_response=False,
stateless=True,
security_settings=_mcp_transport_security(),
)
return _session_manager
@asynccontextmanager
async def mcp_lifespan(_app: FastAPI):
manager = _get_session_manager()
async with manager.run():
yield
def mount_mcp_streamable_http(fastapi_app: FastAPI, *, path: str = "/mcp") -> None:
"""Register Streamable HTTP MCP endpoint on the FastAPI app."""
manager = _get_session_manager()
@fastapi_app.api_route(path, methods=["GET", "POST", "DELETE"], include_in_schema=False)
async def mcp_streamable_http_endpoint(request: Request) -> None:
await manager.handle_request(request.scope, request.receive, request._send) # type: ignore[attr-defined]
logger.info("MCP Streamable HTTP mounted at %s (stateless)", path)

View File

@ -12,7 +12,8 @@ import paramiko
from ..schemas import SshProfileOut, SshScriptOut
PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent
SSH_SCRIPTS_DIR = PROJECT_ROOT / "data" / "ssh-scripts"
_DATA_ROOT = Path(os.getenv("QIP_DATA_DIR", str(PROJECT_ROOT / "data")))
SSH_SCRIPTS_DIR = _DATA_ROOT / "ssh-scripts"
def resolve_script_body(script: SshScriptOut) -> str:

33
docker-compose.yml Normal file
View File

@ -0,0 +1,33 @@
services:
quality-inspection-platform:
build:
context: .
dockerfile: Dockerfile
image: quality-inspection-platform:latest
container_name: quality-inspection-platform
restart: unless-stopped
ports:
- "${QIP_HOST_PORT:-8000}:8000"
env_file:
- .env
environment:
DATABASE_URL: sqlite:////data/ai_test.db
QIP_DATA_DIR: /data
# 容器对外访问时 MCP 建议关闭 DNS 校验或配置白名单
QIP_MCP_DISABLE_DNS_PROTECTION: ${QIP_MCP_DISABLE_DNS_PROTECTION:-true}
MCP_REQUIRE_API_KEY: ${MCP_REQUIRE_API_KEY:-true}
volumes:
# 宿主机目录(部署前执行: sudo mkdir -p /docker_data && sudo chown -R 1000:1000 /docker_data
- ${QIP_DATA_HOST_DIR:-/docker_data}:/data
healthcheck:
test:
[
"CMD",
"python",
"-c",
"import urllib.request; urllib.request.urlopen('http://127.0.0.1:8000/health')",
]
interval: 30s
timeout: 5s
retries: 3
start_period: 40s

6
docker/entrypoint.sh Normal file
View File

@ -0,0 +1,6 @@
#!/bin/sh
set -eu
mkdir -p /data/ssh-scripts
exec "$@"

242
docs/deploy_docker.md Normal file
View File

@ -0,0 +1,242 @@
# Docker / Docker Compose 部署
**CentOS 7** 或其它 Linux 上,用容器自带 Python 3.11**无需**在宿主机安装 Python 3.11 / Node。
---
## 0. 宿主机准备
### CentOS 7 安装 Docker
```bash
sudo yum install -y yum-utils
sudo yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
sudo yum install -y docker-ce docker-ce-cli containerd.io
sudo systemctl enable docker
sudo systemctl start docker
# Compose 插件v2
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-linux-x86_64" \
-o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker compose version
```
内网无法访问外网时,请改用内网镜像源或离线 rpm 包。
---
## 1. 上传源码到服务器
**方式 AGit**
```bash
sudo mkdir -p /opt/quality-inspection-platform
sudo chown -R "$USER":"$USER" /opt/quality-inspection-platform
cd /opt/quality-inspection-platform
git clone <内网仓库地址> .
```
**方式 B本机打包上传无 Git 时)**
在本机项目根目录:
```bash
tar czf qip-src.tar.gz \
--exclude='.venv' --exclude='node_modules' --exclude='frontend-admin/dist' \
--exclude='ai_test.db' --exclude='.git' \
.
scp qip-src.tar.gz user@<服务器>:/tmp/
```
在服务器上:
```bash
sudo mkdir -p /opt/quality-inspection-platform
sudo tar xzf /tmp/qip-src.tar.gz -C /opt/quality-inspection-platform
sudo chown -R "$USER":"$USER" /opt/quality-inspection-platform
```
---
## 2. 配置环境变量
```bash
cp .env.example .env
vi .env # 修改 AUTH_TOKEN_SECRET、DEFAULT_SUPERADMIN_PASSWORD 等
```
---
## 3. 准备数据目录
```bash
sudo mkdir -p /docker_data
sudo chown -R "$(id -u)":"$(id -g)" /docker_data
```
---
## 4. 在服务器上构建并启动
```bash
cd /opt/quality-inspection-platform
docker compose build
docker compose up -d
docker compose ps
docker compose logs -f
```
---
## 5. 验证
```bash
curl -s http://127.0.0.1:8000/health
# {"ok":true}
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/
# 200
```
浏览器:`http://<服务器IP>:8000/`
默认账号见 `.env``DEFAULT_SUPERADMIN_*`
MCPCursor
```json
{
"mcpServers": {
"quality-inspection-platform": {
"url": "http://<服务器IP>:8000/mcp",
"headers": { "Authorization": "Bearer sto-你的密钥" }
}
}
}
```
---
## 6. 数据持久化(宿主机 `/docker_data`
默认把宿主机的 **`/docker_data`** 挂载到容器 **`/data`**
| 宿主机路径 | 容器路径 | 内容 |
|------------|----------|------|
| `/docker_data/ai_test.db` | `/data/ai_test.db` | SQLite 数据库 |
| `/docker_data/ssh-scripts/` | `/data/ssh-scripts/` | SSH 内联脚本等 |
部署前在服务器上准备目录:
```bash
sudo mkdir -p /docker_data
# 官方 python:3.11-slim 镜像内进程用户多为 root若以后改用非 root 用户,再改属主
sudo chown -R "$(id -u)":"$(id -g)" /docker_data
```
改用其它目录时,在 `.env` 中设置:
```bash
QIP_DATA_HOST_DIR=/your/path
```
备份数据库(直接在宿主机):
```bash
cp /docker_data/ai_test.db /docker_data/ai_test.db.bak.$(date +%Y%m%d)
```
---
## 7. 常用命令
```bash
# 停止
docker compose down
# 重启
docker compose restart
# 升级(拉代码后)
git pull
docker compose build --no-cache
docker compose up -d
# 进入容器
docker compose exec quality-inspection-platform sh
```
---
## 8. 防火墙CentOS 7
```bash
sudo firewall-cmd --permanent --add-port=8000/tcp
sudo firewall-cmd --reload
```
---
## 9. 架构说明
```
Dockerfile多阶段
├─ node:20 → npm run build → frontend-admin/dist
└─ python:3.11 → pip install + uvicorn
卷 /data → SQLite + SSH 脚本目录
```
- 镜像内已包含前端静态资源,**宿主机不需要 Node**。
- 镜像内已包含 Python 与 `mcp` 依赖,**宿主机不需要 Python 3.11**。
---
## 10. 排障
### 10.1 `lookup docker.mirrors.ustc.edu.cn: no such host`
说明 Docker 配置了**已失效或内网不可达的镜像加速**,且 DNS 解析失败。
**处理:** 编辑 `/etc/docker/daemon.json`(没有则新建),去掉或更换 `registry-mirrors`
```json
{
"registry-mirrors": []
}
```
或改成你们内网可访问的镜像地址。然后:
```bash
sudo systemctl daemon-reload
sudo systemctl restart docker
docker compose build
```
本仓库 `Dockerfile` 已去掉 `# syntax=docker/dockerfile:1`,避免额外拉取 `docker/dockerfile:1`(在劣质网络下常失败)。
### 10.2 完全无法访问 Docker Hub
需任选其一:
- 配置**内网 Harbor / registry 代理**,在能联网的机器 `docker pull node:20-bookworm-slim python:3.11-slim-bookworm``docker save`,传到服务器 `docker load`
- 让运维开放对 `registry-1.docker.io` 或可靠镜像站的访问
### 10.3 其它
| 现象 | 处理 |
|------|------|
| `git was not found`WARN | 可忽略;或 `yum install -y git` |
| 前端 503 | `docker compose build --no-cache` |
| MCP 连不上 | `.env``QIP_MCP_DISABLE_DNS_PROTECTION=true` |
| `/docker_data` 无写权限 | `chown` 见 §3 |
---
## 11. 相关文档
- 裸机部署(不用 Docker[deploy_server.md](./deploy_server.md)
- MCP 配置:[mcp_install.md](./mcp_install.md)

481
docs/deploy_server.md Normal file
View File

@ -0,0 +1,481 @@
# 服务器部署完整步骤Linux
本文假设在 **一台内网 Linux 服务器** 上从零部署,所有命令均在服务器上执行(通过 SSH 登录)。
- 部署目录示例:`/opt/quality-inspection-platform`
- 运行用户示例:`qip`(可改成你的业务用户)
- 监听端口:`8000`(也可用 Nginx 反代 80/443
**CentOS 7 用户**
- **推荐**:用 Docker无需在宿主机装 Python 3.11 → **[deploy_docker.md](./deploy_docker.md)**
- 裸机部署:系统自带 Python 3.6 不够用 → **[§1B CentOS 7](#1b-安装系统依赖-centos-7)**,再从 [§2](#2-创建运行用户与目录) 继续
---
## 0. 部署前确认
| 项目 | 要求 |
|------|------|
| 系统 | **CentOS 7**(见 §1B或 Ubuntu 20.04+ / Debian 11+(见 §1A |
| Python | **3.10+**(必须,用于 `mcp` / FastAPI |
| Node | **18+**(仅构建 `frontend-admin` 时需要) |
| 权限 | 能 `sudo` 安装软件、创建用户 |
| 网络 | 能访问内网 Git`npm` / `pip` 可用(或本机构建 `dist` 后上传) |
| 磁盘 | 预留 ≥2GB |
**交付能力(一个 `uvicorn` 进程)**
- Web 管理端:`http://<服务器>:8000/`
- API`/api/*`
- MCPCursor/Codex`http://<服务器>:8000/mcp`
- 数据文件:项目根目录 `ai_test.db`SQLite需备份
---
## 1A. 安装系统依赖Ubuntu / Debian
```bash
sudo apt update
sudo apt install -y git curl ca-certificates \
python3 python3-venv python3-pip \
nodejs npm
```
检查版本:
```bash
python3 --version # 须 >= 3.10
node --version # 建议 >= 18
```
`python3` 低于 3.10,请安装更新的 Python 后再继续。
`nodejs` 过旧,改用 [NodeSource](https://github.com/nodesource/distributions) 安装 18+。
---
## 1B. 安装系统依赖CentOS 7
CentOS 7 默认 `python3` 为 3.6**无法直接运行本项目**。推荐用 **IUS 的 Python 3.11** + **NodeSource 18**
### 1B.1 基础工具与编译依赖
```bash
sudo yum install -y epel-release
sudo yum install -y git curl ca-certificates gcc make \
openssl-devel bzip2-devel libffi-devel zlib-devel \
readline-devel sqlite-devel
```
### 1B.2 安装 Python 3.11IUS
```bash
# 若服务器不能访问外网,请改用内网 yum 源或在本机构好 wheel 再上传
sudo yum install -y https://repo.ius.io/ius-release-el7.rpm
sudo yum install -y python311 python311-pip python311-devel
python3.11 --version # 应显示 3.11.x
```
后续所有 `python3` / `pip` 命令在 CentOS 7 上请写成 **`python3.11`** / **`python3.11 -m pip`**。
### 1B.3 安装 Node.js 18构建前端
```bash
curl -fsSL https://rpm.nodesource.com/setup_18.x | sudo bash -
sudo yum install -y nodejs
node --version # 应 >= v18
npm --version
```
**无法访问 NodeSource 时**:在开发机 `npm run build` 后,只把 `frontend-admin/dist/` 上传到服务器,可跳过 Node 安装(见 §5 说明)。
### 1B.4(可选)用 SCL Python 3.8 的说明
部分环境只有 `rh-python38`3.8),可能装不上新版 `mcp` 包。**不推荐** CentOS 7 用 3.8 跑本仓库;请优先 3.11。
---
## 2. 创建运行用户与目录
```bash
sudo useradd -r -m -d /opt/qip -s /bin/bash qip 2>/dev/null || true
sudo mkdir -p /opt/quality-inspection-platform
sudo chown -R qip:qip /opt/quality-inspection-platform
```
后续命令用 `qip` 执行(或你自己的用户):
```bash
sudo -iu qip
cd /opt/quality-inspection-platform
```
---
## 3. 获取代码
**方式 AGit推荐**
```bash
cd /opt/quality-inspection-platform
git clone <你的内网仓库地址> .
# 或指定分支git clone -b main <url> .
```
**方式 B上传压缩包**
在本机打包后 `scp` 到服务器,再解压:
```bash
cd /opt/quality-inspection-platform
tar xzf quality-inspection-platform.tar.gz --strip-components=1
```
---
## 4. 安装 Python 依赖
```bash
cd /opt/quality-inspection-platform
# Ubuntu / Debian
python3 -m venv .venv
# CentOS 7IUS
# python3.11 -m venv .venv
source .venv/bin/activate
pip install --upgrade pip
pip install -r requirements.txt
# 验证关键包
python -c "import fastapi, mcp, uvicorn; print('python deps ok')"
```
> CentOS 7 若 `pip install` 编译失败,确认已执行 §1B.1 的 `gcc``*-devel` 包;或在内网配置 pip 镜像源。
---
## 5. 构建前端(生产必做)
```bash
cd /opt/quality-inspection-platform/frontend-admin
npm install
npm run build
cd ..
ls -la frontend-admin/dist/index.html
```
必须存在 `frontend-admin/dist/index.html`,否则浏览器访问 `/` 会提示前端未构建。
**CentOS 7 未装 Node 时**:在开发机构建后只上传 `dist`
```bash
cd frontend-admin && npm install && npm run build
scp -r dist qip@<服务器>:/opt/quality-inspection-platform/frontend-admin/
```
**可选**:构建完成后删除 `node_modules` 节省空间(以后升级需重新 `npm install`
```bash
rm -rf /opt/quality-inspection-platform/frontend-admin/node_modules
```
---
## 6. 配置环境变量
```bash
cd /opt/quality-inspection-platform
mkdir -p data/ssh-scripts
cat > /opt/quality-inspection-platform/.env <<'EOF'
# 生产务必修改下面三项
AUTH_TOKEN_SECRET=请替换为随机长字符串
DEFAULT_SUPERADMIN_USERNAME=admin
DEFAULT_SUPERADMIN_PASSWORD=请替换为强密码
# MCP强制所有工具带 API Key
MCP_REQUIRE_API_KEY=true
# 同事从其它机器用 Cursor 连 MCP 时,若连不上可开启:
QIP_MCP_DISABLE_DNS_PROTECTION=true
# 或精确白名单(二选一):
# QIP_MCP_ALLOWED_HOSTS=10.0.0.5:8000,qip.internal:8000
# QIP_MCP_ALLOWED_ORIGINS=http://10.0.0.5:8000
# 可选Loki 日志探索
# GENERAL_LOKI_EXPLORE_URL=https://grafana.example/explore
EOF
chmod 600 .env
```
> `.env` 已在 `.gitignore` 中,不会进 Git。
> `uvicorn` 默认**不会**自动读 `.env`;下面 systemd 示例用 `EnvironmentFile` 加载。
---
## 7. 首次试跑(前台)
```bash
cd /opt/quality-inspection-platform
source .venv/bin/activate
set -a
source .env
set +a
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
**另开一个 SSH 窗口**自检:
```bash
curl -s http://127.0.0.1:8000/health
# 期望:{"ok":true}
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:8000/
# 期望200
```
浏览器访问:`http://<服务器IP>:8000/`
使用 `.env` 里的 `DEFAULT_SUPERADMIN_USERNAME` / `DEFAULT_SUPERADMIN_PASSWORD` 登录。
确认无误后,在前台试跑窗口 `Ctrl+C` 停止,进入第 8 步配置 systemd。
---
## 8. 配置 systemd 常驻
```bash
sudo tee /etc/systemd/system/quality-inspection-platform.service <<'EOF'
[Unit]
Description=Quality Inspection Platform
After=network.target
[Service]
Type=simple
User=qip
Group=qip
WorkingDirectory=/opt/quality-inspection-platform
EnvironmentFile=/opt/quality-inspection-platform/.env
Environment=PATH=/opt/quality-inspection-platform/.venv/bin:/usr/bin:/bin
ExecStart=/opt/quality-inspection-platform/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable quality-inspection-platform
sudo systemctl start quality-inspection-platform
sudo systemctl status quality-inspection-platform
```
查看日志:
```bash
journalctl -u quality-inspection-platform -f
```
---
## 9. 防火墙(按需)
**CentOS 7firewalld** — 仅内网访问 8000
```bash
sudo firewall-cmd --permanent --add-rich-rule='rule family="ipv4" source address="10.0.0.0/8" port protocol="tcp" port="8000" accept'
sudo firewall-cmd --reload
```
若用 Nginx 对外 80
```bash
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --reload
```
**Ubuntuufw**
```bash
sudo ufw allow from 10.0.0.0/8 to any port 8000 proto tcp
sudo ufw reload
```
若前面有 Nginx 对外 80/443则只开放 80/443**不要**把 8000 暴露到公网。
---
## 10. Nginx 反向代理(可选)
适用于希望用域名访问、或只暴露 80/443 的场景。
```bash
# Ubuntu / Debian
sudo apt install -y nginx
# CentOS 7
sudo yum install -y nginx
sudo systemctl enable nginx
```
```bash
sudo tee /etc/nginx/sites-available/quality-inspection-platform <<'EOF'
server {
listen 80;
server_name qip.internal; # 改成你的域名或 IP
client_max_body_size 50m;
location / {
proxy_pass http://127.0.0.1:8000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# MCP Streamable HTTP 可能需要较长连接
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;
}
}
EOF
sudo ln -sf /etc/nginx/sites-available/quality-inspection-platform /etc/nginx/sites-enabled/
sudo nginx -t && sudo systemctl reload nginx
```
此时访问地址变为:`http://qip.internal/`
MCP URL`http://qip.internal/mcp`
可将 systemd 里 uvicorn 改为只监听本机:
```ini
ExecStart=.../uvicorn app.main:app --host 127.0.0.1 --port 8000
```
---
## 11. 同事配置 MCP不在服务器装 Python
每人本机 Cursor `~/.cursor/mcp.json`
```json
{
"mcpServers": {
"quality-inspection-platform": {
"url": "http://qip.internal/mcp",
"headers": {
"Authorization": "Bearer sto-个人API密钥"
}
}
}
}
```
- `sto-` Key登录 Web → 个人中心创建
- 详细说明:[mcp_install.md](./mcp_install.md)
---
## 12. 数据备份
数据库与上传的 SSH 脚本目录:
```bash
# 示例:每日备份 cron
0 2 * * * qip cp /opt/quality-inspection-platform/ai_test.db /opt/qip/backup/ai_test.db.$(date +\%Y\%m\%d)
```
**升级代码时不要覆盖** 已有 `ai_test.db`
---
## 13. 版本升级流程
```bash
sudo systemctl stop quality-inspection-platform
sudo -iu qip
cd /opt/quality-inspection-platform
# 备份
cp -a ai_test.db ai_test.db.bak.$(date +%Y%m%d)
# 拉代码
git pull
source .venv/bin/activate
pip install -r requirements.txt
cd frontend-admin
npm install
npm run build
cd ..
exit
sudo systemctl start quality-inspection-platform
curl -s http://127.0.0.1:8000/health
```
---
## 14. 验收清单
| # | 检查 | 命令/操作 |
|---|------|-----------|
| 1 | 服务运行 | `systemctl is-active quality-inspection-platform` |
| 2 | 健康检查 | `curl -s http://127.0.0.1:8000/health` |
| 3 | Web 首页 | 浏览器打开 `/`,能登录 |
| 4 | API 文档 | `http://<主机>:8000/docs` |
| 5 | MCP 工具列表 | `curl -s http://127.0.0.1:8000/mcp/tools -H "Authorization: Bearer sto-xxx"` |
| 6 | 改密 | 登录后修改超管密码API Key 仅个人保管 |
| 7 | 防火墙 | 仅内网可达 |
---
## 15. 常见问题
| 现象 | 处理 |
|------|------|
| 访问 `/` 提示 frontend dist not found | 重新执行第 5 步 `npm run build`,或上传 `frontend-admin/dist` |
| `ModuleNotFoundError: mcp` | 第 4 步 `pip install -r requirements.txt`CentOS 7 须用 3.11 的 venv |
| CentOS 7 `python3` 是 3.6 | 不要用系统 `python3`,改用 `python3.11` 建 venv |
| `pip` 编译卡住 / 失败 | `yum install` §1B.1 开发包;配置内网 pip 镜像 |
| IUS / NodeSource 无法访问 | 内网 yum 镜像;或本机构建 `dist` + 离线 wheel 装 Python 包 |
| MCP 在 Cursor 里连不上 | `.env``QIP_MCP_DISABLE_DNS_PROTECTION=true` 或配置 `QIP_MCP_ALLOWED_HOSTS` |
| 401 / 无 Key | Web 个人中心创建 `sto-` Key或设 `MCP_REQUIRE_API_KEY` 后请求头带 Bearer |
| 端口被占用 | `ss -lntp \| grep 8000` 查占用进程 |
| 权限错误 | 确认 `ai_test.db`、`data/` 目录属主为 `qip` |
| SELinux 拦截CentOS | 临时:`sudo setenforce 0` 排查;长期:`setsebool -P httpd_can_network_connect 1` 或按策略放行 |
---
## 16. 相关文档
| 文档 | 内容 |
|------|------|
| [mcp_install.md](./mcp_install.md) | MCP / Cursor / 内网排障 |
| [mcp_tools_for_ai.md](./mcp_tools_for_ai.md) | AI 工具能力与 Recipe |
| [mcp_quickstart.md](./mcp_quickstart.md) | curl / HTTP 网关示例 |
---
## 快速命令抄录(已装好环境后)
```bash
cd /opt/quality-inspection-platform && source .venv/bin/activate
git pull && pip install -r requirements.txt
cd frontend-admin && npm install && npm run build && cd ..
sudo systemctl restart quality-inspection-platform
```

View File

@ -1,201 +1,248 @@
# 把本平台注册成真正的 MCP Server
# MCP Server 安装与接入说明
本仓库已附带 `mcp_bridge.py`,它是一个标准 stdio MCP Server
对外暴露 MCP 协议JSON-RPC over stdio内部桥接到本平台的 HTTP 网关。
平台在 **同一个 `uvicorn` 进程** 内提供:
只要先启动平台后端,再让 Cursor / Claude Desktop / Codex CLI 启动这个桥接,
它就会被识别成一个真正的 MCP Server并自动暴露所有平台工具含跑批工作流工具
| 能力 | 路径 | 适用场景 |
|------|------|----------|
| **Web 管理端** | `/`、`/api/*` | 人工在浏览器操作 |
| **MCP Server推荐 AI 用)** | `/mcp` | Codex / Claude / CursorStreamable HTTP |
| **HTTP 工具网关(兼容)** | `/mcp/tools`、`/mcp/invoke` | curl、CI、自研脚本 |
三者共用数据库与工作流引擎;**Web UI 与 MCP 可同时使用,互不影响**。
> **历史变更**:已移除 `mcp_bridge.py`。不再需要本机 `python3` 子进程桥接;客户端直接连 `http://10.20.30.42:8000/mcp`
---
## 1. 启动平台后端
## 1. 架构
```
┌─────────────────┐ MCP (Streamable HTTP) ┌──────────────────────────┐
│ Codex / Claude │ ──────────────────────────────►│ uvicorn app.main:app │
│ Claude Desktop │ Authorization: Bearer │ ├─ /mcp (mcp_native) │
└─────────────────┘ │ ├─ /api/* (Web) │
┌─────────────────┐ HTTP 登录 / 点击 │ └─ /mcp/invoke (curl) │
│ 浏览器 Web UI │ ──────────────────────────────►└──────────────────────────┘
└─────────────────┘
```
实现位置:`app/mcp_native.py`(挂载路由 + `lifespan`),工具逻辑:`app/main.py` 中 `MCP_TOOL_SPECS``_dispatch_mcp_tool`
---
## 2. 服务端部署(管理员)
> **完整步骤**用户、Git、前端构建、systemd、Nginx、备份[`deploy_server.md`](./deploy_server.md)
### 2.1 安装与启动
```bash
cd /Users/qihongkun/work/My_app/quality-inspection-platform
# 可选:生产/共享环境建议开启,所有 MCP 调用必须带 API Key
cd /path/to/quality-inspection-platform
python3 -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
# 生产建议:所有 MCP 调用必须带 API Key
# export MCP_REQUIRE_API_KEY=true
uvicorn app.main:app --host 127.0.0.1 --port 8000
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
确认可访问:
### 2.2 启动后确认
- `http://127.0.0.1:8000/mcp/tools`
| 检查项 | 命令或地址 |
|--------|------------|
| 健康 | `curl -s http://10.20.30.42:8000/health` |
| Web | 浏览器打开 `http://10.20.30.42:8000/` |
| 工具列表 | `curl -s http://10.20.30.42:8000/mcp/tools -H "Authorization: Bearer sto-密钥"` |
| MCP 端点 | `http://10.20.30.42:8000/mcp`(供 Codex 配置,非浏览器页面) |
---
### 2.3 内网 systemd 示例(可选)
## 2. 安装到 Cursor
```ini
[Unit]
Description=Quality Inspection Platform
After=network.target
编辑 `~/.cursor/mcp.json`(不存在则新建),加入:
[Service]
User=qip
WorkingDirectory=/opt/quality-inspection-platform
Environment="PATH=/opt/quality-inspection-platform/.venv/bin"
Environment="MCP_REQUIRE_API_KEY=true"
# 使用内网地址访问且 MCP 连不上时可加:
# Environment="QIP_MCP_DISABLE_DNS_PROTECTION=true"
ExecStart=/opt/quality-inspection-platform/.venv/bin/uvicorn app.main:app --host 0.0.0.0 --port 8000
Restart=always
```json
{
"mcpServers": {
"quality-inspection-platform": {
"command": "python3",
"args": ["/Users/qihongkun/work/My_app/quality-inspection-platform/mcp_bridge.py"],
"env": {
"QIP_BASE_URL": "http://127.0.0.1:8000",
"QIP_API_KEY": "sto-你的API密钥"
}
}
}
}
```
- `QIP_BASE_URL`:平台后端地址(兼容旧环境变量 `AI_TEST_BASE_URL`)。
- `QIP_API_KEY`**必须配置**(兼容旧名 `AI_TEST_API_KEY`)。写操作与执行类工具(`workflow_run`、`workflow_batch_run`、`ssh_script_run` 等)无 Key 将返回 401桥接会把 Key 放到 `Authorization: Bearer` 与 invoke body 的 `api_key` 字段。
- 后端可选 `MCP_REQUIRE_API_KEY=true`:所有 MCP 工具(含只读、`GET /mcp/tools`)均要求 Key。
在平台 Web 端右上角「个人中心」生成 `sto-` 开头的 API Key。
重启 Cursor 后,在 MCP 面板里就能看到 `quality-inspection-platform`,里面会自动列出全部工具。
---
## 3. 安装到 Claude Desktop
编辑 `~/Library/Application Support/Claude/claude_desktop_config.json`
```json
{
"mcpServers": {
"quality-inspection-platform": {
"command": "python3",
"args": ["/Users/qihongkun/work/My_app/quality-inspection-platform/mcp_bridge.py"],
"env": {
"QIP_BASE_URL": "http://127.0.0.1:8000",
"QIP_API_KEY": "sto-你的API密钥"
}
}
}
}
[Install]
WantedBy=multi-user.target
```
---
## 4. 安装到 Codex CLI
## 3. Codex 配置
```bash
codex mcp add quality-inspection-platform \
--command python3 \
--args /Users/qihongkun/work/My_app/quality-inspection-platform/mcp_bridge.py \
--env QIP_BASE_URL=http://127.0.0.1:8000 \
--env QIP_API_KEY=sto-你的API密钥
```
编辑 **`~/.codex/config.toml`**。
或在 `~/.codex/config.toml` 中:
> **远程环境**:配置文件要写在 Codex 实际运行的机器上。MCP 请求从 Codex 所在机器发出;团队默认 MCP 地址为 `http://10.20.30.42:8000/mcp`
### 3.1 默认内网地址
```toml
[mcp.servers.quality-inspection-platform]
command = "python3"
args = ["/Users/qihongkun/work/My_app/quality-inspection-platform/mcp_bridge.py"]
[mcp_servers.quality-inspection-platform]
url = "http://10.20.30.42:8000/mcp"
[mcp.servers.quality-inspection-platform.env]
QIP_BASE_URL = "http://127.0.0.1:8000"
QIP_API_KEY = "sto-你的API密钥"
[mcp_servers.quality-inspection-platform.http_headers]
Authorization = "Bearer sto-你的API密钥"
```
### 3.2 连接其他部署地址
如果 MCP 平台部署到其他服务器,将 `10.20.30.42:8000` 替换为实际 IP、端口或域名例如
```toml
[mcp_servers.quality-inspection-platform]
url = "http://qip.corp:8000/mcp"
[mcp_servers.quality-inspection-platform.http_headers]
Authorization = "Bearer sto-你的API密钥"
```
### 3.3 配置生效
配置完成后重启 Codex 会话或 Codex 应用,让 MCP 配置重新加载。验证方式:在对话中要求 Agent 调用 `catalog_snapshot`,应返回 apis / workflows 等 JSON 快照。
### 3.4 API Key 获取
1. 浏览器登录 Web → 右上角「个人中心」
2. 创建 `sto-` 开头的 API Key仅显示一次请妥善保存
3. 写入 `~/.codex/config.toml``http_headers.Authorization`,或在 HTTP 网关调用时使用头 `X-API-Key: sto-...`
**写操作**与**执行类**工具无有效 Key 返回 **401**。生产环境建议 `MCP_REQUIRE_API_KEY=true`,此时**只读工具**也须 Key。
---
## 5. 自检(不连客户端,先确认桥接可用)
## 4. 团队协作(同事无需克隆仓库)
| 角色 | 需要做什么 |
|------|------------|
| **管理员** | 内网部署 `uvicorn`,开放防火墙,分发内网 URL |
| **每位开发者** | 配置 `~/.codex/config.toml`URL + 个人 `sto-` Key |
| **不需要** | 本机 Python、`mcp_bridge`、克隆完整项目(除非要改代码) |
**远程 Codex 统一配置(推荐)**
1. 服务器上部署一次项目(含 `.venv``uvicorn`
2. 同事在远程机器上运行 Codex 或使用远程工作区
3. 在**远程** `~/.codex/config.toml` 使用 `http://10.20.30.42:8000/mcp`(确保 Codex 所在机器可访问该内网地址)
4. 每人使用自己的 `sto-` Key**不要**写入 Git 或共享配置文件
---
## 5. Cursor / Claude Desktop可选
使用 **HTTP URL** 连接 MCP以客户端当前版本支持的 Streamable HTTP / URL 配置为准):
- URL`http://10.20.30.42:8000/mcp`
- 头:`Authorization: Bearer sto-...`
Cursor 示例:
```json
{
"mcpServers": {
"quality-inspection-platform": {
"url": "http://10.20.30.42:8000/mcp",
"headers": {
"Authorization": "Bearer sto-你的API密钥"
}
}
}
}
```
Claude Desktop 具体字段名见其 MCP 配置文档。
---
## 6. 从旧版 `mcp_bridge` 迁移
| 旧配置 | 新配置 |
|--------|--------|
| `"command": "python3"` | 删除 |
| `"args": [".../mcp_bridge.py"]` | 删除 |
| `"env": { "QIP_BASE_URL": "...", "QIP_API_KEY": "..." }` | 改为 Codex `url = "<QIP_BASE_URL>/mcp"` + `http_headers.Authorization = "Bearer <Key>"` |
迁移后只需保证 **`uvicorn` 已启动**;无需再单独运行桥接脚本。
---
## 7. 自检清单
```bash
printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
'{"jsonrpc":"2.0","id":3,"method":"tools/call","params":{"name":"catalog_snapshot","arguments":{}}}' \
| QIP_API_KEY=sto-你的API密钥 python3 mcp_bridge.py
# 1. 后端存活
curl -s http://10.20.30.42:8000/health
# 2. HTTP 网关(与 MCP 工具集一致)
curl -s http://10.20.30.42:8000/mcp/tools \
-H "Authorization: Bearer sto-你的密钥"
# 3. 试调一个工具
curl -s -X POST http://10.20.30.42:8000/mcp/invoke \
-H "Authorization: Bearer sto-你的密钥" \
-H "Content-Type: application/json" \
-d '{"tool":"catalog_snapshot","arguments":{}}'
```
预期:
- `initialize` 返回 `serverInfo`
- `tools/list` 返回工具清单(应包含 `workflow_batch_*` 等)
- `tools/call` 返回 `content[0].text` 内含调用结果
**Codex 侧**:在对话中让 Agent 调用 `catalog_snapshot`,应返回 apis / workflows 等 JSON 快照。
---
## 6. 暴露的工具(自动转发自平台)
## 8. 排障
桥接启动时从 `GET /mcp/tools` 拉取列表,**无需改 `mcp_bridge.py`** 即可随平台升级获得新工具。
| 现象 | 可能原因 | 处理 |
|------|----------|------|
| Codex 无工具 / 连接失败 | `uvicorn` 未启动或 URL 错误 | 确认 `/health`;检查 `~/.codex/config.toml` 中 URL 以 `/mcp` 结尾 |
| 401 / requires API Key | 未配置或 Key 无效 | 个人中心重新生成 Key更新 `Authorization` |
| 内网 IP 可 curl 但 Codex 连不上 | DNS rebinding 校验 | 设置 `QIP_MCP_DISABLE_DNS_PROTECTION=true` 或配置 `QIP_MCP_ALLOWED_HOSTS` |
| 修改工具后客户端仍是旧的 | 客户端缓存 | 重启 Codex 会话或应用;服务端重启 `uvicorn` |
| 远程环境 MCP 失败 | 配在了本机而非 Codex 所在机器 | 改 Codex 实际运行机器上的 `~/.codex/config.toml`;从那台机器验证是否能访问 `http://10.20.30.42:8000/health` |
| Web 正常、MCP 不行 | 只检查了 Web | MCP 走 `/mcp`,与 `/api` 独立;分别自检 |
### 资源管理
- `api_upsert` — 创建/更新接口
- `mock_upsert` — 创建/更新 mock 数据
- `mcp_tool_upsert` — 创建/更新 MCP 工具配置
- `catalog_snapshot` — 全量资源快照(含 `workflow_batches`、目录树、SSH 树)
### 工作流(单次)
- `workflow_upsert` — 创建/更新工作流(支持 `loop`、`condition` + `json_path`
- `workflow_get` — 读取工作流及各节点 `last_run`
- `workflow_node_status` — 单节点最近执行状态
- `workflow_patch_json` — 增量修改工作流 JSON
- `workflow_validate` — 校验 definition JSON
- `workflow_run` / `workflow_run_node` — 执行(需 API Key
- `workflow_analyze_last_run` — 分析最近一次执行
- `workflow_run_list` / `workflow_run_get` — 执行历史
- `workflow_run_replay` / `workflow_run_loki_link` — 重放与 Loki 链接
### 跑批工作流
- `workflow_batch_create` — 创建草稿批跑任务(需 API Key
- `workflow_batch_update` — 更新 `workflow_ids` / `base_url` 等(需 API Key
- `workflow_batch_run` — 按任务配置顺序执行多个工作流
- `workflow_batch_get` — 批跑详情与关联 `workflow_runs`
- `workflow_batch_list` — 批跑任务列表
推荐顺序:`create` → `update`(绑定 `workflow_ids`)→ `run``get`。详见 `docs/mcp_quickstart.md` 示例 H。
### 目录
- `folder_ensure` / `folder_list`
- `api_move_folder` / `workflow_move_folder`
### SSH 脚本
- `ssh_tree` / `ssh_script_get`
- `ssh_script_upsert` / `ssh_script_run`
查看 MCP 相关日志:启动 `uvicorn` 的终端Codex 客户端 MCP 日志。
---
## 9. 环境变量
## 7. 工作机制
```
Cursor / Claude / Codex
|
| (MCP stdio JSON-RPC)
v
mcp_bridge.py
|
| HTTP (httpx) + Authorization / api_key
v
http://127.0.0.1:8000/mcp/invoke
```
- 桥接会在启动时调用一次 `/mcp/tools`,把工具映射成 MCP `tools/list` 返回值。
- 每次 `tools/call`,桥接会以 `{tool, arguments}` POST 到 `/mcp/invoke`
- 后端返回 `{ok, tool, data, error}`,桥接将其作为 `content[0].text` 文本返回,并按 `ok` 设置 `isError`
| 变量 | 含义 |
|------|------|
| `MCP_REQUIRE_API_KEY` | `true` 时所有 MCP 工具(含只读、`GET /mcp/tools`)均需 Key |
| `QIP_MCP_DISABLE_DNS_PROTECTION` | `true` 时关闭 MCP 传输的 Host/Origin 校验 |
| `QIP_MCP_ALLOWED_HOSTS` | 逗号分隔 Host 白名单,如 `10.20.30.42:8000,qip.corp:8000` |
| `QIP_MCP_ALLOWED_ORIGINS` | 逗号分隔 Origin 白名单 |
---
## 8. Loki 日志(可选)
## 10. 维护与文档索引
若需在管理端或 API 中打开 General/Grafana Loki 探索页,在后端进程环境中配置:
- `GENERAL_LOKI_EXPLORE_URL``LOKI_EXPLORE_URL`
- 可选:`LOKI_DATASOURCE`、`LOKI_ORG_ID`、`LOKI_LABEL_SELECTOR`、`LOKI_TIME_PADDING_SECONDS`
配置后,`GET /api/workflow-runs/{run_id}/loki-link` 可为指定 HTTP 节点生成带时间窗与路径过滤的 Explore URL。
- **新增 MCP 工具**:修改 `app/main.py``MCP_TOOL_SPECS``_dispatch_mcp_tool` 分支 → 重启 `uvicorn` → 重启 MCP 客户端。
- **AI 选型与 RecipeAgent 必读)**[mcp_tools_for_ai.md](./mcp_tools_for_ai.md)
- **curl / 批量调用示例**[mcp_quickstart.md](./mcp_quickstart.md)
- **Codex 短约定**:项目根 [AGENTS.md](../AGENTS.md)
---
## 9. 维护建议
## 11. 常见问题
- 平台新增工具时,无需改桥接,重启 MCP 客户端即可重新拉取 `tools/list`
- 团队成员:`git pull` + `pip install -r requirements.txt` + 配置上述 MCP 入口与 `AI_TEST_API_KEY`
- **AI 能力说明(首选)****`docs/mcp_tools_for_ai.md`**
- 人类 curl 示例:**`docs/mcp_quickstart.md`**
- Codex 自动加载:**项目根 `AGENTS.md`**
**还需要 bridge 吗?**
不需要。`/mcp` 已是标准 MCP Server。
**每人都要装 Python 吗?**
仅**运行后端的服务器**需要 Python 环境;只用 Codex 连 MCP 的同事不需要本机 Python。
**能和 Web 一起用吗?**
可以。Web 走登录态 `/api/*`AI 走 `/mcp` + API Key。
**脚本还能用吗?**
可以。继续 `POST /mcp/invoke``GET /mcp/tools`,与 MCP 协议共用同一套工具实现。

View File

@ -1,9 +1,176 @@
# MCP 快速接入与 AI 调用说明书
> **AI Agent 请优先阅读**[mcp_tools_for_ai.md](./mcp_tools_for_ai.md)工具决策表、鉴权、Recipe、节点约定
> 本文档侧重 curl 示例与人工接入;项目根 [AGENTS.md](../AGENTS.md) 供 Codex 自动加载。
> **Codex / 内网安装**[mcp_install.md](./mcp_install.md)`~/.codex/config.toml`、团队协作、排障)。
> 本文档侧重 **HTTP curl** 与脚本调用;项目根 [AGENTS.md](../AGENTS.md) 供 Codex 自动加载。
本文档给 AI Agent 和开发者使用,目标是让 AI 可以直接通过本平台的 MCP 接口完成:
## 接入方式选择
| 方式 | 端点 | 适用 |
|------|------|------|
| **MCP 协议(推荐)** | `http://10.20.30.42:8000/mcp` | Codex、Claude、Cursor见 [mcp_install.md](./mcp_install.md) |
| **HTTP 网关(本文)** | `/mcp/tools`、`/mcp/invoke` | curl、CI、自研 Agent |
| **Web UI** | `/api/*` | 浏览器人工操作 |
---
## 0. MCP 安装与接入(团队必读)
平台现在直接在同一个 `uvicorn` 进程内提供标准 MCP Server
```text
Web UI: http://10.20.30.42:8000/
MCP Server: http://10.20.30.42:8000/mcp
HTTP 网关: http://10.20.30.42:8000/mcp/tools、/mcp/invoke
```
已移除旧版 `mcp_bridge.py`;不再需要为 Codex / Claude / Cursor 额外启动本机 Python 桥接进程。客户端直接连接 `http://10.20.30.42:8000/mcp`
### 0.1 服务端启动
管理员或本地开发者启动平台:
```bash
cd /path/to/quality-inspection-platform
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# 生产或共享环境建议开启:所有 MCP 调用必须带 API Key
# export MCP_REQUIRE_API_KEY=true
uvicorn app.main:app --host 0.0.0.0 --port 8000
```
启动后检查:
```bash
curl -s http://10.20.30.42:8000/health
curl -s http://10.20.30.42:8000/mcp/tools \
-H "Authorization: Bearer sto-你的API密钥"
```
### 0.2 Codex 配置
编辑 `~/.codex/config.toml`
```toml
[mcp_servers.quality-inspection-platform]
url = "http://10.20.30.42:8000/mcp"
[mcp_servers.quality-inspection-platform.http_headers]
Authorization = "Bearer sto-你的API密钥"
```
团队默认 MCP 地址为 `10.20.30.42:8000`。如果 MCP 平台部署到其他服务器,将 `10.20.30.42:8000` 替换为实际 IP、端口或域名
```toml
[mcp_servers.quality-inspection-platform]
url = "http://qip.corp:8000/mcp"
[mcp_servers.quality-inspection-platform.http_headers]
Authorization = "Bearer sto-你的API密钥"
```
配置完成后重启 Codex 会话或 Codex 应用,让 MCP 配置重新加载。验证方式是在对话中要求调用 `catalog_snapshot`
### 0.3 Codex Remote / 远程环境
如果 Codex 运行在远程主机或远程工作区,`~/.codex/config.toml` 要配置在 Codex 实际运行的那台机器上。
如果 Codex 运行在远程机器上URL 仍以 Codex 所在机器能访问到的 MCP 地址为准。团队默认写:
```toml
url = "http://10.20.30.42:8000/mcp"
```
如果你改成其他部署地址,需要先在 Codex 所在机器上确认该地址可访问。
### 0.4 Cursor 配置(可选)
如果团队中有人使用 Cursor再配置 `~/.cursor/mcp.json`
```json
{
"mcpServers": {
"quality-inspection-platform": {
"url": "http://10.20.30.42:8000/mcp",
"headers": {
"Authorization": "Bearer sto-你的API密钥"
}
}
}
}
```
部分 Cursor 版本需要显式指定传输类型:
```json
{
"mcpServers": {
"quality-inspection-platform": {
"type": "streamableHttp",
"url": "http://10.20.30.42:8000/mcp",
"headers": {
"Authorization": "Bearer sto-你的API密钥"
}
}
}
}
```
Cursor Remote-SSH 时,`mcp.json` 要配置在远程主机的 `~/.cursor/mcp.json`,不是本机。
### 0.5 Claude
Claude Desktop 使用 HTTP URL 连接 MCP
```text
URL: http://10.20.30.42:8000/mcp
Header: Authorization: Bearer sto-你的API密钥
```
具体字段名按 Claude 当前 MCP 配置格式填写。优先使用 Streamable HTTP / URL 型 MCP不再配置 `command + args + mcp_bridge.py`
### 0.6 API Key 获取与安全
1. 浏览器登录 Web UI。
2. 右上角进入「个人中心」。
3. 创建 `sto-` 开头的 API Key。
4. 写入 MCP 客户端配置的 `Authorization: Bearer sto-...`
写操作和执行类工具必须带有效 Key生产环境建议设置 `MCP_REQUIRE_API_KEY=true`让只读工具也必须鉴权。API Key 不要提交到 Git不要写入共享文档。
### 0.7 旧版 bridge 迁移
旧配置:
```json
{
"command": "python3",
"args": ["/path/to/mcp_bridge.py"],
"env": {
"QIP_BASE_URL": "http://10.20.30.42:8000",
"QIP_API_KEY": "sto-..."
}
}
```
新配置:
```toml
[mcp_servers.quality-inspection-platform]
url = "http://10.20.30.42:8000/mcp"
[mcp_servers.quality-inspection-platform.http_headers]
Authorization = "Bearer sto-你的API密钥"
```
完整安装、团队协作、systemd 与排障说明见 [mcp_install.md](./mcp_install.md)。
---
本文档给开发者和自动化脚本使用,目标是让调用方通过 HTTP 网关完成:
- 接口创建/更新
- Mock 数据创建/更新
@ -17,11 +184,12 @@
## 1. 基础信息
- 服务地址:`http://127.0.0.1:8000`
- 工具发现:`GET /mcp/tools`
- 单次调用:`POST /mcp/invoke`
- 服务地址:`http://10.20.30.42:8000`
- **Codex MCP 端点**`http://10.20.30.42:8000/mcp`Streamable HTTP见 [mcp_install.md](./mcp_install.md)
- 工具发现HTTP 兼容):`GET /mcp/tools`
- 单次调用HTTP 兼容):`POST /mcp/invoke`
- 批量调用:`POST /mcp/invoke-batch`
- MCP 鉴权:在平台右上角「个人中心」生成 `sto-` 开头的 API Key,配置到 MCP Bridge 环境变量 `QIP_API_KEY`(兼容 `AI_TEST_API_KEY`),或在请求头使`Authorization: Bearer <api_key>` / `X-API-Key: <api_key>`
- MCP 鉴权:在平台右上角「个人中心」生成 `sto-` 开头的 API KeyCodex 在 `~/.codex/config.toml``http_headers.Authorization` 中配置curl/脚本可`Authorization: Bearer <api_key>` / `X-API-Key: <api_key>`body 字段 `api_key` 仍兼容)
- **写操作**(创建/更新资源)与 **执行类操作**跑工作流、批跑、单节点、SSH 执行、重放)**必须**带有效 Key禁止无 Key 回落为 superadmin。
- 可选 **`MCP_REQUIRE_API_KEY=true`**(后端环境变量):所有 MCP 工具(含只读)均要求 Key`GET /mcp/tools` 同样校验。
@ -151,7 +319,7 @@
### 4.1 通用调用模板
```bash
curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sto-api-key>" \
-d '{
@ -163,7 +331,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
### 4.2 批量调用模板
```bash
curl -s -X POST http://127.0.0.1:8000/mcp/invoke-batch \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke-batch \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sto-api-key>" \
-d '{
@ -182,7 +350,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke-batch \
### 示例 AAI 自动创建接口
```bash
curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sto-api-key>" \
-d '{
@ -203,7 +371,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
### 示例 BAI 自动创建 Mock 数据
```bash
curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke \
-H "Content-Type: application/json" \
-d '{
"tool": "mock_upsert",
@ -217,7 +385,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
### 示例 CAI 自动创建 Workflow含条件分支
```bash
curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sto-api-key>" \
-d '{
@ -245,7 +413,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
### 示例 DAI 自动 Patch Workflow JSON
```bash
curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sto-api-key>" \
-d '{
@ -262,7 +430,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
### 示例 EAI 执行工作流并分析结果
```bash
curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke \
-H "Content-Type: application/json" \
-d '{
"tool": "workflow_run",
@ -275,7 +443,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
```
```bash
curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke \
-H "Content-Type: application/json" \
-d '{
"tool": "workflow_analyze_last_run",
@ -286,7 +454,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
### 示例 FAI 批量编排(创建接口 → Mock → Patch → 执行)
```bash
curl -s -X POST http://127.0.0.1:8000/mcp/invoke-batch \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke-batch \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sto-api-key>" \
-d '{
@ -328,7 +496,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke-batch \
### 示例 G按目录分功能管理
```bash
curl -s -X POST http://127.0.0.1:8000/mcp/invoke-batch \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke-batch \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sto-api-key>" \
-d '{
@ -362,7 +530,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke-batch \
```bash
# 1) 创建草稿批跑任务
curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sto-api-key>" \
-d '{
@ -377,7 +545,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
# 假设返回 data.id = 3
# 2) 绑定要执行的工作流 ID 列表
curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke \
-H "Content-Type: application/json" \
-H "Authorization: Bearer <sto-api-key>" \
-d '{
@ -389,7 +557,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
}'
# 3) 执行批跑
curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke \
-H "Content-Type: application/json" \
-d '{
"tool": "workflow_batch_run",
@ -397,7 +565,7 @@ curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
}'
# 4) 查询结果(含每次 workflow_run 的 run_id
curl -s -X POST http://127.0.0.1:8000/mcp/invoke \
curl -s -X POST http://10.20.30.42:8000/mcp/invoke \
-H "Content-Type: application/json" \
-d '{
"tool": "workflow_batch_get",

View File

@ -1,6 +1,6 @@
# MCP 工具能力说明AI 专用)
> **读者**Cursor / Codex / Claude 等通过 `quality-inspection-platform` MCP 工作的 Agent。
> **读者**Codex / Claude / Cursor 等通过 `quality-inspection-platform` MCP 工作的 Agent。
> **目的**:在调用 `tools/list` 之外,提供「何时用哪个工具、按什么顺序、参数怎么填」的固定上下文。
> **维护**:工具以 `app/main.py``MCP_TOOL_SPECS` 和平台 `GET /mcp/tools` 为准;增删工具时请同步更新本文档。
> **重要**:某些 MCP 客户端可能缓存、裁剪或延迟刷新工具列表;不要仅凭当前客户端可见工具反向删减本文档能力。
@ -10,16 +10,16 @@
## 0. Agent 必读30 秒)
1. **先** `catalog_snapshot` 了解现有 apis / workflows / workflow_batches / mocks / folders / ssh_tree避免重复创建。
2. **写操作与执行**必须带 `sto-` API Key`Authorization: Bearer` 或桥接环境变量 `QIP_API_KEY`,兼容 `AI_TEST_API_KEY`)。
2. **写操作与执行**必须带 `sto-` API KeyMCP 请求头 `Authorization: Bearer``X-API-Key`HTTP 脚本可在 body 传 `api_key`,兼容旧名 `AI_TEST_API_KEY`)。
3. **接口**用 `api_upsert`**流程**用 `workflow_upsert``workflow_patch_json`**跑批**固定三步:`workflow_batch_create` → `workflow_batch_update``workflow_batch_run`
4. **排障**`workflow_run_get` 看 request/response → `workflow_run_loki_link` 查日志。
5. 如果当前客户端没有显示某个工具,先确认平台 `GET /mcp/tools` 是否包含该工具,再刷新或重启 MCP 客户端
6. 人类可读安装与 curl 示例见 `docs/mcp_quickstart.md`;桥接安装见 `docs/mcp_install.md`。
5. 如果当前客户端没有显示某个工具,先用 MCP `tools/list``GET /mcp/tools` 核对,再重启 MCP 客户端 / 会话
6. 人类可读安装`docs/mcp_install.md`curl 示例见 `docs/mcp_quickstart.md`。
### 0.1 工具来源与客户端刷新
- 权威工具清单来自后端 `app/main.py``MCP_TOOL_SPECS`,平台通过 `GET /mcp/tools` 对外暴露。
- `mcp_bridge.py` 启动后会从 `QIP_BASE_URL`(或兼容 `AI_TEST_BASE_URL`)的 `/mcp/tools` 拉取工具并转换为 MCP `tools/list`。
- 平台内置 Streamable HTTP MCP`GET/POST /mcp``tools/list` 由 `MCP_TOOL_SPECS` 提供;兼容 HTTP 网关 `GET /mcp/tools`。
- Codex / Cursor / Claude 等客户端可能在会话启动时缓存工具列表;平台新增工具后,通常需要重启 MCP 客户端或重启会话。
- 如果客户端只显示部分工具,不代表平台没有该能力。先用 `/mcp/tools` 或本文档核对,再决定是否降级。
- Agent 更新本文档时,应以源码和 `/mcp/tools` 为准,不以单个客户端当前暴露的工具子集为准。
@ -44,14 +44,14 @@
### 2.1 平台地址
MCP Bridge 通过环境变量访问平台
Codex 等客户端在个人 MCP 配置中配置平台 URL 与 API Key例如
| 环境变量 | 含义 | 示例 |
|----------|------|------|
| `QIP_BASE_URL` | 质量检测平台后端地址(兼容 `AI_TEST_BASE_URL` | `http://127.0.0.1:8000` |
| `QIP_API_KEY` | `sto-` 开头的 API Key兼容 `AI_TEST_API_KEY` | `sto-...` |
| 配置项 | 含义 | 示例 |
|--------|------|------|
| `url` | MCP 端点 | `http://10.20.30.42:8000/mcp` |
| `http_headers.Authorization` / `headers.Authorization` | `Bearer sto-...` | `Bearer sto-xxx` |
Agent 通过 MCP 工具访问平台,不需要在业务项目中启动平台服务;平台进程由质量检测平台项目本身或共享服务负责。
Agent 通过 MCP 工具访问平台;平台进程由质量检测平台项目或共享内网服务负责。兼容环境变量名 `AI_TEST_API_KEY` 仅适用于旧 HTTP 脚本MCP 客户端请用请求头传 Key。
### 2.2 调用鉴权
@ -92,6 +92,8 @@ Agent 通过 MCP 工具访问平台,不需要在业务项目中启动平台服
## 4. 工具清单(按分类)
下文参数均为各工具的 **`arguments` 对象**MCP `tools/call` 直接传该对象)。仅在使用 HTTP 网关时,外层再包一层 `{"tool":"<名称>","arguments":{...}}`,见 [mcp_quickstart.md](./mcp_quickstart.md)。
### 4.1 发现与目录
#### `catalog_snapshot`
@ -247,7 +249,7 @@ Agent 通过 MCP 工具访问平台,不需要在业务项目中启动平台服
#### `mcp_tool_upsert`
- **用途**:平台内「自定义 MCP 工具配置」表,**不是**本桥接工具列表本身。
- **用途**:平台内「自定义 MCP 工具配置」表,**不是** Codex / Claude / Cursor 所见的内置 MCP 工具列表本身。
#### SSH 系列
@ -289,6 +291,15 @@ Agent 通过 MCP 工具访问平台,不需要在业务项目中启动平台服
## 6. 调用协议
### 6.1 Codex / MCP 客户端(推荐)
- **端点**`http://10.20.30.42:8000/mcp`Streamable HTTP
- **鉴权**:请求头 `Authorization: Bearer sto-...`Codex 配置在个人 `config.toml``mcp_servers.<name>.http_headers`
- **工具发现**MCP 方法 `tools/list`(与 `MCP_TOOL_SPECS` 一致)
- **工具调用**MCP 方法 `tools/call``name` + `arguments`)→ `content[0].text` 为 JSON 字符串,解析后字段为 `ok` / `tool` / `data` / `error`;失败时 `isError` 可能为 true
### 6.2 HTTP 网关curl / CI
- **发现**`GET /mcp/tools` → `{ tools, require_api_key, ai_guide }`
- **单次**`POST /mcp/invoke` → `{ ok, tool, data, error }`
- **批量**`POST /mcp/invoke-batch` → `{ results: [...] }``stop_on_error: true` 时任一步失败即停
@ -334,7 +345,7 @@ workflow_analyze_last_run → workflow_patch_json(只改变量/单节点 data)
| error / 现象 | 处理 |
|--------------|------|
| 401 MCP requires API Key | 配置 `QIP_API_KEY`(或 `AI_TEST_API_KEY`)或请求头 Bearer |
| 401 MCP requires API Key | Codex个人 `config.toml` 中配置 `Authorization: Bearer sto-...`curl请求头或 body `api_key` |
| workflow folder not registered | 先 `folder_ensure` workflows |
| 仅 draft 批次可执行 | 已对 running/success 的 batch 再 run |
| unknown mcp tool | `GET /mcp/tools` 刷新名称 |
@ -349,9 +360,9 @@ workflow_analyze_last_run → workflow_patch_json(只改变量/单节点 data)
|------|------|
| **本文** `docs/mcp_tools_for_ai.md` | **AI Agent首选** |
| `docs/mcp_quickstart.md` | 人类 + curl 示例 |
| `docs/mcp_install.md` | MCP 桥接安装 |
| `docs/mcp_install.md` | MCP Server 安装、Codex/内网/远程环境 |
| 项目根 `AGENTS.md` | Codex 自动加载的短指引 |
---
*文档版本:与仓库 `MCP_TOOL_SPECS` 同步(含 workflow_run_* / workflow_validate / 执行类鉴权)。*
*文档版本:与 `MCP_TOOL_SPECS`28 个工具)同步;接入方式为内置 Streamable HTTP `/mcp`,见 `docs/mcp_install.md`。*

View File

@ -1,185 +0,0 @@
"""Stdio MCP server that bridges to the local HTTP MCP gateway.
It speaks the Model Context Protocol over stdin/stdout (newline-delimited
JSON-RPC 2.0) and forwards every tool call to:
GET http://127.0.0.1:8000/mcp/tools
POST http://127.0.0.1:8000/mcp/invoke
Run it with Cursor / Codex / Claude Desktop by configuring stdio MCP entry:
{
"command": "python3",
"args": ["/abs/path/to/mcp_bridge.py"],
"env": {"AI_TEST_BASE_URL": "http://127.0.0.1:8000"}
}
"""
from __future__ import annotations
import json
import os
import sys
from typing import Any
import httpx
def _env(primary: str, legacy: str, default: str = "") -> str:
return (os.environ.get(primary) or os.environ.get(legacy) or default).strip()
BASE_URL = _env("QIP_BASE_URL", "AI_TEST_BASE_URL", "http://127.0.0.1:8000").rstrip("/")
API_KEY = _env("QIP_API_KEY", "AI_TEST_API_KEY", "")
SERVER_NAME = "quality-inspection-platform"
SERVER_VERSION = "0.1.0"
PROTOCOL_VERSION = "2024-11-05"
def log(msg: str) -> None:
sys.stderr.write(f"[mcp_bridge] {msg}\n")
sys.stderr.flush()
def write_message(message: dict[str, Any]) -> None:
sys.stdout.write(json.dumps(message, ensure_ascii=False) + "\n")
sys.stdout.flush()
def fetch_tools() -> list[dict[str, Any]]:
try:
response = httpx.get(f"{BASE_URL}/mcp/tools", headers=_request_headers(), timeout=10.0)
response.raise_for_status()
payload = response.json() or {}
tools = payload.get("tools", [])
except Exception as exc:
log(f"fetch tools failed: {exc}")
return []
converted = []
for tool in tools:
converted.append(
{
"name": tool.get("name", ""),
"description": tool.get("description", ""),
"inputSchema": tool.get("input_schema") or {"type": "object"},
}
)
return converted
def _request_headers() -> dict[str, str]:
headers = {"Content-Type": "application/json"}
if API_KEY:
headers["Authorization"] = f"Bearer {API_KEY}"
return headers
def call_tool(name: str, arguments: dict[str, Any]) -> dict[str, Any]:
payload: dict[str, Any] = {"tool": name, "arguments": arguments or {}}
if API_KEY:
payload["api_key"] = API_KEY
try:
response = httpx.post(
f"{BASE_URL}/mcp/invoke",
json=payload,
headers=_request_headers(),
timeout=60.0,
)
response.raise_for_status()
return response.json() or {}
except Exception as exc:
return {"ok": False, "tool": name, "data": {}, "error": str(exc)}
def make_response(req_id: Any, result: Any) -> dict[str, Any]:
return {"jsonrpc": "2.0", "id": req_id, "result": result}
def make_error(req_id: Any, code: int, message: str) -> dict[str, Any]:
return {"jsonrpc": "2.0", "id": req_id, "error": {"code": code, "message": message}}
def handle_initialize(req_id: Any, _: dict[str, Any]) -> dict[str, Any]:
return make_response(
req_id,
{
"protocolVersion": PROTOCOL_VERSION,
"capabilities": {"tools": {"listChanged": False}},
"serverInfo": {"name": SERVER_NAME, "version": SERVER_VERSION},
"instructions": (
"Quality inspection platform (质量检测平台). Before complex tasks, read project file "
"docs/mcp_tools_for_ai.md (tool picker, auth, recipes). "
"Typical start: catalog_snapshot. "
"Batch runs: workflow_batch_create → workflow_batch_update → workflow_batch_run. "
"Writes and execution require sto- API Key (AI_TEST_API_KEY or QIP_API_KEY)."
),
},
)
def handle_tools_list(req_id: Any, _: dict[str, Any]) -> dict[str, Any]:
return make_response(req_id, {"tools": fetch_tools()})
def handle_tools_call(req_id: Any, params: dict[str, Any]) -> dict[str, Any]:
name = str(params.get("name", ""))
arguments = params.get("arguments") or {}
if not name:
return make_error(req_id, -32602, "missing tool name")
payload = call_tool(name, arguments)
text = json.dumps(payload, ensure_ascii=False, indent=2)
is_error = not bool(payload.get("ok", True))
return make_response(
req_id,
{
"content": [{"type": "text", "text": text}],
"isError": is_error,
},
)
HANDLERS = {
"initialize": handle_initialize,
"tools/list": handle_tools_list,
"tools/call": handle_tools_call,
}
def main() -> None:
log(f"started, base_url={BASE_URL}")
for raw_line in sys.stdin:
line = raw_line.strip()
if not line:
continue
try:
message = json.loads(line)
except json.JSONDecodeError as exc:
log(f"invalid json: {exc}")
continue
method = message.get("method")
req_id = message.get("id")
params = message.get("params") or {}
if method is None:
continue
if "id" not in message:
# JSON-RPC notification, no response required.
continue
handler = HANDLERS.get(method)
if handler is None:
write_message(make_error(req_id, -32601, f"method not found: {method}"))
continue
try:
response = handler(req_id, params)
except Exception as exc:
log(f"handler error: {exc}")
response = make_error(req_id, -32000, str(exc))
write_message(response)
if __name__ == "__main__":
main()

View File

@ -6,3 +6,4 @@ httpx
jinja2
python-multipart
paramiko
mcp>=1.6.0

View File

@ -5,6 +5,7 @@ set -euo pipefail
ROOT_DIR="$(cd "$(dirname "$0")" && pwd)"
BACKEND_PORT=8000
FRONTEND_PORT=5173
VENV_PYTHON="$ROOT_DIR/.venv/bin/python"
require_file() {
local path="$1"
@ -25,6 +26,26 @@ require_command() {
fi
}
ensure_venv() {
if [[ ! -x "$VENV_PYTHON" ]]; then
echo "创建 Python 虚拟环境 (.venv)..."
require_command python3
python3 -m venv "$ROOT_DIR/.venv"
fi
if ! "$VENV_PYTHON" -c "import fastapi, mcp, uvicorn" >/dev/null 2>&1; then
echo "安装/更新 Python 依赖 (requirements.txt)..."
"$VENV_PYTHON" -m pip install -r "$ROOT_DIR/requirements.txt"
fi
if ! "$VENV_PYTHON" -c "import fastapi, mcp, uvicorn" >/dev/null 2>&1; then
echo "Python 依赖安装失败,请检查网络或手动执行:"
echo " $VENV_PYTHON -m pip install -r requirements.txt"
read -r -k 1 "?按任意键关闭..."
exit 1
fi
}
is_port_listening() {
local port="$1"
lsof -iTCP:"$port" -sTCP:LISTEN -t >/dev/null 2>&1
@ -70,13 +91,15 @@ end run
APPLESCRIPT
}
require_file "$ROOT_DIR/.venv/bin/python" "未找到 Python 虚拟环境: .venv/bin/python"
require_file "$ROOT_DIR/frontend-admin/package.json" "未找到前端工程: frontend-admin/package.json"
require_file "$ROOT_DIR/requirements.txt" "未找到 requirements.txt"
require_command npm
require_command curl
require_command lsof
require_command osascript
ensure_venv
if ! is_port_listening "$BACKEND_PORT"; then
echo "启动后端..."
open_backend_terminal
@ -93,14 +116,16 @@ fi
echo "等待服务就绪..."
if ! wait_for_http "http://127.0.0.1:$BACKEND_PORT/health" 30; then
if ! wait_for_http "http://127.0.0.1:$BACKEND_PORT/health" 45; then
echo "后端启动失败,请检查新打开的 Terminal 窗口"
echo "常见原因: 依赖未装全 → 执行: .venv/bin/python -m pip install -r requirements.txt"
read -r -k 1 "?按任意键关闭..."
exit 1
fi
if ! wait_for_http "http://127.0.0.1:$FRONTEND_PORT/" 30; then
if ! wait_for_http "http://127.0.0.1:$FRONTEND_PORT/" 45; then
echo "前端启动失败,请检查新打开的 Terminal 窗口"
echo "常见原因: 未 npm install → 在 frontend-admin 目录执行 npm install"
read -r -k 1 "?按任意键关闭..."
exit 1
fi
@ -111,5 +136,6 @@ echo
echo "启动成功"
echo "前端: http://127.0.0.1:$FRONTEND_PORT/"
echo "后端: http://127.0.0.1:$BACKEND_PORT/"
echo "MCP: http://127.0.0.1:$BACKEND_PORT/mcp"
echo
read -r -k 1 "?按任意键关闭..."