universal-mcp 0.1.24rc13__py3-none-any.whl → 0.1.24rc17__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.
Files changed (53) hide show
  1. universal_mcp/agentr/registry.py +4 -4
  2. universal_mcp/applications/application.py +0 -2
  3. universal_mcp/applications/utils.py +52 -0
  4. universal_mcp/servers/server.py +4 -3
  5. universal_mcp/tools/manager.py +0 -3
  6. universal_mcp/types.py +1 -21
  7. universal_mcp/utils/prompts.py +0 -2
  8. universal_mcp/utils/testing.py +1 -1
  9. {universal_mcp-0.1.24rc13.dist-info → universal_mcp-0.1.24rc17.dist-info}/METADATA +2 -1
  10. universal_mcp-0.1.24rc17.dist-info/RECORD +54 -0
  11. universal_mcp/__init__.py +0 -0
  12. universal_mcp/agents/__init__.py +0 -9
  13. universal_mcp/agents/autoagent/__init__.py +0 -30
  14. universal_mcp/agents/autoagent/__main__.py +0 -25
  15. universal_mcp/agents/autoagent/context.py +0 -26
  16. universal_mcp/agents/autoagent/graph.py +0 -151
  17. universal_mcp/agents/autoagent/prompts.py +0 -9
  18. universal_mcp/agents/autoagent/state.py +0 -27
  19. universal_mcp/agents/autoagent/studio.py +0 -25
  20. universal_mcp/agents/autoagent/utils.py +0 -13
  21. universal_mcp/agents/base.py +0 -129
  22. universal_mcp/agents/bigtool/__init__.py +0 -54
  23. universal_mcp/agents/bigtool/__main__.py +0 -24
  24. universal_mcp/agents/bigtool/context.py +0 -24
  25. universal_mcp/agents/bigtool/graph.py +0 -166
  26. universal_mcp/agents/bigtool/prompts.py +0 -31
  27. universal_mcp/agents/bigtool/state.py +0 -27
  28. universal_mcp/agents/builder.py +0 -80
  29. universal_mcp/agents/cli.py +0 -27
  30. universal_mcp/agents/codeact/__init__.py +0 -243
  31. universal_mcp/agents/codeact/sandbox.py +0 -27
  32. universal_mcp/agents/codeact/test.py +0 -15
  33. universal_mcp/agents/codeact/utils.py +0 -61
  34. universal_mcp/agents/hil.py +0 -104
  35. universal_mcp/agents/llm.py +0 -45
  36. universal_mcp/agents/planner/__init__.py +0 -37
  37. universal_mcp/agents/planner/__main__.py +0 -24
  38. universal_mcp/agents/planner/graph.py +0 -82
  39. universal_mcp/agents/planner/prompts.py +0 -1
  40. universal_mcp/agents/planner/state.py +0 -12
  41. universal_mcp/agents/react.py +0 -84
  42. universal_mcp/agents/shared/agent_node.py +0 -34
  43. universal_mcp/agents/shared/tool_node.py +0 -235
  44. universal_mcp/agents/simple.py +0 -40
  45. universal_mcp/agents/tools.py +0 -35
  46. universal_mcp/agents/utils.py +0 -111
  47. universal_mcp/analytics.py +0 -111
  48. universal_mcp/applications/__init__.py +0 -70
  49. universal_mcp/utils/common.py +0 -278
  50. universal_mcp-0.1.24rc13.dist-info/RECORD +0 -92
  51. {universal_mcp-0.1.24rc13.dist-info → universal_mcp-0.1.24rc17.dist-info}/WHEEL +0 -0
  52. {universal_mcp-0.1.24rc13.dist-info → universal_mcp-0.1.24rc17.dist-info}/entry_points.txt +0 -0
  53. {universal_mcp-0.1.24rc13.dist-info → universal_mcp-0.1.24rc17.dist-info}/licenses/LICENSE +0 -0
