universal-mcp-agents 0.1.3__py3-none-any.whl → 0.1.5__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 (46) hide show
  1. universal_mcp/agents/__init__.py +19 -0
  2. universal_mcp/agents/autoagent/__init__.py +1 -1
  3. universal_mcp/agents/autoagent/__main__.py +1 -1
  4. universal_mcp/agents/autoagent/graph.py +32 -13
  5. universal_mcp/agents/autoagent/studio.py +3 -8
  6. universal_mcp/agents/base.py +80 -22
  7. universal_mcp/agents/bigtool/__init__.py +13 -9
  8. universal_mcp/agents/bigtool/__main__.py +6 -7
  9. universal_mcp/agents/bigtool/graph.py +84 -40
  10. universal_mcp/agents/bigtool/prompts.py +3 -3
  11. universal_mcp/agents/bigtool2/__init__.py +16 -6
  12. universal_mcp/agents/bigtool2/__main__.py +7 -6
  13. universal_mcp/agents/bigtool2/agent.py +4 -2
  14. universal_mcp/agents/bigtool2/graph.py +78 -36
  15. universal_mcp/agents/bigtool2/prompts.py +1 -1
  16. universal_mcp/agents/bigtoolcache/__init__.py +8 -4
  17. universal_mcp/agents/bigtoolcache/__main__.py +1 -1
  18. universal_mcp/agents/bigtoolcache/agent.py +5 -3
  19. universal_mcp/agents/bigtoolcache/context.py +0 -1
  20. universal_mcp/agents/bigtoolcache/graph.py +99 -69
  21. universal_mcp/agents/bigtoolcache/prompts.py +28 -0
  22. universal_mcp/agents/bigtoolcache/tools_all.txt +956 -0
  23. universal_mcp/agents/bigtoolcache/tools_important.txt +474 -0
  24. universal_mcp/agents/builder.py +62 -20
  25. universal_mcp/agents/cli.py +19 -5
  26. universal_mcp/agents/codeact/__init__.py +16 -4
  27. universal_mcp/agents/codeact/test.py +2 -1
  28. universal_mcp/agents/hil.py +16 -4
  29. universal_mcp/agents/llm.py +12 -4
  30. universal_mcp/agents/planner/__init__.py +14 -4
  31. universal_mcp/agents/planner/__main__.py +10 -6
  32. universal_mcp/agents/planner/graph.py +9 -3
  33. universal_mcp/agents/planner/prompts.py +14 -1
  34. universal_mcp/agents/planner/state.py +0 -1
  35. universal_mcp/agents/react.py +36 -22
  36. universal_mcp/agents/shared/tool_node.py +26 -11
  37. universal_mcp/agents/simple.py +27 -4
  38. universal_mcp/agents/tools.py +9 -4
  39. universal_mcp/agents/ui_tools.py +305 -0
  40. universal_mcp/agents/utils.py +55 -17
  41. {universal_mcp_agents-0.1.3.dist-info → universal_mcp_agents-0.1.5.dist-info}/METADATA +3 -2
  42. universal_mcp_agents-0.1.5.dist-info/RECORD +52 -0
  43. universal_mcp/agents/bigtool/context.py +0 -24
  44. universal_mcp/agents/bigtool2/context.py +0 -33
  45. universal_mcp_agents-0.1.3.dist-info/RECORD +0 -51
  46. {universal_mcp_agents-0.1.3.dist-info → universal_mcp_agents-0.1.5.dist-info}/WHEEL +0 -0
@@ -1,15 +1,15 @@
1
1
  """Default prompts used by the agent."""
2
2
 
3
- SYSTEM_PROMPT = """You are a helpful AI assistant.
3
+ SYSTEM_PROMPT = """You are {name}, a helpful AI assistant.
4
4
 
5
5
  **Core Directives:**
6
6
  1. **Always Use Tools for Tasks:** For any user request that requires an action (e.g., sending an email, searching for information, creating an event), you MUST use a tool. Do not answer from your own knowledge or refuse a task if a tool might exist for it.
7
7
  2. **First Step is ALWAYS `retrieve_tools`:** Before you can use any other tool, you MUST first call the `retrieve_tools` function to find the right tool for the user's request. This is your mandatory first action.
8
8
  3. **Strictly Follow the Process:** Your only job in your first turn is to analyze the user's request and call `retrieve_tools` with a concise query describing the core task. Do not engage in conversation.
9
9
 
10
- System time: {system_time}
11
-
12
10
  When multiple tools are available for the same task, you must ask the user.
11
+
12
+ {instructions}
13
13
  """
14
14
 
15
15
  SELECT_TOOL_PROMPT = """You are an AI assistant that helps the user perform tasks using various apps (each app has multiple tools).
@@ -1,9 +1,10 @@
1
1
  from langgraph.checkpoint.base import BaseCheckpointSaver
2
+ from universal_mcp.logger import logger
3
+ from universal_mcp.tools.registry import ToolRegistry
2
4
 
3
5
  from universal_mcp.agents.base import BaseAgent
4
6
  from universal_mcp.agents.llm import load_chat_model
5
- from universal_mcp.logger import logger
6
- from universal_mcp.tools.registry import ToolRegistry
7
+ from universal_mcp.agents.utils import initialize_ui_tools
7
8
 
8
9
  from .graph import build_graph
9
10
  from .prompts import SYSTEM_PROMPT
@@ -19,15 +20,22 @@ class BigToolAgent2(BaseAgent):
19
20
  memory: BaseCheckpointSaver | None = None,
20
21
  **kwargs,
21
22
  ):
22
- # Combine the base system prompt with agent-specific instructions
23
- full_instructions = f"{SYSTEM_PROMPT}\n\n**User Instructions:**\n{instructions}"
24
- super().__init__(name, full_instructions, model, memory, **kwargs)
23
+ super().__init__(name, instructions, model, memory, **kwargs)
25
24
 
26
25
  self.registry = registry
27
26
  self.llm = load_chat_model(self.model)
28
27
  self.recursion_limit = kwargs.get("recursion_limit", 10)
28
+ self.ui_tools = initialize_ui_tools()
29
+
30
+ logger.info(
31
+ f"BigToolAgent '{self.name}' initialized with model '{self.model}'."
32
+ )
29
33
 
30
- logger.info(f"BigToolAgent '{self.name}' initialized with model '{self.model}'.")
34
+ def _build_system_message(self):
35
+ return SYSTEM_PROMPT.format(
36
+ name=self.name,
37
+ instructions=f"**User Instructions:**\n{self.instructions}",
38
+ )
31
39
 
32
40
  async def _build_graph(self):
33
41
  """Build the bigtool agent graph using the existing create_agent function."""
@@ -36,6 +44,8 @@ class BigToolAgent2(BaseAgent):
36
44
  graph_builder = build_graph(
37
45
  tool_registry=self.registry,
38
46
  llm=self.llm,
47
+ system_prompt=self._build_system_message(),
48
+ ui_tools=self.ui_tools,
39
49
  )
40
50
 
41
51
  compiled_graph = graph_builder.compile(checkpointer=self.memory)
@@ -1,9 +1,10 @@
1
1
  import asyncio
2
2
 
3
3
  from loguru import logger
4
-
5
4
  from universal_mcp.agentr.registry import AgentrRegistry
5
+
6
6
  from universal_mcp.agents.bigtool2 import BigToolAgent2
7
+ from universal_mcp.agents.utils import messages_to_list
7
8
 
8
9
 
9
10
  async def main():
@@ -13,11 +14,11 @@ async def main():
13
14
  model="azure/gpt-4.1",
14
15
  registry=AgentrRegistry(),
15
16
  )
16
- async for event in agent.stream(
17
- user_input="Send an email to manoj@agentr.dev",
18
- thread_id="test123",
19
- ):
20
- logger.info(event.content)
17
+ await agent.ainit()
18
+ output = await agent.invoke(
19
+ user_input="Send an email to manoj@agentr.dev"
20
+ )
21
+ logger.info(messages_to_list(output["messages"]))
21
22
 
22
23
 
23
24
  if __name__ == "__main__":
@@ -1,6 +1,8 @@
1
- from universal_mcp.agents.bigtool2 import BigToolAgent2
2
1
  from universal_mcp.agentr.registry import AgentrRegistry
3
2
 
3
+ from universal_mcp.agents.bigtool2 import BigToolAgent2
4
+
5
+
4
6
  async def agent():
5
7
  agent_object = await BigToolAgent2(
6
8
  name="BigTool Agent 2",
@@ -8,4 +10,4 @@ async def agent():
8
10
  model="anthropic/claude-4-sonnet-20250514",
9
11
  registry=AgentrRegistry(),
10
12
  )._build_graph()
11
- return agent_object
13
+ return agent_object
@@ -1,26 +1,21 @@
1
1
  import json
2
2
  from datetime import UTC, datetime
3
- from typing import Literal, TypedDict, cast
3
+ from typing import Literal, cast
4
4
 
5
- from langchain_anthropic import ChatAnthropic
6
5
  from langchain_core.language_models import BaseChatModel
7
6
  from langchain_core.messages import AIMessage, ToolMessage
8
7
  from langchain_core.tools import tool
9
8
  from langgraph.graph import StateGraph
10
- from langgraph.runtime import Runtime
11
9
  from langgraph.types import Command
12
-
13
- from universal_mcp.agents.bigtool2.context import Context
14
- from universal_mcp.agents.bigtool2.state import State
15
10
  from universal_mcp.logger import logger
16
11
  from universal_mcp.tools.registry import ToolRegistry
17
12
  from universal_mcp.types import ToolFormat
18
13
 
14
+ from universal_mcp.agents.bigtool2.state import State
19
15
 
20
16
 
21
17
  def build_graph(
22
- tool_registry: ToolRegistry,
23
- llm: BaseChatModel
18
+ tool_registry: ToolRegistry, llm: BaseChatModel, system_prompt: str, ui_tools: list
24
19
  ):
25
20
  @tool
26
21
  async def search_tools(queries: list[str]) -> str:
@@ -33,12 +28,18 @@ def build_graph(
33
28
  app_ids = await tool_registry.list_all_apps()
34
29
  connections = await tool_registry.list_connected_apps()
35
30
  connection_ids = set([connection["app_id"] for connection in connections])
36
- connected_apps = [app["id"] for app in app_ids if app["id"] in connection_ids]
37
- unconnected_apps = [app["id"] for app in app_ids if app["id"] not in connection_ids]
31
+ connected_apps = [
32
+ app["id"] for app in app_ids if app["id"] in connection_ids
33
+ ]
34
+ [
35
+ app["id"] for app in app_ids if app["id"] not in connection_ids
36
+ ]
38
37
  app_tools = {}
39
38
  for task_query in queries:
40
39
  tools_list = await tool_registry.search_tools(task_query, limit=40)
41
- tool_candidates = [f"{tool['id']}: {tool['description']}" for tool in tools_list]
40
+ tool_candidates = [
41
+ f"{tool['id']}: {tool['description']}" for tool in tools_list
42
+ ]
42
43
  for tool in tool_candidates:
43
44
  app = tool.split("__")[0]
44
45
  if app not in app_tools:
@@ -49,65 +50,95 @@ def build_graph(
49
50
  app_tools[app].append(tool)
50
51
  for app in app_tools:
51
52
  app_status = "connected" if app in connected_apps else "NOT connected"
52
- all_tool_candidates += f"Tools from {app} (status: {app_status} by user):\n"
53
+ all_tool_candidates += (
54
+ f"Tools from {app} (status: {app_status} by user):\n"
55
+ )
53
56
  for tool in app_tools[app]:
54
57
  all_tool_candidates += f" - {tool}\n"
55
58
  all_tool_candidates += "\n"
56
-
57
-
59
+
58
60
  return all_tool_candidates
59
61
  except Exception as e:
60
62
  logger.error(f"Error retrieving tools: {e}")
61
63
  return "Error: " + str(e)
62
-
64
+
63
65
  @tool
64
66
  async def load_tools(tool_ids: list[str]) -> list[str]:
65
67
  """Load the tools for the given tool ids. Returns the tool ids."""
66
68
  return tool_ids
67
69
 
68
-
69
- async def call_model(state: State, runtime: Runtime[Context]) -> Command[Literal["select_tools", "call_tools"]]:
70
+ async def call_model(
71
+ state: State,
72
+ ) -> Command[Literal["select_tools", "call_tools"]]:
70
73
  logger.info("Calling model...")
71
74
  try:
72
- system_message = runtime.context.system_prompt.format(system_time=datetime.now(tz=UTC).isoformat())
73
- messages = [{"role": "system", "content": system_message}, *state["messages"]]
75
+ system_message = system_prompt.format(
76
+ system_time=datetime.now(tz=UTC).isoformat()
77
+ )
78
+ messages = [
79
+ {"role": "system", "content": system_message},
80
+ *state["messages"],
81
+ ]
74
82
 
75
83
  logger.info(f"Selected tool IDs: {state['selected_tool_ids']}")
76
84
  if len(state["selected_tool_ids"]) > 0:
77
- selected_tools = await tool_registry.export_tools(tools=state["selected_tool_ids"], format=ToolFormat.LANGCHAIN)
85
+ selected_tools = await tool_registry.export_tools(
86
+ tools=state["selected_tool_ids"], format=ToolFormat.LANGCHAIN
87
+ )
78
88
  logger.info(f"Exported {len(selected_tools)} tools for model.")
79
89
  else:
80
90
  selected_tools = []
81
91
 
82
92
  model = llm
83
93
 
84
- model_with_tools = model.bind_tools([search_tools, load_tools, *selected_tools], tool_choice="auto")
94
+ model_with_tools = model.bind_tools(
95
+ [search_tools, load_tools, *selected_tools, *ui_tools],
96
+ tool_choice="auto",
97
+ )
85
98
  response = cast(AIMessage, await model_with_tools.ainvoke(messages))
86
99
 
87
100
  if response.tool_calls:
88
- logger.info(f"Model responded with {len(response.tool_calls)} tool calls.")
101
+ logger.info(
102
+ f"Model responded with {len(response.tool_calls)} tool calls."
103
+ )
89
104
  if len(response.tool_calls) > 1:
90
- raise Exception("Not possible in Claude with llm.bind_tools(tools=tools, tool_choice='auto')")
105
+ raise Exception(
106
+ "Not possible in Claude with llm.bind_tools(tools=tools, tool_choice='auto')"
107
+ )
91
108
  tool_call = response.tool_calls[0]
92
109
  if tool_call["name"] == search_tools.name:
93
110
  logger.info("Model requested to select tools.")
94
111
  return Command(goto="select_tools", update={"messages": [response]})
95
112
  elif tool_call["name"] == load_tools.name:
96
113
  logger.info("Model requested to load tools.")
97
- tool_msg = ToolMessage(f"Loaded tools.", tool_call_id=tool_call["id"])
114
+ tool_msg = ToolMessage(
115
+ "Loaded tools.", tool_call_id=tool_call["id"]
116
+ )
98
117
  selected_tool_ids = tool_call["args"]["tool_ids"]
99
118
  logger.info(f"Loaded tools: {selected_tool_ids}")
100
- return Command(goto="call_model", update={ "messages": [response, tool_msg], "selected_tool_ids": selected_tool_ids})
119
+ return Command(
120
+ goto="call_model",
121
+ update={
122
+ "messages": [response, tool_msg],
123
+ "selected_tool_ids": selected_tool_ids,
124
+ },
125
+ )
101
126
 
102
127
  elif tool_call["name"] not in state["selected_tool_ids"]:
103
128
  try:
104
- await tool_registry.export_tools([tool_call["name"]], ToolFormat.LANGCHAIN)
129
+ await tool_registry.export_tools(
130
+ [tool_call["name"]], ToolFormat.LANGCHAIN
131
+ )
105
132
  logger.info(
106
133
  f"Tool '{tool_call['name']}' not in selected tools, but available. Proceeding to call."
107
134
  )
108
- return Command(goto="call_tools", update={"messages": [response]})
135
+ return Command(
136
+ goto="call_tools", update={"messages": [response]}
137
+ )
109
138
  except Exception as e:
110
- logger.error(f"Unexpected tool call: {tool_call['name']}. Error: {e}")
139
+ logger.error(
140
+ f"Unexpected tool call: {tool_call['name']}. Error: {e}"
141
+ )
111
142
  raise Exception(
112
143
  f"Unexpected tool call: {tool_call['name']}. Available tools: {state['selected_tool_ids']}"
113
144
  ) from e
@@ -120,12 +151,14 @@ def build_graph(
120
151
  logger.error(f"Error in call_model: {e}")
121
152
  raise
122
153
 
123
- async def select_tools(state: State, runtime: Runtime[Context]) -> Command[Literal["call_model"]]:
154
+ async def select_tools(state: State) -> Command[Literal["call_model"]]:
124
155
  logger.info("Selecting tools...")
125
156
  try:
126
157
  tool_call = state["messages"][-1].tool_calls[0]
127
- searched_tools= await search_tools.ainvoke(input=tool_call["args"])
128
- tool_msg = ToolMessage(f"Available tools: {searched_tools}", tool_call_id=tool_call["id"])
158
+ searched_tools = await search_tools.ainvoke(input=tool_call["args"])
159
+ tool_msg = ToolMessage(
160
+ f"Available tools: {searched_tools}", tool_call_id=tool_call["id"]
161
+ )
129
162
  return Command(goto="call_model", update={"messages": [tool_msg]})
130
163
  except Exception as e:
131
164
  logger.error(f"Error in select_tools: {e}")
@@ -136,10 +169,16 @@ def build_graph(
136
169
  outputs = []
137
170
  recent_tool_ids = []
138
171
  for tool_call in state["messages"][-1].tool_calls:
139
- logger.info(f"Executing tool: {tool_call['name']} with args: {tool_call['args']}")
172
+ logger.info(
173
+ f"Executing tool: {tool_call['name']} with args: {tool_call['args']}"
174
+ )
140
175
  try:
141
- await tool_registry.export_tools([tool_call["name"]], ToolFormat.LANGCHAIN)
142
- tool_result = await tool_registry.call_tool(tool_call["name"], tool_call["args"])
176
+ await tool_registry.export_tools(
177
+ [tool_call["name"]], ToolFormat.LANGCHAIN
178
+ )
179
+ tool_result = await tool_registry.call_tool(
180
+ tool_call["name"], tool_call["args"]
181
+ )
143
182
  logger.info(f"Tool '{tool_call['name']}' executed successfully.")
144
183
  outputs.append(
145
184
  ToolMessage(
@@ -158,9 +197,12 @@ def build_graph(
158
197
  tool_call_id=tool_call["id"],
159
198
  )
160
199
  )
161
- return Command(goto="call_model", update={"messages": outputs, "selected_tool_ids": recent_tool_ids})
200
+ return Command(
201
+ goto="call_model",
202
+ update={"messages": outputs, "selected_tool_ids": recent_tool_ids},
203
+ )
162
204
 
163
- builder = StateGraph(State, context_schema=Context)
205
+ builder = StateGraph(State)
164
206
 
165
207
  builder.add_node(call_model)
166
208
  builder.add_node(select_tools)
@@ -8,5 +8,5 @@ SYSTEM_PROMPT = """You are a helpful AI assistant.
8
8
  3. **Load Tools:** After looking at the output of `search_tools`, you MUST call the `load_tools` function to load only the tools you want to use. Use your judgement to eliminate irrelevant apps that came up just because of semantic similarity. However, sometimes, multiple apps might be relevant for the same task. Prefer connected apps over unconnected apps while breaking a tie. If more than one relevant app (or none of the relevant apps) are connected, you must ask the user to choose the app. In case the user asks you to use an app that is not connected, call the apps tools normally. The tool will return a link for connecting that you should pass on to the user.
9
9
  3. **Strictly Follow the Process:** Your only job in your first turn is to analyze the user's request and call `search_tools` with a concise query describing the core task. Do not engage in conversation.
10
10
 
11
- System time: {system_time}
11
+ {instructions}
12
12
  """
@@ -1,9 +1,9 @@
1
1
  from langgraph.checkpoint.base import BaseCheckpointSaver
2
+ from universal_mcp.logger import logger
3
+ from universal_mcp.tools.registry import ToolRegistry
2
4
 
3
5
  from universal_mcp.agents.base import BaseAgent
4
6
  from universal_mcp.agents.llm import load_chat_model
5
- from universal_mcp.logger import logger
6
- from universal_mcp.tools.registry import ToolRegistry
7
7
 
8
8
  from .graph import build_graph
9
9
  from .prompts import SYSTEM_PROMPT
@@ -27,7 +27,9 @@ class BigToolAgentCache(BaseAgent):
27
27
  self.llm = load_chat_model(self.model)
28
28
  self.recursion_limit = kwargs.get("recursion_limit", 10)
29
29
 
30
- logger.info(f"BigToolAgent '{self.name}' initialized with model '{self.model}'.")
30
+ logger.info(
31
+ f"BigToolAgent '{self.name}' initialized with model '{self.model}'."
32
+ )
31
33
 
32
34
  async def _build_graph(self):
33
35
  """Build the bigtool agent graph using the existing create_agent function."""
@@ -42,7 +44,9 @@ class BigToolAgentCache(BaseAgent):
42
44
  logger.info("Graph built and compiled successfully.")
43
45
  return compiled_graph
44
46
  except Exception as e:
45
- logger.error(f"Error building graph for BigToolAgentCache '{self.name}': {e}")
47
+ logger.error(
48
+ f"Error building graph for BigToolAgentCache '{self.name}': {e}"
49
+ )
46
50
  raise
47
51
 
48
52
  @property
@@ -1,8 +1,8 @@
1
1
  import asyncio
2
2
 
3
3
  from loguru import logger
4
-
5
4
  from universal_mcp.agentr.registry import AgentrRegistry
5
+
6
6
  from universal_mcp.agents.bigtoolcache import BigToolAgentCache
7
7
 
8
8
 
@@ -1,11 +1,13 @@
1
- from universal_mcp.agents.bigtoolcache import BigToolAgentCache
2
1
  from universal_mcp.agentr.registry import AgentrRegistry
3
2
 
3
+ from universal_mcp.agents.bigtoolcache import BigToolAgentCache
4
+
5
+
4
6
  async def agent():
5
7
  agent_object = await BigToolAgentCache(
6
- name="BigTool Agent 2",
8
+ name="BigTool Agent Cache version",
7
9
  instructions="You are a helpful assistant that can use various tools to complete tasks.",
8
10
  model="anthropic/claude-4-sonnet-20250514",
9
11
  registry=AgentrRegistry(),
10
12
  )._build_graph()
11
- return agent_object
13
+ return agent_object
@@ -30,4 +30,3 @@ class Context:
30
30
  "This is to prevent infinite recursion."
31
31
  },
32
32
  )
33
-