McpServer

class McpServer

The single MCP endpoint the mod exposes: Streamable HTTP, bound to loopback, Bearer-token gated. Three tools: execute_code (runs Kotlin inside the running game via ReplBridge), run_command (runs a Minecraft command) and take_screenshot (the client's framebuffer, as an image).

One JSON-RPC response per POST — no SSE server->client stream. Mcp-Session-Id is required on everything but initialize and scopes cancellation. Issued ids are deliberately NOT recorded: one that outlived a game restart still works, where the spec's 404-unknown-session would send the model back through initialize and cost it its conversation context and its prompt cache. execute_code submits to an execution lane and blocks this thread on the eval's future while the lane steps it. The safety model: loopback-only bind, a constant-time compare of the one credential the request carried — Authorization: Bearer <token> or ?token=<token>, never both (Auth) — and reject, which answers nothing at all to anything that is not a plausible client.

Gson and the JDK's com.sun.net.httpserver both ship with the game — no new dependency, and no JVM flag (see the README's JVM-flags section).

Constructors

Link copied to clipboard
private constructor()

Types

Link copied to clipboard
private enum Auth : Enum<McpServer.Auth>
Link copied to clipboard
object Companion
Link copied to clipboard
private enum Reject : Enum<McpServer.Reject>

Why a request gets no reply.

Properties

Link copied to clipboard
private val hostAuthorities: Set<String>

The Host values reject accepts. Two on port 80: http's default, which a conforming client may normalize away (curl, JDK, python) or send verbatim (go) — both spell this one endpoint.

Link copied to clipboard

In-flight cancellable evals, keyed <session>:<requestId>. The session segment is the client-supplied Mcp-Session-Id, so two clients can't cancel each other's ids — which the request id alone cannot promise, every client numbering its own from 1.

Link copied to clipboard

Which Reject kinds have been logged. Keyed by the kind, never by the offending value: a scanner picks the value, so keying on it would grow this without bound.

Link copied to clipboard
private val port: Int
Link copied to clipboard
private val servers: List<HttpServer>
Link copied to clipboard
private val suppliedPort: Int?

Bound exactly or not at all; null lets the default sweep climb. Nothing ever writes a port back, so a value here was put there by a human — "set" and "meant it" are the same thing.

Link copied to clipboard
private val suppliedToken: String?

Null when nothing supplied one and we had to mint it.

Link copied to clipboard
private val token: String
Link copied to clipboard
private val urlTokenUsable: Boolean

Whether the url form can carry token. On URL_SAFE_TOKEN percent- and form-decoding are both the identity, so the url value compares as it arrived; off it they disagree on + and nothing between the config file and the url is an encoder that could settle it. The header carries any token, so only this spelling drops.

Functions

Link copied to clipboard

The token is spelled either Authorization: Bearer <token> or ?token=<token> — not every MCP client can attach a custom header, and a url is the one thing all of them take. Both at once is Auth.AMBIGUOUS rather than a precedence rule: picking a winner would authorize under a token the caller may not have meant to send. Any Authorization header counts as that one, whatever it spells: reading past a malformed one to the url would authorize a request whose header half is broken.

Link copied to clipboard
private fun dispatch(req: JsonObject, reqSession: String): JsonObject?
Link copied to clipboard
private fun executeCode(id: JsonElement?, args: JsonObject, reqSession: String): JsonObject
Link copied to clipboard
private fun handle(ex: HttpExchange)
Link copied to clipboard
private fun handleCancel(params: JsonObject, reqSession: String)

A notifications/cancelled for an in-flight execute_code: cancel the eval registered under <session>:<requestId>. Its future completes immediately, tagged (cancelled) with whatever it had already printed, and the pump stops driving it. Unknown ids are silently ignored per the MCP spec.

Link copied to clipboard
private fun logRejected(kind: McpServer.Reject, ex: HttpExchange)

The ONLY channel a refusal has, the wire carrying nothing — so it prints every field that decides one. Once per Reject kind: whoever needs this reads it while setting the client up, and past that the same line is a scanner's to repeat.

Link copied to clipboard

The browser-facing half of the safety model, and the whole reason nothing here answers with a status:

Link copied to clipboard
private fun runCommand(id: JsonElement?, args: JsonObject): JsonObject
Link copied to clipboard
private fun takeScreenshot(id: JsonElement?): JsonObject

No AuthGate: this returns what the player is already looking at, so it confers nothing they don't already have — the same reasoning that exempts run_command target="client".

Link copied to clipboard
private fun toolsCall(id: JsonElement?, params: JsonObject, reqSession: String): JsonObject?