universal-mcp 0.1.24rc8__py3-none-any.whl → 0.1.24rc10__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.
@@ -50,7 +50,7 @@ class AgentrClient:
50
50
  raise ValueError("No API key or auth token provided")
51
51
 
52
52
  def me(self):
53
- response = self.client.get("/users/me")
53
+ response = self.client.get("/users/me/")
54
54
  logger.debug(f"Me response: {response.status_code}")
55
55
  response.raise_for_status()
56
56
  data = response.json()
@@ -70,7 +70,7 @@ class AgentrClient:
70
70
  HTTPError: For other API errors.
71
71
  """
72
72
  response = self.client.get(
73
- "/credentials",
73
+ "/credentials/",
74
74
  params={"app_id": app_id},
75
75
  )
76
76
  logger.debug(f"Credentials response: {response.status_code}")
@@ -93,7 +93,7 @@ class AgentrClient:
93
93
  Raises:
94
94
  HTTPError: If the API request fails.
95
95
  """
96
- response = self.client.post("/connections/authorize", json={"app_id": app_id})
96
+ response = self.client.post("/connections/authorize/", json={"app_id": app_id})
97
97
  response.raise_for_status()
98
98
  url = response.json().get("authorize_url")
99
99
  return f"Please ask the user to visit the following url to authorize the application: {url}. Render the url in proper markdown format with a clickable link."
@@ -117,7 +117,7 @@ class AgentrClient:
117
117
  Returns:
118
118
  List[Dict[str, Any]]: A list of user app data dictionaries.
119
119
  """
120
- response = self.client.get("/apps/me")
120
+ response = self.client.get("/apps/me/")
121
121
  response.raise_for_status()
122
122
  return response.json().get("items", [])
123
123
 
@@ -127,7 +127,7 @@ class AgentrClient:
127
127
  Returns:
128
128
  List[Dict[str, Any]]: A list of user connection data dictionaries.
129
129
  """
130
- response = self.client.get("/connections")
130
+ response = self.client.get("/connections/")
131
131
  response.raise_for_status()
132
132
  return response.json().get("items", [])
133
133
 
@@ -143,7 +143,7 @@ class AgentrClient:
143
143
  Raises:
144
144
  httpx.HTTPError: If the API request fails.
145
145
  """
146
- response = self.client.get(f"/apps/{app_id}")
146
+ response = self.client.get(f"/apps/{app_id}/")
147
147
  response.raise_for_status()
148
148
  return response.json()
149
149
 
@@ -159,7 +159,7 @@ class AgentrClient:
159
159
  params = {}
160
160
  if app_id:
161
161
  params["app_id"] = app_id
162
- response = self.client.get("/tools", params=params)
162
+ response = self.client.get("/tools/", params=params)
163
163
  response.raise_for_status()
164
164
  return response.json().get("items", [])
165
165
 
@@ -175,7 +175,7 @@ class AgentrClient:
175
175
  Raises:
176
176
  httpx.HTTPError: If the API request fails.
177
177
  """
178
- response = self.client.get(f"/tools/{tool_id}")
178
+ response = self.client.get(f"/tools/{tool_id}/")
179
179
  response.raise_for_status()
180
180
  return response.json()
181
181
 
@@ -189,7 +189,7 @@ class AgentrClient:
189
189
  Returns:
190
190
  List[Dict[str, Any]]: A list of app data dictionaries.
