"""Real MCP SDK over loopback HTTP inside a disposable Computer.""" import json import os from pathlib import Path import subprocess import threading import unittest from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer TOKEN = 'runner-mcp-secret-canary' class Handler(BaseHTTPRequestHandler): protocol_version = 'HTTP/1.1' def log_message(self, *_args): pass def reply(self, status, body=None): data = json.dumps(body).encode() if body is not None else b'' self.send_response(status) self.send_header('Mcp-Session-Id', 'fixture-session') self.send_header('Content-Type', 'application/json') self.send_header('Content-Length', str(len(data))) self.end_headers() self.wfile.write(data) def do_GET(self): self.reply(405) def do_DELETE(self): self.reply(204) def do_POST(self): request = json.loads(self.rfile.read(int(self.headers['Content-Length']))) if self.headers.get('Authorization') != 'Bearer ' + TOKEN: self.reply(401, {'error': TOKEN}) return if 'id' not in request: self.reply(202) return method = request['method'] if method == 'initialize': result = {'protocolVersion': request['params']['protocolVersion'], 'capabilities': {'tools': {}}, 'serverInfo': {'name': 'fixture', 'version': '1'}} elif method == 'tools/list': result = {'tools': [{'name': 'echo', 'description': 'Echo Unicode', 'inputSchema': {'type': 'object', 'properties': {'text': {'type': 'string'}}}}]} elif method == 'tools/call': args = request['params']['arguments'] self.server.calls += 1 if args.get('expired'): self.reply(404) return if args.get('disconnect'): self.close_connection = True return result = {'content': [{'type': 'text', 'text': 'x' * (5 * 1024 * 1024) if args.get('large') else args.get('text', '')}], 'isError': args.get('fail', False)} else: self.reply(200, {'jsonrpc': '2.0', 'id': request['id'], 'error': {'code': -32601, 'message': 'unknown'}}) return self.reply(200, {'jsonrpc': '2.0', 'id': request['id'], 'result': result}) class ComputerMcp(unittest.TestCase): @classmethod def setUpClass(cls): if os.environ.get('LAZYBOY_RUNNER_TEST_CONTAINER') != '1' or not Path('/.dockerenv').exists(): raise RuntimeError('requires disposable Computer fixture') cls.server = ThreadingHTTPServer(('127.0.0.1', 0), Handler) cls.server.calls = 0 cls.thread = threading.Thread(target=cls.server.serve_forever, daemon=True) cls.thread.start() @classmethod def tearDownClass(cls): cls.server.shutdown() cls.server.server_close() cls.thread.join() def invoke(self, method, arguments=None, authorized=True): config = {'url': f'http://127.0.0.1:{self.server.server_port}/mcp', 'method': method, 'headers': {'Authorization': 'Bearer ' + TOKEN} if authorized else {}, 'name': 'echo', 'arguments': arguments or {}} request = {'action': 'mcp', 'identity': {'computer_id': 'fixture', 'bot_id': 'mcp-agent', 'generation': 1}, 'input': json.dumps(config)} return subprocess.run(['python3', '-I', '/var/lib/lazyboy-runner/runner_jobs.py'], input=json.dumps(request), capture_output=True, text=True, timeout=55) def test_discovery_and_call_execute_in_computer(self): listed = self.invoke('tools/list') self.assertEqual(listed.returncode, 0, listed.stdout + listed.stderr) tools = json.loads(json.loads(listed.stdout)['stdout']) self.assertEqual(tools[0]['name'], 'echo') called = self.invoke('tools/call', {'text': '中文 response'}) self.assertEqual(called.returncode, 0, called.stdout + called.stderr) result = json.loads(json.loads(called.stdout)['stdout']) self.assertEqual(result['content'][0]['text'], '中文 response') def test_auth_error_does_not_leak_remote_secret_response(self): failed = self.invoke('tools/list', authorized=False) self.assertNotEqual(failed.returncode, 0) self.assertNotIn(TOKEN, failed.stdout + failed.stderr) self.assertIn('EFFECT_UNKNOWN', failed.stdout) def test_session_expiry_and_lost_response_never_replay_mutation(self): for failure in ('expired', 'disconnect'): before = self.server.calls failed = self.invoke('tools/call', {failure: True}) self.assertNotEqual(failed.returncode, 0) self.assertEqual(self.server.calls - before, 1) self.assertIn('EFFECT_UNKNOWN', failed.stdout) def test_oversized_response_is_bounded_and_not_reported_as_success(self): failed = self.invoke('tools/call', {'large': True}) self.assertNotEqual(failed.returncode, 0) self.assertLess(len(failed.stdout), 1024) self.assertIn('EFFECT_UNKNOWN', failed.stdout) def test_tool_error_is_preserved_for_api_reconciliation(self): called = self.invoke('tools/call', {'fail': True}) self.assertEqual(called.returncode, 0, called.stdout) self.assertTrue(json.loads(json.loads(called.stdout)['stdout'])['isError']) if __name__ == '__main__': unittest.main()