feat: MMP 主站改用 /api/auth/login 自动鉴权

新增 mmp_auth.py,通过 MMP_USERNAME/PASSWORD 调用登录接口获取 Token;保留 MMP_BEARER_TOKEN 兼容;更新文档与 debug_auth 自检。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
somunslotus 2026-05-19 17:27:59 +08:00
parent 662dde740a
commit 82361568ad
8 changed files with 181 additions and 36 deletions

View File

@ -46,13 +46,14 @@ where python
```powershell
cd D:\技术文档\训练skills\mmp-training-workflow\mcp
copy .env.example .env
# 编辑 .env填入 MMP_BEARER_TOKEN、JSM_USERNAME、JSM_PASSWORD
# 编辑 .env填入 MMP_USERNAME、MMP_PASSWORD、JSM_USERNAME、JSM_PASSWORD
```
或在当前终端临时设置(仅本窗口有效):
```powershell
$env:MMP_BEARER_TOKEN = "你的ai4mats_Bearer_Token"
$env:MMP_USERNAME = "你的ai4mats用户名"
$env:MMP_PASSWORD = "你的密码"
$env:JSM_USERNAME = "云际用户名"
$env:JSM_PASSWORD = "云际密码"
$env:MMP_CLUSTER_ID = "1865927992266461184"
@ -60,7 +61,7 @@ $env:MMP_CLUSTER_ID = "1865927992266461184"
| 变量 | 说明 |
|------|------|
| `MMP_BEARER_TOKEN` | ai4mats 主站 Bearer**必填** |
| `MMP_USERNAME` / `MMP_PASSWORD` | ai4mats 主站账号MCP 自动登录换 Token |
| `JSM_USERNAME` / `JSM_PASSWORD` | 云际账号MCP 自动登录换 JSM Token |
| `MMP_CLUSTER_ID` | 资源集群 ID默认 `1865927992266461184` |
@ -88,7 +89,8 @@ python scripts/build_workflow_cli.py
"command": "C:/Users/你的用户名/anaconda3/envs/mmp-training/python.exe",
"args": ["D:/技术文档/训练skills/mmp-training-workflow/mcp/server.py"],
"env": {
"MMP_BEARER_TOKEN": "你的token",
"MMP_USERNAME": "你的ai4mats用户名",
"MMP_PASSWORD": "你的密码",
"JSM_USERNAME": "你的用户名",
"JSM_PASSWORD": "你的密码",
"MMP_CLUSTER_ID": "1865927992266461184"
@ -101,7 +103,7 @@ python scripts/build_workflow_cli.py
说明:
- 路径中的 `/``\\` 均可,建议使用正斜杠。
- MCP **不会自动读取** `.env`Token 必须写在 `mcp.json``env` 里(或改用下方 `conda run` 方式)。
- MCP **不会自动读取** `.env`账号密码须写在 `mcp.json``env` 里(或改用下方 `conda run` 方式)。
- 修改后 **重启 Cursor** 或 Reload MCP在 Settings → MCP 确认 `mmp-training` 已连接。
### 可选:用 `conda run` 启动(无需写死 python 路径)
@ -117,7 +119,8 @@ python scripts/build_workflow_cli.py
"D:/技术文档/训练skills/mmp-training-workflow/mcp/server.py"
],
"env": {
"MMP_BEARER_TOKEN": "你的token",
"MMP_USERNAME": "你的ai4mats用户名",
"MMP_PASSWORD": "你的密码",
"JSM_USERNAME": "你的用户名",
"JSM_PASSWORD": "你的密码",
"MMP_CLUSTER_ID": "1865927992266461184"
@ -159,7 +162,7 @@ Agent 应调用 MCP `run_training_pipeline`,超参示例:`global_params_json
| `conda: 无法识别` | 用 Anaconda Prompt或执行 `conda init powershell` 后重开终端 |
| MCP 无 tools | 检查 `python.exe` 路径是否为 **mmp-training** 环境;是否已 `pip install -r requirements.txt` |
| JSM 登录失败 | 检查 `JSM_USERNAME` / `JSM_PASSWORD` |
| MMP 401 | 更新 `MMP_BEARER_TOKEN` |
| MMP 401 | 检查 `MMP_USERNAME`/`MMP_PASSWORD` 或清空 Token 缓存后重试 |
| 修改依赖后 | `conda activate mmp-training``pip install -r requirements.txt` → 重启 Cursor |
## 更新环境