191
191
  """
192
- response = self.client.get("/apps", params={"search": query, "limit": limit})
192
+ response = self.client.get("/apps/", params={"search": query, "limit": limit})
193
193
  response.raise_for_status()
194
194
  return response.json().get("items", [])
195
195
 
@@ -204,6 +204,6 @@ class AgentrClient:
204
204
  params = {"search": query, "limit": limit}
205
205
  if app_id:
206
206
  params["app_id"] = app_id
207
- response = self.client.get("/tools", params=params)
207
+ response = self.client.get("/tools/", params=params)
208
208
  response.raise_for_status()
209
209
  return response.json().get("items", [])
@@ -8,13 +8,11 @@ async def main():
8
8
  agent = AutoAgent(
9
9
  name="autoagent",
10
10
  instructions="You are a helpful assistant that can use tools to help the user.",
11
- model="anthropic/claude-4-sonnet-20250514",
11
+ model="azure/gpt-4.1",
12
12
  tool_registry=AgentrRegistry(),
13
13
  )
14
- result = await agent.invoke(
15
- user_input="Send an email to Manoj from my google mail account, manoj@agentr.dev, with the subject 'Hello from auto agent' and the body 'testing'"
16
- )
17
- print(result)
14
+ await agent.run_interactive()
15
+ # print(result)
18
16
 
19
17
 
20
18
  if __name__ == "__main__":
@@ -18,6 +18,7 @@ class Context:
18
18
 
19
19
  model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
20
20
  default="anthropic/claude-4-sonnet-20250514",
21
+ # default="vertex/gemini-2.5-flash",
21
22
  metadata={
22
23
  "description": "The name of the language model to use for the agent's main interactions. "
23
24
  "Should be in the form: provider/model-name."
@@ -18,7 +18,7 @@ from universal_mcp.types import ToolFormat
18
18
  async def build_graph(tool_registry: ToolRegistry, instructions: str = ""):
19
19
  @tool()
20
20
  async def search_tools(query: str, app_ids: list[str] | None = None) -> list[str]:
21
- """Retrieve tools using a search query and app id. Use multiple times if you require tools for different tasks."""
21
+ """Retrieve tools using a search query and a list of app ids. Use multiple times if you require tools for different queries."""
22
22
  tools_list = []
23
23
  if app_ids is not None:
24
24
  for app_id in app_ids:
@@ -33,12 +33,11 @@ async def build_graph(tool_registry: ToolRegistry, instructions: str = ""):
33
33
  """Ask the user a question. Use this tool to ask the user for any missing information for performing a task, or when you have multiple apps to choose from for performing a task."""
34
34
  full_question = question
35
35
  return f"ASKING_USER: {full_question}"
36
-
36
+
37
37
  @tool()
38
38
  async def load_tools(tools: list[str]) -> list[str]:
39
39
  """Choose the tools you want to use by passing their tool ids. Loads the tools for the chosen tools and returns the tool ids."""
40
40
  return tools
41
-
42
41
 
43
42
  async def call_model(
44
43
  state: State,
@@ -46,26 +45,28 @@ async def build_graph(tool_registry: ToolRegistry, instructions: str = ""):
46
45
  ):
47
46
  system_prompt = runtime.context.system_prompt if runtime.context.system_prompt else SYSTEM_PROMPT
48
47
  app_ids = await tool_registry.list_all_apps()
49
- app_id_descriptions = "\n".join([f"{app['id']}: {app['description']}" for app in app_ids])
48
+ connections = tool_registry.client.list_my_connections()
49
+ connection_ids = set([connection["app_id"] for connection in connections])
50
+ connected_apps = [app["id"] for app in app_ids if app["id"] in connection_ids]
51
+ unconnected_apps = [app["id"] for app in app_ids if app["id"] not in connection_ids]
52
+ app_id_descriptions = "These are the apps connected to the user's account:\n" + "\n".join(
53
+ [f"{app}" for app in connected_apps]
54
+ )
55
+ if unconnected_apps:
56
+ app_id_descriptions += "\n\nOther (not connected) apps: " + "\n".join(
57
+ [f"{app}" for app in unconnected_apps]
58
+ )
50
59
  print(app_id_descriptions)
51
60
  system_prompt = system_prompt.format(system_time=datetime.now(tz=UTC).isoformat(), app_ids=app_id_descriptions)
52
-
61
+
53
62
  messages = [{"role": "system", "content": system_prompt + "\n" + instructions}, *state["messages"]]
54
63
  model = load_chat_model(runtime.context.model)
55
64
  # Load tools from tool registry
56
65
  loaded_tools = await tool_registry.export_tools(tools=state["selected_tool_ids"], format=ToolFormat.LANGCHAIN)
57
66
  model_with_tools = model.bind_tools([search_tools, ask_user, load_tools, *loaded_tools], tool_choice="auto")
58
67
  response_raw = model_with_tools.invoke(messages)
59
- token_usage = state.get("token_usage", {})
60
- for key in ["input_tokens", "output_tokens", "total_tokens"]:
61
- if key in token_usage:
62
- token_usage[key] += response_raw.usage_metadata[key]
63
- else:
64
- token_usage[key] = response_raw.usage_metadata[key]
65
- print(response_raw.usage_metadata)
66
- print(token_usage)
67
- response = cast(AIMessage, response_raw)
68
- return {"messages": [response], "token_usage": token_usage}
68
+ response = cast(AIMessage, response_raw)
69
+ return {"messages": [response]}
69
70
 
70
71
  # Define the conditional edge that determines whether to continue or not
71
72
  def should_continue(state: State):
@@ -104,12 +105,12 @@ async def build_graph(tool_registry: ToolRegistry, instructions: str = ""):
104
105
  tools = await search_tools.ainvoke(tool_call["args"])
105
106
  outputs.append(
106
107
  ToolMessage(
107
- content=json.dumps(tools)+"\n\nUse the load_tools tool to load the tools you want to use.",
108
+ content=json.dumps(tools) + "\n\nUse the load_tools tool to load the tools you want to use.",
108
109
  name=tool_call["name"],
109
110
  tool_call_id=tool_call["id"],
110
111
  )
111
112
  )
112
-
113
+
113
114
  elif tool_call["name"] == load_tools.name:
114
115
  tool_ids = await load_tools.ainvoke(tool_call["args"])
115
116
  print(tool_ids)
@@ -122,20 +123,25 @@ async def build_graph(tool_registry: ToolRegistry, instructions: str = ""):
122
123
  )
123
124
  else:
124
125
  await tool_registry.export_tools([tool_call["name"]], ToolFormat.LANGCHAIN)
125
- tool_result = await tool_registry.call_tool(tool_call["name"], tool_call["args"])
126
- outputs.append(
127
- ToolMessage(
128
- content=json.dumps(tool_result),
129
- name=tool_call["name"],
130
- tool_call_id=tool_call["id"],
126
+ try:
127
+ tool_result = await tool_registry.call_tool(tool_call["name"], tool_call["args"])
128
+ outputs.append(
129
+ ToolMessage(
130
+ content=json.dumps(tool_result),
131
+ name=tool_call["name"],
132
+ tool_call_id=tool_call["id"],
133
+ )
134
+ )
135
+ except Exception as e:
136
+ outputs.append(
137
+ ToolMessage(
138
+ content=json.dumps("Error: " + str(e)),
139
+ name=tool_call["name"],
140
+ tool_call_id=tool_call["id"],
141
+ )
131
142
  )
132
- )
133
143
  return {"messages": outputs, "selected_tool_ids": tool_ids}
134
144
 
135
-
136
-
137
-
138
-
139
145
  builder = StateGraph(State, context_schema=Context)
140
146
 
141
147
  builder.add_node("agent", call_model)
@@ -3,6 +3,7 @@
3
3
  SYSTEM_PROMPT = """You are a helpful AI assistant. When you lack tools for any task you should use the `search_tools` function to unlock relevant tools. Whenever you need to ask the user for any information, or choose between multiple different applications, you can ask the user using the `ask_user` function.
