内容
# LeetCode MCP — 通过实践学习 MCP
本项目是 **两件事情的结合**:
1. 一个温和的、从头开始的教程,讲解 **什么是 MCP 以及如何构建一个 MCP**。
2. 一个可用的 **LeetCode MCP**,它连接到你的 LeetCode 个人资料,以便 AI 助手(Cursor / Claude)可以告诉你是否已解决某个问题,并按主题和难度推荐新问题。
---
## 第一部分 — 什么是 MCP?(思维模型)
**MCP = 模型上下文协议。** 它是一个开放标准,允许 AI 模型安全地使用 **外部工具和数据**。可以把它想象成 **AI 的 USB-C 接口**:任何支持 MCP 的 AI 客户端都可以插入到任何 MCP 服务器,并立即获得新的能力。
三种角色:
| 角色 | 它是什么 | 在我们的例子中 |
| ------------ | ----------------------------------------- | ------------------------- |
| **主机** | 你聊天的应用程序 | Cursor / Claude 桌面版 |
| **客户端** | 位于主机内部,支持 MCP | 集成到 Cursor 中 |
| **服务器** | 你的程序,提供工具/数据 | `server.py`(此仓库) |
MCP 服务器可以公开三种东西:
- **工具** — AI 可以调用的函数(例如 `recommend_questions`)。← 我们使用这个
- **资源** — AI 可以加载的只读数据(像文件一样)。
- **提示** — 可重用的提示模板。
**它们如何通信:** 主机将你的服务器作为子进程启动,并通过 **标准输入/输出** 交换 JSON-RPC 消息。你不需要管理这个过程 — SDK 会处理。你只需要编写 Python 函数并装饰它们。
SDK 的魔力:一个函数的 **名称**、**文档字符串** 和 **类型提示** 会自动转换为 AI 读取的模式,以便知道何时/如何调用它。这就是为什么我们的函数有描述性的名称和详细的文档字符串。
```python
@mcp.tool()
async def recommend_questions(topic: str, difficulty: str, count: int = 5) -> dict:
"""按主题和难度推荐 LeetCode 问题..."""
...
```
这个装饰器是创建 MCP 的 90%。其他部分都是普通的代码。
---
## 第二部分 — 这个 MCP 的功能
它公开了三个工具:
| 工具 | 它做什么 |
| -------------------- | ------------------------------------------------------------------- |
| `get_profile` | 你的已解决计数(简单/中等/困难)+ 全球排名。 |
| `check_if_solved` | "我是否解决了 *Two Sum*?"→ 已解决 / 已尝试 / 从未尝试。 |
| `recommend_questions`| 查找新的问题,按主题 + 难度,**跳过已解决的问题**。 |
### 数据来源
LeetCode **没有官方公共 API**,但其网站运行在 `https://leetcode.com/graphql` 的 GraphQL 端点上。我们查询它的方式与网站相同(参见 `leetcode_client.py`)。
- **公共数据**(问题列表、难度、主题)无需登录。
- **你的私人 "已解决" 状态** 需要你的浏览器会话 cookie,以便 LeetCode 知道是你。有了这些,每个问题都带有 `status`:`"ac"` = 已解决,`"notac"` = 已尝试,`null` = 从未尝试。
---
## 第三部分 — 设置(5 分钟)
### 1. 安装依赖项
```bash
cd /Users/akshayapratapsingh/Desktop/Leetcode-MCP
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
```
### 2. 配置你的身份 + cookie
```bash
cp .env.example .env
```
然后编辑 `.env`:
- `LEETCODE_USERNAME` → 你的用户名,来自 `leetcode.com/u/<username>/`。
- `LEETCODE_SESSION` 和 `LEETCODE_CSRF`(可选但推荐)→ 这些解锁个人 "已解决 / 未解决" 检测。
**如何获取 cookie:**
1. 在 Chrome 中登录 <https://leetcode.com>。
2. 打开开发者工具(`F12` 或 `Cmd+Option+I`)。
3. 进入 **应用程序 → 存储 → Cookie → https://leetcode.com**。
4. 复制 `LEETCODE_SESSION` 的 **值** → 粘贴到 `.env` 中。
5. 复制 `csrftoken` 的 **值** → 粘贴到 `.env` 中,作为 `LEETCODE_CSRF`。
> ⚠️ 这些 cookie 就像密码一样。`.env` 已经在 `.gitignore` 中,因此它不会被提交。永远不要分享它们。它们每 ~2 周过期;当你的工具停止看到你的进度时,只需重新复制它们。
### 3. (可选)无需 AI 的本地快速测试
```bash
source .venv/bin/activate
python smoke_test.py # 打印你的个人资料 + 一些推荐
```
---
## 第四部分 — 连接到 Cursor
Cursor 从 JSON 配置文件中读取 MCP 服务器。创建/编辑 `~/.cursor/mcp.json`(全局)**或** 在此项目中编辑 `.cursor/mcp.json`,并添加:
```json
{
"mcpServers": {
"leetcode": {
"command": "/Users/akshayapratapsingh/Desktop/Leetcode-MCP/.venv/bin/python",
"args": ["/Users/akshayapratapsingh/Desktop/Leetcode-MCP/server.py"]
}
}
}
```
一个现成的副本已经存在于此仓库的 `.cursor/mcp.json` 中。
然后:**Cursor 设置 → MCP** → 你应该看到 `leetcode` 带有一个绿色点和它的 3 个工具。启用它。(对于 Claude Desktop,相同的块位于 `claude_desktop_config.json` 中。)
### 在聊天中试用
- "使用 leetcode,我是否解决了 *Two Sum*?"
- "推荐 5 个我尚未解决的中等动态编程问题。"
- "我的 LeetCode 个人资料摘要是什么?"
- "给我难度为中等、我尚未解决的图问题。"
---
## 第 5 部分 — 代码组织方式
```
Leetcode-MCP/
├── server.py # MCP 服务器:定义 3 个工具(从这里开始)
├── leetcode_client.py # 与 LeetCode 的 GraphQL API 通信
├── smoke_test.py # 直接运行工具,无需 AI
├── requirements.txt # Python 依赖项
├── .env.example # 你的用户名 + cookie 的模板
├── .cursor/mcp.json # Cursor 集成配置
└── README.md # 本文
```
**学习顺序:** `server.py`(工具 + 装饰器)→ `leetcode_client.py`(API 调用)→ `smoke_test.py`(如何调用它们)。
---
## 第 6 部分 — 扩展它的想法(非常适合提升技能)
- 添加 `get_daily_challenge`(LeetCode 的每日问题)。
- 添加 `get_recent_submissions(username)` 以显示你的最新 AC。
- 添加一个 **资源**,将你的已解决列表作为可浏览的文档公开。
- 缓存结果,以便你不必每次调用都重新访问 LeetCode。
- 跟踪 "学习计划",并在精选列表中推荐下一个问题。
---
## 故障排除
| 问题 | 解决方法 |
| ------- | --- |
| 工具显示 "solved: null" 或关于会话的说明 | 在 `.env` 中添加有效的 `LEETCODE_SESSION` + `LEETCODE_CSRF`。 |
| Cursor 显示服务器红色 / 未连接 | 在 `mcp.json` 中使用 **绝对路径**;确认虚拟环境 Python 路径存在。 |
| `User not found` | 检查 `LEETCODE_USERNAME` 是否与你的个人资料 URL 完全匹配。 |
| 空推荐 | 放宽过滤器(删除主题或难度),或设置 `include_paid=true`。 |
| 来自 LeetCode 的 403 / 错误 | cookie 过期 — 从浏览器中重新复制它们。 |