View File

@ -1,6 +1,9 @@
# MMP 主站(流水线 / 实验)— Bearer Token 需手动配置
# MMP 主站(流水线 / 实验)— 账号密码自动登录获取 Token
MMP_BASE_URL=https://www.ai4mats.com
MMP_BEARER_TOKEN=
MMP_USERNAME=
MMP_PASSWORD=
# 可选:静态 Bearer设置后跳过登录接口
# MMP_BEARER_TOKEN=
# JSM 云际(镜像 / 资源)— 自动登录获取 Token配置账号即可
JSM_BASE_URL=https://ai4m.jointcloud.net

View File

@ -9,7 +9,8 @@ mcp/
├── server.py # MCP 入口Cursor 启动此文件)
├── requirements.txt
├── mmp_training/ # 业务逻辑包(含中文注释)
│ ├── config.py # 环境变量、MMP Bearer
│ ├── config.py # 环境变量
│ ├── mmp_auth.py # MMP 自动登录
│ ├── jsm_auth.py # JSM 自动登录
│ ├── client.py # HTTP 客户端
│ ├── resolver.py # 资源名称解析
@ -51,7 +52,8 @@ pip install -r requirements.txt
| 变量 | 说明 |
|------|------|
| `MMP_BEARER_TOKEN` | ai4mats 主站 Bearer必填 |
| `MMP_USERNAME` / `MMP_PASSWORD` | ai4mats 主站账号,`POST /api/auth/login` 自动换 Token |
| `MMP_BEARER_TOKEN` | 可选,静态 Bearer设置后跳过登录接口 |
| `JSM_USERNAME` / `JSM_PASSWORD` | 云际账号,自动登录换 Token |
| `MMP_CLUSTER_ID` | 资源查询集群,默认 `1865927992266461184` |
| `MMP_BASE_URL` / `JSM_BASE_URL` | 可选,有默认值 |
@ -67,7 +69,8 @@ pip install -r requirements.txt
"command": "C:/Users/你的用户名/anaconda3/envs/mmp-training/python.exe",
"args": ["D:/技术文档/训练skills/mmp-training-workflow/mcp/server.py"],
"env": {
"MMP_BEARER_TOKEN": "你的token",
"MMP_USERNAME": "你的ai4mats用户名",
"MMP_PASSWORD": "你的密码",
"JSM_USERNAME": "你的用户名",
"JSM_PASSWORD": "你的密码",
"MMP_CLUSTER_ID": "1865927992266461184"
@ -134,7 +137,7 @@ python scripts/build_workflow_cli.py
```powershell
conda activate mmp-training
cd D:\技术文档\训练skills\mmp-training-workflow\mcp
# .env 放在服务器上的 mcp/.env含 MMP_BEARER_TOKEN、JSM 账号)
# .env 放在服务器上的 mcp/.env含 MMP_USERNAME/PASSWORD、JSM 账号)
# 注意:.env 里不要设置 MCP_TRANSPORT=http
$env:MCP_TRANSPORT = "streamable-http" # 也可写 http别名

View File

@ -1,8 +1,9 @@
"""
环境变量与鉴权配置
- MMPwww.ai4mats.com流水线实验代码/数据集/模型 使用 Bearer Token手动配置
- JSMai4m.jointcloud.net镜像资源规格 使用账号密码自动登录换 Token
- MMPwww.ai4mats.com流水线实验代码/数据集/模型 账号密码调用
``POST /api/auth/login`` 自动获取 Token ``mmp_auth.py``
- JSMai4m.jointcloud.net镜像资源规格 账号密码自动登录换 Token
启动时会自动加载 ``mcp/.env``若存在便于本地运行 ``scripts/debug_auth.py``
Cursor MCP 仍建议在 mcp.json env 中配置不读 .env
@ -33,7 +34,10 @@ MMP_BASE_URL = os.getenv("MMP_BASE_URL", "https://www.ai4mats.com").rstrip("/")
JSM_BASE_URL = os.getenv("JSM_BASE_URL", "https://ai4m.jointcloud.net").rstrip("/")
CLUSTER_ID = os.getenv("MMP_CLUSTER_ID", "1865927992266461184")
# MMP 主站 Bearer必填
# MMP 主站账号(用于 POST /api/auth/login
MMP_USERNAME = os.getenv("MMP_USERNAME", "").strip()
MMP_PASSWORD = os.getenv("MMP_PASSWORD", "").strip()
# 可选:静态 Bearer兼容旧配置优先于自动登录
MMP_BEARER_TOKEN = os.getenv("MMP_BEARER_TOKEN", "").strip()
# JSM 云际账号(用于 POST /jcc-admin/admin/login
@ -42,16 +46,7 @@ JSM_PASSWORD = os.getenv("JSM_PASSWORD", "").strip()
def mmp_auth_headers() -> dict[str, str]:
"""构造 MMP 请求头;未配置 Token 时抛出明确错误。"""
if not MMP_BEARER_TOKEN:
raise ValueError(
"缺少 MMP_BEARER_TOKEN请在环境变量或 Cursor mcp.json 的 env 中配置 ai4mats 的 Bearer Token"
)
token = MMP_BEARER_TOKEN
if not token.lower().startswith("bearer "):
token = f"Bearer {token}"
return {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": token,
}
"""构造 MMP 请求头(自动登录或静态 Token"""
from .mmp_auth import mmp_auth_headers as _headers
return _headers()

View File

@ -0,0 +1,122 @@
"""
MMPai4mats 主站自动登录与 Token 缓存
接口文档见流水线和实验接口.md
POST https://www.ai4mats.com/api/auth/login
"""
from __future__ import annotations
import time
from typing import Optional
import httpx
from .config import MMP_BASE_URL, MMP_BEARER_TOKEN, MMP_PASSWORD, MMP_USERNAME
# 内存缓存authorization 与过期时间戳(秒)
_cache: dict[str, Optional[float | str]] = {
"authorization": None,
"expires_at": 0.0,
}
# 提前 5 分钟刷新,避免边界时刻 401
_REFRESH_BUFFER_SEC = 5 * 60
def clear_mmp_token_cache() -> None:
"""调试或账号变更时清空缓存。"""
_cache["authorization"] = None
_cache["expires_at"] = 0.0
def _normalize_bearer(token: str) -> str:
t = token.strip()
if not t.lower().startswith("bearer "):
return f"Bearer {t}"
return t
def _parse_expires_at(expires_in: object, now: float) -> float:
"""
解析 expires_in平台返回绝对毫秒时间戳或相对秒数
示例1779268009845 绝对毫秒3600 相对秒
"""
if expires_in is None:
return now + 3600
try:
val = float(expires_in)
except (TypeError, ValueError):
return now + 3600
if val > 1e12:
return val / 1000.0
if val > 1e9:
return val
return now + val
def get_mmp_authorization() -> str:
"""
获取 MMP Authorization 头完整值例如 ``Bearer eyJ...``
优先使用环境变量 ``MMP_BEARER_TOKEN``静态 Token兼容旧配置
否则用 ``MMP_USERNAME`` / ``MMP_PASSWORD`` 调用登录接口并缓存
"""
if MMP_BEARER_TOKEN:
return _normalize_bearer(MMP_BEARER_TOKEN)
now = time.time()
auth = _cache.get("authorization")
expires_at = float(_cache.get("expires_at") or 0)
if auth and expires_at > now + _REFRESH_BUFFER_SEC:
return str(auth)
if not MMP_USERNAME or not MMP_PASSWORD:
raise ValueError(
"缺少 MMP_USERNAME / MMP_PASSWORDMMP 将调用 POST /api/auth/login 获取 Token"
"请在 .env 或 mcp.json 的 env 中配置;也可设置 MMP_BEARER_TOKEN 使用静态 Token"
)
url = f"{MMP_BASE_URL}/api/auth/login"
with httpx.Client(timeout=60.0) as client:
resp = client.post(
url,
json={"username": MMP_USERNAME, "password": MMP_PASSWORD},
headers={"Content-Type": "application/json", "Accept": "application/json"},
)
try:
data = resp.json()
except Exception as exc:
raise ValueError(f"MMP 登录返回非 JSON: {resp.text[:300]}") from exc
if resp.status_code >= 400:
raise ValueError(
f"MMP 登录 HTTP {resp.status_code}: "
f"{data.get('msg') or data.get('message') or resp.text[:200]}"
)
if data.get("code") not in (200, "200"):
raise ValueError(f"MMP 登录失败: {data.get('msg') or data.get('message') or 'unknown'}")
payload = data.get("data") or {}
access_token = payload.get("access_token") or payload.get("token")
if not access_token:
raise ValueError("MMP 登录响应缺少 data.access_token")
authorization = _normalize_bearer(str(access_token))
_cache["authorization"] = authorization
_cache["expires_at"] = _parse_expires_at(payload.get("expires_in"), now)
return authorization
def mmp_auth_headers() -> dict[str, str]:
"""MMP 请求通用头(含自动登录得到的 Authorization"""
return {
"Content-Type": "application/json",
"Accept": "application/json",
"Authorization": get_mmp_authorization(),
}

View File

@ -25,9 +25,12 @@ from mmp_training.config import (
JSM_USERNAME,
MMP_BASE_URL,
MMP_BEARER_TOKEN,
MMP_PASSWORD,
MMP_USERNAME,
mmp_auth_headers,
)
from mmp_training.jsm_auth import clear_jsm_token_cache, get_jsm_authorization
from mmp_training.mmp_auth import clear_mmp_token_cache, get_mmp_authorization
def main() -> None:
@ -35,9 +38,11 @@ def main() -> None:
print("MMP_BASE_URL:", MMP_BASE_URL)
print("JSM_BASE_URL:", JSM_BASE_URL)
print("MMP_CLUSTER_ID:", CLUSTER_ID)
print("MMP_USERNAME:", MMP_USERNAME or "未配置")
print("MMP_PASSWORD:", "已配置" if MMP_PASSWORD else "未配置")
print(
"MMP_BEARER_TOKEN:",
f"已配置 ({MMP_BEARER_TOKEN[:20]}...)" if MMP_BEARER_TOKEN else "未配置",
f"已配置 ({MMP_BEARER_TOKEN[:20]}...)" if MMP_BEARER_TOKEN else "未配置(将走登录接口)",
)
print("JSM_USERNAME:", JSM_USERNAME or "未配置")
print("JSM_PASSWORD:", "已配置" if JSM_PASSWORD else "未配置")
@ -88,10 +93,13 @@ def main() -> None:
exit_code = 1
try:
clear_mmp_token_cache()
auth = get_mmp_authorization()
print("[OK] MMP 登录成功")
print(" Authorization:", auth[:40] + "...\n")
mmp_auth_headers()
print("[OK] MMP_BEARER_TOKEN 已设置\n")
except Exception as e:
print("[FAIL] MMP:", e)
print("[FAIL] MMP 登录:", e)
sys.exit(1)
try:

View File

@ -13,7 +13,7 @@
| 基址 | 环境变量 | 说明 |
|------|----------|------|
| MMP | `MMP_BEARER_TOKEN` | 手动注入 Bearer Token |
| MMP | `MMP_USERNAME` / `MMP_PASSWORD` | `POST /api/auth/login` 自动换 Token可选 `MMP_BEARER_TOKEN` |
| JSM | `JSM_USERNAME`、`JSM_PASSWORD` | MCP 自动调用 `POST /jcc-admin/admin/login` 获取 Token |
勿将 Token/密码写入 Skill 或提交到 Git。

View File

@ -4241,7 +4241,7 @@ resp:
}
}
选择数据集:
选择数据集:is_public可以是false和true, 是true的话就是公开的数据集false是个人的数据集
GET https://www.ai4mats.com/api/mmp/newdataset/queryDatasets?is_public=false&page=0&size=2000&is_hot_stone=false
resp:
@ -5549,9 +5549,20 @@ resp
https://www.ai4mats.com/ 获取token接口
POST https://www.ai4mats.com/api/auth/login
req:
{
"username": "fanshuai",
"password": "h1n2x3j4y5@"
}
resp:
{
"code": 200,
"msg": null,
"data": {
"access_token": "eyJhbGciOiJIUzUxMiJ9.eyJ1c2VyX2lkIjoxLCJ1c2VyX2tleSI6IjdkOTQxZjg2LTdiYjktNGMzMy1iNzQ1LWFlMDE1YzA1OThhMiIsInVzZXJuYW1lIjoiZmFuc2h1YWkifQ.rCtmAweqoas3_oUFC8es2W024pFoffPDgEhhrXn8JSRq-UriYokDax8Yjc071qnFTeQdH8qKsadAHVKPK3LXHA",
"expires_in": 1779268009845
}
}