4
4
 
5
5
  System time: {system_time}
6
- App IDs: {app_ids}
7
- Note that when multiple apps seem relevant for a task, you should ask the user to choose the app.
6
+ These are the list of apps available to you:
7
+ {app_ids}
8
+ Note that when multiple apps seem relevant for a task, you MUST ask the user to choose the app. 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.
8
9
  """
@@ -25,4 +25,3 @@ def _enqueue(left: list, right: list) -> list:
25
25
 
26
26
  class State(AgentState):
27
27
  selected_tool_ids: Annotated[list[str], _enqueue]
28
- token_usage: dict[str, int]
@@ -33,6 +33,7 @@ class BaseAgent:
33
33
  async for event, _ in self._graph.astream(
34
34
  {"messages": [{"role": "user", "content": user_input}]},
35
35
  config={"configurable": {"thread_id": thread_id}},
36
+ context={"system_prompt": self.instructions, "model": self.model},
36
37
  stream_mode="messages",
37
38
  ):
38
39
  event = cast(AIMessageChunk, event)
@@ -101,4 +102,7 @@ class BaseAgent:
101
102
  self.cli.display_info("\nGoodbye! 👋")
102
103
  break
103
104
  except Exception as e:
105
+ import traceback
106
+
107
+ traceback.print_exc()
104
108
  self.cli.display_error(f"An error occurred: {str(e)}")
@@ -1,12 +1,12 @@
1
1
  from langchain_anthropic import ChatAnthropic
2
2
  from langchain_core.language_models import BaseChatModel
3
3
  from langchain_google_vertexai.model_garden import ChatAnthropicVertex
4
+ from langchain_google_vertexai import ChatVertexAI
4
5
  from langchain_openai import AzureChatOpenAI
5
6
 
6
7
 
7
8
  def load_chat_model(fully_specified_name: str, tags: list[str] | None = None) -> BaseChatModel:
8
9
  """Load a chat model from a fully specified name.
