内容
<div align="center">
# 🔗 LinkedIn 内部 API 参考及 MCP 服务器
**一个反向工程的、诚实记录的 LinkedIn 私有 Web API 客户端,以及一个现成的 MCP 服务器,允许 AI 助手为你读写 LinkedIn。**
`Voyager + SDUI` · `130 端点(141 个原始捕获)` · `26 个 MCP 工具` · `browserless-first` · `每个 ✅ 从真实流量中捕获`
</div>
---
> ⚠️ **免责声明 — 使用前请阅读。** 这是一个独立的 **研究 / 个人自动化** 项目。它与 LinkedIn **无关,不受其支持或连接**。它自动化了 LinkedIn 的 **私有** Web API,这在 LinkedIn 的服务条款下是一个灰色地带。请 **仅在您拥有的帐户上使用**,并自行承担风险;自动化活动可能会导致帐户被限制或封禁。无担保(参见 [`LICENSE`](LICENSE))。LinkedIn 会在其部署中轮换 GraphQL 查询 ID 哈希,因此端点可能会在没有通知的情况下中断。
---
## 🎯 这是什么?
LinkedIn 的 **官方** API 无法编辑您的个人资料、发送消息、管理您的网络或完全读取您自己的帖子。但 **LinkedIn 网站** 可以通过与两个私有后端 API 通信来完成所有这些操作。本项目 **反向工程了这些 API**,从真实的登录流量中记录每一个端点,并附上诚实的文档(已验证 ✅ 与发现 🔍 或推断 🔩),并附带一个 **模型上下文协议(MCP)服务器**,以便像 Claude 这样的工具可以以编程方式驱动一个 LinkedIn 帐户。
> **每个标记为 ✅ 的端点都是从实时流量中捕获并针对真实客户端执行的 — 从未被猜测过。** 在捕获过程中创建的测试数据被删除,并在之后被验证为干净。**范围:** 控制帐户 **所有者自己的** 个人资料 — 编辑、帖子、消息、评论、网络。**身份从未存储在仓库中** — 您通过环境变量提供它。
---
## 🗺️ 两个 API 世界
LinkedIn 的前端与 **两个并行后端** 通信。许多功能仅存在于一个后端 — 您需要了解这两个后端。
```mermaid
flowchart TD
B["🌐 浏览器(已登录)<br/>cookies: li_at + JSESSIONID"]
B --> V["<b>VOYAGER</b><br/>/voyager/api/…<br/>REST.li + GraphQL<br/><i>已建立 · 广泛</i>"]
B --> S["<b>SDUI</b>(rsc-action)<br/>/flagship-web/rsc-action/…<br/>服务器驱动 UI · protobuf-JSON<br/><i>较新 · 增长中</i>"]
V --> V1["读取 · 点赞 · 创建帖子<br/>关注公司 · 连接 · 所有消息"]
S --> S1["取消点赞 · 删除帖子 · 评论<br/>所有个人资料编辑 · 推荐 · 删除连接"]
style B fill:#0a66c2,color:#fff
style V fill:#e8f0fe,color:#000
style S fill:#fef7e8,color:#000
```
**经验法则:** 如果它适用于 Voyager,请使用它(稳定,无浏览器)。仅在 Voyager 失败时才使用 SDUI。请注意混合模式 — **创建帖子 = Voyager,删除帖子 = SDUI;点赞 = Voyager,取消点赞 = SDUI。**
---
## 🔌 Browserless-first
关键发现:**大多数操作不需要浏览器。** 使用新鲜的会话 cookie,普通的 Python `requests` 直接命中 Voyager。隐身浏览器仅作为 **会话源** 和少数需要页面绑定上下文的 SDUI 写入的后备。
```mermaid
sequenceDiagram
participant U as 你(一次)
participant SB as 隐身浏览器
participant C as 客户端(requests)
participant LI as LinkedIn API
U->>SB: 登录一次
SB->>SB: 持久化会话(重启后仍然存在)
SB-->>C: 转储 cookies(li_at,JSESSIONID)
Note over C,LI: 从这里开始 — 没有浏览器
C->>LI: GET /voyager/api/… (读取)
LI-->>C: 200 JSON
C->>LI: POST … (点赞 · 帖子 · 消息 · 关注)
LI-->>C: 200 / 201
Note over C,SB: 只有需要当前Actor的SDUI写入<br/>才回退到浏览器
```
**为什么是隐身?** 普通的自动化 Chrome 很快就会被登录退出。持久的 patchright 上下文(`navigator.webdriver = false`)在崩溃后仍然保持登录状态 — 您只需 **登录一次**。
---
## 🧩 `states[]` 突破
SDUI 个人资料表单看起来无法重放 — 它们的字段值是 opaque `MemoryNamespace` 状态引用。但它们会 **两次** 发送每个值:也作为顶级 `states[]` 数组中的实际字面量。这使得它们可以从纯 `requests` 中重放。一个带有自生成 uuid 和 `states[]` 值的 `saveProfileLanguageForm` 创建被重放为 **HTTP 200,对个人资料进行实时更改。** → `docs/BROWSERLESS-REPLAY.md`
---
## 🤖 MCP 服务器(26 个工具)
`mcp/` 是一个 [FastMCP](https://github.com/jlowin/fastmcp) 服务器,以工具形式公开 API,任何 MCP 客户端(Claude Desktop、Cursor 等)都可以调用。
```mermaid
flowchart LR
AI["🤖 Claude / Cursor"] -->|MCP| SRV["server.py<br/>26 @mcp.tool"]
SRV --> CL["client.py<br/>requests-first"]
CL -->|browserless| LI["LinkedIn Voyager"]
CL -. 备用 .-> BR["隐身浏览器"]
BR -.-> LI
style AI fill:#0a66c2,color:#fff
style SRV fill:#e8f0fe,color:#000
```
| 领域 | 工具 |
|---|---|
| **读取**(browserless) | `get_me` · `get_my_posts` · `get_profile` · `get_notifications` · `get_conversations` · `get_connections_summary` · `get_post_comments` · `get_link_preview` |
| **帖子** | `create_post` (+poll) · `edit_post` · `delete_post` · `create_poll` · `save_post` · `repost` · `delete_repost` |
| **互动** | `like` (browserless)· `unlike` (浏览器) |
| **消息** | `send_dm` · `recall_message` · `react_to_message` |
| **网络** | `follow_company` · `connect` · `endorse_skill` · `remove_connection` |
| **会话** | `session_status` · `refresh_session` |
🔒 **防护措施:** 面向人 / 破坏性工具(`create_post`、`edit_post`、`delete_post`、`send_dm`、`recall_message`、`repost`、`delete_repost`、`connect`、`remove_connection`)需要 `confirm=True`。
### 运行它
```bash
cd mcp
uv venv .venv --python 3.11
uv pip install --python .venv/bin/python fastmcp patchright requests
.venv/bin/patchright install chromium
# 您的身份来自环境 — 仓库中没有硬编码:
export LI_OWNER_URN="urn:li:fsd_profile:<your-id>"
export LI_OWNER_VANITY="<your-vanity-name>"
.venv/bin/python tests/test_server.py # offline → 4/4
.venv/bin/python tests/test_client.py # offline → 19/19
.venv/bin/python bootstrap_login.py # 首次运行:在隐身窗口中登录一次
.venv/bin/python server.py # stdio(Claude Desktop / Cursor / Hermes)
```
---
## 🔬 方法:点击并记录
您无法猜测写入端点 — 请求体是深度嵌套且特定于部署的。唯一可靠的方法:**执行实际操作,记录它触发的确切请求。**
```mermaid
flowchart LR
A["执行操作<br/>(例如点击'点赞')"] --> B["捕获<br/>网络请求"]
B --> C["提取端点<br/>+ 正文 + ID"]
C --> D["通过请求重放"]
D --> E{"browserless?"}
E -->|是| F["✅ 文档 + 工具"]
E -->|500| G["浏览器后备<br/>+ 诚实注释"]
```
读取由自我发现的爬虫找到;写入通过点击并记录。→ `docs/05-VERIFICATION.md`,`CAPTURE-PLAYBOOK.md` 中的可重复使用程序。
---
## ✅ 覆盖率
- **个人资料编辑** — 16 个部分 **已记录**(反向工程 `saveProfile<X>Form` 模式)。持久化捕获工件支持子集:5 个的完整添加/编辑/删除,3 个仅添加,1 个仅删除 — 每个部分的文档(`docs/09`–`21`)都说明了其确切状态。*(参考文档 — 个人资料编辑未作为 MCP 工具公开;26 个工具是读取/帖子/消息/网络。)*
- **帖子** — 创建、编辑、删除、投票、媒体、@提及、保存/取消保存、转发、链接预览
- **消息** — 发送、撤销、反应、列出对话(所有browserless)
- **互动** — 点赞(browserless)、取消点赞(浏览器)
- **网络** — 连接(+备注)、推荐技能、删除连接、关注公司
- **读取** — 个人资料、自己的帖子 *全文*(Composio 无法填补的空白)、通知、收件箱
- **联系信息** — 保存端点捕获
**开放(诚实记录):** 哈希标签关注、个人资料语言、照片/封面上传、职位发布、设置、推荐、引用转发。
---
## 📚 文档地图
| 开始这里 | 什么 |
|---|---|
| **[`docs/00-OVERVIEW.md`](docs/00-OVERVIEW.md)** | 两个 API 世界,决策规则 |
| **[`docs/COVERAGE-MAP.md`](docs/COVERAGE-MAP.md)** | 每个函数 → 端点 → 状态(实时检查清单) |
| **[`docs/ENDPOINTS.md`](docs/ENDPOINTS.md)** | 所有端点(130 个不同 / 141 个原始)+ 已验证写入表 |
| `docs/01` · `02` · `03` | 身份验证/cookies · Voyager 深入分析 · SDUI 深入分析 |
| `docs/04-WRITE-OPERATIONS.md` · `docs/05` | 每个已验证写入,带有请求正文 · 馈送/读取发现 |
| `docs/06` · `07` · `08` | 消息 · 评论 · 网络 |
| `docs/09`–`22` | 个人资料编辑(16 个部分 + 联系信息 + 关于 + 开放工作) |
| `docs/10` · `23` | 帖子交互(点赞/保存/转发)· 读取和发现端点 |
| `docs/24` · `25` | 高级帖子(编辑/投票/媒体/提及)· 网络和联系操作 |
| `docs/BROWSERLESS-REPLAY.md` | 深入 `states[]` 查找 |
| `docs/MCP-DESIGN.md` | 为什么服务器的设计如此 |
---
## ⚠️ 注意事项
- **仓库中没有个人身份信息。** 所有者 URN / vanity 来自 `LI_OWNER_URN` / `LI_OWNER_VANITY`;文档中的个人标识符是占位符(`OWNER_PROFILE_ID`、`alex-rivera`、`SECTION_ITEM_ID`、`COMPANY_ID`,...)。一些结构常量在公共且非个人时仍保留为真实值(例如 Microsoft 的公司 ID `1035` 在测试中,GraphQL `queryId` 哈希,LinkedIn URN 类型名称)。
- **服务条款。** 自动化 LinkedIn 是其服务条款下的灰色地带。本项目是用于 **自己的** 帐户的研究/个人自动化项目。请负责任地使用,并自行承担风险。
- **无担保。** LinkedIn 在部署时轮换 GraphQL 查询 ID 哈希;当调用开始返回 404 时,捕获当前的哈希(文档说明了在哪里)。