在 Cursor 中安装 Montis.icu MCP 教练

概述

Montis.icu Coach 是基于 Intervals.icu 的开源 AI 耐力教练,官方支持 ChatGPT App、Claude MCP 和 Gemini App。本文介绍如何将其 MCP(Model Context Protocol) 接入 Cursor,让 Cursor 的 Agent 调用 Montis 工具,解读 Weekly Report、数据质量审计等报告。

Montis 采用两层架构:

层级负责方职责
分析引擎(URF)Montis 云端从 Intervals.icu 拉取数据,运行确定性生理学分析,生成报告
语言层Cursor / ChatGPT / Claude 等调用 MCP 工具、解读报告、回答追问

因此:Cursor 模型不会替代 Montis 做运动生理学计算,而是作为对话界面,配合 Montis 工具使用。

前置条件

  • 已注册 Intervals.icu 账号,并有训练数据同步
  • 已安装 Cursor(建议 v1.0 及以上,支持 MCP)
  • 已安装 Node.js(用于 npx 运行 mcp-remote
  • 网络可访问 https://montis.icu

第一步:绑定 Intervals.icu(只需一次)

Montis 需要两层授权,第一层是 Montis 与 Intervals.icu 的数据连接:

  1. 浏览器打开 https://geminiapp.montis.icu
  2. 登录 Montis 账号
  3. 点击 Connect Intervals.icu,勾选全部权限并批准
  4. 确认页面显示 Intervals.icu 已连接

注意: 此步骤与 Cursor MCP OAuth 是两套独立授权。在 geminiapp 登录成功,不代表 Cursor 侧已完成 MCP 授权。

第二步:配置 Cursor MCP

配置文件位置

全局配置路径(Windows):

%USERPROFILE%\.cursor\mcp.json

例如:C:\Users\你的用户名\.cursor\mcp.json

推荐配置:mcp-remote 桥接

Cursor 原生远程 MCP OAuth 存在已知问题:日志显示已发起授权,但浏览器不一定弹出。推荐使用 mcp-remote 作为本地桥接:

{
  "mcpServers": {
    "montis-icu": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote@latest",
        "https://montis.icu/mcp",
        "3334",
        "--host",
        "127.0.0.1",
        "--static-oauth-client-info",
        "{\"client_id\":\"intervals-mcp\"}",
        "--auth-timeout",
        "120"
      ]
    }
  }
}

配置说明:

参数含义
3334OAuth 回调端口(Montis 白名单端口,必须紧跟 URL 之后)
--host 127.0.0.1避免 Windows 上 localhost 解析到 IPv6 导致回调失败
intervals-mcpMontis 官方 OAuth Client ID,不要改成运动员 ID

保存文件时的编码要求

