Skip to content

Commit 0a46ebc

Browse files
cursoragentalex
andcommitted
Add MCP Agent instrumentation support for AgentOps tracing
Co-authored-by: alex <alex@agentops.ai>
1 parent 77e3930 commit 0a46ebc

4 files changed

Lines changed: 127 additions & 0 deletions

File tree

agentops/instrumentation/__init__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,12 @@ class InstrumentorConfig(TypedDict):
107107
"class_name": "SmolagentsInstrumentor",
108108
"min_version": "1.0.0",
109109
},
110+
"mcp_agent": {
111+
"module_name": "agentops.instrumentation.agentic.mcp_agent",
112+
"class_name": "MCPAgentInstrumentor",
113+
"min_version": "0.1.0",
114+
"package_name": "mcp-agent",
115+
},
110116
"langgraph": {
111117
"module_name": "agentops.instrumentation.agentic.langgraph",
112118
"class_name": "LanggraphInstrumentor",
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
"""MCP Agent instrumentation for AgentOps."""
2+
3+
from agentops.instrumentation.agentic.mcp_agent.instrumentor import MCPAgentInstrumentor
4+
5+
__all__ = ["MCPAgentInstrumentor"]
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
"""MCP Agent Instrumentation for AgentOps
2+
3+
This instrumentor hooks into mcp-agent's telemetry to ensure spans are created
4+
with the AgentOps tracer provider and to prevent duplicate or conflicting tracer usage.
5+
"""
6+
7+
from typing import Dict, Any
8+
9+
from opentelemetry.metrics import Meter
10+
11+
from agentops.logging import logger
12+
from agentops.instrumentation.common import CommonInstrumentor, StandardMetrics, InstrumentorConfig
13+
from agentops.instrumentation.agentic.mcp_agent.patch import patch_mcp_agent, unpatch_mcp_agent
14+
15+
# Library info for tracer/meter
16+
LIBRARY_NAME = "agentops.instrumentation.agentic.mcp_agent"
17+
LIBRARY_VERSION = "0.1.0"
18+
19+
20+
class MCPAgentInstrumentor(CommonInstrumentor):
21+
"""An instrumentor for Lastmile MCP Agent.
22+
23+
This instrumentor patches mcp-agent's telemetry get_tracer to return the
24+
AgentOps tracer, ensuring spans flow through AgentOps' configured provider.
25+
"""
26+
27+
def __init__(self):
28+
"""Initialize the MCP Agent instrumentor."""
29+
config = InstrumentorConfig(
30+
library_name=LIBRARY_NAME,
31+
library_version=LIBRARY_VERSION,
32+
wrapped_methods=[], # We use patching
33+
metrics_enabled=True,
34+
dependencies=["mcp-agent >= 0.1.0"],
35+
)
36+
super().__init__(config)
37+
38+
def _create_metrics(self, meter: Meter) -> Dict[str, Any]:
39+
"""Create metrics for the instrumentor."""
40+
return StandardMetrics.create_standard_metrics(meter)
41+
42+
def _custom_wrap(self, **kwargs):
43+
"""Apply custom patching for MCP Agent."""
44+
patch_mcp_agent(self._tracer)
45+
logger.info("MCP Agent instrumentation enabled")
46+
47+
def _custom_unwrap(self, **kwargs):
48+
"""Remove custom patching from MCP Agent."""
49+
unpatch_mcp_agent()
50+
logger.info("MCP Agent instrumentation disabled")
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
"""Patching utilities for mcp-agent telemetry integration.
2+
3+
We replace mcp_agent.tracing.telemetry.get_tracer to return the AgentOps tracer,
4+
so spans created by mcp-agent flow through AgentOps' configured provider.
5+
"""
6+
7+
from typing import Optional
8+
9+
from opentelemetry.trace import Tracer
10+
11+
from agentops.logging import logger
12+
13+
14+
_original_get_tracer = None # type: ignore[var-annotated]
15+
16+
17+
def patch_mcp_agent(agentops_tracer: Optional[Tracer]) -> None:
18+
"""Patch mcp-agent telemetry to use the AgentOps tracer.
19+
20+
If the AgentOps tracer is None (unexpected), the patch is skipped.
21+
"""
22+
if agentops_tracer is None:
23+
logger.debug("patch_mcp_agent: AgentOps tracer is None; skipping patch")
24+
return
25+
26+
global _original_get_tracer
27+
28+
try:
29+
import mcp_agent.tracing.telemetry as mcp_telemetry # type: ignore
30+
31+
get_tracer_func = getattr(mcp_telemetry, "get_tracer", None)
32+
if get_tracer_func is None:
33+
logger.debug("patch_mcp_agent: mcp_agent.tracing.telemetry.get_tracer not found; skipping patch")
34+
return
35+
36+
if getattr(get_tracer_func, "__agentops_patched__", False):
37+
logger.debug("patch_mcp_agent: already patched; skipping")
38+
return
39+
40+
_original_get_tracer = get_tracer_func
41+
42+
def _agentops_get_tracer(_context): # context is ignored; AgentOps manages provider
43+
return agentops_tracer
44+
45+
# Tag function to avoid double patching
46+
setattr(_agentops_get_tracer, "__agentops_patched__", True)
47+
48+
mcp_telemetry.get_tracer = _agentops_get_tracer # type: ignore[attr-defined]
49+
logger.debug("Patched mcp_agent.tracing.telemetry.get_tracer to use AgentOps tracer")
50+
except Exception as e:
51+
logger.debug(f"patch_mcp_agent: failed to patch mcp-agent telemetry: {e}")
52+
53+
54+
def unpatch_mcp_agent() -> None:
55+
"""Restore original mcp-agent telemetry.get_tracer if it was patched."""
56+
global _original_get_tracer
57+
try:
58+
if _original_get_tracer is None:
59+
return
60+
import mcp_agent.tracing.telemetry as mcp_telemetry # type: ignore
61+
62+
mcp_telemetry.get_tracer = _original_get_tracer # type: ignore[attr-defined]
63+
_original_get_tracer = None
64+
logger.debug("Restored original mcp_agent.tracing.telemetry.get_tracer")
65+
except Exception as e:
66+
logger.debug(f"unpatch_mcp_agent: failed to restore mcp-agent telemetry: {e}")

0 commit comments

Comments
 (0)