mcp-use 1.3.0__py3-none-any.whl → 1.3.2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of mcp-use might be problematic. Click here for more details.
- mcp_use/__init__.py +2 -0
- mcp_use/adapters/base.py +2 -6
- mcp_use/adapters/langchain_adapter.py +4 -11
- mcp_use/agents/base.py +1 -3
- mcp_use/agents/mcpagent.py +121 -45
- mcp_use/agents/prompts/system_prompt_builder.py +1 -3
- mcp_use/client.py +26 -11
- mcp_use/config.py +9 -9
- mcp_use/connectors/base.py +136 -32
- mcp_use/connectors/http.py +100 -30
- mcp_use/connectors/sandbox.py +11 -16
- mcp_use/connectors/stdio.py +8 -5
- mcp_use/connectors/websocket.py +8 -5
- mcp_use/logging.py +1 -1
- mcp_use/managers/server_manager.py +5 -16
- mcp_use/managers/tools/disconnect_server.py +1 -3
- mcp_use/managers/tools/get_active_server.py +1 -4
- mcp_use/managers/tools/search_tools.py +29 -36
- mcp_use/managers/tools/use_tool.py +5 -18
- mcp_use/observability/__init__.py +8 -0
- mcp_use/observability/laminar.py +21 -0
- mcp_use/observability/langfuse.py +35 -0
- mcp_use/session.py +1 -4
- mcp_use/task_managers/__init__.py +2 -1
- mcp_use/task_managers/base.py +10 -4
- mcp_use/task_managers/streamable_http.py +81 -0
- mcp_use/task_managers/websocket.py +5 -0
- mcp_use/telemetry/__init__.py +0 -0
- mcp_use/telemetry/events.py +93 -0
- mcp_use/telemetry/telemetry.py +306 -0
- mcp_use/telemetry/utils.py +48 -0
- mcp_use/utils.py +27 -0
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.2.dist-info}/METADATA +82 -26
- mcp_use-1.3.2.dist-info/RECORD +49 -0
- mcp_use/types/clientoptions.py +0 -23
- mcp_use-1.3.0.dist-info/RECORD +0 -41
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.2.dist-info}/WHEEL +0 -0
- {mcp_use-1.3.0.dist-info → mcp_use-1.3.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -48,36 +48,12 @@ class SearchToolsTool(MCPServerTool):
|
|
|
48
48
|
results = await self._search_tool.search_tools(
|
|
49
49
|
query, top_k=top_k, active_server=self.server_manager.active_server
|
|
50
50
|
)
|
|
51
|
-
return
|
|
51
|
+
return results
|
|
52
52
|
|
|
53
53
|
def _run(self, query: str, top_k: int = 100) -> str:
|
|
54
54
|
"""Synchronous version that raises a NotImplementedError - use _arun instead."""
|
|
55
55
|
raise NotImplementedError("SearchToolsTool requires async execution. Use _arun instead.")
|
|
56
56
|
|
|
57
|
-
def format_search_results(self, results: list[tuple[BaseTool, str, float]]) -> str:
|
|
58
|
-
"""Format search results in a consistent format."""
|
|
59
|
-
|
|
60
|
-
# Only show top_k results
|
|
61
|
-
results = results
|
|
62
|
-
|
|
63
|
-
formatted_output = "Search results\n\n"
|
|
64
|
-
|
|
65
|
-
for i, (tool, server_name, score) in enumerate(results):
|
|
66
|
-
# Format score as percentage
|
|
67
|
-
if i < 5:
|
|
68
|
-
score_pct = f"{score * 100:.1f}%"
|
|
69
|
-
logger.info(f"{i}: {tool.name} ({score_pct} match)")
|
|
70
|
-
formatted_output += f"[{i + 1}] Tool: {tool.name} ({score_pct} match)\n"
|
|
71
|
-
formatted_output += f" Server: {server_name}\n"
|
|
72
|
-
formatted_output += f" Description: {tool.description}\n\n"
|
|
73
|
-
|
|
74
|
-
# Add footer with information about how to use the results
|
|
75
|
-
formatted_output += (
|
|
76
|
-
"\nTo use a tool, connect to the appropriate server first, then invoke the tool."
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
return formatted_output
|
|
80
|
-
|
|
81
57
|
|
|
82
58
|
class ToolSearchEngine:
|
|
83
59
|
"""
|
|
@@ -115,13 +91,18 @@ class ToolSearchEngine:
|
|
|
115
91
|
|
|
116
92
|
try:
|
|
117
93
|
from fastembed import TextEmbedding # optional dependency install with [search]
|
|
118
|
-
except ImportError:
|
|
94
|
+
except ImportError as exc:
|
|
119
95
|
logger.error(
|
|
120
96
|
"The 'fastembed' library is not installed. "
|
|
121
97
|
"To use the search functionality, please install it by running: "
|
|
122
98
|
"pip install mcp-use[search]"
|
|
123
99
|
)
|
|
124
|
-
|
|
100
|
+
raise ImportError(
|
|
101
|
+
"The 'fastembed' library is not installed. "
|
|
102
|
+
"To use the server_manager functionality, please install it by running: "
|
|
103
|
+
"pip install mcp-use[search] "
|
|
104
|
+
"or disable the server_manager by setting use_server_manager=False in the MCPAgent constructor."
|
|
105
|
+
) from exc
|
|
125
106
|
|
|
126
107
|
try:
|
|
127
108
|
self.model = TextEmbedding(model_name="BAAI/bge-small-en-v1.5")
|
|
@@ -282,11 +263,7 @@ class ToolSearchEngine:
|
|
|
282
263
|
)
|
|
283
264
|
|
|
284
265
|
# If the server manager has an active server but it wasn't provided, use it
|
|
285
|
-
if (
|
|
286
|
-
active_server is None
|
|
287
|
-
and self.server_manager
|
|
288
|
-
and hasattr(self.server_manager, "active_server")
|
|
289
|
-
):
|
|
266
|
+
if active_server is None and self.server_manager and hasattr(self.server_manager, "active_server"):
|
|
290
267
|
active_server = self.server_manager.active_server
|
|
291
268
|
|
|
292
269
|
results = self.search(query, top_k=top_k)
|
|
@@ -303,11 +280,27 @@ class ToolSearchEngine:
|
|
|
303
280
|
marked_results = []
|
|
304
281
|
for tool, server_name, score in results:
|
|
305
282
|
# If this is the active server, add "(ACTIVE)" marker
|
|
306
|
-
display_server = (
|
|
307
|
-
f"{server_name} (ACTIVE)" if server_name == active_server else server_name
|
|
308
|
-
)
|
|
283
|
+
display_server = f"{server_name} (ACTIVE)" if server_name == active_server else server_name
|
|
309
284
|
marked_results.append((tool, display_server, score))
|
|
310
285
|
results = marked_results
|
|
311
286
|
|
|
312
287
|
# Format and return the results
|
|
313
|
-
return results
|
|
288
|
+
return self._format_search_results(results)
|
|
289
|
+
|
|
290
|
+
def _format_search_results(self, results: list[tuple[BaseTool, str, float]]) -> str:
|
|
291
|
+
"""Format search results in a consistent format."""
|
|
292
|
+
formatted_output = "Search results\n\n"
|
|
293
|
+
|
|
294
|
+
for i, (tool, server_name, score) in enumerate(results):
|
|
295
|
+
# Format score as percentage
|
|
296
|
+
score_pct = f"{score * 100:.1f}%"
|
|
297
|
+
if i < 5:
|
|
298
|
+
logger.info(f"{i}: {tool.name} ({score_pct} match)")
|
|
299
|
+
formatted_output += f"[{i + 1}] Tool: {tool.name} ({score_pct} match)\n"
|
|
300
|
+
formatted_output += f" Server: {server_name}\n"
|
|
301
|
+
formatted_output += f" Description: {tool.description}\n\n"
|
|
302
|
+
|
|
303
|
+
# Add footer with information about how to use the results
|
|
304
|
+
formatted_output += "\nTo use a tool, connect to the appropriate server first, then invoke the tool."
|
|
305
|
+
|
|
306
|
+
return formatted_output
|
|
@@ -30,9 +30,7 @@ class UseToolFromServerTool(MCPServerTool):
|
|
|
30
30
|
)
|
|
31
31
|
args_schema: ClassVar[type[BaseModel]] = UseToolInput
|
|
32
32
|
|
|
33
|
-
async def _arun(
|
|
34
|
-
self, server_name: str, tool_name: str, tool_input: dict[str, Any] | str
|
|
35
|
-
) -> str:
|
|
33
|
+
async def _arun(self, server_name: str, tool_name: str, tool_input: dict[str, Any] | str) -> str:
|
|
36
34
|
"""Execute a tool from a specific server."""
|
|
37
35
|
# Check if server exists
|
|
38
36
|
servers = self.server_manager.client.get_server_names()
|
|
@@ -78,20 +76,14 @@ class UseToolFromServerTool(MCPServerTool):
|
|
|
78
76
|
|
|
79
77
|
if not target_tool:
|
|
80
78
|
tool_names = [t.name for t in server_tools]
|
|
81
|
-
return (
|
|
82
|
-
f"Tool '{tool_name}' not found on server '{server_name}'. "
|
|
83
|
-
f"Available tools: {', '.join(tool_names)}"
|
|
84
|
-
)
|
|
79
|
+
return f"Tool '{tool_name}' not found on server '{server_name}'. Available tools: {', '.join(tool_names)}"
|
|
85
80
|
|
|
86
81
|
# Execute the tool with the provided input
|
|
87
82
|
try:
|
|
88
83
|
# Parse the input based on target tool's schema
|
|
89
84
|
structured_input = self._parse_tool_input(target_tool, tool_input)
|
|
90
85
|
if structured_input is None:
|
|
91
|
-
return
|
|
92
|
-
f"Could not parse input for tool '{tool_name}'."
|
|
93
|
-
" Please check the input format and try again."
|
|
94
|
-
)
|
|
86
|
+
return f"Could not parse input for tool '{tool_name}'. Please check the input format and try again."
|
|
95
87
|
|
|
96
88
|
# Store the previous active server
|
|
97
89
|
previous_active = self.server_manager.active_server
|
|
@@ -100,10 +92,7 @@ class UseToolFromServerTool(MCPServerTool):
|
|
|
100
92
|
self.server_manager.active_server = server_name
|
|
101
93
|
|
|
102
94
|
# Execute the tool
|
|
103
|
-
logger.info(
|
|
104
|
-
f"Executing tool '{tool_name}' on server '{server_name}'"
|
|
105
|
-
"with input: {structured_input}"
|
|
106
|
-
)
|
|
95
|
+
logger.info(f"Executing tool '{tool_name}' on server '{server_name}'with input: {{structured_input}}")
|
|
107
96
|
result = await target_tool._arun(**structured_input)
|
|
108
97
|
|
|
109
98
|
# Restore the previous active server
|
|
@@ -162,6 +151,4 @@ class UseToolFromServerTool(MCPServerTool):
|
|
|
162
151
|
|
|
163
152
|
def _run(self, server_name: str, tool_name: str, tool_input: dict[str, Any] | str) -> str:
|
|
164
153
|
"""Synchronous version that raises a NotImplementedError."""
|
|
165
|
-
raise NotImplementedError(
|
|
166
|
-
"UseToolFromServerTool requires async execution. Use _arun instead."
|
|
167
|
-
)
|
|
154
|
+
raise NotImplementedError("UseToolFromServerTool requires async execution. Use _arun instead.")
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
# Check if Laminar is disabled via environment variable
|
|
7
|
+
_laminar_disabled = os.getenv("MCP_USE_LAMINAR", "").lower() == "false"
|
|
8
|
+
|
|
9
|
+
# Only initialize if not disabled and API key is present
|
|
10
|
+
if _laminar_disabled:
|
|
11
|
+
logger.debug("Laminar tracing disabled via MCP_USE_LAMINAR environment variable")
|
|
12
|
+
elif not os.getenv("LAMINAR_PROJECT_API_KEY"):
|
|
13
|
+
logger.debug("Laminar API key not found - tracing disabled. Set LAMINAR_PROJECT_API_KEY to enable")
|
|
14
|
+
else:
|
|
15
|
+
try:
|
|
16
|
+
from lmnr import Laminar
|
|
17
|
+
|
|
18
|
+
Laminar.initialize(project_api_key=os.getenv("LAMINAR_PROJECT_API_KEY"))
|
|
19
|
+
logger.debug("Laminar observability initialized successfully")
|
|
20
|
+
except ImportError:
|
|
21
|
+
logger.debug("Laminar package not installed - tracing disabled. Install with: pip install lmnr")
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
logger = logging.getLogger(__name__)
|
|
5
|
+
|
|
6
|
+
# Check if Langfuse is disabled via environment variable
|
|
7
|
+
_langfuse_disabled = os.getenv("MCP_USE_LANGFUSE", "").lower() == "false"
|
|
8
|
+
|
|
9
|
+
# Only initialize if not disabled and required keys are present
|
|
10
|
+
if _langfuse_disabled:
|
|
11
|
+
logger.debug("Langfuse tracing disabled via MCP_USE_LANGFUSE environment variable")
|
|
12
|
+
langfuse = None
|
|
13
|
+
langfuse_handler = None
|
|
14
|
+
elif not os.getenv("LANGFUSE_PUBLIC_KEY") or not os.getenv("LANGFUSE_SECRET_KEY"):
|
|
15
|
+
logger.debug(
|
|
16
|
+
"Langfuse API keys not found - tracing disabled. Set LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY to enable"
|
|
17
|
+
)
|
|
18
|
+
langfuse = None
|
|
19
|
+
langfuse_handler = None
|
|
20
|
+
else:
|
|
21
|
+
try:
|
|
22
|
+
from langfuse import Langfuse
|
|
23
|
+
from langfuse.langchain import CallbackHandler
|
|
24
|
+
|
|
25
|
+
langfuse = Langfuse(
|
|
26
|
+
public_key=os.getenv("LANGFUSE_PUBLIC_KEY"),
|
|
27
|
+
secret_key=os.getenv("LANGFUSE_SECRET_KEY"),
|
|
28
|
+
host=os.getenv("LANGFUSE_HOST", "https://cloud.langfuse.com"),
|
|
29
|
+
)
|
|
30
|
+
langfuse_handler = CallbackHandler()
|
|
31
|
+
logger.debug("Langfuse observability initialized successfully")
|
|
32
|
+
except ImportError:
|
|
33
|
+
logger.debug("Langfuse package not installed - tracing disabled. Install with: pip install langfuse")
|
|
34
|
+
langfuse = None
|
|
35
|
+
langfuse_handler = None
|
mcp_use/session.py
CHANGED
|
@@ -7,8 +7,6 @@ which handles authentication, initialization, and tool discovery.
|
|
|
7
7
|
|
|
8
8
|
from typing import Any
|
|
9
9
|
|
|
10
|
-
from mcp.types import Tool
|
|
11
|
-
|
|
12
10
|
from .connectors.base import BaseConnector
|
|
13
11
|
|
|
14
12
|
|
|
@@ -32,7 +30,6 @@ class MCPSession:
|
|
|
32
30
|
"""
|
|
33
31
|
self.connector = connector
|
|
34
32
|
self.session_info: dict[str, Any] | None = None
|
|
35
|
-
self.tools: list[Tool] = []
|
|
36
33
|
self.auto_connect = auto_connect
|
|
37
34
|
|
|
38
35
|
async def __aenter__(self) -> "MCPSession":
|
|
@@ -84,4 +81,4 @@ class MCPSession:
|
|
|
84
81
|
Returns:
|
|
85
82
|
True if the connector is connected, False otherwise.
|
|
86
83
|
"""
|
|
87
|
-
return
|
|
84
|
+
return self.connector.is_connected
|
|
@@ -8,12 +8,13 @@ through different transport mechanisms.
|
|
|
8
8
|
from .base import ConnectionManager
|
|
9
9
|
from .sse import SseConnectionManager
|
|
10
10
|
from .stdio import StdioConnectionManager
|
|
11
|
+
from .streamable_http import StreamableHttpConnectionManager
|
|
11
12
|
from .websocket import WebSocketConnectionManager
|
|
12
13
|
|
|
13
14
|
__all__ = [
|
|
14
15
|
"ConnectionManager",
|
|
15
|
-
"HttpConnectionManager",
|
|
16
16
|
"StdioConnectionManager",
|
|
17
17
|
"WebSocketConnectionManager",
|
|
18
18
|
"SseConnectionManager",
|
|
19
|
+
"StreamableHttpConnectionManager",
|
|
19
20
|
]
|
mcp_use/task_managers/base.py
CHANGED
|
@@ -70,9 +70,7 @@ class ConnectionManager(Generic[T], ABC):
|
|
|
70
70
|
self._exception = None
|
|
71
71
|
|
|
72
72
|
# Create a task to establish and maintain the connection
|
|
73
|
-
self._task = asyncio.create_task(
|
|
74
|
-
self._connection_task(), name=f"{self.__class__.__name__}_task"
|
|
75
|
-
)
|
|
73
|
+
self._task = asyncio.create_task(self._connection_task(), name=f"{self.__class__.__name__}_task")
|
|
76
74
|
|
|
77
75
|
# Wait for the connection to be ready or fail
|
|
78
76
|
await self._ready_event.wait()
|
|
@@ -105,6 +103,14 @@ class ConnectionManager(Generic[T], ABC):
|
|
|
105
103
|
await self._done_event.wait()
|
|
106
104
|
logger.debug(f"{self.__class__.__name__} task completed")
|
|
107
105
|
|
|
106
|
+
def get_streams(self) -> T | None:
|
|
107
|
+
"""Get the current connection streams.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
The current connection (typically a tuple of read_stream, write_stream) or None if not connected.
|
|
111
|
+
"""
|
|
112
|
+
return self._connection
|
|
113
|
+
|
|
108
114
|
async def _connection_task(self) -> None:
|
|
109
115
|
"""Run the connection task.
|
|
110
116
|
|
|
@@ -137,7 +143,7 @@ class ConnectionManager(Generic[T], ABC):
|
|
|
137
143
|
self._ready_event.set()
|
|
138
144
|
|
|
139
145
|
finally:
|
|
140
|
-
# Close the connection if it was
|
|
146
|
+
# Close the connection if it was established
|
|
141
147
|
if self._connection is not None:
|
|
142
148
|
try:
|
|
143
149
|
await self._close_connection()
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Streamable HTTP connection management for MCP implementations.
|
|
3
|
+
|
|
4
|
+
This module provides a connection manager for streamable HTTP-based MCP connections
|
|
5
|
+
that ensures proper task isolation and resource cleanup.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from datetime import timedelta
|
|
9
|
+
from typing import Any
|
|
10
|
+
|
|
11
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
12
|
+
|
|
13
|
+
from ..logging import logger
|
|
14
|
+
from .base import ConnectionManager
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class StreamableHttpConnectionManager(ConnectionManager[tuple[Any, Any]]):
|
|
18
|
+
"""Connection manager for streamable HTTP-based MCP connections.
|
|
19
|
+
|
|
20
|
+
This class handles the proper task isolation for HTTP streaming connections
|
|
21
|
+
to prevent the "cancel scope in different task" error. It runs the http_stream_client
|
|
22
|
+
in a dedicated task and manages its lifecycle.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(
|
|
26
|
+
self,
|
|
27
|
+
url: str,
|
|
28
|
+
headers: dict[str, str] | None = None,
|
|
29
|
+
timeout: float = 5,
|
|
30
|
+
read_timeout: float = 60 * 5,
|
|
31
|
+
):
|
|
32
|
+
"""Initialize a new streamable HTTP connection manager.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
url: The HTTP endpoint URL
|
|
36
|
+
headers: Optional HTTP headers
|
|
37
|
+
timeout: Timeout for HTTP operations in seconds
|
|
38
|
+
read_timeout: Timeout for HTTP read operations in seconds
|
|
39
|
+
"""
|
|
40
|
+
super().__init__()
|
|
41
|
+
self.url = url
|
|
42
|
+
self.headers = headers or {}
|
|
43
|
+
self.timeout = timedelta(seconds=timeout)
|
|
44
|
+
self.read_timeout = timedelta(seconds=read_timeout)
|
|
45
|
+
self._http_ctx = None
|
|
46
|
+
|
|
47
|
+
async def _establish_connection(self) -> tuple[Any, Any]:
|
|
48
|
+
"""Establish a streamable HTTP connection.
|
|
49
|
+
|
|
50
|
+
Returns:
|
|
51
|
+
A tuple of (read_stream, write_stream)
|
|
52
|
+
|
|
53
|
+
Raises:
|
|
54
|
+
Exception: If connection cannot be established.
|
|
55
|
+
"""
|
|
56
|
+
# Create the context manager
|
|
57
|
+
self._http_ctx = streamablehttp_client(
|
|
58
|
+
url=self.url,
|
|
59
|
+
headers=self.headers,
|
|
60
|
+
timeout=self.timeout,
|
|
61
|
+
sse_read_timeout=self.read_timeout,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
# Enter the context manager. Ignoring the session id callback
|
|
65
|
+
read_stream, write_stream, _ = await self._http_ctx.__aenter__()
|
|
66
|
+
|
|
67
|
+
# Return the streams
|
|
68
|
+
return (read_stream, write_stream)
|
|
69
|
+
|
|
70
|
+
async def _close_connection(self) -> None:
|
|
71
|
+
"""Close the streamable HTTP connection."""
|
|
72
|
+
|
|
73
|
+
if self._http_ctx:
|
|
74
|
+
# Exit the context manager
|
|
75
|
+
try:
|
|
76
|
+
await self._http_ctx.__aexit__(None, None, None)
|
|
77
|
+
except Exception as e:
|
|
78
|
+
# Only log if it's not a normal connection termination
|
|
79
|
+
logger.debug(f"Streamable HTTP context cleanup: {e}")
|
|
80
|
+
finally:
|
|
81
|
+
self._http_ctx = None
|
|
@@ -22,14 +22,17 @@ class WebSocketConnectionManager(ConnectionManager[tuple[Any, Any]]):
|
|
|
22
22
|
def __init__(
|
|
23
23
|
self,
|
|
24
24
|
url: str,
|
|
25
|
+
headers: dict[str, str] | None = None,
|
|
25
26
|
):
|
|
26
27
|
"""Initialize a new WebSocket connection manager.
|
|
27
28
|
|
|
28
29
|
Args:
|
|
29
30
|
url: The WebSocket URL to connect to
|
|
31
|
+
headers: Optional HTTP headers
|
|
30
32
|
"""
|
|
31
33
|
super().__init__()
|
|
32
34
|
self.url = url
|
|
35
|
+
self.headers = headers or {}
|
|
33
36
|
|
|
34
37
|
async def _establish_connection(self) -> tuple[Any, Any]:
|
|
35
38
|
"""Establish a WebSocket connection.
|
|
@@ -42,6 +45,8 @@ class WebSocketConnectionManager(ConnectionManager[tuple[Any, Any]]):
|
|
|
42
45
|
"""
|
|
43
46
|
logger.debug(f"Connecting to WebSocket: {self.url}")
|
|
44
47
|
# Create the context manager
|
|
48
|
+
# Note: The current MCP websocket_client implementation doesn't support headers
|
|
49
|
+
# If headers need to be passed, this would need to be updated when MCP supports it
|
|
45
50
|
self._ws_ctx = websocket_client(self.url)
|
|
46
51
|
|
|
47
52
|
# Enter the context manager
|
|
File without changes
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
from abc import ABC, abstractmethod
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BaseTelemetryEvent(ABC):
|
|
7
|
+
"""Base class for all telemetry events"""
|
|
8
|
+
|
|
9
|
+
@property
|
|
10
|
+
@abstractmethod
|
|
11
|
+
def name(self) -> str:
|
|
12
|
+
"""Event name for tracking"""
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def properties(self) -> dict[str, Any]:
|
|
18
|
+
"""Event properties to send with the event"""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
@dataclass
|
|
23
|
+
class MCPAgentExecutionEvent(BaseTelemetryEvent):
|
|
24
|
+
"""Comprehensive event for tracking complete MCP agent execution"""
|
|
25
|
+
|
|
26
|
+
# Execution method and context
|
|
27
|
+
execution_method: str # "run" or "astream"
|
|
28
|
+
query: str # The actual user query
|
|
29
|
+
success: bool
|
|
30
|
+
|
|
31
|
+
# Agent configuration
|
|
32
|
+
model_provider: str
|
|
33
|
+
model_name: str
|
|
34
|
+
server_count: int
|
|
35
|
+
server_identifiers: list[dict[str, str]]
|
|
36
|
+
total_tools_available: int
|
|
37
|
+
tools_available_names: list[str]
|
|
38
|
+
max_steps_configured: int
|
|
39
|
+
memory_enabled: bool
|
|
40
|
+
use_server_manager: bool
|
|
41
|
+
|
|
42
|
+
# Execution PARAMETERS
|
|
43
|
+
max_steps_used: int | None
|
|
44
|
+
manage_connector: bool
|
|
45
|
+
external_history_used: bool
|
|
46
|
+
|
|
47
|
+
# Execution results
|
|
48
|
+
steps_taken: int | None = None
|
|
49
|
+
tools_used_count: int | None = None
|
|
50
|
+
tools_used_names: list[str] | None = None
|
|
51
|
+
response: str | None = None # The actual response
|
|
52
|
+
execution_time_ms: int | None = None
|
|
53
|
+
error_type: str | None = None
|
|
54
|
+
|
|
55
|
+
# Context
|
|
56
|
+
conversation_history_length: int | None = None
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def name(self) -> str:
|
|
60
|
+
return "mcp_agent_execution"
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def properties(self) -> dict[str, Any]:
|
|
64
|
+
return {
|
|
65
|
+
# Core execution info
|
|
66
|
+
"execution_method": self.execution_method,
|
|
67
|
+
"query": self.query,
|
|
68
|
+
"query_length": len(self.query),
|
|
69
|
+
"success": self.success,
|
|
70
|
+
# Agent configuration
|
|
71
|
+
"model_provider": self.model_provider,
|
|
72
|
+
"model_name": self.model_name,
|
|
73
|
+
"server_count": self.server_count,
|
|
74
|
+
"server_identifiers": self.server_identifiers,
|
|
75
|
+
"total_tools_available": self.total_tools_available,
|
|
76
|
+
"tools_available_names": self.tools_available_names,
|
|
77
|
+
"max_steps_configured": self.max_steps_configured,
|
|
78
|
+
"memory_enabled": self.memory_enabled,
|
|
79
|
+
"use_server_manager": self.use_server_manager,
|
|
80
|
+
# Execution parameters (always include, even if None)
|
|
81
|
+
"max_steps_used": self.max_steps_used,
|
|
82
|
+
"manage_connector": self.manage_connector,
|
|
83
|
+
"external_history_used": self.external_history_used,
|
|
84
|
+
# Execution results (always include, even if None)
|
|
85
|
+
"steps_taken": self.steps_taken,
|
|
86
|
+
"tools_used_count": self.tools_used_count,
|
|
87
|
+
"tools_used_names": self.tools_used_names,
|
|
88
|
+
"response": self.response,
|
|
89
|
+
"response_length": len(self.response) if self.response else None,
|
|
90
|
+
"execution_time_ms": self.execution_time_ms,
|
|
91
|
+
"error_type": self.error_type,
|
|
92
|
+
"conversation_history_length": self.conversation_history_length,
|
|
93
|
+
}
|