universal-mcp 0.1.24rc2__py3-none-any.whl → 0.1.24rc3__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/__init__.py +6 -0
- universal_mcp/agentr/agentr.py +30 -0
- universal_mcp/{utils/agentr.py → agentr/client.py} +19 -3
- universal_mcp/agentr/integration.py +104 -0
- universal_mcp/agentr/registry.py +91 -0
- universal_mcp/agentr/server.py +51 -0
- universal_mcp/agents/__init__.py +6 -0
- universal_mcp/agents/auto.py +576 -0
- universal_mcp/agents/base.py +88 -0
- universal_mcp/agents/cli.py +27 -0
- universal_mcp/agents/codeact/__init__.py +243 -0
- universal_mcp/agents/codeact/sandbox.py +27 -0
- universal_mcp/agents/codeact/test.py +15 -0
- universal_mcp/agents/codeact/utils.py +61 -0
- universal_mcp/agents/hil.py +104 -0
- universal_mcp/agents/llm.py +10 -0
- universal_mcp/agents/react.py +58 -0
- universal_mcp/agents/simple.py +40 -0
- universal_mcp/agents/utils.py +111 -0
- universal_mcp/analytics.py +5 -7
- universal_mcp/applications/__init__.py +42 -75
- universal_mcp/applications/application.py +1 -1
- universal_mcp/applications/sample/app.py +245 -0
- universal_mcp/cli.py +10 -3
- universal_mcp/config.py +33 -7
- universal_mcp/exceptions.py +4 -0
- universal_mcp/integrations/__init__.py +0 -15
- universal_mcp/integrations/integration.py +9 -91
- universal_mcp/servers/__init__.py +2 -14
- universal_mcp/servers/server.py +10 -51
- universal_mcp/tools/__init__.py +3 -0
- universal_mcp/tools/adapters.py +20 -11
- universal_mcp/tools/manager.py +29 -56
- universal_mcp/tools/registry.py +41 -0
- universal_mcp/tools/tools.py +22 -1
- universal_mcp/types.py +10 -0
- universal_mcp/utils/common.py +245 -0
- universal_mcp/utils/openapi/api_generator.py +46 -18
- universal_mcp/utils/openapi/cli.py +445 -19
- universal_mcp/utils/openapi/openapi.py +284 -21
- universal_mcp/utils/openapi/postprocessor.py +275 -0
- universal_mcp/utils/openapi/preprocessor.py +1 -1
- universal_mcp/utils/openapi/test_generator.py +287 -0
- universal_mcp/utils/prompts.py +188 -341
- universal_mcp/utils/testing.py +190 -2
- {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/METADATA +16 -2
- universal_mcp-0.1.24rc3.dist-info/RECORD +70 -0
- universal_mcp/applications/sample_tool_app.py +0 -80
- universal_mcp/client/agents/__init__.py +0 -4
- universal_mcp/client/agents/base.py +0 -38
- universal_mcp/client/agents/llm.py +0 -115
- universal_mcp/client/agents/react.py +0 -67
- universal_mcp/client/cli.py +0 -181
- universal_mcp-0.1.24rc2.dist-info/RECORD +0 -53
- {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/entry_points.txt +0 -0
- {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,30 @@
|
|
1
|
+
import os
|
2
|
+
|
3
|
+
from universal_mcp.tools import Tool, ToolFormat, ToolManager
|
4
|
+
|
5
|
+
from .client import AgentrClient
|
6
|
+
from .registry import AgentrRegistry
|
7
|
+
|
8
|
+
|
9
|
+
class Agentr:
|
10
|
+
def __init__(
|
11
|
+
self,
|
12
|
+
api_key: str | None = None,
|
13
|
+
base_url: str | None = None,
|
14
|
+
registry: AgentrRegistry | None = None,
|
15
|
+
format: ToolFormat | None = None,
|
16
|
+
manager: ToolManager | None = None,
|
17
|
+
):
|
18
|
+
self.api_key = api_key or os.getenv("AGENTR_API_KEY")
|
19
|
+
self.base_url = base_url or os.getenv("AGENTR_BASE_URL")
|
20
|
+
self.client = AgentrClient(api_key=self.api_key, base_url=self.base_url)
|
21
|
+
self.registry = registry or AgentrRegistry(client=self.client)
|
22
|
+
self.format = format or ToolFormat.NATIVE
|
23
|
+
self.manager = manager or ToolManager()
|
24
|
+
|
25
|
+
def load_tools(self, tool_names: list[str]) -> None:
|
26
|
+
self.registry.load_tools(tool_names, self.manager)
|
27
|
+
return
|
28
|
+
|
29
|
+
def list_tools(self, format: ToolFormat | None = None) -> list[Tool]:
|
30
|
+
return self.manager.list_tools(format=format or self.format)
|
@@ -82,6 +82,22 @@ class AgentrClient:
|
|
82
82
|
data = response.json()
|
83
83
|
return [AppConfig.model_validate(app) for app in data]
|
84
84
|
|
85
|
+
def fetch_app(self, app_id: str) -> dict:
|
86
|
+
"""Fetch a specific app from AgentR API.
|
87
|
+
|
88
|
+
Args:
|
89
|
+
app_id (str): ID of the app to fetch
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
dict: App configuration data
|
93
|
+
|
94
|
+
Raises:
|
95
|
+
httpx.HTTPError: If API request fails
|
96
|
+
"""
|
97
|
+
response = self.client.get(f"/apps/{app_id}/")
|
98
|
+
response.raise_for_status()
|
99
|
+
return response.json()
|
100
|
+
|
85
101
|
def list_all_apps(self) -> list:
|
86
102
|
"""List all apps from AgentR API.
|
87
103
|
|
@@ -92,16 +108,16 @@ class AgentrClient:
|
|
92
108
|
response.raise_for_status()
|
93
109
|
return response.json()
|
94
110
|
|
95
|
-
def list_actions(self,
|
111
|
+
def list_actions(self, app_id: str):
|
96
112
|
"""List actions for an app.
|
97
113
|
|
98
114
|
Args:
|
99
|
-
|
115
|
+
app_id (str): ID of the app to list actions for
|
100
116
|
|
101
117
|
Returns:
|
102
118
|
List of action configurations
|
103
119
|
"""
|
104
120
|
|
105
|
-
response = self.client.get(f"/apps/{
|
121
|
+
response = self.client.get(f"/apps/{app_id}/actions/")
|
106
122
|
response.raise_for_status()
|
107
123
|
return response.json()
|
@@ -0,0 +1,104 @@
|
|
1
|
+
from universal_mcp.integrations.integration import Integration
|
2
|
+
|
3
|
+
from .client import AgentrClient
|
4
|
+
|
5
|
+
|
6
|
+
class AgentrIntegration(Integration):
|
7
|
+
"""Manages authentication and authorization via the AgentR platform.
|
8
|
+
|
9
|
+
This integration uses an `AgentrClient` to interact with the AgentR API
|
10
|
+
for operations like retrieving authorization URLs and fetching stored
|
11
|
+
credentials. It simplifies integration with services supported by AgentR.
|
12
|
+
|
13
|
+
Attributes:
|
14
|
+
name (str): Name of the integration (e.g., "github", "google").
|
15
|
+
store (BaseStore): Store, typically not used directly by this class
|
16
|
+
as AgentR manages the primary credential storage.
|
17
|
+
client (AgentrClient): Client for communicating with the AgentR API.
|
18
|
+
_credentials (dict | None): Cached credentials.
|
19
|
+
"""
|
20
|
+
|
21
|
+
def __init__(
|
22
|
+
self,
|
23
|
+
name: str,
|
24
|
+
client: AgentrClient | None = None,
|
25
|
+
api_key: str | None = None,
|
26
|
+
base_url: str | None = None,
|
27
|
+
**kwargs,
|
28
|
+
):
|
29
|
+
"""Initializes the AgentRIntegration.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
name (str): The name of the service integration as configured on
|
33
|
+
the AgentR platform (e.g., "github").
|
34
|
+
client (AgentrClient | None, optional): The AgentR client. If not provided,
|
35
|
+
a new `AgentrClient` will be created.
|
36
|
+
api_key (str | None, optional): API key for AgentR. If not provided,
|
37
|
+
will be loaded from environment variables.
|
38
|
+
base_url (str | None, optional): Base URL for AgentR API. If not provided,
|
39
|
+
will be loaded from environment variables.
|
40
|
+
**kwargs: Additional arguments passed to the parent `Integration`.
|
41
|
+
"""
|
42
|
+
super().__init__(name, **kwargs)
|
43
|
+
self.type = "agentr"
|
44
|
+
self.client = client or AgentrClient(api_key=api_key, base_url=base_url)
|
45
|
+
self._credentials = None
|
46
|
+
|
47
|
+
def set_credentials(self, credentials: dict[str, str] | None = None) -> str:
|
48
|
+
"""Not used for direct credential setting; initiates authorization instead.
|
49
|
+
|
50
|
+
For AgentR integrations, credentials are set via the AgentR platform's
|
51
|
+
OAuth flow. This method effectively redirects to the `authorize` flow.
|
52
|
+
|
53
|
+
Args:
|
54
|
+
credentials (dict | None, optional): Not used by this implementation.
|
55
|
+
|
56
|
+
Returns:
|
57
|
+
str: The authorization URL or message from the `authorize()` method.
|
58
|
+
"""
|
59
|
+
raise NotImplementedError("AgentR integrations do not support direct credential setting")
|
60
|
+
|
61
|
+
@property
|
62
|
+
def credentials(self):
|
63
|
+
"""Retrieves credentials from the AgentR API, with caching.
|
64
|
+
|
65
|
+
If credentials are not cached locally (in `_credentials`), this property
|
66
|
+
fetches them from the AgentR platform using `self.client.get_credentials`.
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
dict: The credentials dictionary obtained from AgentR.
|
70
|
+
|
71
|
+
Raises:
|
72
|
+
NotAuthorizedError: If credentials are not found (e.g., 404 from AgentR).
|
73
|
+
httpx.HTTPStatusError: For other API errors from AgentR.
|
74
|
+
"""
|
75
|
+
if self._credentials is not None:
|
76
|
+
return self._credentials
|
77
|
+
self._credentials = self.client.get_credentials(self.name)
|
78
|
+
return self._credentials
|
79
|
+
|
80
|
+
def get_credentials(self):
|
81
|
+
"""Retrieves credentials from the AgentR API. Alias for `credentials` property.
|
82
|
+
|
83
|
+
Returns:
|
84
|
+
dict: The credentials dictionary obtained from AgentR.
|
85
|
+
|
86
|
+
Raises:
|
87
|
+
NotAuthorizedError: If credentials are not found.
|
88
|
+
httpx.HTTPStatusError: For other API errors.
|
89
|
+
"""
|
90
|
+
return self.credentials
|
91
|
+
|
92
|
+
def authorize(self) -> str:
|
93
|
+
"""Retrieves the authorization URL from the AgentR platform.
|
94
|
+
|
95
|
+
This URL should be presented to the user to initiate the OAuth flow
|
96
|
+
managed by AgentR for the service associated with `self.name`.
|
97
|
+
|
98
|
+
Returns:
|
99
|
+
str: The authorization URL.
|
100
|
+
|
101
|
+
Raises:
|
102
|
+
httpx.HTTPStatusError: If the API request to AgentR fails.
|
103
|
+
"""
|
104
|
+
return self.client.get_authorization_url(self.name)
|
@@ -0,0 +1,91 @@
|
|
1
|
+
from loguru import logger
|
2
|
+
|
3
|
+
from universal_mcp.agentr.client import AgentrClient
|
4
|
+
from universal_mcp.applications import app_from_slug
|
5
|
+
from universal_mcp.tools.manager import ToolManager, _get_app_and_tool_name
|
6
|
+
from universal_mcp.tools.registry import ToolRegistry
|
7
|
+
|
8
|
+
from .integration import AgentrIntegration
|
9
|
+
|
10
|
+
|
11
|
+
class AgentrRegistry(ToolRegistry):
|
12
|
+
"""Platform manager implementation for AgentR platform."""
|
13
|
+
|
14
|
+
def __init__(self, client: AgentrClient | None = None, **kwargs):
|
15
|
+
"""Initialize the AgentR platform manager."""
|
16
|
+
|
17
|
+
self.client = client or AgentrClient(**kwargs)
|
18
|
+
logger.debug("AgentrRegistry initialized successfully")
|
19
|
+
|
20
|
+
async def list_apps(self) -> list[dict[str, str]]:
|
21
|
+
"""Get list of available apps from AgentR.
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
List of app dictionaries with id, name, description, and available fields
|
25
|
+
"""
|
26
|
+
try:
|
27
|
+
all_apps = await self.client.list_all_apps()
|
28
|
+
available_apps = [
|
29
|
+
{"id": app["id"], "name": app["name"], "description": app.get("description", "")}
|
30
|
+
for app in all_apps
|
31
|
+
if app.get("available", False)
|
32
|
+
]
|
33
|
+
logger.info(f"Found {len(available_apps)} available apps from AgentR")
|
34
|
+
return available_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, str]:
|
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 = await self.client.fetch_app(app_id)
|
50
|
+
return {
|
51
|
+
"id": app_info.get("id"),
|
52
|
+
"name": app_info.get("name"),
|
53
|
+
"description": app_info.get("description"),
|
54
|
+
"category": app_info.get("category"),
|
55
|
+
"available": app_info.get("available", True),
|
56
|
+
}
|
57
|
+
except Exception as e:
|
58
|
+
logger.error(f"Error getting details for app {app_id}: {e}")
|
59
|
+
return {
|
60
|
+
"id": app_id,
|
61
|
+
"name": app_id,
|
62
|
+
"description": "Error loading details",
|
63
|
+
"category": "Unknown",
|
64
|
+
"available": True,
|
65
|
+
}
|
66
|
+
|
67
|
+
def load_tools(self, tools: list[str] | None, tool_manager: ToolManager) -> None:
|
68
|
+
"""Load tools from AgentR and register them as tools.
|
69
|
+
|
70
|
+
Args:
|
71
|
+
tools: The list of tools to load ( prefixed with app name )
|
72
|
+
tool_manager: The tool manager to register tools with
|
73
|
+
"""
|
74
|
+
if not tools:
|
75
|
+
return
|
76
|
+
logger.info(f"Loading all actions for app: {tools}")
|
77
|
+
# Group all tools by app_name, tools
|
78
|
+
tools_by_app = {}
|
79
|
+
for tool_name in tools:
|
80
|
+
app_name, _ = _get_app_and_tool_name(tool_name)
|
81
|
+
if app_name not in tools_by_app:
|
82
|
+
tools_by_app[app_name] = []
|
83
|
+
tools_by_app[app_name].append(tool_name)
|
84
|
+
|
85
|
+
for app_name, tool_names in tools_by_app.items():
|
86
|
+
app = app_from_slug(app_name)
|
87
|
+
integration = AgentrIntegration(name=app_name)
|
88
|
+
# TODO: Import with name param, some apps are written incorrectly and hence passing name fails
|
89
|
+
app_instance = app(integration=integration)
|
90
|
+
tool_manager.register_tools_from_app(app_instance, tool_names=tool_names)
|
91
|
+
return
|
@@ -0,0 +1,51 @@
|
|
1
|
+
from loguru import logger
|
2
|
+
|
3
|
+
from universal_mcp.applications import app_from_config
|
4
|
+
from universal_mcp.config import AppConfig, ServerConfig
|
5
|
+
from universal_mcp.servers.server import BaseServer
|
6
|
+
from universal_mcp.tools import ToolManager
|
7
|
+
|
8
|
+
from .client import AgentrClient
|
9
|
+
from .integration import AgentrIntegration
|
10
|
+
|
11
|
+
|
12
|
+
def load_from_agentr_server(client: AgentrClient, tool_manager: ToolManager) -> None:
|
13
|
+
"""Load apps from AgentR server and register their tools."""
|
14
|
+
try:
|
15
|
+
apps = client.fetch_apps()
|
16
|
+
for app in apps:
|
17
|
+
try:
|
18
|
+
app_config = AppConfig.model_validate(app)
|
19
|
+
integration = (
|
20
|
+
AgentrIntegration(name=app_config.integration.name, client=client) # type: ignore
|
21
|
+
if app_config.integration
|
22
|
+
else None
|
23
|
+
)
|
24
|
+
app_instance = app_from_config(app_config)(integration=integration)
|
25
|
+
tool_manager.register_tools_from_app(app_instance, app_config.actions)
|
26
|
+
logger.info(f"Loaded app from AgentR: {app_config.name}")
|
27
|
+
except Exception as e:
|
28
|
+
logger.error(f"Failed to load app from AgentR: {e}", exc_info=True)
|
29
|
+
except Exception as e:
|
30
|
+
logger.error(f"Failed to fetch apps from AgentR: {e}", exc_info=True)
|
31
|
+
raise
|
32
|
+
|
33
|
+
|
34
|
+
class AgentrServer(BaseServer):
|
35
|
+
"""Server that loads apps from AgentR server."""
|
36
|
+
|
37
|
+
def __init__(self, config: ServerConfig, **kwargs):
|
38
|
+
super().__init__(config, **kwargs)
|
39
|
+
self._tools_loaded = False
|
40
|
+
self.api_key = config.api_key.get_secret_value() if config.api_key else None
|
41
|
+
self.base_url = config.base_url
|
42
|
+
self.client = AgentrClient(api_key=self.api_key, base_url=self.base_url)
|
43
|
+
|
44
|
+
@property
|
45
|
+
def tool_manager(self) -> ToolManager:
|
46
|
+
if self._tool_manager is None:
|
47
|
+
self._tool_manager = ToolManager(warn_on_duplicate_tools=True)
|
48
|
+
if not self._tools_loaded:
|
49
|
+
load_from_agentr_server(self.client, self._tool_manager)
|
50
|
+
self._tools_loaded = True
|
51
|
+
return self._tool_manager
|