summaryrefslogtreecommitdiff
path: root/src/sailfish_devel_mcp
diff options
context:
space:
mode:
Diffstat (limited to 'src/sailfish_devel_mcp')
-rw-r--r--src/sailfish_devel_mcp/runner.py20
-rw-r--r--src/sailfish_devel_mcp/server.py33
2 files changed, 45 insertions, 8 deletions
diff --git a/src/sailfish_devel_mcp/runner.py b/src/sailfish_devel_mcp/runner.py
index b0b88bf..74748f4 100644
--- a/src/sailfish_devel_mcp/runner.py
+++ b/src/sailfish_devel_mcp/runner.py
@@ -4,7 +4,7 @@ from dataclasses import dataclass
from pathlib import Path
import shlex
import subprocess
-from typing import Iterable, Sequence
+from typing import Any, Iterable, Sequence
from .config import DeviceConfig
@@ -39,6 +39,16 @@ def truncate(text: str, limit: int) -> tuple[str, bool]:
return text[:limit] + f"\n[truncated after {limit} characters]", True
+def ensure_text(value: Any) -> str:
+ if value is None:
+ return ""
+ if isinstance(value, str):
+ return value
+ if isinstance(value, bytes):
+ return value.decode("utf-8", errors="replace")
+ return str(value)
+
+
def run(
argv: Sequence[str],
*,
@@ -57,15 +67,15 @@ def run(
return CommandResult(
argv=tuple(str(arg) for arg in argv),
returncode=completed.returncode,
- stdout=completed.stdout,
- stderr=completed.stderr,
+ stdout=ensure_text(completed.stdout),
+ stderr=ensure_text(completed.stderr),
)
except subprocess.TimeoutExpired as exc:
return CommandResult(
argv=tuple(str(arg) for arg in argv),
returncode=124,
- stdout=exc.stdout or "",
- stderr=(exc.stderr or "") + f"\ncommand timed out after {timeout}s",
+ stdout=ensure_text(exc.stdout),
+ stderr=ensure_text(exc.stderr) + f"\ncommand timed out after {timeout}s",
)
diff --git a/src/sailfish_devel_mcp/server.py b/src/sailfish_devel_mcp/server.py
index 4fb2f91..7982b21 100644
--- a/src/sailfish_devel_mcp/server.py
+++ b/src/sailfish_devel_mcp/server.py
@@ -119,6 +119,15 @@ class McpServer:
return self.registry[name].handler(args)
except ValueError as exc:
return tool_error(str(exc))
+ except Exception as exc:
+ return tool_error(
+ f"{name} failed: {exc}",
+ {
+ "error": str(exc),
+ "exception": type(exc).__name__,
+ "tool": name,
+ },
+ )
def _resources_list(self) -> dict[str, Any]:
return {
@@ -158,6 +167,7 @@ def run_stdio(server: McpServer, stdin: TextIO = sys.stdin, stdout: TextIO = sys
for line in stdin:
if not line.strip():
continue
+ message: Any = None
try:
message = json.loads(line)
except json.JSONDecodeError as exc:
@@ -169,8 +179,26 @@ def run_stdio(server: McpServer, stdin: TextIO = sys.stdin, stdout: TextIO = sys
else:
response = server.handle(message)
if response is not None:
- stdout.write(json.dumps(response, separators=(",", ":")) + "\n")
- stdout.flush()
+ try:
+ response_text = json.dumps(response, separators=(",", ":"))
+ except TypeError as exc:
+ request_id = message.get("id") if isinstance(message, dict) else None
+ response_text = json.dumps(
+ {
+ "jsonrpc": "2.0",
+ "id": request_id,
+ "error": {
+ "code": -32603,
+ "message": f"response serialization failed: {exc}",
+ },
+ },
+ separators=(",", ":"),
+ )
+ try:
+ stdout.write(response_text + "\n")
+ stdout.flush()
+ except BrokenPipeError:
+ return
def main(argv: list[str] | None = None) -> None:
@@ -193,4 +221,3 @@ def main(argv: list[str] | None = None) -> None:
if __name__ == "__main__":
main()
-