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:
parent
662dde740a
commit
82361568ad
|
|
@ -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 |
|
||||
|
||||
## 更新环境
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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(别名)
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
"""
|
||||
环境变量与鉴权配置。
|
||||
|
||||
- MMP(www.ai4mats.com):流水线、实验、代码/数据集/模型 — 使用 Bearer Token(手动配置)
|
||||
- JSM(ai4m.jointcloud.net):镜像、资源规格 — 使用账号密码自动登录换 Token
|
||||
- MMP(www.ai4mats.com):流水线、实验、代码/数据集/模型 — 账号密码调用
|
||||
``POST /api/auth/login`` 自动获取 Token(见 ``mmp_auth.py``)
|
||||
- JSM(ai4m.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()
|
||||
|
|
|
|||
|
|
@ -0,0 +1,122 @@
|
|||
"""
|
||||
MMP(ai4mats 主站)自动登录与 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_PASSWORD:MMP 将调用 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(),
|
||||
}
|
||||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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。
|
||||
|
|
|
|||
17
流水线和实验接口.md
17
流水线和实验接口.md
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue