From 365e4342d66eee7ca5b5b4008aa49db2e7b12790 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:16:34 +0000 Subject: [PATCH 1/5] Initial plan From b033aa430f30f1484c05eaa4b6879bfb4b88f1ab Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:19:39 +0000 Subject: [PATCH 2/5] Address code review comments for LiteRT-LM MCP server Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com> --- mcp-servers/litert-mcp/README.md | 7 ++-- mcp-servers/litert-mcp/server.py | 69 ++++++++++++++++---------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/mcp-servers/litert-mcp/README.md b/mcp-servers/litert-mcp/README.md index f2437a77e..46e1e25d0 100644 --- a/mcp-servers/litert-mcp/README.md +++ b/mcp-servers/litert-mcp/README.md @@ -44,13 +44,14 @@ Runs inference using the configured LiteRT-LM model. { "name": "run_inference", "arguments": { - "prompt": "Describe this image.", - "image_path": "/path/to/image.jpg", - "backend": "gpu" + "prompt": "Explain the theory of relativity in simple terms.", + "backend": "cpu" } } ``` +**Note**: The current CLI wrapper only supports text-only inference. For multimodal capabilities (image/audio), use the LiteRT-LM C++ or Python API directly. + ## Setup for Development This server uses a manual JSON-RPC implementation to avoid external dependencies in the base environment. Just run: diff --git a/mcp-servers/litert-mcp/server.py b/mcp-servers/litert-mcp/server.py index 6dc0a0fe0..65e3355de 100644 --- a/mcp-servers/litert-mcp/server.py +++ b/mcp-servers/litert-mcp/server.py @@ -11,7 +11,6 @@ import logging import sys import os -import subprocess from typing import Dict, Any, Optional # Configure logging @@ -143,10 +142,26 @@ async def _handle_tools_call(self, request_id, params): async def _run_inference(self, args: Dict[str, Any]) -> Dict[str, Any]: prompt = args.get("prompt") + + # Validate prompt parameter + if not prompt or not isinstance(prompt, str) or not prompt.strip(): + return { + "status": "error", + "message": "Invalid or empty prompt. Please provide a non-empty text prompt." + } + model_path = args.get("model_path") or self.default_model_path image_path = args.get("image_path") audio_path = args.get("audio_path") backend = args.get("backend", "cpu") + + # Validate backend parameter + valid_backends = {"cpu", "gpu", "npu"} + if backend not in valid_backends: + return { + "status": "error", + "message": f"Invalid backend '{backend}'. Must be one of {sorted(valid_backends)}." + } if not model_path: return { @@ -154,15 +169,6 @@ async def _run_inference(self, args: Dict[str, Any]) -> Dict[str, Any]: "message": "No model path provided. Set LIT_MODEL_PATH env var or pass model_path argument." } - # Check if binary exists (simple check) - try: - # We assume the binary handles --help or similar to check existence, - # but simpler to just try running it or check existence if it's a path. - # If it's just 'lit' in PATH, shutil.which would be needed, but let's just try-catch execution. - pass - except Exception: - pass - # Construct command # We assume the binary accepts flags similar to litert_lm_main demo cmd = [self.lit_binary] @@ -173,10 +179,10 @@ async def _run_inference(self, args: Dict[str, Any]) -> Dict[str, Any]: # The current 'lit' CLI wrapper does not support verified multimodal input flags. # We restrict to text-only to avoid speculative errors. if image_path or audio_path: - return { - "status": "error", - "message": "Multimodal input (image/audio) is not yet supported via the 'lit' CLI wrapper. Please use the LiteRT-LM C++ or Python API directly, or update this server implementation once CLI flags are verified." - } + return { + "status": "error", + "message": "Multimodal input (image/audio) is not yet supported via the 'lit' CLI wrapper. Please use the LiteRT-LM C++ or Python API directly, or update this server implementation once CLI flags are verified." + } cmd.extend(["--input_prompt", prompt]) @@ -213,10 +219,10 @@ async def _run_inference(self, args: Dict[str, Any]) -> Dict[str, Any]: } except FileNotFoundError: - return { - "status": "error", - "message": f"LiteRT binary '{self.lit_binary}' not found. Please set LIT_BINARY_PATH or install LiteRT-LM." - } + return { + "status": "error", + "message": f"LiteRT binary '{self.lit_binary}' not found. Please set LIT_BINARY_PATH or install LiteRT-LM." + } except Exception as e: return { "status": "error", @@ -233,18 +239,10 @@ async def main(): writer = None if sys.platform != "win32": - try: - w_transport, w_protocol = await asyncio.get_event_loop().connect_write_pipe( - asyncio.Protocol, sys.stdout - ) - writer = asyncio.StreamWriter(w_transport, w_protocol, None, asyncio.get_event_loop()) - except Exception as e: - LOGGER.warning(f"Could not connect write pipe to stdout: {e}. Falling back to print.") - writer = None - else: - # Windows fallback (simplified, might not work perfectly with async stdio without extra loop config) - # But matches common patterns. - pass + w_transport, w_protocol = await asyncio.get_event_loop().connect_write_pipe( + asyncio.Protocol, sys.stdout + ) + writer = asyncio.StreamWriter(w_transport, w_protocol, None, asyncio.get_event_loop()) while True: try: @@ -262,12 +260,13 @@ async def main(): writer.write(response_str.encode()) try: await writer.drain() - except (AttributeError, BrokenPipeError) as e: - LOGGER.warning(f"Error while draining writer ({type(e).__name__}): {e}. " - "Disabling async writer and falling back to print().") - writer = None + except (AttributeError, BrokenPipeError) as drain_error: + # Non-fatal issues when flushing output (e.g., client closed pipe or writer lacks drain). + # We log at debug level and continue to preserve existing behavior. + LOGGER.debug("Non-fatal error while draining writer: %s", drain_error) else: - print(response_str, flush=True) + sys.stdout.write(response_str) + sys.stdout.flush() except json.JSONDecodeError: LOGGER.error(f"Invalid JSON received: {line}") From 75c5956aac7500290637ea1b9079b33007a4472b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:20:38 +0000 Subject: [PATCH 3/5] Improve error handling robustness in LiteRT MCP server Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com> --- mcp-servers/litert-mcp/server.py | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/mcp-servers/litert-mcp/server.py b/mcp-servers/litert-mcp/server.py index 65e3355de..30ad56cfd 100644 --- a/mcp-servers/litert-mcp/server.py +++ b/mcp-servers/litert-mcp/server.py @@ -239,10 +239,14 @@ async def main(): writer = None if sys.platform != "win32": - w_transport, w_protocol = await asyncio.get_event_loop().connect_write_pipe( - asyncio.Protocol, sys.stdout - ) - writer = asyncio.StreamWriter(w_transport, w_protocol, None, asyncio.get_event_loop()) + try: + w_transport, w_protocol = await asyncio.get_event_loop().connect_write_pipe( + asyncio.Protocol, sys.stdout + ) + writer = asyncio.StreamWriter(w_transport, w_protocol, None, asyncio.get_event_loop()) + except Exception as e: + LOGGER.warning(f"Could not connect write pipe to stdout: {e}. Falling back to sys.stdout.write().") + writer = None while True: try: @@ -262,8 +266,10 @@ async def main(): await writer.drain() except (AttributeError, BrokenPipeError) as drain_error: # Non-fatal issues when flushing output (e.g., client closed pipe or writer lacks drain). - # We log at debug level and continue to preserve existing behavior. - LOGGER.debug("Non-fatal error while draining writer: %s", drain_error) + # We disable async writer and fall back to sys.stdout to prevent repeated errors. + LOGGER.warning("Error while draining writer (%s): %s. Falling back to sys.stdout.write().", + type(drain_error).__name__, drain_error) + writer = None else: sys.stdout.write(response_str) sys.stdout.flush() From 40d16ed794ed2c653353a16598e6e9433dbc2aca Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:21:07 +0000 Subject: [PATCH 4/5] Fix logging style to use f-strings Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com> --- mcp-servers/litert-mcp/server.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mcp-servers/litert-mcp/server.py b/mcp-servers/litert-mcp/server.py index 30ad56cfd..d5b1e4510 100644 --- a/mcp-servers/litert-mcp/server.py +++ b/mcp-servers/litert-mcp/server.py @@ -267,8 +267,7 @@ async def main(): except (AttributeError, BrokenPipeError) as drain_error: # Non-fatal issues when flushing output (e.g., client closed pipe or writer lacks drain). # We disable async writer and fall back to sys.stdout to prevent repeated errors. - LOGGER.warning("Error while draining writer (%s): %s. Falling back to sys.stdout.write().", - type(drain_error).__name__, drain_error) + LOGGER.warning(f"Error while draining writer ({type(drain_error).__name__}): {drain_error}. Falling back to sys.stdout.write().") writer = None else: sys.stdout.write(response_str) From a68af399b2a99365d36055a975a4b630969d0815 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 27 Jan 2026 17:21:37 +0000 Subject: [PATCH 5/5] Use list instead of set for consistent backend validation error messages Co-authored-by: groupthinking <154503486+groupthinking@users.noreply.github.com> --- mcp-servers/litert-mcp/server.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mcp-servers/litert-mcp/server.py b/mcp-servers/litert-mcp/server.py index d5b1e4510..8cfad7df2 100644 --- a/mcp-servers/litert-mcp/server.py +++ b/mcp-servers/litert-mcp/server.py @@ -156,11 +156,11 @@ async def _run_inference(self, args: Dict[str, Any]) -> Dict[str, Any]: backend = args.get("backend", "cpu") # Validate backend parameter - valid_backends = {"cpu", "gpu", "npu"} + valid_backends = ["cpu", "gpu", "npu"] if backend not in valid_backends: return { "status": "error", - "message": f"Invalid backend '{backend}'. Must be one of {sorted(valid_backends)}." + "message": f"Invalid backend '{backend}'. Must be one of {valid_backends}." } if not model_path: