Protocol for MCP handler revised.
This commit is contained in:
@@ -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():
|
||||
|
||||
@@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
exec /usr/bin/nc localhost 9847
|
||||
Reference in New Issue
Block a user