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.
- universal_mcp/agentr/client.py +10 -10
- universal_mcp/agents/autoagent/__main__.py +3 -5
- universal_mcp/agents/autoagent/context.py +1 -0
- universal_mcp/agents/autoagent/graph.py +34 -28
- universal_mcp/agents/autoagent/prompts.py +3 -2
- universal_mcp/agents/autoagent/state.py +0 -1
- universal_mcp/agents/base.py +4 -0
- universal_mcp/agents/llm.py +3 -1
- {universal_mcp-0.1.24rc8.dist-info → universal_mcp-0.1.24rc10.dist-info}/METADATA +1 -1
- {universal_mcp-0.1.24rc8.dist-info → universal_mcp-0.1.24rc10.dist-info}/RECORD +13 -13
- {universal_mcp-0.1.24rc8.dist-info → universal_mcp-0.1.24rc10.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.24rc8.dist-info → universal_mcp-0.1.24rc10.dist-info}/entry_points.txt +0 -0
- {universal_mcp-0.1.24rc8.dist-info → universal_mcp-0.1.24rc10.dist-info}/licenses/LICENSE +0 -0
universal_mcp/agentr/client.py
CHANGED
@@ -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="
|
11
|
+
model="azure/gpt-4.1",
|
12
12
|
tool_registry=AgentrRegistry(),
|
13
13
|
)
|
14
|
-
|
15
|
-
|
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
|
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
|
-
|
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
|
-
|
60
|
-
|
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
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
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
|
-
|
7
|
-
|
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
|
"""
|
universal_mcp/agents/base.py
CHANGED
@@ -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)}")
|
universal_mcp/agents/llm.py
CHANGED
@@ -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.
|
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
|
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=
|
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=
|
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=
|
27
|
-
universal_mcp/agents/autoagent/context.py,sha256=
|
28
|
-
universal_mcp/agents/autoagent/graph.py,sha256=
|
29
|
-
universal_mcp/agents/autoagent/prompts.py,sha256=
|
30
|
-
universal_mcp/agents/autoagent/state.py,sha256=
|
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.
|
76
|
-
universal_mcp-0.1.
|
77
|
-
universal_mcp-0.1.
|
78
|
-
universal_mcp-0.1.
|
79
|
-
universal_mcp-0.1.
|
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,,
|
File without changes
|
File without changes
|
File without changes
|