mcp.json 必须是 UTF-8 无 BOM。若被保存为 UTF-16,Cursor 会报 JSON 语法错误(显示 {□□□□ 乱码)。

PowerShell 或部分编辑器容易写入 UTF-16,建议用 VS Code / Cursor 另存为 UTF-8,或在 Git Bash 中写入:

cat > ~/.cursor/mcp.json << 'EOF'
{
  "mcpServers": {
    "montis-icu": {
      "command": "npx",
      "args": [
        "-y",
        "mcp-remote@latest",
        "https://montis.icu/mcp",
        "3334",
        "--host",
        "127.0.0.1",
        "--static-oauth-client-info",
        "{\"client_id\":\"intervals-mcp\"}",
        "--auth-timeout",
        "120"
      ]
    }
  }
}
EOF

备选:Cursor 原生远程 MCP(可能需手动授权)

若你的 Cursor 版本 OAuth 正常,也可尝试直连:

{
  "mcpServers": {
    "montis-icu": {
      "url": "https://montis.icu/mcp",
      "auth": {
        "CLIENT_ID": "intervals-mcp"
      }
    }
  }
}

若 Connect 按钮无反应或浏览器不弹出,请改用上文的 mcp-remote 方案。

第三步:完成 MCP OAuth 授权

方法一:独立 OAuth 脚本(推荐,最稳定)

mcp-remote 有时会在打开浏览器前未稳定监听回调端口,导致授权完成后浏览器报 「localhost 拒绝了连接」。可用独立脚本:先启动 127.0.0.1:3334 监听,再打开浏览器。

  1. 在 Cursor 中 关闭 montis-icu MCP 开关(避免端口冲突)
  2. 将以下脚本保存为 %USERPROFILE%\.cursor\montis-oauth.js
  3. 在外部终端(Windows Terminal / Git Bash)运行:
node %USERPROFILE%\.cursor\montis-oauth.js
  1. 浏览器自动打开 Montis 授权页,登录并点击 Approve
  2. 看到 Montis authorization successful 后,回到 Cursor:
    • Ctrl+Shift+PReload Window
    • 打开 Settings → Tools & MCP,开启 montis-icu

montis-oauth.js 完整内容:

const http = require("http");
const crypto = require("crypto");
const fs = require("fs");
const path = require("path");
const { exec } = require("child_process");

const SERVER_HASH = "4fc5af4c74b51ffd2695d25415247135";
const CONFIG_DIR = path.join(
  process.env.USERPROFILE || process.env.HOME,
  ".mcp-auth",
  "mcp-remote-0.1.37"
);
const CLIENT_ID = "intervals-mcp";
const REDIRECT_URI = "http://127.0.0.1:3334/oauth/callback";
const PORT = 3334;

function base64url(buf) {
  return buf
    .toString("base64")
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
}

const codeVerifier = base64url(crypto.randomBytes(32));
const codeChallenge = base64url(
  crypto.createHash("sha256").update(codeVerifier).digest()
);
const state = crypto.randomUUID();

const authUrl = new URL("https://montis.icu/mcp/oauth/authorize");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("client_id", CLIENT_ID);
authUrl.searchParams.set("code_challenge", codeChallenge);
authUrl.searchParams.set("code_challenge_method", "S256");
authUrl.searchParams.set("redirect_uri", REDIRECT_URI);
authUrl.searchParams.set("state", state);
authUrl.searchParams.set("scope", "mcp");
authUrl.searchParams.set("resource", "https://montis.icu/mcp");

const server = http.createServer(async (req, res) => {
  const url = new URL(req.url, `http://127.0.0.1:${PORT}`);
  if (url.pathname !== "/oauth/callback") {
    res.writeHead(404);
    res.end("Not found");
    return;
  }

  const code = url.searchParams.get("code");
  if (!code) {
    res.writeHead(400);
    res.end("Missing authorization code");
    return;
  }

  try {
    const params = new URLSearchParams({
      grant_type: "authorization_code",
      code,
      code_verifier: codeVerifier,
      redirect_uri: REDIRECT_URI,
      client_id: CLIENT_ID,
      resource: "https://montis.icu/mcp",
    });

    const tokenRes = await fetch("https://montis.icu/mcp/oauth/token", {
      method: "POST",
      headers: {
        "Content-Type": "application/x-www-form-urlencoded",
        Accept: "application/json",
      },
      body: params,
    });

    const tokens = await tokenRes.json();
    if (!tokenRes.ok) {
      throw new Error(JSON.stringify(tokens));
    }

    fs.mkdirSync(CONFIG_DIR, { recursive: true });
    fs.writeFileSync(
      path.join(CONFIG_DIR, `${SERVER_HASH}_code_verifier.txt`),
      codeVerifier
    );
    fs.writeFileSync(
      path.join(CONFIG_DIR, `${SERVER_HASH}_tokens.json`),
      JSON.stringify(tokens, null, 2)
    );

    res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
    res.end(
      "<h1>Montis authorization successful</h1><p>You can close this tab and return to Cursor.</p>"
    );
    console.log("Tokens saved to", CONFIG_DIR);
    setTimeout(() => process.exit(0), 500);
  } catch (err) {
    console.error("Token exchange failed:", err.message);
    res.writeHead(500);
    res.end("Token exchange failed: " + err.message);
    setTimeout(() => process.exit(1), 500);
  }
});

server.listen(PORT, "127.0.0.1", () => {
  console.log("OAuth callback listening on", REDIRECT_URI);
  console.log("Opening browser...");
  console.log(authUrl.toString());
  exec(`start "" "${authUrl.toString()}"`);
});

Token 保存位置:

%USERPROFILE%\.mcp-auth\mcp-remote-0.1.37\4fc5af4c74b51ffd2695d25415247135_tokens.json

方法二:在 Cursor Agent 中触发授权

  1. 开启 montis-icu MCP 开关
  2. Ctrl+Shift+PReload Window
  3. 在 Agent 对话中发送:Run a data quality audit
  4. 若浏览器弹出 Montis 授权页,完成登录并批准
  5. 授权完成前不要关闭 MCP 开关或 Reload Window

若浏览器跳转到 localhost 却连接被拒,说明回调时监听进程已退出,请改用法一。

第四步:验证安装

授权成功后,在 Cursor Agent 中尝试以下指令:

Run a data quality audit
Run a weekly report
Analyze my last activity

正常表现:

  • Settings → Tools & MCPmontis-icu 显示已连接,约 30+ 个工具
  • 工具调用不再返回 Unauthorized
  • Agent 能返回 Montis 生成的结构化报告

常见问题排查

现象可能原因解决方法
JSON 语法错误,显示 {□□□□mcp.json 被保存为 UTF-16用 UTF-8 无 BOM 重新写入
MCP 已连接但工具返回 UnauthorizedOAuth 未完成或 token 过期运行 montis-oauth.js 重新授权
Connect 按钮无反应Cursor 远程 MCP OAuth Bug改用 mcp-remote 桥接
localhost 拒绝了连接回调端口无进程监听用法一;确保端口 3334,host 为 127.0.0.1
端口一直是随机值(如 23756)3334 参数位置错误端口必须紧跟 URL 之后,见推荐配置
反复 user_logoutOAuth 状态异常关闭 MCP 开关 → Reload → 重新授权
CLIENT_ID 被改成运动员 ID手动改错配置改回 intervals-mcp
只列出工具、不弹浏览器Montis 允许未认证访问 tools/list实际调用工具时才需 token,或运行 OAuth 脚本
Token 过期刷新 token 失败关闭 MCP 开关,运行 node montis-oauth.js

重新授权步骤(Token 过期时)

# 1. 在 Cursor 中关闭 montis-icu 开关
# 2. 运行 OAuth 脚本
node %USERPROFILE%\.cursor\montis-oauth.js
# 3. 浏览器完成授权后,Reload Window 并重新开启 MCP

使用示例

安装完成后,可在 Cursor Agent 中进行训练分析对话,例如:

  • Run a data quality audit — 检查 Intervals.icu 数据完整性
  • Run a weekly report — 生成本周训练负荷与恢复建议
  • Analyze my last ride — 分析最近一次骑行(功率、心率解耦、热应激等)
  • What's my current CTL/ATL/TSB? — 查询当前训练状态

Montis 会根据 Intervals.icu 中的功率、心率、Wellness(HRV、睡眠等)数据,结合 URF 引擎输出结构化报告;Cursor 模型负责解读报告并回答追问。

备选方案

若 Cursor MCP 仍不稳定,可使用 Montis 官方其他入口:

方案地址说明
Gemini Appgeminiapp.montis.icu网页对话,OAuth 最稳定
ChatGPT AppMontis 官方 GPT需在 ChatGPT 中安装
Claude MCPClaude Desktop / Code官方原生支持
Codex CLIcodex mcp add montis-icu --url https://montis.icu/mcp --oauth-client-id intervals-mcp命令行 Agent

参考链接