内容
# RuneLite Dev MCP
RuneLite Dev MCP 服务端,为 RuneLite 提供只读的 [模型上下文协议](https://modelcontextprotocol.io) 服务,将实时游戏状态通过 JSON-RPC 和 REST 接口暴露给 AI 助手(如 Claude 等),监听地址为 `localhost:3000`。
设计上仅为只读 - 仅允许游戏状态观察,不支持点击、输入或自动化。
## 演示
工具通过 JSON-RPC 2.0 在 `POST /mcp` 接口调用,使用标准的 MCP `tools/call` 方法。每个调用返回一个结果信封,包含工具的 JSON 输出作为 `text` 内容块。下面显示的响应为工具输出(`result.content[0].text` 的值,解析后)- 为简洁起见,省略了包装信封。
### 筛选银行中的护身符
请求:
```json
{
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {
"name": "bank",
"arguments": {"n": "amulet"}
}
}
```
响应(工具输出):
```json
{
"_meta": {"gameTick": 184213},
"open": true,
"total": 312,
"filter": "amulet",
"matched": 2,
"items": [
{"slot": 42, "id": 6585, "name": "护身符", "quantity": 1, "actions": ["Withdraw-1", "Withdraw-5", "Withdraw-10"]},
{"slot": 43, "id": 1712, "name": "荣耀护身符", "quantity": 1, "actions": ["Withdraw-1", "Withdraw-5", "Withdraw-10"]}
]
}
```
注意:`bank` 仅返回物品标识(`id`、`name`、`quantity`、`actions`)。它**不**返回装备属性 - 如果客户端想要按强度奖励排序护身符,则必须将物品 ID 映射到其自己的数据源。
### 玩家周围的 NPC
请求:
```json
{
"jsonrpc": "2.0",
"id": 2,
"method": "tools/call",
"params": {
"name": "npc",
"arguments": {}
}
}
```
响应(最近 15 个,按 Chebyshev/Manhattan 距离从玩家排序):
```json
{
"_meta": {"gameTick": 184215},
"total": 5,
"shown": 5,
"items": [
{"index": 12, "id": 494, "name": "银行家", "pos": [3094, 3492, 0], "hp": [-1, -1], "animation": -1, "actions": ["Bank", "Examine"], "dist": 2},
{"index": 18, "id": 495, "name": "银行家", "pos": [3093, 3492, 0], "hp": [-1, -1], "animation": -1, "actions": ["Bank", "Examine"], "dist": 3},
{"index": 23, "id": 6362, "name": "银行家导师", "pos": [3091, 3493, 0], "hp": [-1, -1], "animation": -1, "actions": ["Talk-to", "Examine"], "dist": 4},
{"index": 31, "id": 3375, "name": "预言家", "pos": [3088, 3490, 0], "hp": [-1, -1], "animation": -1, "actions": ["Talk-to", "Examine"], "dist": 8},
{"index": 47, "id": 3105, "name": "Lawgof 上尉", "pos": [3084, 3496, 0], "hp": [-1, -1], "animation": -1, "actions": ["Talk-to", "Examine"], "dist": 12}
]
}
```
### 最近 20 个刻度(增量缓冲区)
请求:
```json
{
"jsonrpc": "2.0",
"id": 3,
"method": "tools/call",
"params": {
"name": "buffer",
"arguments": {"t": -20, "types": "player"}
}
}
```
响应(稀疏 - 没有变化的刻度被丢弃并计入 `ticksOmitted`):
```json
{
"_meta": {"gameTick": 184230},
"bufferCapacity": 200,
"bufferFilled": 184,
"type": "delta",
"ticksOmitted": 14,
"ticks": [
{"tick": 184219, "timestampMs": 1716579234120, "deltas": {"player": {"pos": [3094, 3493, 0], "runEnergy": 9821}}},
{"tick": 184221, "timestampMs": 1716579235320, "deltas": {"player": {"pos": [3094, 3494, 0], "runEnergy": 9783}}},
{"tick": 184223, "timestampMs": 1716579236520, "deltas": {"player": {"pos": [3094, 3495, 0], "runEnergy": 9745}}},
{"tick": 184225, "timestampMs": 1716579237720, "deltas": {"player": {"pos": [3094, 3496, 0], "runEnergy": 9707}}},
{"tick": 184227, "timestampMs": 1716579238920, "deltas": {"player": {"pos": [3094, 3497, 0], "runEnergy": 9669}}},
{"tick": 184229, "timestampMs": 1716579240120, "deltas": {"player": {"pos": [3094, 3498, 0], "runEnergy": 9631}}}
]
}
```
相同的 JSON 结构返回给任何支持 MCP 的客户端(Claude、自定义脚本、其他 LLM)- 协议中没有任何 Claude 特有的内容。
## 端点
- `POST /mcp` — JSON-RPC 2.0 MCP 请求(工具发现 + 调用)
- `GET /mcp` — 服务器→客户端 SSE 流(流式 HTTP 传输,规范 `2025-03-26`)
- `DELETE /mcp` — 客户端发起的会话终止
- `GET /sse`, `GET /mcp/sse` — 旧版 HTTP + SSE 传输(规范 `2024-11-05`),为向后兼容而保留
- `GET /api/status` — 插件状态快照
- `GET /api/telemetry` — 诊断计数器
- `GET /health`
会话 ID 在 `initialize` 的响应中作为 `Mcp-Session-Id` 返回;客户端在后续请求中回显它,但服务器不严格执行它。
## MCP 工具(只读)
### 时刻状态查询
| 工具 | 参数 | 用途 |
|------|------|---------|
| `state` | `inc` (CSV:player, resources, inventory, equipment, npcs, players, skills, instance, projectiles, graphics, camera, world) | 玩家 + 世界快照。`world` 部分给出世界编号 + 活动 `WorldType` 标志(MEMBERS, PVP, SEASONAL, …)。 |
| `npc` | `n` (名称),`i` (ID CSV),`r` (半径) | 玩家附近的 NPC |
| `obj` | `n` (名称),`i` (ID CSV) | 场景中的游戏对象 |
| `ground` | `n` (名称) | 玩家附近的地面物品 |
| `inv` | `m` (模式:`q` 默认 \| `s`) | 库存快照 |
| `equip` | — | 已装备的物品 |
| `bank` | `n` (名称子字符串,不区分大小写) | 银行内容(仅当银行打开时) |
| `dialog` | — | 当前对话状态 |
| `widget` | `m` (`get` \| `pick`),`g` (组),`c` (子项) | 小部件树内省 |
| `var` | `m` (`v` varbit,默认 \| `p` varp \| `ci` varc-int \| `cs` varc-string),`id` | 按 ID 读取变量。`varbitId` 在 varbit 模式下保留为 `id` 的废弃别名。Varc 模式显示客户端界面/会话状态(例如键入搜索文本),这些状态不在 varbits/varps 中。 |
| `menu` | — | 光标处的右键菜单项 |
| `chat` | `lines` (默认 10) | 最近的聊天消息 |
| `screenshot` | — | 游戏视口 PNG。通过 MCP `tools/call`,它包装为 `{"type":"image","data":"<base64>","mimeType":"image/png"}`,因此 MCP 感知客户端可以内联渲染它。 |
| `loginstate` | — | 登录状态(`LOGGED_IN`、`LOGGING_IN` 等) |
| `prayer` | — | 活动祈祷(列表)+ 祈祷点池(`current` / `max`) |
| `collision` | `x`、`y`、`plane`、`radius` | 本地玩家图块或加载的场景世界图块/半径的冲突标志,包括原始标志、解码的移动/LOS 阻挡器和基数行进检查 |
| `container` | `id` (数字 InventoryID 或名称:`inventory`、`equipment`、`bank`、`looting_bag`、`seed_vault`、`group_storage`) | 按 ID 读取任何物品容器 - 将 `inv`/`equip`/`bank` 泛化为种子保险库、掠夺袋、GIM 存储等。返回 `loaded`(false 表示容器从未在此会话打开,与 loaded-but-empty 不同)+ `total` + `items`。 |
| `def` | `type` (`item` \| `npc` \| `obj`),`id` | 通过 ID 解析缓存定义 → 名称、操作和统计数据(物品:成员/可堆叠/可交易/`storePrice`(缓存存储值,不是实时 GE 价格)/haPrice;NPC:战斗等级/大小;对象:大小),无需实体在场景中 |
| `ge` | — | 活跃的 Grand Exchange 报价:每个槽 `state`(BUYING/SELLING/BOUGHT/SOLD/CANCELLED_*)、`buy` 标志、物品 ID/名称、`price`(每个物品)、`quantitySold`/`totalQuantity` 进度,以及 `spent`(总 GP 移动 - 在购买上花费,在销售上收到)。EMPTY 槽省略。 |
### 历史/事件流查询(服务器端环形缓冲区,每刻更新)
| 工具 | 参数 | 用途 |
|------|------|---------|
| `buffer` | `t` (默认 `-5`),`types`、`names`、`ids`、`tile`、`area` | 玩家 / NPC / 对象 / 地面物品 / 其他玩家 / 技能 / 命中 / 投射物 / 图形的状态。`t > 0` 返回在该刻度的完整绝对快照;`t < 0` 返回过去 `|t|` 刻度的稀疏增量,带有 `added` / `removed` / `changed`,每个实体类型。`skills` 类型发出每个技能的对象,只有更改的字段(`gained` 经验、`real` 等级提升、`boosted` 临时提升/伤害/再生)。`hits`、`projectile` 和 `graphics` 类型是瞬态的 - 它们发出在该刻度上存在的所有列表(无差异),因此单个攻击可以被捕获,即使它只持续几个刻度。没有匹配更改的刻度被省略并计入 `ticksOmitted`。容量 200 刻度(~2 分钟)。 |
| `actions` | `t` (默认 50),`option`、`target`、`opcodes`、`ids`、`since` | 最近的 `MenuOptionClicked` 事件:用户点击 + 通过公共菜单 API 调用插件/宏操作(`Client.invokeMenuAction`、`Client.menuAction`)。**不**捕获绕过菜单并发送原始数据包的操作。新版本-最新。容量 500 操作。 |
所有响应都包含 `_meta.gameTick`(OSRS 以 600ms/刻度运行)。演员响应
包括动画、移动、健康、交互目标和最近的伤害板上下文
如有。
### `state inc=instance` - 实例/区域翻译
在 OSRS 实例内部(POH、突袭、NMZ 等),104×104 场景从世界其他地方的 8×8 模板块复制 - 并且旋转 - 组装,因此演员报告的世界坐标是任意数字,不映射到任何真实位置。`instance` 部分公开了将它们翻译回来的必要数据:
- `instanced`、`baseX`、`baseY`、`plane`、`mapRegions` - 始终存在。
- 当实例化时,`player` 提供本地玩家的 `scene` 瓦片及其翻译的 `template` 世界瓦片 `[x, y, plane]`,`chunks` 列出每个非空场景块的 `rotation` 和源 `templateX` / `templateY` / `templatePlane`,以便消费者可以自己翻译任何瓦片。翻译反映了 RuneLite 的 `WorldPoint.fromLocalInstance`。
### `state inc=projectiles,graphics,camera` - 场景效果和相机
- **`projectiles`** - 飞行中的投射物(远程/魔法射击、炮弹、boss 攻击)。每个携带 `spotanim`、`source` / `target` 端点(世界 `pos` 加 `actorIndex` / `actorType` / `local`,当端点是演员时),以及 `ticksToImpact` - “躲避此”时序的标题字段。按本地玩家目标排序,然后按最近的影响排序。
- **`graphics`** - 地面图形对象 / spotanims(AoE 远程警告、飞溅):`id`、世界 `pos`、`z`(本地垂直高度高于瓦片,不是平面)、`ageTicks`、`frame`,以及 `finished`(当过期时)。
- **`camera`** - `yaw`、`pitch`、`zoom`、`pos` `[x,y,z]` 和 `canvas` `[w,h]` - 足以将世界瓦片投影到屏幕空间,并将 `screenshot` 输出与实体坐标关联。注意 `pos` 以 **本地场景单位**(每个瓦片 128)表示,而不是像实体 `pos` 那样表示为世界瓦片。
在原始相机基础上,实时 `npc` / `obj` / `ground` 工具现在为每个实际在屏幕上的实体附加一个 `screen` `[px, py]` 字段(如果不在屏幕上则省略),以便消费者可以将实体与渲染的视口对齐而无需自己进行投影。
投射物是短暂的,因此点时间 `state` 投票仅在刻度内捕获一个,如果它在刻度内发射,则在两个刻度内可靠地显示连续源(炮、NMZ)。
## 下载
预构建的 JAR 文件附加到 [GitHub Release](https://github.com/runbunbun/runelite-dev-mcp/releases/latest) 中,并附带 `.sha256` 校验和文件。验证后侧载:
```sh
shasum -a 256 -c runelite-dev-mcp-*.jar.sha256
```
## 从源代码构建
```sh
./gradlew jar
```
这会生成 `build/libs/runelite-dev-mcp-<version>.jar`。
## 安装插件
> **插件中心姿势**。此插件仅限侧载设计。它不会被提交到 [RuneLite 插件中心](https://github.com/runelite/plugin-hub),其指南禁止暴露游戏状态到外部自动化。下面的安装步骤是支持的路径。
> **安全姿势**。服务器仅绑定到 `127.0.0.1` - 它无法从本地网络访问。CORS 限制在一个允许列表(`localhost`、`127.0.0.1`、`vscode-webview://*`);其他 Web 来源无法读取响应。本地套接字上没有身份验证,因此任何以您的用户身份运行的进程都可以读取游戏状态。
### 载入 JAR 文件到侧载目录
```sh
cp build/libs/runelite-dev-mcp-*.jar ~/.runelite/sideloaded-plugins/
```
### 2. 通过直接 JVM 调用启动 RuneLite
RuneLite 应用程序将 JRE 捆绑在一起,启动器将客户端 JAR 文件下载到 `~/.runelite/repository2/`。直接使用这些 JAR 文件在类路径上运行客户端:
MacOS:
```sh
JAVA="/Applications/RuneLite.app/Contents/Resources/jre/bin/java"
REPO="$HOME/.runelite/repository2"
CP=$(find "$REPO" -name '*.jar' | tr '\n' ':')
"$JAVA" -ea -Xmx768m -Xss2m \
--add-opens=java.base/java.net=ALL-UNNAMED \
--add-opens=java.base/java.io=ALL-UNNAMED \
--add-opens=java.desktop/com.apple.eawt=ALL-UNNAMED \
-Dapple.awt.application.appearance=system \
-Drunelite.pluginsdir="$HOME/.runelite/sideloaded-plugins" \
-cp "$CP" \
net.runelite.client.RuneLite \
--developer-mode --debug
```
在 Linux / Windows 上,请相应地调整 `JAVA` 和 `repository2` 路径(例如 Linux 上的 `~/.local/share/RuneLite/repository2/`)。
### 3. 启用插件
在 RuneLite 插件列表中,搜索 **RuneLite Dev MCP** 并启用它。服务器启动在 `localhost:3000`。
### 验证
```sh
curl http://localhost:3000/health # {"status":"ok"}
curl http://localhost:3000/api/status # {gameTick, loginState, uptimeMs}
```
## 连接 Claude 代码
服务器运行时,在 Claude Code 中将其注册为流式 HTTP MCP 服务器:
```sh
claude mcp add --transport http runelite-dev-mcp http://localhost:3000/mcp
```
或手动添加到 `.mcp.json`(项目范围)或 `~/.claude.json`(用户范围):
```json
{
"mcpServers": {
"runelite-dev-mcp": {
"type": "http",
"url": "http://localhost:3000/mcp"
}
}
}
```
在 Claude Code 中运行 `/mcp` 以确认服务器已连接并加载了工具。任何其他支持流式 HTTP(`2025-03-26`)或旧版 HTTP + SSE 传输(`2024-11-05`)的 MCP 感知客户端都应与相同的 URL 对该服务器起作用。
## 许可
MIT License。参见 [LICENSE](LICENSE)。