universal-mcp 0.1.24rc6__tar.gz → 0.1.24rc8__tar.gz
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-0.1.24rc6 → universal_mcp-0.1.24rc8}/PKG-INFO +4 -1
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/pyproject.toml +4 -1
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agentr/README.md +43 -34
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agentr/__init__.py +1 -2
- universal_mcp-0.1.24rc8/src/universal_mcp/agentr/client.py +209 -0
- universal_mcp-0.1.24rc8/src/universal_mcp/agentr/registry.py +182 -0
- universal_mcp-0.1.24rc8/src/universal_mcp/agents/__init__.py +6 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agents/auto.py +8 -9
- universal_mcp-0.1.24rc8/src/universal_mcp/agents/autoagent/__init__.py +31 -0
- universal_mcp-0.1.24rc8/src/universal_mcp/agents/autoagent/__main__.py +21 -0
- universal_mcp-0.1.24rc8/src/universal_mcp/agents/autoagent/context.py +25 -0
- universal_mcp-0.1.24rc8/src/universal_mcp/agents/autoagent/graph.py +148 -0
- universal_mcp-0.1.24rc8/src/universal_mcp/agents/autoagent/prompts.py +8 -0
- universal_mcp-0.1.24rc8/src/universal_mcp/agents/autoagent/state.py +28 -0
- universal_mcp-0.1.24rc8/src/universal_mcp/agents/autoagent/studio.py +25 -0
- universal_mcp-0.1.24rc8/src/universal_mcp/agents/autoagent/utils.py +13 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agents/base.py +13 -10
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agents/codeact/test.py +2 -2
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agents/hil.py +2 -2
- universal_mcp-0.1.24rc8/src/universal_mcp/agents/llm.py +28 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agents/react.py +27 -7
- universal_mcp-0.1.24rc8/src/universal_mcp/agents/simple.py +40 -0
- universal_mcp-0.1.24rc8/src/universal_mcp/tools/registry.py +83 -0
- universal_mcp-0.1.24rc6/src/universal_mcp/agentr/agentr.py +0 -30
- universal_mcp-0.1.24rc6/src/universal_mcp/agentr/client.py +0 -123
- universal_mcp-0.1.24rc6/src/universal_mcp/agentr/registry.py +0 -91
- universal_mcp-0.1.24rc6/src/universal_mcp/agents/__init__.py +0 -6
- universal_mcp-0.1.24rc6/src/universal_mcp/agents/llm.py +0 -10
- universal_mcp-0.1.24rc6/src/universal_mcp/agents/simple.py +0 -40
- universal_mcp-0.1.24rc6/src/universal_mcp/tools/registry.py +0 -41
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/.gitignore +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/LICENSE +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/README.md +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/tests/__init__.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/tests/conftest.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/tests/test_api_generator.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/tests/test_api_integration.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/tests/test_applications.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/tests/test_localserver.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/tests/test_stores.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/tests/test_tool.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/tests/test_tool_manager.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/tests/test_zenquotes.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/__init__.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agentr/integration.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agentr/server.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agents/cli.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agents/codeact/__init__.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agents/codeact/sandbox.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agents/codeact/utils.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agents/tools.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/agents/utils.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/analytics.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/applications/__init__.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/applications/application.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/applications/sample/app.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/cli.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/client/oauth.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/client/token_store.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/client/transport.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/config.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/exceptions.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/integrations/__init__.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/integrations/integration.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/logger.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/py.typed +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/servers/__init__.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/servers/server.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/stores/__init__.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/stores/store.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/tools/__init__.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/tools/adapters.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/tools/docstring_parser.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/tools/func_metadata.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/tools/manager.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/tools/tools.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/types.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/__init__.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/common.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/installation.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/openapi/__inti__.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/openapi/api_generator.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/openapi/api_splitter.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/openapi/cli.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/openapi/docgen.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/openapi/filters.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/openapi/openapi.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/openapi/postprocessor.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/openapi/preprocessor.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/openapi/readme.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/openapi/test_generator.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/prompts.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/singleton.py +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/templates/README.md.j2 +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/templates/api_client.py.j2 +0 -0
- {universal_mcp-0.1.24rc6 → universal_mcp-0.1.24rc8}/src/universal_mcp/utils/testing.py +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: universal-mcp
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.24rc8
|
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
|
@@ -9,9 +9,12 @@ Requires-Python: >=3.11
|
|
9
9
|
Requires-Dist: black>=25.1.0
|
10
10
|
Requires-Dist: cookiecutter>=2.6.0
|
11
11
|
Requires-Dist: gql>=4.0.0
|
12
|
+
Requires-Dist: httpx-aiohttp>=0.1.8
|
12
13
|
Requires-Dist: jinja2>=3.1.3
|
13
14
|
Requires-Dist: jsonref>=1.1.0
|
14
15
|
Requires-Dist: keyring>=25.6.0
|
16
|
+
Requires-Dist: langchain-anthropic>=0.3.19
|
17
|
+
Requires-Dist: langchain-google-vertexai>=2.0.28
|
15
18
|
Requires-Dist: langchain-mcp-adapters>=0.1.9
|
16
19
|
Requires-Dist: langchain-openai>=0.3.27
|
17
20
|
Requires-Dist: langgraph-cli[inmem]>=0.3.4
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "universal-mcp"
|
7
|
-
version = "0.1.24-
|
7
|
+
version = "0.1.24-rc8"
|
8
8
|
description = "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."
|
9
9
|
readme = "README.md"
|
10
10
|
authors = [
|
@@ -17,8 +17,11 @@ dependencies = [
|
|
17
17
|
"black>=25.1.0",
|
18
18
|
"cookiecutter>=2.6.0",
|
19
19
|
"gql>=4.0.0",
|
20
|
+
"httpx-aiohttp>=0.1.8",
|
20
21
|
"jsonref>=1.1.0",
|
21
22
|
"keyring>=25.6.0",
|
23
|
+
"langchain-anthropic>=0.3.19",
|
24
|
+
"langchain-google-vertexai>=2.0.28",
|
22
25
|
"langchain-mcp-adapters>=0.1.9",
|
23
26
|
"langchain-openai>=0.3.27",
|
24
27
|
"langgraph>=0.5.2",
|
@@ -1,20 +1,23 @@
|
|
1
1
|
# AgentR Python SDK
|
2
2
|
|
3
3
|
The official Python SDK for the AgentR platform, a component of the Universal MCP framework.
|
4
|
-
Currently in beta, breaking changes are expected.
|
5
4
|
|
6
|
-
|
7
|
-
|
5
|
+
*Currently in beta, breaking changes are expected.*
|
6
|
+
|
7
|
+
The AgentR Python SDK provides convenient access to the AgentR REST API from any Python 3.10+ application, allowing for dynamic loading and management of tools and integrations.
|
8
8
|
|
9
9
|
## Installation
|
10
|
+
|
10
11
|
```bash
|
11
12
|
pip install universal-mcp
|
12
13
|
```
|
13
14
|
|
14
15
|
## Usage
|
16
|
+
|
15
17
|
The AgentR platform is designed to seamlessly integrate a wide array of tools into your agentic applications. The primary entry point for this is the `Agentr` class, which provides a high-level interface for loading and listing tools.
|
16
18
|
|
17
19
|
### High-Level Client (`Agentr`)
|
20
|
+
|
18
21
|
This is the recommended way to get started. It abstracts away the details of the registry and tool management.
|
19
22
|
|
20
23
|
```python
|
@@ -29,7 +32,7 @@ agentr = Agentr(
|
|
29
32
|
)
|
30
33
|
|
31
34
|
# Load specific tools from the AgentR server into the tool manager
|
32
|
-
agentr.load_tools(["
|
35
|
+
agentr.load_tools(["reddit__search_subreddits", "google-drive__list_files"])
|
33
36
|
|
34
37
|
# List the tools that are now loaded and ready to be used
|
35
38
|
# You can specify a format compatible with your LLM (e.g., OPENAI)
|
@@ -41,10 +44,10 @@ print(tools)
|
|
41
44
|
|
42
45
|
For more granular control over the AgentR platform, you can use the lower-level components directly.
|
43
46
|
|
44
|
-
|
45
|
-
|
47
|
+
#### AgentrClient
|
48
|
+
|
49
|
+
The `AgentrClient` provides direct, one-to-one access to the AgentR REST API endpoints. The following examples have been updated to reflect the latest API structure.
|
46
50
|
|
47
|
-
#### Methods
|
48
51
|
```python
|
49
52
|
import os
|
50
53
|
from universal_mcp.agentr import AgentrClient
|
@@ -55,39 +58,43 @@ client = AgentrClient(
|
|
55
58
|
api_key=os.environ.get("AGENTR_API_KEY")
|
56
59
|
)
|
57
60
|
|
58
|
-
# Fetch
|
59
|
-
apps = client.
|
60
|
-
print(apps)
|
61
|
+
# Fetch a list of available applications from the AgentR server
|
62
|
+
apps = client.list_apps()
|
63
|
+
print("Available Apps:", apps)
|
61
64
|
|
62
|
-
# Get credentials for a specific
|
63
|
-
# This will raise a NotAuthorizedError if the user needs to authenticate
|
65
|
+
# Get credentials for a specific app by its ID (e.g., 'reddit')
|
66
|
+
# This will raise a NotAuthorizedError if the user needs to authenticate.
|
64
67
|
try:
|
65
|
-
credentials = client.get_credentials("reddit")
|
68
|
+
credentials = client.get_credentials(app_id="reddit")
|
66
69
|
print("Reddit credentials found.")
|
67
70
|
except NotAuthorizedError as e:
|
68
|
-
print(e)
|
69
|
-
|
70
|
-
# Example of fetching a single app and its actions
|
71
|
-
if apps:
|
72
|
-
app_id = apps[0].id # Assuming AppConfig has an 'id' attribute
|
71
|
+
print(e) # "Please ask the user to visit the following url to authorize..."
|
73
72
|
|
74
|
-
|
75
|
-
|
76
|
-
|
73
|
+
# List all available tools globally
|
74
|
+
all_tools = client.list_tools()
|
75
|
+
print("All Available Tools:", all_tools)
|
77
76
|
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
77
|
+
# Example of fetching a single app and a single tool
|
78
|
+
if apps:
|
79
|
+
# Note: We access dictionary keys, not attributes
|
80
|
+
app_id = apps[0]['id']
|
81
|
+
|
82
|
+
# Fetch a single app's details
|
83
|
+
app_details = client.get_app(app_id)
|
84
|
+
print(f"Fetched details for app '{app_id}':", app_details)
|
85
|
+
|
86
|
+
if all_tools:
|
87
|
+
tool_id = all_tools[0]['id']
|
88
|
+
|
89
|
+
# Fetch a single tool's details
|
90
|
+
tool_details = client.get_tool(tool_id)
|
91
|
+
print(f"Fetched details for tool '{tool_id}':", tool_details)
|
85
92
|
```
|
86
93
|
|
87
|
-
|
94
|
+
#### AgentrIntegration
|
95
|
+
|
88
96
|
This class handles the authentication and authorization flow for a single integration (e.g., "reddit"). It's used under the hood by applications to acquire credentials.
|
89
97
|
|
90
|
-
#### Methods
|
91
98
|
```python
|
92
99
|
from universal_mcp.agentr import AgentrIntegration, AgentrClient
|
93
100
|
from universal_mcp.exceptions import NotAuthorizedError
|
@@ -114,10 +121,10 @@ except NotAuthorizedError:
|
|
114
121
|
print("Still not authorized.")
|
115
122
|
```
|
116
123
|
|
117
|
-
|
124
|
+
#### AgentrRegistry
|
125
|
+
|
118
126
|
The registry is responsible for discovering which tools are available on the AgentR platform.
|
119
127
|
|
120
|
-
#### Methods
|
121
128
|
```python
|
122
129
|
import asyncio
|
123
130
|
from universal_mcp.agentr import AgentrRegistry, AgentrClient
|
@@ -148,7 +155,8 @@ if __name__ == "__main__":
|
|
148
155
|
asyncio.run(main())
|
149
156
|
```
|
150
157
|
|
151
|
-
|
158
|
+
#### AgentrServer
|
159
|
+
|
152
160
|
For server-side deployments, `AgentrServer` can be used to load all configured applications and their tools from an AgentR instance on startup.
|
153
161
|
|
154
162
|
```python
|
@@ -170,6 +178,7 @@ print(tool_manager.list_tools())
|
|
170
178
|
```
|
171
179
|
|
172
180
|
## Executing Tools
|
181
|
+
|
173
182
|
Once tools are loaded, you can execute them using the `call_tool` method on the `ToolManager` instance, which is available via `agentr.manager`.
|
174
183
|
|
175
184
|
```python
|
@@ -182,7 +191,7 @@ async def main():
|
|
182
191
|
agentr = Agentr(api_key=os.environ.get("AGENTR_API_KEY"))
|
183
192
|
|
184
193
|
# 2. Load the tool(s) you want to use
|
185
|
-
tool_name = "
|
194
|
+
tool_name = "reddit__search_subreddits"
|
186
195
|
agentr.load_tools([tool_name])
|
187
196
|
|
188
197
|
# 3. Execute the tool using the tool manager
|
@@ -1,6 +1,5 @@
|
|
1
|
-
from .agentr import Agentr
|
2
1
|
from .client import AgentrClient
|
3
2
|
from .integration import AgentrIntegration
|
4
3
|
from .registry import AgentrRegistry
|
5
4
|
|
6
|
-
__all__ = ["
|
5
|
+
__all__ = ["AgentrClient", "AgentrRegistry", "AgentrIntegration"]
|
@@ -0,0 +1,209 @@
|
|
1
|
+
import os
|
2
|
+
from typing import Any
|
3
|
+
|
4
|
+
import httpx
|
5
|
+
from loguru import logger
|
6
|
+
|
7
|
+
from universal_mcp.exceptions import NotAuthorizedError
|
8
|
+
|
9
|
+
|
10
|
+
class AgentrClient:
|
11
|
+
"""Helper class for AgentR API operations.
|
12
|
+
|
13
|
+
This class provides utility methods for interacting with the AgentR API,
|
14
|
+
including authentication, authorization, and credential management.
|
15
|
+
|
16
|
+
Args:
|
17
|
+
api_key (str, optional): AgentR API key. If not provided, will look for AGENTR_API_KEY env var.
|
18
|
+
base_url (str, optional): Base URL for AgentR API. Defaults to https://api.agentr.dev.
|
19
|
+
auth_token (str, optional): Auth token for AgentR API. If not provided, will look for AGENTR_AUTH_TOKEN env var.
|
20
|
+
"""
|
21
|
+
|
22
|
+
def __init__(
|
23
|
+
self, api_key: str | None = None, base_url: str | None = None, auth_token: str | None = None, **kwargs
|
24
|
+
):
|
25
|
+
base_url = base_url or os.getenv("AGENTR_BASE_URL", "https://api.agentr.dev")
|
26
|
+
self.base_url = f"{base_url.rstrip('/')}/v1"
|
27
|
+
api_key = api_key or os.getenv("AGENTR_API_KEY")
|
28
|
+
if api_key:
|
29
|
+
self.client = httpx.Client(
|
30
|
+
base_url=self.base_url,
|
31
|
+
headers={"X-API-KEY": api_key, "accept": "application/json"},
|
32
|
+
timeout=30,
|
33
|
+
follow_redirects=True,
|
34
|
+
verify=False,
|
35
|
+
)
|
36
|
+
me_data = self.me()
|
37
|
+
logger.debug(f"Client initialized with user: {me_data['email']}")
|
38
|
+
elif auth_token:
|
39
|
+
logger.debug("Initializing client with auth token")
|
40
|
+
self.client = httpx.Client(
|
41
|
+
base_url=self.base_url,
|
42
|
+
headers={"Authorization": f"Bearer {auth_token}", "accept": "application/json"},
|
43
|
+
timeout=30,
|
44
|
+
follow_redirects=True,
|
45
|
+
verify=False,
|
46
|
+
)
|
47
|
+
me_data = self.me()
|
48
|
+
logger.debug(f"Client initialized with user: {me_data['email']}")
|
49
|
+
else:
|
50
|
+
raise ValueError("No API key or auth token provided")
|
51
|
+
|
52
|
+
def me(self):
|
53
|
+
response = self.client.get("/users/me")
|
54
|
+
logger.debug(f"Me response: {response.status_code}")
|
55
|
+
response.raise_for_status()
|
56
|
+
data = response.json()
|
57
|
+
return data
|
58
|
+
|
59
|
+
def get_credentials(self, app_id: str) -> dict[str, Any]:
|
60
|
+
"""Get credentials for an integration from the AgentR API.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
app_id (str): The ID of the app (e.g., 'asana', 'google-drive').
|
64
|
+
|
65
|
+
Returns:
|
66
|
+
dict: Credentials data from API response.
|
67
|
+
|
68
|
+
Raises:
|
69
|
+
NotAuthorizedError: If credentials are not found (404 response).
|
70
|
+
HTTPError: For other API errors.
|
71
|
+
"""
|
72
|
+
response = self.client.get(
|
73
|
+
"/credentials",
|
74
|
+
params={"app_id": app_id},
|
75
|
+
)
|
76
|
+
logger.debug(f"Credentials response: {response.status_code}")
|
77
|
+
if response.status_code == 404:
|
78
|
+
logger.warning(f"No credentials found for app '{app_id}'. Requesting authorization...")
|
79
|
+
action_url = self.get_authorization_url(app_id)
|
80
|
+
raise NotAuthorizedError(action_url)
|
81
|
+
response.raise_for_status()
|
82
|
+
return response.json()
|
83
|
+
|
84
|
+
def get_authorization_url(self, app_id: str) -> str:
|
85
|
+
"""Get the authorization URL to connect an app.
|
86
|
+
|
87
|
+
Args:
|
88
|
+
app_id (str): The ID of the app to authorize.
|
89
|
+
|
90
|
+
Returns:
|
91
|
+
str: A message containing the authorization URL.
|
92
|
+
|
93
|
+
Raises:
|
94
|
+
HTTPError: If the API request fails.
|
95
|
+
"""
|
96
|
+
response = self.client.post("/connections/authorize", json={"app_id": app_id})
|
97
|
+
response.raise_for_status()
|
98
|
+
url = response.json().get("authorize_url")
|
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."
|
100
|
+
|
101
|
+
def list_all_apps(self):
|
102
|
+
"""Fetch available apps from AgentR API.
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
List[Dict[str, Any]]: A list of application data dictionaries.
|
106
|
+
|
107
|
+
Raises:
|
108
|
+
httpx.HTTPError: If the API request fails.
|
109
|
+
"""
|
110
|
+
response = self.client.get("/apps/")
|
111
|
+
response.raise_for_status()
|
112
|
+
return response.json().get("items", [])
|
113
|
+
|
114
|
+
def list_my_apps(self):
|
115
|
+
"""Fetch user apps from AgentR API.
|
116
|
+
|
117
|
+
Returns:
|
118
|
+
List[Dict[str, Any]]: A list of user app data dictionaries.
|
119
|
+
"""
|
120
|
+
response = self.client.get("/apps/me")
|
121
|
+
response.raise_for_status()
|
122
|
+
return response.json().get("items", [])
|
123
|
+
|
124
|
+
def list_my_connections(self):
|
125
|
+
"""Fetch user connections from AgentR API.
|
126
|
+
|
127
|
+
Returns:
|
128
|
+
List[Dict[str, Any]]: A list of user connection data dictionaries.
|
129
|
+
"""
|
130
|
+
response = self.client.get("/connections")
|
131
|
+
response.raise_for_status()
|
132
|
+
return response.json().get("items", [])
|
133
|
+
|
134
|
+
def get_app_details(self, app_id: str):
|
135
|
+
"""Fetch a specific app from AgentR API.
|
136
|
+
|
137
|
+
Args:
|
138
|
+
app_id (str): ID of the app to fetch.
|
139
|
+
|
140
|
+
Returns:
|
141
|
+
dict: App configuration data.
|
142
|
+
|
143
|
+
Raises:
|
144
|
+
httpx.HTTPError: If the API request fails.
|
145
|
+
"""
|
146
|
+
response = self.client.get(f"/apps/{app_id}")
|
147
|
+
response.raise_for_status()
|
148
|
+
return response.json()
|
149
|
+
|
150
|
+
def list_all_tools(self, app_id: str | None = None):
|
151
|
+
"""List all available tools from the AgentR API.
|
152
|
+
|
153
|
+
Note: In the backend, tools are globally listed and not tied to a
|
154
|
+
specific app at this endpoint.
|
155
|
+
|
156
|
+
Returns:
|
157
|
+
List[Dict[str, Any]]: A list of tool configurations.
|
158
|
+
"""
|
159
|
+
params = {}
|
160
|
+
if app_id:
|
161
|
+
params["app_id"] = app_id
|
162
|
+
response = self.client.get("/tools", params=params)
|
163
|
+
response.raise_for_status()
|
164
|
+
return response.json().get("items", [])
|
165
|
+
|
166
|
+
def get_tool_details(self, tool_id: str):
|
167
|
+
"""Fetch a specific tool configuration from the AgentR API.
|
168
|
+
|
169
|
+
Args:
|
170
|
+
tool_id (str): ID of the tool to fetch.
|
171
|
+
|
172
|
+
Returns:
|
173
|
+
dict: Tool configuration data.
|
174
|
+
|
175
|
+
Raises:
|
176
|
+
httpx.HTTPError: If the API request fails.
|
177
|
+
"""
|
178
|
+
response = self.client.get(f"/tools/{tool_id}")
|
179
|
+
response.raise_for_status()
|
180
|
+
return response.json()
|
181
|
+
|
182
|
+
def search_all_apps(self, query: str, limit: int = 2):
|
183
|
+
"""Search for apps from the AgentR API.
|
184
|
+
|
185
|
+
Args:
|
186
|
+
query (str): The query to search for.
|
187
|
+
limit (int, optional): The number of apps to return. Defaults to 2.
|
188
|
+
|
189
|
+
Returns:
|
190
|
+
List[Dict[str, Any]]: A list of app data dictionaries.
|
191
|
+
"""
|
192
|
+
response = self.client.get("/apps", params={"search": query, "limit": limit})
|
193
|
+
response.raise_for_status()
|
194
|
+
return response.json().get("items", [])
|
195
|
+
|
196
|
+
def search_all_tools(self, query: str, limit: int = 2, app_id: str | None = None):
|
197
|
+
"""Search for tools from the AgentR API.
|
198
|
+
|
199
|
+
Args:
|
200
|
+
query (str): The query to search for.
|
201
|
+
limit (int, optional): The number of tools to return. Defaults to 2.
|
202
|
+
app_id (str, optional): The ID of the app to search tools for.
|
203
|
+
"""
|
204
|
+
params = {"search": query, "limit": limit}
|
205
|
+
if app_id:
|
206
|
+
params["app_id"] = app_id
|
207
|
+
response = self.client.get("/tools", params=params)
|
208
|
+
response.raise_for_status()
|
209
|
+
return response.json().get("items", [])
|
@@ -0,0 +1,182 @@
|
|
1
|
+
from typing import Any
|
2
|
+
|
3
|
+
from loguru import logger
|
4
|
+
|
5
|
+
from universal_mcp.agentr.client import AgentrClient
|
6
|
+
from universal_mcp.applications import app_from_slug
|
7
|
+
from universal_mcp.tools.manager import ToolManager, _get_app_and_tool_name
|
8
|
+
from universal_mcp.tools.registry import ToolRegistry
|
9
|
+
from universal_mcp.types import ToolConfig, ToolFormat
|
10
|
+
|
11
|
+
from .integration import AgentrIntegration
|
12
|
+
|
13
|
+
|
14
|
+
class AgentrRegistry(ToolRegistry):
|
15
|
+
"""Platform manager implementation for AgentR platform."""
|
16
|
+
|
17
|
+
def __init__(self, client: AgentrClient | None = None, **kwargs):
|
18
|
+
"""Initialize the AgentR platform manager."""
|
19
|
+
|
20
|
+
self.client = client or AgentrClient(**kwargs)
|
21
|
+
self.tool_manager = ToolManager()
|
22
|
+
logger.debug("AgentrRegistry initialized successfully")
|
23
|
+
|
24
|
+
async def list_all_apps(self) -> list[dict[str, Any]]:
|
25
|
+
"""Get list of available apps from AgentR.
|
26
|
+
|
27
|
+
Returns:
|
28
|
+
List of app dictionaries with id, name, description, and available fields
|
29
|
+
"""
|
30
|
+
if self.client is None:
|
31
|
+
raise ValueError("Client is not initialized")
|
32
|
+
try:
|
33
|
+
all_apps = self.client.list_all_apps()
|
34
|
+
return all_apps
|
35
|
+
except Exception as e:
|
36
|
+
logger.error(f"Error fetching apps from AgentR: {e}")
|
37
|
+
return []
|
38
|
+
|
39
|
+
async def get_app_details(self, app_id: str) -> dict[str, Any]:
|
40
|
+
"""Get detailed information about a specific app from AgentR.
|
41
|
+
|
42
|
+
Args:
|
43
|
+
app_id: The ID of the app to get details for
|
44
|
+
|
45
|
+
Returns:
|
46
|
+
Dictionary containing app details
|
47
|
+
"""
|
48
|
+
try:
|
49
|
+
app_info = self.client.get_app_details(app_id)
|
50
|
+
return app_info
|
51
|
+
except Exception as e:
|
52
|
+
logger.error(f"Error getting details for app {app_id}: {e}")
|
53
|
+
return {}
|
54
|
+
|
55
|
+
async def search_apps(
|
56
|
+
self,
|
57
|
+
query: str,
|
58
|
+
limit: int = 10,
|
59
|
+
) -> list[dict[str, Any]]:
|
60
|
+
"""Search for apps by a query.
|
61
|
+
|
62
|
+
Args:
|
63
|
+
query: The query to search for
|
64
|
+
limit: The number of apps to return
|
65
|
+
|
66
|
+
Returns:
|
67
|
+
List of app dictionaries matching the query
|
68
|
+
"""
|
69
|
+
try:
|
70
|
+
apps = self.client.search_all_apps(query, limit)
|
71
|
+
return apps
|
72
|
+
except Exception as e:
|
73
|
+
logger.error(f"Error searching apps from AgentR: {e}")
|
74
|
+
return []
|
75
|
+
|
76
|
+
async def list_tools(
|
77
|
+
self,
|
78
|
+
app_id: str,
|
79
|
+
) -> list[dict[str, Any]]:
|
80
|
+
"""List all tools available on the platform, filter by app_id.
|
81
|
+
|
82
|
+
Args:
|
83
|
+
app_id: The ID of the app to list tools for
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
List of tool dictionaries for the specified app
|
87
|
+
"""
|
88
|
+
try:
|
89
|
+
all_tools = self.client.list_all_tools(app_id=app_id)
|
90
|
+
return all_tools
|
91
|
+
except Exception as e:
|
92
|
+
logger.error(f"Error listing tools for app {app_id}: {e}")
|
93
|
+
return []
|
94
|
+
|
95
|
+
async def search_tools(
|
96
|
+
self,
|
97
|
+
query: str,
|
98
|
+
limit: int = 2,
|
99
|
+
app_id: str | None = None,
|
100
|
+
) -> list[dict[str, Any]]:
|
101
|
+
"""Search for tools by a query.
|
102
|
+
|
103
|
+
Args:
|
104
|
+
query: The query to search for
|
105
|
+
limit: The number of tools to return
|
106
|
+
app_id: The ID of the app to list tools for
|
107
|
+
Returns:
|
108
|
+
List of tool dictionaries matching the query
|
109
|
+
"""
|
110
|
+
try:
|
111
|
+
tools = self.client.search_all_tools(query, limit, app_id)
|
112
|
+
return tools
|
113
|
+
except Exception as e:
|
114
|
+
logger.error(f"Error searching tools from AgentR: {e}")
|
115
|
+
return []
|
116
|
+
|
117
|
+
async def export_tools(
|
118
|
+
self,
|
119
|
+
tools: list[str] | ToolConfig,
|
120
|
+
format: ToolFormat,
|
121
|
+
) -> str:
|
122
|
+
"""Export given tools to required format.
|
123
|
+
|
124
|
+
Args:
|
125
|
+
tools: List of tool identifiers to export
|
126
|
+
format: The format to export tools to (native, mcp, langchain, openai)
|
127
|
+
|
128
|
+
Returns:
|
129
|
+
String representation of tools in the specified format
|
130
|
+
"""
|
131
|
+
try:
|
132
|
+
# Clear tools from tool manager before loading new tools
|
133
|
+
self.tool_manager.clear_tools()
|
134
|
+
if isinstance(tools, ToolConfig):
|
135
|
+
print("Loading tools from tool config")
|
136
|
+
self._load_tools_from_tool_config(tools, self.tool_manager)
|
137
|
+
else:
|
138
|
+
print("Loading tools from list")
|
139
|
+
self._load_agentr_tools_from_list(tools, self.tool_manager)
|
140
|
+
loaded_tools = self.tool_manager.list_tools(format=format)
|
141
|
+
logger.info(f"Exporting {len(loaded_tools)} tools to {format} format")
|
142
|
+
return loaded_tools
|
143
|
+
except Exception as e:
|
144
|
+
logger.error(f"Error exporting tools: {e}")
|
145
|
+
return ""
|
146
|
+
|
147
|
+
def _load_tools(self, app_name: str, tool_names: list[str], tool_manager: ToolManager) -> None:
|
148
|
+
"""Helper method to load and register tools for an app."""
|
149
|
+
app = app_from_slug(app_name)
|
150
|
+
integration = AgentrIntegration(name=app_name, client=self.client)
|
151
|
+
app_instance = app(integration=integration)
|
152
|
+
tool_manager.register_tools_from_app(app_instance, tool_names=tool_names)
|
153
|
+
|
154
|
+
def _load_agentr_tools_from_list(self, tools: list[str], tool_manager: ToolManager) -> None:
|
155
|
+
"""Load tools from AgentR and register them as tools.
|
156
|
+
|
157
|
+
Args:
|
158
|
+
tools: The list of tools to load (prefixed with app name)
|
159
|
+
tool_manager: The tool manager to register tools with
|
160
|
+
"""
|
161
|
+
logger.info(f"Loading all tools: {tools}")
|
162
|
+
tools_by_app = {}
|
163
|
+
for tool_name in tools:
|
164
|
+
app_name, _ = _get_app_and_tool_name(tool_name)
|
165
|
+
tools_by_app.setdefault(app_name, []).append(tool_name)
|
166
|
+
|
167
|
+
for app_name, tool_names in tools_by_app.items():
|
168
|
+
self._load_tools(app_name, tool_names, tool_manager)
|
169
|
+
|
170
|
+
def _load_tools_from_tool_config(self, tool_config: ToolConfig, tool_manager: ToolManager) -> None:
|
171
|
+
"""Load tools from ToolConfig and register them as tools.
|
172
|
+
|
173
|
+
Args:
|
174
|
+
tool_config: The tool configuration containing app names and tools
|
175
|
+
tool_manager: The tool manager to register tools with
|
176
|
+
"""
|
177
|
+
for app_name, tool_data in tool_config.agentrServers.items():
|
178
|
+
self._load_tools(app_name, tool_data.tools, tool_manager)
|
179
|
+
|
180
|
+
async def call_tool(self, tool_name: str, tool_args: dict[str, Any]) -> dict[str, Any]:
|
181
|
+
"""Call a tool with the given name and arguments."""
|
182
|
+
return await self.tool_manager.call_tool(tool_name, tool_args)
|
@@ -0,0 +1,6 @@
|
|
1
|
+
from universal_mcp.agents.autoagent import AutoAgent
|
2
|
+
from universal_mcp.agents.base import BaseAgent
|
3
|
+
from universal_mcp.agents.react import ReactAgent
|
4
|
+
from universal_mcp.agents.simple import SimpleAgent
|
5
|
+
|
6
|
+
__all__ = ["BaseAgent", "ReactAgent", "SimpleAgent", "AutoAgent"]
|
@@ -12,13 +12,12 @@ from pydantic import BaseModel
|
|
12
12
|
from typing_extensions import TypedDict
|
13
13
|
|
14
14
|
from universal_mcp.agentr.registry import AgentrRegistry
|
15
|
+
from universal_mcp.agents.base import BaseAgent
|
16
|
+
from universal_mcp.agents.llm import load_chat_model
|
15
17
|
from universal_mcp.tools import ToolManager
|
16
18
|
from universal_mcp.tools.adapters import ToolFormat
|
17
19
|
from universal_mcp.tools.registry import ToolRegistry
|
18
20
|
|
19
|
-
from .base import BaseAgent
|
20
|
-
from .llm import get_llm
|
21
|
-
|
22
21
|
# Auto Agent
|
23
22
|
# Working
|
24
23
|
# 1. For every message, and given list of tools, figure out if external tools are needed
|
@@ -61,9 +60,9 @@ class AutoAgent(BaseAgent):
|
|
61
60
|
def __init__(self, name: str, instructions: str, model: str, app_registry: ToolRegistry):
|
62
61
|
super().__init__(name, instructions, model)
|
63
62
|
self.app_registry = app_registry
|
64
|
-
self.llm_tools =
|
65
|
-
self.llm_choice =
|
66
|
-
self.llm_quiet =
|
63
|
+
self.llm_tools = load_chat_model(model, tags=["tools"])
|
64
|
+
self.llm_choice = load_chat_model(model, tags=["choice"])
|
65
|
+
self.llm_quiet = load_chat_model(model, tags=["quiet"])
|
67
66
|
self.tool_manager = ToolManager()
|
68
67
|
|
69
68
|
self.task_analysis_prompt = """You are a task analysis expert. Given a task description and available apps, determine:
|
@@ -522,7 +521,7 @@ Be friendly and concise, but list each set of apps clearly. Do not return any ot
|
|
522
521
|
return result
|
523
522
|
|
524
523
|
# Get all available apps from platform manager
|
525
|
-
available_apps =
|
524
|
+
available_apps = self.app_registry.list_apps()
|
526
525
|
|
527
526
|
logger.info(f"Found {len(available_apps)} available apps")
|
528
527
|
|
@@ -563,10 +562,10 @@ if __name__ == "__main__":
|
|
563
562
|
|
564
563
|
# Create platform manager
|
565
564
|
app_registry = AgentrRegistry(api_key=agentr_api_key)
|
566
|
-
want_instructions = input("Do you want to add a system prompt/instructions? (Y/N)")
|
565
|
+
want_instructions = input("Do you want to add a system prompt/instructions? (Y/N): ")
|
567
566
|
instructions = "" if want_instructions.upper() == "N" else input("Enter your instructions/system prompt: ")
|
568
567
|
|
569
|
-
agent = AutoAgent("Auto Agent", instructions, "gpt-4.1", app_registry=app_registry)
|
568
|
+
agent = AutoAgent("Auto Agent", instructions, "azure/gpt-4.1", app_registry=app_registry)
|
570
569
|
|
571
570
|
print("AutoAgent created successfully!")
|
572
571
|
print(f"Agent name: {agent.name}")
|