lcu-mcp

lcu-mcp

Provides MCP tools to interact with a running League of Legends client, including REST API calls, live event streaming, and DOM/JavaScript access.

Category
访问服务器

README

lcu-mcp

License: MIT Node Tests

An MCP server that exposes a running League of Legends client to any MCP host — the LCU REST API, its live OnJsonApiEvent stream, and the client UI's own DOM and JavaScript context, as nine tools over stdio.

Ask your assistant what queue you are in, watch champ select unfold event by event, inspect the client's DOM, or drive the client itself — without writing a line of glue code.

Contents

How it works

Two independent subsystems run inside one Node process:

  • LcuClient reads the client's lockfile to discover the port and password, then talks REST over HTTPS with Riot's root CA pinned, and holds a WebSocket tap on OnJsonApiEvent that feeds an in-process ring buffer.
  • CdpClient attaches to the client's Chrome DevTools Protocol endpoint (exposed by Pengu Loader) for DOM queries and JavaScript evaluation.

Both connect lazily and survive client restarts — the lockfile port changes on every launch, so the directory is watched rather than the file. Events are polled rather than pushed, because MCP has no server-to-client push.

Design rationale and the live-verified protocol details live in docs/design.md.

Requirements

Node.js >= 24 (ESM, no build step)
League of Legends Running. The lockfile at C:\Riot Games\League of Legends\lockfile supplies the port and password.
Pengu Loader Optional — required only for lol_dom_query and lol_eval. Everything else works without it.

Windows only in practice: the default lockfile path and the Pengu integration are Windows-specific.

Installation

git clone https://github.com/Triggered0/lcu-mcp.git
cd lcu-mcp
npm install

Runtime dependencies are exactly three: @modelcontextprotocol/sdk, zod, and ws.

Registering with an MCP host

Claude Code

claude mcp add lcu --scope user -- node C:\path\to\lcu-mcp\src\index.js

Any host that reads .mcp.json

{
  "mcpServers": {
    "lcu": {
      "command": "node",
      "args": ["C:\\path\\to\\lcu-mcp\\src\\index.js"],
      "env": { "LCU_MCP_CONFIG": "C:\\path\\to\\lcu-mcp\\config\\allowlist.json" }
    }
  }
}

LCU_MCP_CONFIG is optional; without it the server looks for config/allowlist.json relative to its working directory, and falls back to built-in defaults if that file does not exist.

Tools

Tool Purpose
lol_status Per-subsystem health, resolved LCU port, configured CDP port, whether allowEval is on
lol_get(path) GET any LCU path
lol_request(method, path, body?) Any verb, subject to the write allowlist
lol_endpoints(filter?) List the curated endpoint table
lol_events_start(filters?) Open the WebSocket tap and begin buffering
lol_events_poll(since?, limit?, filter?) Drain the ring buffer
lol_events_stop() Close the tap
lol_dom_query(selector, all?, props?) Query the client DOM
lol_eval(expression, awaitPromise?) Evaluate JavaScript in the page

lol_status first. When anything else fails it tells you which half is down — a closed client looks nothing like a missing Pengu install.

Events are polled. lol_events_poll returns a cursor; pass it back as since next time. A non-zero dropped means the ring buffer wrapped and that many events were lost after your cursor. Entries with truncated: true had their data clipped at 4 KB — re-fetch the full body with lol_get on the entry's uri.

The client only emits when state changes. Sitting idle on the home screen it can stay silent indefinitely; navigating the UI or entering a lobby produces bursts. An empty poll usually means nothing happened, not that the tap is broken — check running and lol_status to tell the two apart.

Filters are URI prefixes applied at ingest. The unfiltered firehose fills the buffer quickly, so pass something like ["/lol-champ-select/", "/lol-gameflow/"] unless you genuinely want everything.

Configuration

config/allowlist.json:

{
  "allowEval": true,
  "cdpPort": 8888,
  "eventBufferSize": 1000,
  "writeAllowlist": [
    "POST /lol-matchmaking/v1/ready-check/accept",
    "PATCH /lol-champ-select/v1/session/actions/*"
  ]
}
Key Default Meaning
allowEval true Whether lol_eval may run JavaScript in the page
cdpPort 8888 Pengu Loader's remote debugging port
eventBufferSize 1000 Ring buffer capacity; oldest entries are evicted first
writeAllowlist [] Which mutating requests lol_request may send

