From f0039088c2505911c73e6a7374aa105415378277 Mon Sep 17 00:00:00 2001 From: Chris Christiansen Date: Wed, 23 Sep 2026 04:29:03 +0000 Subject: [PATCH] feat(opax-mcp): add internal Cloud Run v2 fetch helper - Add a private helper that performs an authenticated, read-only GET for the configured opax-mcp Cloud Run v2 service. - Return only generation, observedGeneration, and updateTime on success, with sanitized reason codes for token, HTTP, network, timeout, and parsing errors. - Add mocked unit coverage for successful responses and handled failure paths. --- agents/mcp_server/server.py | 58 ++++++++++++++ agents/mcp_server/test_server.py | 131 ++++++++++++++++++++++++++++++- 2 files changed, 188 insertions(+), 1 deletion(-) diff --git a/agents/mcp_server/server.py b/agents/mcp_server/server.py index 073aa8a..dff7885 100644 --- a/agents/mcp_server/server.py +++ b/agents/mcp_server/server.py @@ -116,6 +116,64 @@ def _sanitize_opax_deployment_status(service_data: dict) -> dict: } +def _fetch_opax_deployment_service_data() -> dict: + """ + Fetches the raw Cloud Run v2 service object for OPAX-MCP, returning + only a minimal subset of fields or a sanitized failure reason. + """ + import json + import socket + import urllib.error + import urllib.request + + try: + token = _get_access_token() + if not token: + return {"reason_code": "ADC_TOKEN_UNAVAILABLE"} + + endpoint_url = ( + f"https://run.googleapis.com/v2/projects/{PROJECT_ID}/" + f"locations/{_OPAX_MCP_REGION}/services/{_OPAX_MCP_SERVICE}" + ) + req = urllib.request.Request( + endpoint_url, headers={"Authorization": f"Bearer {token}"}, method="GET" + ) + + with urllib.request.urlopen(req, timeout=10) as resp: + status = getattr(resp, "status", None) + if not isinstance(status, int): + return {"reason_code": "UNEXPECTED_LOCAL_FAILURE"} + if not (200 <= status < 300): + return {"reason_code": "UNEXPECTED_NON_2XX_STATUS"} + + body_bytes = resp.read() + + body = json.loads(body_bytes) + if not isinstance(body, dict): + return {"reason_code": "INVALID_RESPONSE_SHAPE"} + + return { + "generation": body.get("generation"), + "observedGeneration": body.get("observedGeneration"), + "updateTime": body.get("updateTime"), + } + + except json.JSONDecodeError: + return {"reason_code": "INVALID_JSON_RESPONSE"} + except urllib.error.HTTPError as e: + if e.code in (401, 403): + return {"reason_code": "CLOUD_RUN_UNAUTHORIZED"} + if e.code == 404: + return {"reason_code": "CLOUD_RUN_NOT_FOUND"} + return {"reason_code": "UNEXPECTED_NON_2XX_STATUS"} + except socket.timeout: + return {"reason_code": "REQUEST_TIMEOUT"} + except urllib.error.URLError: + return {"reason_code": "NETWORK_FAILURE"} + except Exception: + return {"reason_code": "UNEXPECTED_LOCAL_FAILURE"} + + class PushStaticRequest(BaseModel): file_path: str content: str diff --git a/agents/mcp_server/test_server.py b/agents/mcp_server/test_server.py index 852833d..e898fbf 100644 --- a/agents/mcp_server/test_server.py +++ b/agents/mcp_server/test_server.py @@ -1,5 +1,12 @@ import unittest -from agents.mcp_server.server import _sanitize_opax_deployment_status +import json +import socket +import urllib.error +from unittest.mock import patch, MagicMock +from agents.mcp_server.server import ( + _fetch_opax_deployment_service_data, + _sanitize_opax_deployment_status, +) class TestSanitizeOpaxDeploymentStatus(unittest.TestCase): @@ -69,5 +76,127 @@ class TestSanitizeOpaxDeploymentStatus(unittest.TestCase): self.assertEqual(result["region"], "us-central1") self.assertIsNone(result["reason_code"]) + +class TestFetchOpaxDeploymentServiceData(unittest.TestCase): + + @patch('urllib.request.urlopen') + @patch('agents.mcp_server.server._get_access_token') + def test_success_filters_fields(self, mock_get_token, mock_urlopen): + mock_get_token.return_value = "fake-token" + mock_response = MagicMock() + mock_response.status = 200 + mock_response.read.return_value = json.dumps({ + "generation": 5, "observedGeneration": 5, "updateTime": "T", + "uri": "forbidden" + }).encode() + mock_urlopen.return_value.__enter__.return_value = mock_response + + result = _fetch_opax_deployment_service_data() + self.assertEqual(result, {"generation": 5, "observedGeneration": 5, "updateTime": "T"}) + self.assertEqual(set(result.keys()), {"generation", "observedGeneration", "updateTime"}) + + @patch('urllib.request.urlopen') + @patch('agents.mcp_server.server._get_access_token') + def test_success_missing_fields(self, mock_get_token, mock_urlopen): + mock_get_token.return_value = "fake-token" + mock_response = MagicMock() + mock_response.status = 200 + mock_response.read.return_value = json.dumps({"generation": 1}).encode() + mock_urlopen.return_value.__enter__.return_value = mock_response + + result = _fetch_opax_deployment_service_data() + self.assertEqual(result, {"generation": 1, "observedGeneration": None, "updateTime": None}) + + @patch('urllib.request.urlopen') + @patch('agents.mcp_server.server._get_access_token') + def test_falsy_token_skips_call(self, mock_get_token, mock_urlopen): + for token in [None, ""]: + with self.subTest(token=token): + mock_get_token.return_value = token + result = _fetch_opax_deployment_service_data() + self.assertEqual(result, {"reason_code": "ADC_TOKEN_UNAVAILABLE"}) + mock_urlopen.assert_not_called() + + @patch('urllib.request.urlopen') + @patch('agents.mcp_server.server._get_access_token') + def test_get_token_exception(self, mock_get_token, mock_urlopen): + mock_get_token.side_effect = Exception("local failure") + result = _fetch_opax_deployment_service_data() + self.assertEqual(result, {"reason_code": "UNEXPECTED_LOCAL_FAILURE"}) + mock_urlopen.assert_not_called() + + @patch('urllib.request.urlopen') + @patch('agents.mcp_server.server._get_access_token') + def test_absent_response_status(self, mock_get_token, mock_urlopen): + mock_get_token.return_value = "fake-token" + class MockResp: + def __init__(self): + self.read_called = False + def read(self): + self.read_called = True + return b"" + def __enter__(self): + return self + def __exit__(self, *args): + return False + + mock_response = MockResp() + mock_urlopen.return_value = mock_response + result = _fetch_opax_deployment_service_data() + self.assertEqual(result, {"reason_code": "UNEXPECTED_LOCAL_FAILURE"}) + self.assertFalse(mock_response.read_called) + + @patch('urllib.request.urlopen') + @patch('agents.mcp_server.server._get_access_token') + def test_non_2xx_status_skips_read(self, mock_get_token, mock_urlopen): + mock_get_token.return_value = "fake-token" + mock_response = MagicMock() + mock_response.status = 503 + mock_urlopen.return_value.__enter__.return_value = mock_response + + result = _fetch_opax_deployment_service_data() + self.assertEqual(result, {"reason_code": "UNEXPECTED_NON_2XX_STATUS"}) + mock_response.read.assert_not_called() + + @patch('urllib.request.urlopen') + @patch('agents.mcp_server.server._get_access_token') + def test_http_and_network_errors(self, mock_get_token, mock_urlopen): + mock_get_token.return_value = "fake-token" + errors_to_reasons = [ + (urllib.error.HTTPError(None, 401, "", {}, None), "CLOUD_RUN_UNAUTHORIZED"), + (urllib.error.HTTPError(None, 403, "", {}, None), "CLOUD_RUN_UNAUTHORIZED"), + (urllib.error.HTTPError(None, 404, "", {}, None), "CLOUD_RUN_NOT_FOUND"), + (urllib.error.HTTPError(None, 500, "", {}, None), "UNEXPECTED_NON_2XX_STATUS"), + (socket.timeout(), "REQUEST_TIMEOUT"), + (urllib.error.URLError("DNS failure"), "NETWORK_FAILURE"), + ] + for error, reason in errors_to_reasons: + with self.subTest(error=error.__class__.__name__, code=getattr(error, 'code', 'N/A')): + mock_urlopen.side_effect = error + result = _fetch_opax_deployment_service_data() + self.assertEqual(result, {"reason_code": reason}) + mock_urlopen.side_effect = None + + @patch('urllib.request.urlopen') + @patch('agents.mcp_server.server._get_access_token') + def test_invalid_json_body(self, mock_get_token, mock_urlopen): + mock_get_token.return_value = "fake-token" + mock_response = MagicMock() + mock_response.status = 200 + mock_urlopen.return_value.__enter__.return_value = mock_response + + # Malformed JSON + with self.subTest(case="malformed"): + mock_response.read.return_value = b'{"key":' + result = _fetch_opax_deployment_service_data() + self.assertEqual(result, {"reason_code": "INVALID_JSON_RESPONSE"}) + + # Valid JSON, but not a dictionary object + with self.subTest(case="non-object"): + mock_response.read.return_value = b'[1, 2, 3]' + result = _fetch_opax_deployment_service_data() + self.assertEqual(result, {"reason_code": "INVALID_RESPONSE_SHAPE"}) + + if __name__ == '__main__': unittest.main()