Skip to content

Commit 8621644

Browse files
committed
test coverage and lint
1 parent 1f4f476 commit 8621644

File tree

2 files changed

+43
-125
lines changed

2 files changed

+43
-125
lines changed

aws-opentelemetry-distro/src/amazon/opentelemetry/distro/instrumentation/mcp/mcp_instrumentor.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
# SPDX-License-Identifier: Apache-2.0
33
from typing import Any, Callable, Collection, Dict, Tuple
44

5-
import mcp.types as types
65
from wrapt import register_post_import_hook, wrap_function_wrapper
76

87
from opentelemetry import trace
@@ -129,6 +128,8 @@ async def _wrap_handle_request(
129128

130129
@staticmethod
131130
def _generate_mcp_attributes(span: trace.Span, request: Any, is_client: bool) -> None:
131+
import mcp.types as types # pylint: disable=import-outside-toplevel,consider-using-from-import
132+
132133
operation = MCPOperations.UNKNOWN_OPERATION
133134

134135
if isinstance(request, types.ListToolsRequest):
@@ -184,6 +185,9 @@ def _extract_span_context_from_traceparent(traceparent: str):
184185

185186
@staticmethod
186187
def _get_mcp_operation(req: Any) -> str:
188+
189+
import mcp.types as types # pylint: disable=import-outside-toplevel,consider-using-from-import
190+
187191
span_name = "unknown"
188192

189193
if isinstance(req, types.ListToolsRequest):

aws-opentelemetry-distro/tests/amazon/opentelemetry/distro/test_mcpinstrumentor.py

Lines changed: 38 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
import sys
1010
import unittest
1111
from typing import Any, Dict, List, Optional
12-
from unittest.mock import MagicMock, patch
12+
from unittest.mock import MagicMock
1313

1414
from amazon.opentelemetry.distro.instrumentation.mcp import version
1515
from amazon.opentelemetry.distro.instrumentation.mcp.constants import MCPEnvironmentVariables
@@ -885,147 +885,61 @@ def model_validate(cls, data):
885885
return cls(params=FakeParams(name=data["params"].get("name")))
886886

887887

888-
class TestMCPTypesCoverage(unittest.TestCase):
889-
"""Test isinstance checks in _generate_mcp_attributes and _get_mcp_operation"""
888+
class TestGetMCPOperation(unittest.TestCase):
889+
"""Test _get_mcp_operation function with patched types"""
890890

891-
def setUp(self) -> None:
891+
def setUp(self):
892892
self.instrumentor = MCPInstrumentor()
893-
self.mock_span = MagicMock()
894893

895-
def test_generate_mcp_attributes_list_tools(self) -> None:
896-
"""Test _generate_mcp_attributes with ListToolsRequest"""
897-
from amazon.opentelemetry.distro.instrumentation.mcp.semconv import MCPAttributes
894+
@unittest.mock.patch("mcp.types")
895+
def test_get_mcp_operation_coverage(self, mock_types):
896+
"""Test _get_mcp_operation with all request types"""
898897

899-
class MockListToolsRequest:
898+
# Create actual classes for isinstance checks
899+
class FakeListToolsRequest:
900900
pass
901901

902-
with patch("amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor.types") as mock_types:
903-
mock_types.ListToolsRequest = MockListToolsRequest
904-
mock_types.CallToolRequest = type("CallToolRequest", (), {})
905-
mock_types.InitializeRequest = type("InitializeRequest", (), {})
906-
907-
request = MockListToolsRequest()
908-
self.instrumentor._generate_mcp_attributes(self.mock_span, request, is_client=True)
909-
self.mock_span.set_attribute.assert_any_call(MCPAttributes.MCP_LIST_TOOLS, True)
910-
911-
def test_generate_mcp_attributes_call_tool(self) -> None:
912-
"""Test _generate_mcp_attributes with CallToolRequest"""
913-
from amazon.opentelemetry.distro.instrumentation.mcp.semconv import MCPAttributes
914-
915-
class MockCallToolRequest:
902+
class FakeCallToolRequest:
916903
def __init__(self):
917904
self.params = type("Params", (), {"name": "test_tool"})()
918905

919-
with patch("amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor.types") as mock_types:
920-
mock_types.ListToolsRequest = type("ListToolsRequest", (), {})
921-
mock_types.CallToolRequest = MockCallToolRequest
922-
mock_types.InitializeRequest = type("InitializeRequest", (), {})
923-
924-
request = MockCallToolRequest()
925-
self.instrumentor._generate_mcp_attributes(self.mock_span, request, is_client=True)
926-
self.mock_span.set_attribute.assert_any_call(MCPAttributes.MCP_CALL_TOOL, True)
906+
# Set up mock types
907+
mock_types.ListToolsRequest = FakeListToolsRequest
908+
mock_types.CallToolRequest = FakeCallToolRequest
927909

928-
def test_generate_mcp_attributes_initialize(self) -> None:
929-
"""Test _generate_mcp_attributes with InitializeRequest"""
930-
from amazon.opentelemetry.distro.instrumentation.mcp.semconv import MCPAttributes
910+
# Test ListToolsRequest path
911+
result1 = self.instrumentor._get_mcp_operation(FakeListToolsRequest())
912+
self.assertEqual(result1, "tools/list")
931913

932-
class MockInitializeRequest:
933-
pass
934-
935-
with patch("amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor.types") as mock_types:
936-
mock_types.ListToolsRequest = type("ListToolsRequest", (), {})
937-
mock_types.CallToolRequest = type("CallToolRequest", (), {})
938-
mock_types.InitializeRequest = MockInitializeRequest
914+
# Test CallToolRequest path
915+
result2 = self.instrumentor._get_mcp_operation(FakeCallToolRequest())
916+
self.assertEqual(result2, "tools/test_tool")
939917

940-
request = MockInitializeRequest()
941-
self.instrumentor._generate_mcp_attributes(self.mock_span, request, is_client=True)
942-
self.mock_span.set_attribute.assert_any_call(MCPAttributes.MCP_INITIALIZE, True)
918+
# Test unknown request type
919+
result3 = self.instrumentor._get_mcp_operation(object())
920+
self.assertEqual(result3, "unknown")
943921

944-
def test_get_mcp_operation_list_tools(self) -> None:
945-
"""Test _get_mcp_operation with ListToolsRequest"""
922+
@unittest.mock.patch("mcp.types")
923+
def test_generate_mcp_attributes_coverage(self, mock_types):
924+
"""Test _generate_mcp_attributes with all request types"""
946925

947-
class MockListToolsRequest:
926+
class FakeListToolsRequest:
948927
pass
949928

950-
with patch("amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor.types") as mock_types:
951-
mock_types.ListToolsRequest = MockListToolsRequest
952-
mock_types.CallToolRequest = type("CallToolRequest", (), {})
953-
954-
request = MockListToolsRequest()
955-
result = self.instrumentor._get_mcp_operation(request)
956-
self.assertEqual(result, "tools/list")
957-
958-
def test_get_mcp_operation_call_tool(self) -> None:
959-
"""Test _get_mcp_operation with CallToolRequest"""
960-
961-
class MockCallToolRequest:
929+
class FakeCallToolRequest:
962930
def __init__(self):
963-
self.params = type("Params", (), {"name": "my_tool"})()
964-
965-
with patch("amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor.types") as mock_types:
966-
mock_types.ListToolsRequest = type("ListToolsRequest", (), {})
967-
mock_types.CallToolRequest = MockCallToolRequest
968-
969-
request = MockCallToolRequest()
970-
result = self.instrumentor._get_mcp_operation(request)
971-
self.assertEqual(result, "tools/my_tool")
972-
973-
def test_get_mcp_operation_unknown(self) -> None:
974-
"""Test _get_mcp_operation with unknown request type"""
975-
with patch("amazon.opentelemetry.distro.instrumentation.mcp.mcp_instrumentor.types") as mock_types:
976-
mock_types.ListToolsRequest = type("ListToolsRequest", (), {})
977-
mock_types.CallToolRequest = type("CallToolRequest", (), {})
931+
self.params = type("Params", (), {"name": "test_tool"})()
978932

979-
unknown_request = object()
980-
result = self.instrumentor._get_mcp_operation(unknown_request)
981-
self.assertEqual(result, "unknown")
933+
class FakeInitializeRequest:
934+
pass
982935

936+
mock_types.ListToolsRequest = FakeListToolsRequest
937+
mock_types.CallToolRequest = FakeCallToolRequest
938+
mock_types.InitializeRequest = FakeInitializeRequest
983939

984-
class TestAdditionalCoverage(unittest.TestCase):
985-
def setUp(self):
986-
self.instrumentor = MCPInstrumentor()
987-
mock_tracer = MagicMock()
988940
mock_span = MagicMock()
989-
mock_span_context = MagicMock(trace_id=123, span_id=456)
990-
mock_span.get_span_context.return_value = mock_span_context
991-
mock_tracer.start_as_current_span.return_value.__enter__.return_value = mock_span
992-
self.instrumentor.tracer = mock_tracer
993-
994-
def test_wrap_send_request_kwargs_only_with_extra_args(self):
995-
"""Covers case where kwargs['request'] is set and args has extra elements."""
996-
997-
async def dummy_wrapped(*args, **kwargs):
998-
return {"ok": True}
999-
1000-
req = FakeRequest(params=FakeParams("tool1"))
1001-
with patch.object(self.instrumentor, "_generate_mcp_attributes"):
1002-
result = asyncio.run(self.instrumentor._wrap_send_request(dummy_wrapped, None, (), {"request": req}))
1003-
self.assertTrue(result["ok"])
1004-
1005-
def test_wrap_handle_request_invalid_traceparent_path(self):
1006-
"""Covers case where traceparent exists but is invalid -> no span created."""
1007-
bad_meta = type("Meta", (), {"traceparent": "invalid-format"})
1008-
req = FakeRequest(params=FakeParams(name="tool1", meta=bad_meta))
1009-
1010-
async def dummy_wrapped(*args, **kwargs):
1011-
return {"ok": True}
1012-
1013-
result = asyncio.run(self.instrumentor._wrap_handle_request(dummy_wrapped, None, (None, req), {}))
1014-
self.assertTrue(result["ok"])
1015-
self.instrumentor.tracer.start_as_current_span.assert_not_called()
1016-
1017-
def test_add_client_attributes_missing_name_env_var(self):
1018-
"""Covers _add_client_attributes with env var set but no name."""
1019-
span = MagicMock()
1020-
req = FakeRequest(params=FakeParams(name=None))
1021-
with patch.dict("os.environ", {"MCP_INSTRUMENTATION_SERVER_NAME": "env_server"}):
1022-
self.instrumentor._add_client_attributes(span, "op", req)
1023-
span.set_attribute.assert_any_call("rpc.service", "env_server")
1024-
1025-
def test_add_server_attributes_name_missing(self):
1026-
"""Covers _add_server_attributes when .params exists but .name is None."""
1027-
span = MagicMock()
1028-
req = FakeRequest(params=FakeParams(name=None))
1029-
self.instrumentor._add_server_attributes(span, "op", req)
1030-
# The method should still be called but with None value, so we just verify it was called
1031-
span.set_attribute.assert_called_once()
941+
self.instrumentor._generate_mcp_attributes(mock_span, FakeListToolsRequest(), True)
942+
self.instrumentor._generate_mcp_attributes(mock_span, FakeCallToolRequest(), True)
943+
self.instrumentor._generate_mcp_attributes(mock_span, FakeInitializeRequest(), True)
944+
mock_span.set_attribute.assert_any_call("mcp.list_tools", True)
945+
mock_span.set_attribute.assert_any_call("mcp.call_tool", True)

0 commit comments

Comments
 (0)