Protocol for MCP handler revised.

This commit is contained in:
2026-03-07 19:32:19 -05:00
parent 862eb697cb
commit 4befd070db
5 changed files with 176 additions and 368 deletions

View File

@@ -2,21 +2,42 @@
"""
MCP stdio-to-TCP bridge for BlueprintMCP.
Reads JSON-RPC messages from stdin, forwards them to the BlueprintMCP TCP server,
and writes responses to stdout. If the editor isn't running, returns valid MCP
error responses instead of crashing.
Exposes a single MCP tool "unreal" that forwards JSON commands to the
BlueprintMCP TCP server in the Unreal Editor. The tool list is static,
so it works regardless of whether the editor is running at startup.
"""
import sys
import json
import socket
import time
HOST = "localhost"
PORT = 9847
CONNECT_TIMEOUT = 2
READ_TIMEOUT = 120
TOOL_DESCRIPTION = (
"Send a command to the Unreal Editor's BlueprintMCP plugin. "
"The 'command' field specifies which operation to perform; "
"additional fields are command-specific parameters. "
'Use {"command": "show_commands"} to list available commands. '
"If the editor is not running, the call will return an error; "
"just ask the user to start the editor and try again."
)
TOOL_SCHEMA = {
"name": "unreal",
"description": TOOL_DESCRIPTION,
"inputSchema": {
"type": "object",
"properties": {
"command": {"type": "string", "description": "The command to execute"},
},
"required": ["command"],
"additionalProperties": True,
},
}
sock = None
@@ -47,7 +68,7 @@ def disconnect():
def send_and_receive(message):
"""Send a JSON-RPC message to the editor and return the response."""
"""Send a JSON message to the editor and return the response."""
data = json.dumps(message) + "\n"
sock.sendall(data.encode())
@@ -63,86 +84,61 @@ def send_and_receive(message):
continue
def editor_down_response(msg):
"""Return a valid MCP error response indicating the editor is down."""
msg_id = msg.get("id", 0)
method = msg.get("method", "")
if method == "initialize":
return {
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {"name": "blueprint-mcp", "version": "1.0.0"},
"_notice": "Unreal Editor is not running. No tools are available. Start the editor and restart Claude Code.",
},
}
if method == "notifications/initialized":
return None # No response needed for notifications
if method == "tools/list":
return {
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"tools": [{
"name": "editor_not_running",
"description": "Unreal Editor is not running. Start the editor and restart Claude Code to get BlueprintMCP tools.",
"inputSchema": {"type": "object", "properties": {}},
}],
},
}
if method == "tools/call":
return {
"jsonrpc": "2.0",
"id": msg_id,
"result": {
"content": [
{"type": "text", "text": json.dumps({"error": "Unreal Editor is down."})}
],
"isError": True,
},
}
return {
"jsonrpc": "2.0",
"id": msg_id,
"error": {"code": -32000, "message": "Unreal Editor is down."},
}
def handle_message(msg):
"""Handle one JSON-RPC message. Try the editor first, fall back to offline responses."""
method = msg.get("method", "")
# Notifications don't get responses
if "id" not in msg:
if connect():
try:
sock.sendall((json.dumps(msg) + "\n").encode())
except Exception:
disconnect()
return None
# Try connecting (or reconnecting) to the editor
def forward_to_editor(arguments):
"""Forward arguments to the editor, return the result dict."""
if not connect():
return editor_down_response(msg)
return {"error": "Unreal Editor is not running. Start the editor and try again."}
try:
return send_and_receive(msg)
return send_and_receive(arguments)
except Exception:
disconnect()
# Retry once in case the connection was stale
if connect():
try:
return send_and_receive(msg)
return send_and_receive(arguments)
except Exception:
disconnect()
return editor_down_response(msg)
return {"error": "Lost connection to Unreal Editor."}
def make_jsonrpc(msg_id, result):
return {"jsonrpc": "2.0", "id": msg_id, "result": result}
def handle_message(msg):
"""Handle one JSON-RPC message from Claude Code."""
msg_id = msg.get("id")
method = msg.get("method", "")
# Notifications don't get responses
if msg_id is None:
return None
if method == "initialize":
return make_jsonrpc(msg_id, {
"protocolVersion": "2024-11-05",
"capabilities": {"tools": {}},
"serverInfo": {"name": "blueprint-mcp", "version": "1.0.0"},
})
if method == "tools/list":
return make_jsonrpc(msg_id, {"tools": [TOOL_SCHEMA]})
if method == "tools/call":
params = msg.get("params", {})
arguments = params.get("arguments", {})
result = forward_to_editor(arguments)
is_error = "error" in result
return make_jsonrpc(msg_id, {
"content": [{"type": "text", "text": json.dumps(result)}],
**({"isError": True} if is_error else {}),
})
return {
"jsonrpc": "2.0",
"id": msg_id,
"error": {"code": -32601, "message": f"Method not found: {method}"},
}
def main():

View File

@@ -1,2 +0,0 @@
#!/bin/bash
exec /usr/bin/nc localhost 9847