Skip to main content
Glama
Triggered0

lcu-mcp

by Triggered0

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

Related MCP server: League of Legends MCP Server

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

Install Server
A
license - permissive license
A
quality
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    C
    quality
    D
    maintenance
    An MCP (Model-Controller-Processor) server for accessing League of Legends client data. This server provides a collection of tools that communicate with the League of Legends Live Client Data API to retrieve in-game data.
    12
    12
    Apache 2.0
  • A
    license
    A
    quality
    C
    maintenance
    Provides MCP tools to query Liquipedia esports data (matches, teams, players, tournaments, placements, standings) via the Liquipedia v3 API and MediaWiki action API.
    8
    MIT

View all related MCP servers

Related MCP Connectors

  • Riot Games API MCP.

  • Access Kernel's cloud-based browsers and app actions via MCP (remote HTTP + OAuth).

  • Speedrun.com MCP — wraps the Speedrun.com API v1 (speedrun.com/api/v1)

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/Triggered0/lcu-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server