Minimal MCP client for testing. Speaks pure JSON-RPC 2.0 over TCP (newline-delimited). Does NOT use the legacy {"type":"command"} envelope.
| 34 | |
| 35 | |
| 36 | class MCPClient: |
| 37 | """ |
| 38 | Minimal MCP client for testing. |
| 39 | |
| 40 | Speaks pure JSON-RPC 2.0 over TCP (newline-delimited). |
| 41 | Does NOT use the legacy {"type":"command"} envelope. |
| 42 | """ |
| 43 | |
| 44 | HOST = "127.0.0.1" |
| 45 | PORT = 7777 |
| 46 | TIMEOUT = 5.0 |
| 47 | |
| 48 | def __init__(self, host: str = HOST, port: int = PORT, timeout: float = TIMEOUT): |
| 49 | self.host = host |
| 50 | self.port = port |
| 51 | self.timeout = timeout |
| 52 | self._sock: Optional[socket.socket] = None |
| 53 | self._buf = b"" |
| 54 | self._id = 0 |
| 55 | |
| 56 | # ------------------------------------------------------------------ |
| 57 | # Connection helpers |
| 58 | # ------------------------------------------------------------------ |
| 59 | |
| 60 | def connect(self) -> None: |
| 61 | self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
| 62 | self._sock.settimeout(self.timeout) |
| 63 | try: |
| 64 | self._sock.connect((self.host, self.port)) |
| 65 | except (ConnectionRefusedError, socket.timeout) as exc: |
| 66 | raise ConnectionError( |
| 67 | f"Cannot connect to Serial Studio MCP at {self.host}:{self.port}. " |
| 68 | "Ensure Serial Studio is running with API Server enabled." |
| 69 | ) from exc |
| 70 | |
| 71 | def disconnect(self) -> None: |
| 72 | if self._sock: |
| 73 | try: |
| 74 | self._sock.close() |
| 75 | except Exception: |
| 76 | pass |
| 77 | self._sock = None |
| 78 | self._buf = b"" |
| 79 | |
| 80 | def __enter__(self): |
| 81 | self.connect() |
| 82 | return self |
| 83 | |
| 84 | def __exit__(self, *_): |
| 85 | self.disconnect() |
| 86 | |
| 87 | # ------------------------------------------------------------------ |
| 88 | # Send / receive |
| 89 | # ------------------------------------------------------------------ |
| 90 | |
| 91 | def _next_id(self) -> int: |
| 92 | self._id += 1 |
| 93 | return self._id |