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.
README
lcu-mcp
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
- Requirements
- Installation
- Registering with an MCP host
- Tools
- Configuration
- Enabling DOM access
- Security
- Development
- Troubleshooting
- Disclaimer
- License
How it works
Two independent subsystems run inside one Node process:
LcuClientreads 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 onOnJsonApiEventthat feeds an in-process ring buffer.CdpClientattaches 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. GETandHEADare always allowed and need no entry.*is only meaningful as a trailing path segment:/a/b/*matches/a/b/cbut not/a/b/c/dand 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 setsrejectUnauthorized: false. - The password never leaves the process. It is held only to build the
Authorizationheader — 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_evalbypasses the write allowlist by construction. The client page canfetchany LCU endpoint from its own origin, so evaluated JavaScript can do anything the client can. This is accepted, not fixed: it is gated by theallowEvalflag, whose statelol_statusreports.
Treat the write allowlist as a guardrail against mistakes, not as a security boundary — while
allowEvalistrueit can be bypassed. SetallowEvaltofalsefor a real boundary.lol_dom_querykeeps 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
百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。
Playwright MCP Server
一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。
Magic Component Platform (MCP)
一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。
Audiense Insights MCP Server
通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。
VeyraX
一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。
graphlit-mcp-server
模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。
Kagi MCP Server
一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。
e2b-mcp-server
使用 MCP 通过 e2b 运行代码。
Neon MCP Server
用于与 Neon 管理 API 和数据库交互的 MCP 服务器
Exa MCP Server
模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。