9
-
10
10
  Args:
11
11
  fully_specified_name (str): String in the format 'provider/model'.
12
12
  """
@@ -19,6 +19,8 @@ def load_chat_model(fully_specified_name: str, tags: list[str] | None = None) ->
19
19
  ) # pyright: ignore[reportCallIssue]
20
20
  elif provider == "azure":
21
21
  return AzureChatOpenAI(model=model, api_version="2024-12-01-preview", azure_deployment=model, tags=tags)
22
+ elif provider == "vertex":
23
+ return ChatVertexAI(model=model, temperature=0.1, tags=tags)
22
24
  else:
23
25
  raise ValueError(f"Unsupported provider: {provider}")
24
26
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp
3
- Version: 0.1.24rc8
3
+ Version: 0.1.24rc10
4
4
  Summary: Universal MCP acts as a middle ware for your API applications. It can store your credentials, authorize, enable disable apps on the fly and much more.
5
5
  Author-email: Manoj Bajaj <manojbajaj95@gmail.com>
6
6
  License: MIT
@@ -8,26 +8,26 @@ universal_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  universal_mcp/types.py,sha256=jeUEkUnwdGWo3T_qSRSF83u0fYpuydaWzdKlCYBlCQA,770
9
9
  universal_mcp/agentr/README.md,sha256=t15pVgkCwZM5wzgLgrf0Zv6hVL7dPmKXvAeTf8CiXPQ,6641
10
10
  universal_mcp/agentr/__init__.py,sha256=fv1ZnOCduIUiJ9oN4e6Ya_hA2oWQvcEuDU3Ek1vEufI,180
11
- universal_mcp/agentr/client.py,sha256=-oyNlR5hWSV9CFaTp3JmzP99P44_A0QJ-tMuY6LIGWk,7419
11
+ universal_mcp/agentr/client.py,sha256=TQgwrNc7dEMXuprELf0Q-fdYdrH92Ppd7PUDZoD-KcY,7429
12
12
  universal_mcp/agentr/integration.py,sha256=V5GjqocqS02tRoI8MeV9PL6m-BzejwBzgJhOHo4MxAE,4212
13
13
  universal_mcp/agentr/registry.py,sha256=O60qOsuby1GTkgc9GXVf9BjHmjMTyDbxqbDfIJ13CJ4,6583
14
14
  universal_mcp/agentr/server.py,sha256=bIPmHMiKKwnUYnxmfZVRh1thcn7Rytm_-bNiXTfANzc,2098
15
15
  universal_mcp/agents/__init__.py,sha256=AMBDQs3p5PePjzdoHYNoPYEsUK_PLHGNVPGxK7yrKVo,270
16
16
  universal_mcp/agents/auto.py,sha256=BknCoeexTbFvwmVzYdGiiH72S_r6_5s9tmjH9M0I4d4,25410
17
- universal_mcp/agents/base.py,sha256=o41gxvwOqWY0IZnupAHfKeH7OG0dgHUQDDpqntsFg6M,4128
17
+ universal_mcp/agents/base.py,sha256=8CZD1ADTIbun6uQE9O7QO-5oz4UUJdwdFWIMBLq8E10,4279
18
18
  universal_mcp/agents/cli.py,sha256=7GdRBpu9rhZPiC2vaNQXWI7K-0yCnvdlmE0IFpvy2Gk,539
19
19
  universal_mcp/agents/hil.py,sha256=6xi0hhK5g-rhCrAMcGbjcKMReLWPC8rnFZMBOF3N_cY,3687
20
- universal_mcp/agents/llm.py,sha256=0HUI2Srh3RWtGyrjJCKqsroEgc1Rtkta3T8I1axl1mg,1232
20
+ universal_mcp/agents/llm.py,sha256=jollWOAUv15IouzbLpuqKzbjj2x2ioZUukSQNyNjb4Y,1382
21
21
  universal_mcp/agents/react.py,sha256=g2PwqxYe7v7wCMDCCQhTtGU1eFmSsF4g7T_t9d-v0ho,3012
22
22
  universal_mcp/agents/simple.py,sha256=JL8TFyXlA1F4zcArgKhlqVIbLWXetwM05z4MPDJgFeI,1367
23
23
  universal_mcp/agents/tools.py,sha256=7Vdw0VZYxXVAzAYSpRKWHzVl9Ll6NOnVRlc4cTXguUQ,1335
24
24
  universal_mcp/agents/utils.py,sha256=7kwFpD0Rv6JqHG-LlNCVwSu_xRX-N119mUmiBroHJL4,4109
25
25
  universal_mcp/agents/autoagent/__init__.py,sha256=E_vMnFz8Z120qdqaKXPNP_--4Tes4jImen7m_iZvtVo,913
26
- universal_mcp/agents/autoagent/__main__.py,sha256=ps0cT7b9DN-jQK8pOKFVRZf3Oz_dVSBSYWOa8sc7VNc,647
27
- universal_mcp/agents/autoagent/context.py,sha256=1ic3sIL14XZeiMjpkwysLImRTQFKXRFSx7rvgVh4plY,802
28
- universal_mcp/agents/autoagent/graph.py,sha256=f6RF9Cm1aiYTHoEhNHG_FNQrU-MgEb8QIHdg9pQSyuA,6390
29
- universal_mcp/agents/autoagent/prompts.py,sha256=5EiQVE75LrERYAh29CltjMxI7znt_eW_LP4zX_7LxHY,502
30
- universal_mcp/agents/autoagent/state.py,sha256=pTKbgTK8TTx7qIioVt98u9KZpeWacWaRmKJWRv3Jj40,791
26
+ universal_mcp/agents/autoagent/__main__.py,sha256=tvA-fGN-8x9wA0MyPXydgJ2PxNTY1t3YtweYuZGrIX0,468
27
+ universal_mcp/agents/autoagent/context.py,sha256=RgjW1uCslucxYJpdmi4govd-0V1_9e6Y_kjWl3FpLrE,847
28
+ universal_mcp/agents/autoagent/graph.py,sha256=8q44BXZLjB00OBXcqEdV-DrZOFkTkdeov62cI7kRwX8,6901
29
+ universal_mcp/agents/autoagent/prompts.py,sha256=ptnXyOarigq96nVW-P1ceT2WRilKvh7NPfE_Cy0NTz4,719
30
+ universal_mcp/agents/autoagent/state.py,sha256=TQeGZD99okclkoCh5oz-VYIlEsC9yLQyDpnBnm7QCN8,759
31
31
  universal_mcp/agents/autoagent/studio.py,sha256=nfVRzPXwBjDORHA0wln2k3Nz-zQXNKgZMvgeqBvkdtM,644
32
32
  universal_mcp/agents/autoagent/utils.py,sha256=AFq-8scw_WlSZxDnTzxSNrOSiGYsIlqkqtQLDWf_rMU,431
33
33
  universal_mcp/agents/codeact/__init__.py,sha256=5D_I3lI_3tWjZERRoFav_bPe9UDaJ53pDzZYtyixg3E,10097
@@ -72,8 +72,8 @@ universal_mcp/utils/openapi/readme.py,sha256=R2Jp7DUXYNsXPDV6eFTkLiy7MXbSULUj1vH
72
72
  universal_mcp/utils/openapi/test_generator.py,sha256=h44gQXEXmrw4pD3-XNHKB7T9c2lDomqrJxVO6oszCqM,12186
73
73
  universal_mcp/utils/templates/README.md.j2,sha256=Mrm181YX-o_-WEfKs01Bi2RJy43rBiq2j6fTtbWgbTA,401
74
74
  universal_mcp/utils/templates/api_client.py.j2,sha256=972Im7LNUAq3yZTfwDcgivnb-b8u6_JLKWXwoIwXXXQ,908
75
- universal_mcp-0.1.24rc8.dist-info/METADATA,sha256=7ioJQnlDsraVLDJ-rGDX0xb-W5yBgZQkTMzPtke-7JQ,3143
76
- universal_mcp-0.1.24rc8.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
77
- universal_mcp-0.1.24rc8.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
78
- universal_mcp-0.1.24rc8.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
79
- universal_mcp-0.1.24rc8.dist-info/RECORD,,
75
+ universal_mcp-0.1.24rc10.dist-info/METADATA,sha256=3VAPupygqW8ScZVKMma6ymtFMaE6rv_CCvedJVMQAtQ,3144
76
+ universal_mcp-0.1.24rc10.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
77
+ universal_mcp-0.1.24rc10.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
78
+ universal_mcp-0.1.24rc10.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
79
+ universal_mcp-0.1.24rc10.dist-info/RECORD,,