Описание
Turn a running Space Engineers game into an MCP server
README
Turn a running Space Engineers game into an
MCP server — SE1 (client or dedicated
server) and SE2 (client). An LLM connects over local HTTP and executes C#
directly inside the live engine, with full .NET and game API access — right
down to internal types and members.
⚠️ This is remote code execution by design
The bearer token is the root password. Anyone holding it can run arbitrary C# in the game process: file I/O, spawning processes, native
[DllImport]— the lot. There is no sandbox.
- Never share, screenshot, or commit the token.
- Never port-forward the listener or expose it past
localhost.- The server binds
127.0.0.1only, rejects browser-origin (CSRF) and foreignHostheaders, and uses constant-time token comparison. In multiplayer the client additionally gates execution (SE1: Admin/Owner promote level; SE2: host / local-server sessions only).- The per-script watchdog (1 s/frame, 700 KB stack) exists to stop runaway loops from hanging the game thread. It is not a security boundary — a token holder already has full RCE.
What it is
MCP client (Claude, etc.)
│ HTTP · JSON-RPC · Bearer token (127.0.0.1 only)
▼
┌──────────────────────────────┐
│ McpServer (Shared) │ HttpListener · JSON-RPC · auth · sessions
│ ITool dispatch │
│ Executor · Compiler · Guard │ Roslyn compile → Cecil IL guard → coroutine
└──────────────────────────────┘
│ runs your C# on a game thread
┌─────┴───────────────┐
▼ ▼
main lane render lane ← client only
per-frame pump postfix on the render
(game / API state) thread (hook per host)
Three hosts share one MCP core (Shared): SE1 ClientPlugin (Pulsar,
in-game), SE1 ServerPlugin (Magnetar, dedicated server), and SE2
Client2Plugin (Pulsar Modern, in-game — ships as SeMcp2). Code runs on the
game's main thread or, on either client, on the render thread. The
per-frame pump differs by host:
- main lane — SE1 client
IPlugin.Update, SE1 serverUpdate; SE2 a Harmony postfix onVRageCore.Update. - render lane (client only) — SE1 a Harmony postfix on
MyRenderThread.RenderFrame; SE2 onRender12EngineComponent.RenderFrame.
execute_code works on all three; take_screenshot and the render lane are
client-only.
Connecting
On first launch the plugin auto-generates a token. Where to find it and the URL:
- Client — open the in-game settings dialog and click Copy URL. You get
http://localhost:9876/?token=<token>on the clipboard. (Default port9876, or6789on the SE2 client; if taken it climbs9876→9885— the live port shows in the dialog title and the log linelistening on :<port>.) - Server — on Quasar-managed servers, open the Plugin configuration page
to view the token and edit the port. Standalone servers without Quasar can read
the token from
<UserDataPath>/SeMcp.cfg. Default port9000; same9000→9009climb if taken, with the bound port in the log.
Use
localhost, not127.0.0.1— theHostheader is checked and a mismatch returns403.
Transport is MCP Streamable HTTP (not SSE): POST JSON-RPC to
http://localhost:<port>/. Auth is either an Authorization: Bearer <token>
header or a ?token=<token> query parameter (not both). Point any standard
MCP client at it — it will initialize, pick up the Mcp-Session-Id, and manage
the session for you:
{
"mcpServers": {
"se-mcp": {
"type": "streamable-http",
"url": "http://localhost:9876/",
"headers": { "Authorization": "Bearer <token>" }
}
}
}
Driving the raw protocol by hand (note: every call after initialize must echo
the session id):
# 1. initialize — the Mcp-Session-Id comes back in the response headers
curl -i http://localhost:9876/ \
-H 'Authorization: Bearer <token>' -H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{}}'
# 2. call a tool
curl http://localhost:9876/ \
-H 'Authorization: Bearer <token>' -H 'Mcp-Session-Id: <id>' \
-H 'Content-Type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
"params":{"name":"execute_code",
"arguments":{"code":"Console.WriteLine(MySession.Static?.Name);"}}}'
execute_code
Your input maps 1:1 onto three C# layers, which are spliced into a wrapper:
// <usings> ← extra "using" lines (defaults already imported)
public class __REPL__
{
// <class_body> ← methods, fields, nested types, [DllImport] — class-level
public IEnumerable<object> Run(TextWriter Console)
{
// <code> ← statements only; this is the entry point
yield break;
}
}
| field | required | what goes in it |
|---|---|---|
code |
yes | Statements only. Output via Console.WriteLine(). Pause until the next frame with yield return null. |
class_body |
no | Class-level declarations — anything that can't live in a method body, e.g. [DllImport] P/Invoke. |
usings |
no | Extra namespace imports — bare paths like "System.Runtime.InteropServices", no using keyword, no ;. |
target |
no | "main" (default) or "render" (client only). |
- A large set of namespaces is pre-imported — SE1:
System.*,VRageMath,VRage.*,Sandbox.*,SpaceEngineers.Game.*; SE2:System.*,Keen.VRage.*,Keen.Game2.*. Use short type names (SE1MySession.Static, SE2GameAppComponent), not fully-qualified ones. - Compiled with Roslyn 5.3 against every loaded assembly (.NET + game +
other plugins), with ignore-accessibility turned on:
internalclasses, methods, fields and properties are callable directly, no reflection — this works across the game's own assemblies and other loaded plugins (only trulyprivatemembers still need reflection).unsafeand[DllImport]are allowed. - Compile errors come back per field with corrected line numbers:
code (3,9): error CS0103: .... - Scripts are coroutines and run in parallel; each step is bounded by the watchdog above.
Examples
Read game state on the main thread:
var s = MyAPIGateway.Session;
Console.WriteLine($"World: {s.Name}");
Console.WriteLine($"You are at: {s.Player?.GetPosition()}");
The same on SE2 — the API root is GameAppComponent, reached through the
engine singleton, not MyAPIGateway:
var engine = Singleton<VRageCore>.Instance.Engine;
var session = engine.Get<GameAppComponent>().ClientSession;
Console.WriteLine($"in a session: {session != null}");
P/Invoke via class_body + usings (usings: ["System.Runtime.InteropServices"]):
// class_body
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
static extern int MessageBox(IntPtr hWnd, string text, string caption, uint type);
// code
MessageBox(IntPtr.Zero, "Hello from Space Engineers", "SeMcp", 0);
Console.WriteLine("shown");
Spread work across frames:
for (int i = 3; i > 0; i--)
{
Console.WriteLine($"tick {i}");
yield return null; // resume next frame
}
Console.WriteLine("done");
The
rendertarget runs on the render thread — use it only to inspect other plugins' Harmony hooks that execute there. The game API (SE1MyAPIGateway, SE2 session / scene access) asserts off the main thread.
Both clients also expose take_screenshot (captures the current frame as an
image; optional ignore_sprites to drop the HUD). Full parameters are in the
tool's inputSchema.
Client vs. server
| SE1 client (Pulsar) | SE1 server (Magnetar) | SE2 client (Pulsar Modern) | |
|---|---|---|---|
| Default port | 9876 (9876–9885) |
9000 (9000–9009) |
6789 (6789–6798) |
render lane |
✅ | ❌ | ✅ |
take_screenshot |
✅ | ❌ | ✅ |
| Multiplayer gate | Admin/Owner required | none — token is full access | host / local-server only¹ |
| Settings GUI | ✅ (MyGui) | ✅ (Quasar Plugin config) | ✅ (Avalonia) |
| Config file | <UserDataPath>/Storage/SeMcp.cfg |
<UserDataPath>/SeMcp.cfg |
Pulsar Data\SeMcp2\SeMcp2.cfg |
| API root | MyAPIGateway / MySession |
same | GameAppComponent (Keen.*) |
¹ SE2 has no multiplayer yet; the gate pre-emptively blocks a pure client (one with no local authoritative server), so in practice it only ever runs single-player today.
Configuration
Port and SecretKey persist to the .cfg (auto-saved). On either client both are
editable in the settings dialog, with Regenerate Token and Copy URL buttons;
on the server they are editable through Quasar's Plugin configuration page.
Changing the port requires a restart.
How it works
For anyone reading or extending the code:
McpServer— oneHttpListener, single (non-batched) JSON-RPC, auth + CSRF/host checks, pre-renderedtools/list. Sessions namespace in-flight request ids but carry no server state.Executor— compilation runs on the thread pool; the compiled coroutine is then stepped oneMoveNextper frame on its owning game thread. Each lane has its own executor andScriptGuard, so finally-blocks and thread-affine state stay on the right thread.Compiler— drives Roslyn entirely through reflection (no compile-time binding, so it works against both the game's ancient Roslyn and the NuGet 5.3 one), references every loaded assembly, and enables ignore-accessibility so scripts can reachinternalmembers:MetadataImportOptions.Internal+BinderFlags.IgnoreAccessibilityat compile time, plus an injected[assembly: IgnoresAccessChecksTo]per referenced assembly at runtime (its attribute is self-declared — source wins over the copies Harmony and other plugins ship, so no ambiguity). It then rewrites the emitted IL with Mono.Cecil to inject the guard.ScriptGuard— injectedBail()on backward branches andcatch→filter rewrites (so acatchcan't swallow the abort), plus a stack-depthStackCheckat call sites. A background 1 s timer sets theDeadflag the injected checks read.
License
MIT — see LICENSE.
Установка Se
У этого сервера нет опубликованного пакета — он собирается из исходников. Открой репозиторий и следуй инструкции в README.
▸ github.com/lolifamily/se-mcpFAQ
Se MCP бесплатный?
Да, Se MCP бесплатный — установка в пару кликов через Unyly без оплаты.
Нужен ли API-ключ для Se?
Нет, Se работает без API-ключей и переменных окружения.
Se — hosted или self-hosted?
Self-hosted: сервер запускается локально на твоей машине командой из раздела установки.
Как установить Se в Claude Desktop, Claude Code или Cursor?
Открой Se на unyly.org, выбери вкладку своего клиента (Claude Desktop, Claude Code, Cursor) и нажми Install — конфиг сгенерируется автоматически, без правки JSON.
Похожие MCP
GitHub
PRs, issues, code search, CI status
автор: GitHubFilesystem
Secure file operations with configurable access controls.
Memory
Knowledge graph-based persistent memory system.
Template MCP Server
A CLI tool to create a new Model Context Protocol server project with TypeScript support, dual transport options, and an extensible structure
автор: mcpdotdirectCompare Se with
Не уверен что выбрать?
Найди свой стек за 60 секунд
Автор?
Embed-бейдж для README
Похожее
Все в категории development