@@ -1,84 +0,0 @@
1
- from langgraph.checkpoint.base import BaseCheckpointSaver
2
- from langgraph.prebuilt import create_react_agent
3
- from loguru import logger
4
-
5
- from universal_mcp.agentr.registry import AgentrRegistry
6
- from universal_mcp.agents.base import BaseAgent
7
- from universal_mcp.agents.llm import load_chat_model
8
- from universal_mcp.agents.tools import load_mcp_tools
9
- from universal_mcp.tools.registry import ToolRegistry
10
- from universal_mcp.types import ToolConfig, ToolFormat
11
-
12
-
13
- class ReactAgent(BaseAgent):
14
- def __init__(
15
- self,
16
- name: str,
17
- instructions: str,
18
- model: str,
19
- memory: BaseCheckpointSaver | None = None,
20
- tools: ToolConfig | None = None,
21
- registry: ToolRegistry | None = None,
22
- max_iterations: int = 10,
23
- **kwargs,
24
- ):
25
- super().__init__(name, instructions, model, memory, **kwargs)
26
- self.llm = load_chat_model(model)
27
- self.tools = tools
28
- self.max_iterations = max_iterations
29
- self.registry = registry
30
-
31
- async def _build_graph(self):
32
- if self.tools:
33
- config = self.tools.model_dump(exclude_none=True)
34
- if config.get("agentrServers") and not self.registry:
35
- raise ValueError("Agentr servers are configured but no registry is provided")
36
- agentr_tools = (
37
- await self.registry.export_tools(self.tools, ToolFormat.LANGCHAIN)
38
- if config.get("agentrServers")
39
- else []
40
- )
41
- logger.debug(agentr_tools)
42
- mcp_tools = await load_mcp_tools(config["mcpServers"]) if config.get("mcpServers") else []
43
- logger.debug(mcp_tools)
44
- tools = agentr_tools + mcp_tools
45
- else:
46
- tools = []
47
- logger.debug(f"Initialized ReactAgent: name={self.name}, model={self.model}")
48
- return create_react_agent(
49
- self.llm,
50
- tools,
51
- prompt=self._build_system_message(),
52
- checkpointer=self.memory,
53
- )
54
-
55
- def _build_system_message(self) -> str:
56
- system_message = f"""You are {self.name}.
57
-
58
- You have access to various tools that can help you answer questions and complete tasks. When you need to use a tool:
59
-
60
- 1. Think about what information you need
61
- 2. Call the appropriate tool with the right parameters
62
- 3. Use the tool results to provide a comprehensive answer
63
-
64
- Always explain your reasoning and be thorough in your responses. If you need to use multiple tools to answer a question completely, do so.
65
-
66
- {self.instructions}
67
- """
68
- return system_message
69
-
70
-
71
- if __name__ == "__main__":
72
- import asyncio
73
-
74
- agent = ReactAgent(
75
- "Universal React Agent",
76
- instructions="",
77
- model="azure/gpt-4o",
78
- tools=ToolConfig(agentrServers={"google-mail": {"tools": ["send_email"]}}),
79
- registry=AgentrRegistry(),
80
- )
81
- result = asyncio.run(
82
- agent.invoke(user_input="Send an email with the subject 'testing react agent' to manoj@agentr.dev")
83
- )
84
- logger.info(result["messages"][-1].content)
@@ -1,34 +0,0 @@
1
- from pydantic import BaseModel, Field
2
-
3
-
4
- class Agent(BaseModel):
5
- name: str = Field(description="The name of the agent")
6
- description: str = Field(description="A small paragraph description of the agent")
7
- expertise: str = Field(description="Agents expertise. Growth expert, SEO expert, etc")
8
- instructions: str = Field(description="The instructions for the agent")
9
- schedule: str = Field(
10
- description="The schedule for the agent in crontab syntax (e.g., '0 9 * * *' for daily at 9 AM)"
11
- )
12
-
13
-
14
- AGENT_PROMPT = """You are an AI assistant that creates autonomous agents based on user requests.
15
-
16
- Your task is to analyze the user's request and create a structured agent definition that includes:
17
- - A clear name for the agent
18
- - A concise description of what the agent does
19
- - The agent's area of expertise
20
- - Detailed instructions for executing the task
21
- - A cron schedule for when the agent should run
22
- - A list of apps/services the agent will need to use
23
-
24
- Be specific and actionable in your agent definitions. Consider the user's intent and create agents that can effectively accomplish their goals.
25
-
26
- <query>
27
- {query}
28
- </query>
29
- """
30
-
31
-
32
- async def generate_agent(llm, query):
33
- response = await llm.with_structured_output(Agent).ainvoke(input=AGENT_PROMPT.format(query=query))
34
- return response
@@ -1,235 +0,0 @@
1
- # tool_node.py
2
-
3
- import asyncio
4
- from typing import Annotated, TypedDict
5
-
6
- from langchain_core.language_models import BaseChatModel
7
- from langchain_core.messages import AIMessage, AnyMessage, HumanMessage
8
- from langgraph.graph import END, StateGraph
9
- from langgraph.graph.message import add_messages
10
- from loguru import logger
11
- from pydantic import BaseModel, Field
12
-
13
- from universal_mcp.tools.registry import ToolRegistry
14
- from universal_mcp.types import AgentrConnection, AgentrToolConfig
15
-
16
- # --- LangGraph Agent ---
17
-
18
-
19
- class AgentState(TypedDict):
20
- task: str
21
- apps_required: bool
22
- relevant_apps: list[str]
23
- apps_with_tools: AgentrToolConfig
24
- messages: Annotated[list[AnyMessage], add_messages]
25
- reasoning: str
26
-
27
-
28
- class ToolSelectionOutput(BaseModel):
29
- tool_ids: list[str] = Field(description="The ids of the tools to use")
30
-
31
-
32
- def build_tool_node_graph(llm: BaseChatModel, registry: ToolRegistry) -> StateGraph:
33
- """Builds the LangGraph workflow."""
34
-
35
- async def _check_if_app_needed(state: AgentState) -> AgentState:
36
- """Checks if an external application is needed for the given task."""
37
- task = state["task"]
38
- prompt = f"""
39
- Given the user's task: "{task}"
40
- Does this task require an external application to be completed?
41
- Your answer should be a simple "Yes" or "No", followed by a brief explanation.
42
- For example:
43
- Yes, an external application is needed to send emails.
44
- No, this is a general question that can be answered directly.
45
- """
46
- response = await llm.ainvoke(prompt)
47
- content = response.content.strip()
48
- reasoning = f"Initial check for app requirement. LLM response: {content}"
49
-
50
- if content.lower().startswith("yes"):
51
- return {
52
- **state,
53
- "messages": [AIMessage(content=content)],
54
- "apps_required": True,
55
- "reasoning": reasoning,
56
- }
57
- else:
58
- return {
59
- **state,
60
- "messages": [AIMessage(content=content)],
61
- "apps_required": False,
62
- "reasoning": reasoning,
63
- }
64
-
65
- async def _find_relevant_apps(state: AgentState) -> AgentState:
66
- """Identifies relevant apps for the given task, preferring connected apps."""
67
- task = state["task"]
68
- all_apps = await registry.list_all_apps()
69
- connected_apps = await registry.list_connected_apps()
70
- prompt = """
71
- You are an expert at identifying which applications are needed to complete specific tasks.
72
-
73
- TASK: "{task}"
74
-
75
- AVAILABLE APPS:
76
- {all_apps}
77
-
78
- CONNECTED APPS (user has already authenticated these):
79
- {connected_apps}
80
-
81
- INSTRUCTIONS:
82
- 1. Analyze the task carefully to understand what functionality is required.
83
- 2. Review the available apps and their descriptions to identify which ones could help.
84
- 3. If multiple apps can perform the task, prefer connected apps, but you MUST include all relevant apps.
85
- 4. Consider apps that provide complementary functionality for complex tasks.
86
- 5. Only suggest apps that are directly relevant to the core task requirements.
87
- 6. Your output should be a list of app IDs.
88
-
89
- """
90
-
91
- class AppList(BaseModel):
92
- app_list: list[str]
93
- reasoning: str
94
-
95
- response = await llm.with_structured_output(AppList).ainvoke(
96
- input=prompt.format(task=task, all_apps=all_apps, connected_apps=connected_apps)
97
- )
98
- app_list = response.app_list
99
- reasoning = f"Found relevant apps: {app_list}. Reasoning: {response.reasoning}"
100
- logger.info(f"Found relevant apps: {app_list}.")
101
-
102
- return {
103
- **state,
104
- "messages": [AIMessage(content=f"Identified relevant apps: {', '.join(app_list)}")],
105
- "relevant_apps": app_list,
106
- "reasoning": state.get("reasoning", "") + "\n" + reasoning,
107
- }
108
-
109
- async def _select_tools(task: str, tools: list[dict]) -> list[str]:
110
- """Selects the most appropriate tools from a list for a given task."""
111
- tool_candidates = [f"{tool['name']}: {tool['description']}" for tool in tools]
112
-
113
- SELECT_TOOL_PROMPT = f"""You are an AI assistant that helps the user perform tasks using various apps (each app has multiple tools).
114
- You will be provided with a task and a list of tools which might be relevant for this task.
115
-
116
- Your goal is to select the most appropriate tool for the given task.
117
- <task>
118
- {task}
119
- </task>
120
-
121
- <tool_candidates>
122
- - {tool_candidates}
123
- </tool_candidates>
124
-
125
- Only return tool ids.
126
- """
127
-
128
- response = await llm.with_structured_output(schema=ToolSelectionOutput).ainvoke(input=SELECT_TOOL_PROMPT)
129
-
130
- selected_tool_ids = response.tool_ids
131
- return selected_tool_ids
132
-
133
- async def _generate_search_query(task: str) -> str:
134
- """Generates a concise search query from the user's task."""
135
- prompt = f"""
136
- You are an expert at summarizing a user's task into a concise search query for finding relevant tools.
137
- The query should capture all the main actions or intents of the task.
138
-
139
- For example:
140
- Task: "Send an email to abc@the-read-example.com with the subject 'Hello'"
141
- Query: "send email"
142
-
143
- Task: "Create a new contact in my CRM for John Doe"
144
- Query: "create contact"
145
-
146
- Task: "Find the latest news about artificial intelligence"
147
- Query: "search news"
148
-
149
- Task: "Post a message to the #general channel in Slack and create a new issue in Jira"
150
- Query: "send message, create issue"
151
-
152
- Task: "{task}"
153
- """
154
-
155
- class SearchQuery(BaseModel):
156
- query: str
157
-
158
- response = await llm.with_structured_output(SearchQuery).ainvoke(input=prompt.format(task=task))
159
- query = response.query
160
- logger.info(f"Generated search query '{query}' for task '{task}'")
161
- return query
162
-
163
- async def _search_tools(state: AgentState) -> AgentState:
164
- """Searches for and filters tools in the relevant apps."""
165
- task = state["task"]
166
- logger.info(f"Searching for tools in relevant apps for task: {task}")
167
- search_query = await _generate_search_query(task)
168
- apps_with_tools_dict = {}
169
- reasoning_steps = []
170
- for app_name in state["relevant_apps"]:
171
- logger.info(f"Searching for tools in {app_name} for task: {task} with query '{search_query}'")
172
- found_tools = await registry.search_tools(query=search_query, app_id=app_name)
173
- selected_tools = await _select_tools(task, found_tools)
174
- apps_with_tools_dict[app_name] = selected_tools
175
- reasoning_steps.append(f"For '{app_name}', selected tool(s): {', '.join(selected_tools)}.")
176
-
177
- agentr_servers = {app_name: AgentrConnection(tools=tools) for app_name, tools in apps_with_tools_dict.items()}
178
- tool_config = AgentrToolConfig(agentrServers=agentr_servers)
179
-
180
- return {
181
- **state,
182
- "apps_with_tools": tool_config,
183
- "reasoning": state.get("reasoning", "") + "\n" + "\n".join(reasoning_steps),
184
- }
185
-
186
- def _handle_no_apps_found(state: AgentState) -> AgentState:
187
- """Handles the case where no relevant apps are found."""
188
- reasoning = "No suitable application was found among the available apps."
189
- return {
190
- **state,
191
- "apps_with_tools": AgentrToolConfig(agentrServers={}),
192
- "reasoning": state.get("reasoning", "") + "\n" + reasoning,
193
- }
194
-
195
- workflow = StateGraph(AgentState)
196
-
197
- workflow.add_node("check_if_app_needed", _check_if_app_needed)
198
- workflow.add_node("find_relevant_apps", _find_relevant_apps)
199
- workflow.add_node("search_tools", _search_tools)
200
- workflow.add_node("handle_no_apps_found", _handle_no_apps_found)
201
-
202
- workflow.set_entry_point("check_if_app_needed")
203
-
204
- workflow.add_conditional_edges(
205
- "check_if_app_needed",
206
- lambda state: "find_relevant_apps" if state["apps_required"] else END,
207
- )
208
- workflow.add_conditional_edges(
209
- "find_relevant_apps",
210
- lambda state: "search_tools" if state["relevant_apps"] else "handle_no_apps_found",
211
- )
212
-
213
- workflow.add_edge("search_tools", END)
214
- workflow.add_edge("handle_no_apps_found", END)
215
-
216
- return workflow.compile()
217
-
218
-
219
- async def main():
220
- from universal_mcp.agentr.registry import AgentrRegistry
221
- from universal_mcp.agents.llm import load_chat_model
222
-
223
- registry = AgentrRegistry()
224
- llm = load_chat_model("gemini/gemini-2.5-flash")
225
- graph = build_tool_node_graph(llm, registry)
226
- initial_state = {
227
- "task": "Send an email to manoj@agentr.dev",
228
- "messages": [HumanMessage(content="Send an email to manoj@agentr.dev")],
229
- }
230
- result = await graph.ainvoke(initial_state)
231
- print(result)
232
-
233
-
234
- if __name__ == "__main__":
235
- asyncio.run(main())
@@ -1,40 +0,0 @@
1
- import asyncio
2
- from typing import Annotated
3
-
4
- from langgraph.checkpoint.base import BaseCheckpointSaver
5
- from langgraph.graph import END, START, StateGraph
6
- from langgraph.graph.message import add_messages
7
- from typing_extensions import TypedDict
8
-
9
- from universal_mcp.agents.base import BaseAgent
10
- from universal_mcp.agents.llm import load_chat_model
11
-
12
-
13
- class State(TypedDict):
14
- messages: Annotated[list, add_messages]
15
-
16
-
17
- class SimpleAgent(BaseAgent):
18
- def __init__(self, name: str, instructions: str, model: str, memory: BaseCheckpointSaver = None, **kwargs):
19
- super().__init__(name, instructions, model, memory, **kwargs)
20
- self.llm = load_chat_model(model)
21
-
22
- async def _build_graph(self):
23
- graph_builder = StateGraph(State)
24
-
25
- async def chatbot(state: State):
26
- messages = [
27
- {"role": "system", "content": self.instructions},
28
- *state["messages"],
29
- ]
30
- return {"messages": [await self.llm.ainvoke(messages)]}
31
-
32
- graph_builder.add_node("chatbot", chatbot)
33
- graph_builder.add_edge(START, "chatbot")
34
- graph_builder.add_edge("chatbot", END)
35
- return graph_builder.compile(checkpointer=self.memory)
36
-
37
-
38
- if __name__ == "__main__":
39
- agent = SimpleAgent("Simple Agent", "You are a helpful assistant", "azure/gpt-4o")
40
- asyncio.run(agent.run_interactive())
@@ -1,35 +0,0 @@
1
- import json
2
-
3
- from langchain_mcp_adapters.client import MultiServerMCPClient
4
-
5
- from universal_mcp.agentr.integration import AgentrIntegration
6
- from universal_mcp.applications import app_from_slug
7
- from universal_mcp.tools.adapters import ToolFormat
8
- from universal_mcp.tools.manager import ToolManager
9
- from universal_mcp.types import ToolConfig
10
-
11
-
12
- async def load_agentr_tools(agentr_servers: dict):
13
- tool_manager = ToolManager()
14
- for app_name, tool_names in agentr_servers.items():
15
- app = app_from_slug(app_name)
16
- integration = AgentrIntegration(name=app_name)
17
- app_instance = app(integration=integration)
18
- tool_manager.register_tools_from_app(app_instance, tool_names=tool_names["tools"])
19
- tools = tool_manager.list_tools(format=ToolFormat.LANGCHAIN)
20
- return tools
21
-
22
-
23
- async def load_mcp_tools(mcp_servers: dict):
24
- client = MultiServerMCPClient(mcp_servers)
25
- tools = await client.get_tools()
26
- return tools
27
-
28
-
29
- async def load_tools(path: str) -> ToolConfig:
30
- with open(path) as f:
31
- data = json.load(f)
32
- config = ToolConfig.model_validate(data)
33
- agentr_tools = await load_agentr_tools(config.model_dump(exclude_none=True)["agentrServers"])
34
- mcp_tools = await load_mcp_tools(config.model_dump(exclude_none=True)["mcpServers"])
35
- return agentr_tools + mcp_tools
@@ -1,111 +0,0 @@
1
- import json
2
- from contextlib import contextmanager
3
-
4
- from rich.console import Console
5
- from rich.live import Live
6
- from rich.markdown import Markdown
7
- from rich.panel import Panel
8
- from rich.prompt import Prompt
9
- from rich.table import Table
10
-
11
-
12
- class RichCLI:
13
- def __init__(self):
14
- self.console = Console()
15
-
16
- def display_welcome(self, agent_name: str):
17
- """Display welcome message"""
18
- welcome_text = f"""
19
- # Welcome to {agent_name}!
20
-
21
- Available commands:
22
- - Type your questions naturally
23
- - `/help` - Show help
24
- - `/tools` - List available tools
25
- - `/exit` - Exit the application
26
- """
27
- self.console.print(Panel(Markdown(welcome_text), title="🤖 AI Agent CLI", border_style="blue"))
28
-
29
- def display_agent_response(self, response: str, agent_name: str):
30
- """Display agent response with formatting"""
31
- self.console.print(Panel(Markdown(response), title=f"🤖 {agent_name}", border_style="green", padding=(1, 2)))
32
-
33
- @contextmanager
34
- def display_agent_response_streaming(self, agent_name: str):
35
- """Context manager for streaming agent response updates."""
36
-
37
- with Live(refresh_per_second=10, console=self.console) as live:
38
-
39
- class StreamUpdater:
40
- content = []
41
-
42
- def update(self, chunk: str):
43
- self.content.append(chunk)
44
- panel = Panel(
45
- Markdown("".join(self.content)),
46
- title=f"🤖 {agent_name}",
47
- border_style="green",
48
- padding=(1, 2),
49
- )
50
- live.update(panel)
51
-
52
- yield StreamUpdater()
53
-
54
- def display_thinking(self, thought: str):
55
- """Display agent's thinking process"""
56
- if thought:
57
- self.console.print(Panel(thought, title="💭 Thinking", border_style="yellow", padding=(1, 2)))
58
-
59
- def display_tools(self, tools: list):
60
- """Display available tools in a table"""
61
- table = Table(title="🛠️ Available Tools")
62
- table.add_column("Tool Name", style="cyan")
63
- table.add_column("Description", style="white")
64
-
65
- for tool in tools:
66
- func_info = tool["function"]
67
- table.add_row(func_info["name"], func_info["description"])
68
-
69
- self.console.print(table)
70
-
71
- def display_tool_call(self, tool_call: dict):
72
- """Display tool call"""
73
- tool_call_str = json.dumps(tool_call, indent=2)
74
- self.console.print(Panel(tool_call_str, title="🛠️ Tool Call", border_style="green", padding=(1, 2)))
75
-
76
- def display_tool_result(self, tool_result: dict):
77
- """Display tool result"""
78
- tool_result_str = json.dumps(tool_result, indent=2)
79
- self.console.print(Panel(tool_result_str, title="🛠️ Tool Result", border_style="green", padding=(1, 2)))
80
-
81
- def display_error(self, error: str):
82
- """Display error message"""
83
- self.console.print(Panel(error, title="❌ Error", border_style="red"))
84
-
85
- def get_user_input(self) -> str:
86
- """Get user input with rich prompt"""
87
- return Prompt.ask("[bold blue]You[/bold blue]", console=self.console)
88
-
89
- def display_info(self, message: str):
90
- """Display info message"""
91
- self.console.print(f"[bold cyan]ℹ️ {message}[/bold cyan]")
92
-
93
- def clear_screen(self):
94
- """Clear the screen"""
95
- self.console.clear()
96
-
97
- def handle_interrupt(self, interrupt) -> str | bool:
98
- interrupt_type = interrupt.value["type"]
99
- if interrupt_type == "text":
100
- value = Prompt.ask(interrupt.value["question"])
101
- return value
102
- elif interrupt_type == "bool":
103
- value = Prompt.ask(interrupt.value["question"], choices=["y", "n"], default="y")
104
- return value
105
- elif interrupt_type == "choice":
106
- value = Prompt.ask(
107
- interrupt.value["question"], choices=interrupt.value["choices"], default=interrupt.value["choices"][0]
108
- )
109
- return value
110
- else:
111
- raise ValueError(f"Invalid interrupt type: {interrupt.value['type']}")
@@ -1,111 +0,0 @@
1
- import os
2
- import uuid
3
- from functools import lru_cache
4
- from importlib.metadata import version
5
-
6
- import posthog
7
- from loguru import logger
8
-
9
-
10
- class Analytics:
11
- """A singleton class for tracking analytics events using PostHog.
12
-
13
- This class handles the initialization of the PostHog client and provides
14
- methods to track key events such as application loading and tool execution.
15
- Telemetry can be disabled by setting the TELEMETRY_DISABLED environment
16
- variable to "true".
17
- """
18
-
19
- _instance = None
20
-
21
- def __new__(cls):
22
- if cls._instance is None:
23
- cls._instance = super().__new__(cls)
24
- cls._instance._initialize()
25
- return cls._instance
26
-
27
- def _initialize(self):
28
- """Initializes the PostHog client and sets up analytics properties.
29
-
30
- This internal method configures the PostHog API key and host.
31
- It also determines if analytics should be enabled based on the
32
- TELEMETRY_DISABLED environment variable and generates a unique
33
- user ID.
34
- """
35
- posthog.host = "https://us.i.posthog.com"
36
- posthog.api_key = "phc_6HXMDi8CjfIW0l04l34L7IDkpCDeOVz9cOz1KLAHXh8"
37
- self.enabled = os.getenv("TELEMETRY_DISABLED", "false").lower() != "true"
38
- self.user_id = str(uuid.uuid4())[:8]
39
-
40
- @staticmethod
41
- @lru_cache(maxsize=1)
42
- def get_version():
43
- """Retrieves the installed version of the universal_mcp package.
44
-
45
- Uses importlib.metadata to get the package version.
46
- Caches the result for efficiency.
47
-
48
- Returns:
49
- str: The package version string, or "unknown" if not found.
50
- """
51
- try:
52
- return version("universal_mcp")
53
- except ImportError: # Should be PackageNotFoundError, but matching existing code
54
- return "unknown"
55
-
56
- def track_app_loaded(self, app_name: str):
57
- """Tracks an event when an application is successfully loaded.
58
-
59
- This event helps understand which applications are being utilized.
60
-
61
- Args:
62
- app_name (str): The name of the application that was loaded.
63
- """
64
- if not self.enabled:
65
- return
66
- try:
67
- properties = {
68
- "version": self.get_version(),
69
- "app_name": app_name,
70
- "user_id": self.user_id,
71
- }
72
- posthog.capture("app_loaded", properties=properties)
73
- except Exception as e:
74
- logger.error(f"Failed to track app_loaded event: {e}")
75
-
76
- def track_tool_called(
77
- self,
78
- tool_name: str,
79
- app_name: str,
80
- status: str,
81
- error: str | None = None,
82
- ):
83
- """Tracks an event when a tool is called within an application.
84
-
85
- This event provides insights into tool usage patterns, success rates,
86
- and potential errors.
87
-
88
- Args:
89
- tool_name (str): The name of the tool that was called.
90
- app_name (str): The name of the application the tool belongs to.
91
- status (str): The status of the tool call (e.g., "success", "error").
92
- error (str, optional): The error message if the tool call failed.
93
- Defaults to None.
94
- """
95
- if not self.enabled:
96
- return
97
- try:
98
- properties = {
99
- "tool_name": tool_name,
100
- "app_name": app_name,
101
- "status": status,
102
- "error": error,
103
- "version": self.get_version(),
104
- "user_id": self.user_id,
105
- }
106
- posthog.capture("tool_called", properties=properties)
107
- except Exception as e:
108
- logger.error(f"Failed to track tool_called event: {e}")
109
-
110
-
111
- analytics = Analytics()