121 lines
5.3 KiB
Python
Executable File
121 lines
5.3 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import sys
|
|
import json
|
|
import urllib.request
|
|
import argparse
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description="MCP Stdio-to-HTTP Bridge for ESP32-S3-RLCD-4.2")
|
|
parser.add_argument("--ip", required=True, help="IP address of the ESP32 board (e.g. 192.168.1.123)")
|
|
parser.add_argument("--port", type=int, default=80, help="Port the MCP server is listening on (default 80)")
|
|
args = parser.parse_args()
|
|
|
|
url = f"http://{args.ip}:{args.port}/api/mcp"
|
|
sys.stderr.write(f"ESP32 MCP Stdio-to-HTTP Bridge started. Routing stdio to {url}\n")
|
|
sys.stderr.flush()
|
|
|
|
# Sits in loop reading stdio requests from LLM client and routing them to ESP32
|
|
while True:
|
|
try:
|
|
line = sys.stdin.readline()
|
|
if not line:
|
|
break
|
|
|
|
# Parse request to ensure it's valid JSON
|
|
req_data = json.loads(line.strip())
|
|
|
|
# Forward the JSON-RPC request to the ESP32 board via HTTP POST
|
|
req = urllib.request.Request(
|
|
url,
|
|
data=json.dumps(req_data).encode("utf-8"),
|
|
headers={"Content-Type": "application/json"},
|
|
method="POST"
|
|
)
|
|
|
|
with urllib.request.urlopen(req, timeout=10.0) as response:
|
|
resp_data = response.read().decode("utf-8")
|
|
|
|
try:
|
|
resp_json = json.loads(resp_data)
|
|
result = resp_json.get("result", {})
|
|
content_list = result.get("content", [])
|
|
if len(content_list) > 0 and content_list[0].get("type") == "text":
|
|
text_val = content_list[0].get("text", "")
|
|
if text_val.startswith("__PBM_BASE64__:"):
|
|
b64_pbm = text_val.split(":", 1)[1]
|
|
|
|
import base64
|
|
import subprocess
|
|
import os
|
|
|
|
pbm_bytes = base64.b64decode(b64_pbm)
|
|
with open("temp_mcp.pbm", "wb") as pf:
|
|
pf.write(pbm_bytes)
|
|
|
|
# Convert PBM to PNG on macOS using standard sips tool
|
|
subprocess.run(["sips", "-s", "format", "png", "temp_mcp.pbm", "--out", "temp_mcp.png"],
|
|
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
|
|
if os.path.exists("temp_mcp.png"):
|
|
with open("temp_mcp.png", "rb") as pngf:
|
|
png_bytes = pngf.read()
|
|
b64_png = base64.b64encode(png_bytes).decode("utf-8")
|
|
|
|
# Replace standard text content with MCP image schema
|
|
content_list[0] = {
|
|
"type": "image",
|
|
"data": b64_png,
|
|
"mimeType": "image/png"
|
|
}
|
|
# Cleanup temp files
|
|
try:
|
|
os.remove("temp_mcp.pbm")
|
|
os.remove("temp_mcp.png")
|
|
except:
|
|
pass
|
|
else:
|
|
content_list[0] = {
|
|
"type": "text",
|
|
"text": "Screenshot captured, but PNG conversion failed on host PC."
|
|
}
|
|
resp_data = json.dumps(resp_json)
|
|
except Exception as ex:
|
|
sys.stderr.write(f"Bridge image conversion error: {ex}\n")
|
|
sys.stderr.flush()
|
|
|
|
# Write the response back to stdio for the LLM client
|
|
sys.stdout.write(resp_data + "\n")
|
|
sys.stdout.flush()
|
|
|
|
except urllib.error.URLError as e:
|
|
# Send standard JSON-RPC internal error response
|
|
err_resp = {
|
|
"jsonrpc": "2.0",
|
|
"error": {
|
|
"code": -32000,
|
|
"message": f"Bridge failed to reach ESP32: {e.reason}"
|
|
},
|
|
"id": req_data.get("id") if "req_data" in locals() else None
|
|
}
|
|
sys.stdout.write(json.dumps(err_resp) + "\n")
|
|
sys.stdout.flush()
|
|
sys.stderr.write(f"Bridge error: Failed to connect to ESP32 at {url}: {e}\n")
|
|
sys.stderr.flush()
|
|
|
|
except Exception as e:
|
|
err_resp = {
|
|
"jsonrpc": "2.0",
|
|
"error": {
|
|
"code": -32603,
|
|
"message": f"Bridge Internal Error: {str(e)}"
|
|
},
|
|
"id": req_data.get("id") if "req_data" in locals() else None
|
|
}
|
|
sys.stdout.write(json.dumps(err_resp) + "\n")
|
|
sys.stdout.flush()
|
|
sys.stderr.write(f"Bridge error: {e}\n")
|
|
sys.stderr.flush()
|
|
|
|
if __name__ == "__main__":
|
|
main()
|