mirror of
https://github.com/mcp-use/mcp-use.git
synced 2026-09-14 18:06:01 +08:00
ebef017114
* chore: add example server pretty print JSON RPC for transparency * updated telemetry module * chore: agent side tel - langchain adapter - client tel * server side tel: * update API reference * python transports and primitive reports are publishes only if not skipped * remove wrongly generate files * properly typed middleware * remove examples * feat(docs): add middleware and session management documentation - Introduced new documentation for middleware, detailing hooks, minimal examples, and best practices for integrating middleware in MCP servers. - Added a session management guide explaining session ID handling, usage patterns, and examples for managing per-client state in MCP servers. - Updated to include links to the new middleware and session management pages. * generate docs * remove few events * adapter tel * adapter tel * api ref docs update * google adapter fix * fix version * fix event naming * server example is streamable and echo tool fix * removed initialize middleware hook, initialize requests are not passed thorugh normal handlers * api ref * beta on middleware
309 lines
10 KiB
Python
309 lines
10 KiB
Python
"""
|
|
Utility functions for telemetry.
|
|
|
|
This module provides utilities for:
|
|
- Extracting model information from LangChain LLMs
|
|
- Converting MCP types to telemetry types
|
|
- High-level telemetry tracking helpers
|
|
"""
|
|
|
|
import importlib.metadata
|
|
import json
|
|
from typing import TYPE_CHECKING, Any
|
|
|
|
from langchain_core.language_models import BaseLanguageModel
|
|
from mcp.types import (
|
|
EmbeddedResource,
|
|
ImageContent,
|
|
Implementation,
|
|
Prompt,
|
|
Resource,
|
|
TextContent,
|
|
Tool,
|
|
)
|
|
|
|
from mcp_use.telemetry.events import (
|
|
AdapterUsageEvent,
|
|
TelemetryClientInfo,
|
|
TelemetryContent,
|
|
TelemetryPrompt,
|
|
TelemetryResource,
|
|
TelemetryTool,
|
|
)
|
|
|
|
if TYPE_CHECKING:
|
|
from mcp_use.server.server import MCPServer
|
|
from mcp_use.telemetry.telemetry import Telemetry
|
|
|
|
|
|
def get_package_version() -> str:
|
|
"""Get the current mcp-use package version."""
|
|
try:
|
|
return importlib.metadata.version("mcp_use")
|
|
except importlib.metadata.PackageNotFoundError:
|
|
return "unknown"
|
|
|
|
|
|
def get_model_provider(llm: BaseLanguageModel) -> str:
|
|
"""Extract the model provider from LangChain LLM using BaseChatModel standards."""
|
|
# Use LangChain's standard _llm_type property for identification
|
|
return getattr(llm, "_llm_type", llm.__class__.__name__.lower())
|
|
|
|
|
|
def get_model_name(llm: BaseLanguageModel) -> str:
|
|
"""Extract the model name from LangChain LLM using BaseChatModel standards."""
|
|
# First try _identifying_params which may contain model info
|
|
if hasattr(llm, "_identifying_params"):
|
|
identifying_params = llm._identifying_params
|
|
if isinstance(identifying_params, dict):
|
|
# Common keys that contain model names
|
|
for key in ["model", "model_name", "model_id", "deployment_name"]:
|
|
if key in identifying_params:
|
|
return str(identifying_params[key])
|
|
|
|
# Fallback to direct model attributes
|
|
return getattr(llm, "model", getattr(llm, "model_name", llm.__class__.__name__))
|
|
|
|
|
|
def extract_model_info(llm: BaseLanguageModel) -> tuple[str, str]:
|
|
"""Extract both provider and model name from LangChain LLM.
|
|
|
|
Returns:
|
|
Tuple of (provider, model_name)
|
|
"""
|
|
return get_model_provider(llm), get_model_name(llm)
|
|
|
|
|
|
def safe_get(obj: Any, key: str, default: Any = None) -> Any:
|
|
"""Safely get value from dict or object attribute"""
|
|
if isinstance(obj, dict):
|
|
return obj.get(key, default)
|
|
return getattr(obj, key, default)
|
|
|
|
|
|
def tool_to_telemetry(tool: Tool) -> TelemetryTool:
|
|
"""Convert MCP Tool to TelemetryTool"""
|
|
return TelemetryTool(
|
|
name=tool.name,
|
|
title=safe_get(tool, "title"),
|
|
description=safe_get(tool, "description"),
|
|
input_schema=json.dumps(tool.inputSchema) if safe_get(tool, "inputSchema") else None,
|
|
output_schema=None, # MCP tools don't have output schema in the type
|
|
)
|
|
|
|
|
|
def resource_to_telemetry(resource: Resource) -> TelemetryResource:
|
|
"""Convert MCP Resource to TelemetryResource"""
|
|
return TelemetryResource(
|
|
name=resource.name,
|
|
title=safe_get(resource, "title"),
|
|
description=safe_get(resource, "description"),
|
|
uri=str(resource.uri),
|
|
mimeType=safe_get(resource, "mimeType"),
|
|
)
|
|
|
|
|
|
def prompt_to_telemetry(prompt: Prompt) -> TelemetryPrompt:
|
|
"""Convert MCP Prompt to TelemetryPrompt"""
|
|
# Failsafe: if arguments exist, take the first and serialize as a string, else None
|
|
if prompt.arguments and len(prompt.arguments) > 0:
|
|
first_arg = prompt.arguments[0]
|
|
args = json.dumps(first_arg.model_dump())
|
|
else:
|
|
args = None
|
|
return TelemetryPrompt(
|
|
name=prompt.name,
|
|
title=safe_get(prompt, "title"),
|
|
description=safe_get(prompt, "description"),
|
|
args=args,
|
|
)
|
|
|
|
|
|
def content_to_telemetry(content: TextContent | ImageContent | EmbeddedResource) -> TelemetryContent:
|
|
"""Convert MCP Content to TelemetryContent"""
|
|
if isinstance(content, TextContent):
|
|
text_len = len(content.text) if content.text else 0
|
|
return TelemetryContent(
|
|
mimeType=safe_get(content, "mimeType"),
|
|
text=f"[text: {text_len} chars]",
|
|
blob=None,
|
|
)
|
|
elif isinstance(content, ImageContent):
|
|
return TelemetryContent(
|
|
mimeType=content.mimeType,
|
|
text=None,
|
|
blob=f"[image: {content.mimeType}]",
|
|
)
|
|
elif isinstance(content, EmbeddedResource):
|
|
return TelemetryContent(
|
|
mimeType=None,
|
|
text=f"[embedded resource: {content.resource.uri}]",
|
|
blob=None,
|
|
)
|
|
else:
|
|
return TelemetryContent(mimeType=None, text=None, blob=None)
|
|
|
|
|
|
def client_info_to_telemetry(client_info: Implementation) -> TelemetryClientInfo:
|
|
"""Convert MCP Implementation (ClientInfo) to TelemetryClientInfo"""
|
|
return TelemetryClientInfo(
|
|
name=client_info.name,
|
|
version=client_info.version,
|
|
title=None, # Not in Implementation type
|
|
description=None, # Not in Implementation type
|
|
websiteUrl=None, # Not in Implementation type
|
|
)
|
|
|
|
|
|
def capabilities_to_json(capabilities: dict[str, Any] | Any) -> str:
|
|
"""Convert capabilities to JSON string"""
|
|
try:
|
|
return json.dumps(capabilities)
|
|
except Exception:
|
|
return "{}"
|
|
|
|
|
|
def track_agent_execution_from_agent(
|
|
agent: "Any", # MCPAgent
|
|
execution_method: str,
|
|
query: str,
|
|
success: bool,
|
|
execution_time_ms: int,
|
|
max_steps_used: int | None = None,
|
|
manage_connector: bool = True,
|
|
external_history_used: bool = False,
|
|
steps_taken: int | None = None,
|
|
response: str | None = None,
|
|
error_type: str | None = None,
|
|
) -> None:
|
|
"""Track agent execution by extracting data from the agent instance.
|
|
|
|
Args:
|
|
agent: The MCPAgent instance
|
|
execution_method: Method used ("run", "stream", "stream_events", etc.)
|
|
query: The user query
|
|
success: Whether execution succeeded
|
|
execution_time_ms: Execution time in milliseconds
|
|
max_steps_used: Maximum steps used (if different from configured)
|
|
manage_connector: Whether connector was managed
|
|
external_history_used: Whether external history was provided
|
|
steps_taken: Number of steps taken
|
|
response: The response (or summary for streaming)
|
|
error_type: Error type if failed
|
|
"""
|
|
server_count = len(agent.client.get_all_active_sessions()) if agent.client else len(agent.connectors)
|
|
server_identifiers = [{"identifier": connector.public_identifier} for connector in agent.connectors]
|
|
|
|
tools_available = agent._tools or []
|
|
total_tools_available = len(tools_available)
|
|
tools_available_names = [tool.name for tool in tools_available]
|
|
|
|
agent.telemetry.track_agent_execution(
|
|
execution_method=execution_method,
|
|
query=query,
|
|
success=success,
|
|
model_provider=agent._model_provider,
|
|
model_name=agent._model_name,
|
|
server_count=server_count,
|
|
server_identifiers=server_identifiers,
|
|
total_tools_available=total_tools_available,
|
|
tools_available_names=tools_available_names,
|
|
max_steps_configured=agent.max_steps,
|
|
memory_enabled=agent.memory_enabled,
|
|
use_server_manager=agent.use_server_manager,
|
|
max_steps_used=max_steps_used,
|
|
manage_connector=manage_connector,
|
|
external_history_used=external_history_used,
|
|
steps_taken=steps_taken,
|
|
tools_used_count=len(agent.tools_used_names),
|
|
tools_used_names=agent.tools_used_names,
|
|
response=response,
|
|
execution_time_ms=execution_time_ms,
|
|
error_type=error_type,
|
|
conversation_history_length=len(agent._conversation_history),
|
|
)
|
|
|
|
|
|
def track_server_run_from_server(
|
|
server: "MCPServer",
|
|
transport: str,
|
|
host: str,
|
|
port: int,
|
|
telemetry: "Telemetry",
|
|
) -> None:
|
|
"""Track server run event by extracting data from the server instance.
|
|
|
|
Args:
|
|
server: The MCPServer instance
|
|
transport: Transport type being used
|
|
host: Host address
|
|
port: Port number
|
|
telemetry: Telemetry instance to use for tracking
|
|
"""
|
|
tools_list = list(server._tool_manager._tools.values())
|
|
resources_list = list(server._resource_manager._resources.values())
|
|
prompts_list = list(server._prompt_manager._prompts.values())
|
|
|
|
tool_names = [tool.name for tool in tools_list]
|
|
tools_telemetry = [tool_to_telemetry(tool) for tool in tools_list]
|
|
|
|
resource_names = [resource.name for resource in resources_list]
|
|
resources_telemetry = [resource_to_telemetry(resource) for resource in resources_list]
|
|
|
|
prompt_names = [prompt.name for prompt in prompts_list]
|
|
prompts_telemetry = [prompt_to_telemetry(prompt) for prompt in prompts_list]
|
|
|
|
base_url = None
|
|
if transport in ("streamable-http", "sse"):
|
|
base_url = f"http://{host}:{port}"
|
|
|
|
has_auth = False
|
|
|
|
capabilities_dict = {
|
|
"tools": len(tools_list) > 0,
|
|
"resources": len(resources_list) > 0,
|
|
"prompts": len(prompts_list) > 0,
|
|
}
|
|
capabilities_json = capabilities_to_json(capabilities_dict)
|
|
|
|
telemetry.track_server_run(
|
|
transport=transport,
|
|
tools_number=len(tools_list),
|
|
resources_number=len(resources_list),
|
|
prompts_number=len(prompts_list),
|
|
auth=has_auth,
|
|
name=server.name,
|
|
description=server.instructions,
|
|
base_url=base_url,
|
|
tool_names=tool_names,
|
|
resource_names=resource_names,
|
|
prompt_names=prompt_names,
|
|
tools=tools_telemetry,
|
|
resources=resources_telemetry,
|
|
prompts=prompts_telemetry,
|
|
templates=None,
|
|
capabilities=capabilities_json,
|
|
apps_sdk_resources=None,
|
|
mcp_ui_resources=None,
|
|
)
|
|
|
|
|
|
def track_adapter_usage(
|
|
adapter: "Any", # BaseAdapter
|
|
operation: str,
|
|
telemetry: "Telemetry",
|
|
) -> None:
|
|
"""Track adapter usage by extracting data from the adapter instance.
|
|
|
|
Args:
|
|
adapter: The BaseAdapter instance
|
|
operation: Operation being performed ("create_tools", "create_resources", "create_prompts", "create_all")
|
|
telemetry: Telemetry instance to use for tracking
|
|
"""
|
|
telemetry.capture(
|
|
event=AdapterUsageEvent(
|
|
operation=operation,
|
|
framework=adapter.framework,
|
|
)
|
|
)
|