Allowlist matching rules:

  • An entry is METHOD path. The method is compared case-insensitively, the path case-sensitively.
  • GET and HEAD are always allowed and need no entry.
  • * is only meaningful as a trailing path segment: /a/b/* matches /a/b/c but not /a/b/c/d and not /a/b. Anywhere else it is a literal character.
  • A refused call returns the exact config line that would permit it, and the request is never sent.

Enabling DOM access

lol_dom_query and lol_eval need the client's CEF remote debugging port, which Riot's build only opens through Pengu Loader — an externally added --remote-debugging-port flag is ignored.

Pengu's config is plain key=value text, one pair per line — not JSON, not INI. In C:\Program Files\Pengu Loader\config, set:

RemoteDebuggingPort=8888

Then restart the client UX so CEF picks the port up:

POST /riotclient/kill-and-restart-ux

This leaves a live game untouched. Until it happens, both tools fail with these exact instructions rather than a bare ECONNREFUSED.

Security

  • TLS verification stays on. The LCU's self-signed certificate is validated against Riot's root CA, vendored at certs/riotgames.pem. The server never sets rejectUnauthorized: false.
  • The password never leaves the process. It is held only to build the Authorization header — no tool returns it, nothing logs it, and error text is scrubbed of it before it reaches the host. CDP target URLs embed it too, so they are redacted before any tool returns them.
  • lol_eval bypasses the write allowlist by construction. The client page can fetch any LCU endpoint from its own origin, so evaluated JavaScript can do anything the client can. This is accepted, not fixed: it is gated by the allowEval flag, whose state lol_status reports.

Treat the write allowlist as a guardrail against mistakes, not as a security boundary — while allowEval is true it can be bypassed. Set allowEval to false for a real boundary. lol_dom_query keeps working, because it injects the selector as data rather than as code.

Development

npm test        # unit tests via node:test — no League client needed
npm run smoke   # live end-to-end check against a running client
npm start       # run the server on stdio

npm run smoke prints one line per stage and exits 1 if any stage fails. It is never run in CI. The event stage waits for real delivery and reports three outcomes: PASS when events arrived, SKIP when the tap connected but an idle client sent nothing, and FAIL when the tap could not connect.

src/
  index.js          # stdio transport and tool registration
  config.js         # config loading and validation
  allowlist.js      # pure write-allowlist matching
  redact.js         # strip passwords from URLs and strings
  lcu/
    lockfile.js     # parse, read, and watch the lockfile
    client.js       # REST with the pinned CA
    buffer.js       # ring buffer with cursor and drop accounting
    ingest.js       # pure ingest policy: prefix filters, truncation
    events.js       # WebSocket tap with backoff reconnect
  cdp/
    discover.js     # probe the debugging port, pick and redact the target
    client.js       # attach, evaluate, DOM query
  tools/            # one module per tool group
tests/              # one test file per source module

Troubleshooting

Symptom Cause
League client is not running: no lockfile at ... The client is closed, or installed somewhere other than the default path.
Every CDP tool fails with a Pengu hint Pengu Loader is not active, or RemoteDebuggingPort is unset. Follow Enabling DOM access.
no "page" target CDP is reachable but the UX is still starting. Retry once the client is visible.
lol_events_poll returns nothing Usually an idle client, not a fault. Navigate the UI and poll again; check running in the response.
A write is refused The verb and path are not on the allowlist. The error message contains the exact line to add.
TLS errors on every REST call The vendored CA is wrong or stale. Fix the PEM — never disable verification.

Disclaimer

lcu-mcp is not endorsed by Riot Games and does not reflect the views or opinions of Riot Games or anyone officially involved in producing or managing Riot Games properties. Riot Games and all associated properties are trademarks or registered trademarks of Riot Games, Inc.

This project uses the client's own local API. You are responsible for how you use it; automating gameplay may violate Riot's Terms of Service.

License

MIT © Triggered

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选