universal-mcp 0.1.24rc26__tar.gz → 0.1.24rc28__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.24rc26 → universal_mcp-0.1.24rc28}/PKG-INFO +1 -1
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/pyproject.toml +1 -1
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/agentr/registry.py +0 -61
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/tools/adapters.py +23 -3
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/tools/manager.py +6 -16
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/tools/registry.py +57 -44
- universal_mcp-0.1.24rc28/src/universal_mcp/tools/utils.py +30 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/.gitignore +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/LICENSE +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/README.md +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/tests/__init__.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/tests/conftest.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/tests/test_api_generator.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/tests/test_apps.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/tests/test_local_registry.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/tests/test_localserver.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/tests/test_stores.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/tests/test_tool.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/tests/test_tool_manager.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/agentr/README.md +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/agentr/__init__.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/agentr/client.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/agentr/integration.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/agentr/server.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/applications/application.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/applications/sample/app.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/applications/utils.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/cli.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/client/oauth.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/client/token_store.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/client/transport.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/config.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/exceptions.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/integrations/__init__.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/integrations/integration.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/logger.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/py.typed +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/servers/__init__.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/servers/server.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/stores/__init__.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/stores/store.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/tools/__init__.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/tools/docstring_parser.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/tools/func_metadata.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/tools/local_registry.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/tools/tools.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/types.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/__init__.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/installation.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/__inti__.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/api_generator.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/api_splitter.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/cli.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/docgen.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/filters.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/openapi.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/postprocessor.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/preprocessor.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/readme.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/test_generator.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/prompts.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/singleton.py +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/templates/README.md.j2 +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/templates/api_client.py.j2 +0 -0
- {universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/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.24rc28
|
|
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
|
|
@@ -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-rc28"
|
|
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 = [
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import base64
|
|
2
|
-
import inspect
|
|
3
2
|
from typing import Any
|
|
4
3
|
|
|
5
4
|
from loguru import logger
|
|
@@ -8,9 +7,7 @@ from universal_mcp.agentr.client import AgentrClient
|
|
|
8
7
|
from universal_mcp.applications.application import BaseApplication
|
|
9
8
|
from universal_mcp.applications.utils import app_from_slug
|
|
10
9
|
from universal_mcp.exceptions import ToolError, ToolNotFoundError
|
|
11
|
-
from universal_mcp.tools.adapters import convert_tools
|
|
12
10
|
from universal_mcp.tools.registry import ToolRegistry
|
|
13
|
-
from universal_mcp.types import ToolConfig, ToolFormat
|
|
14
11
|
|
|
15
12
|
from .integration import AgentrIntegration
|
|
16
13
|
|
|
@@ -145,64 +142,6 @@ class AgentrRegistry(ToolRegistry):
|
|
|
145
142
|
logger.error(f"Error searching tools from AgentR: {e}")
|
|
146
143
|
return []
|
|
147
144
|
|
|
148
|
-
async def export_tools(
|
|
149
|
-
self,
|
|
150
|
-
tools: list[str] | ToolConfig,
|
|
151
|
-
format: ToolFormat,
|
|
152
|
-
) -> list[Any]:
|
|
153
|
-
"""Export given tools to required format.
|
|
154
|
-
|
|
155
|
-
Args:
|
|
156
|
-
tools: List of tool identifiers to export
|
|
157
|
-
format: The format to export tools to (native, mcp, langchain, openai)
|
|
158
|
-
|
|
159
|
-
Returns:
|
|
160
|
-
List of tools in the specified format
|
|
161
|
-
"""
|
|
162
|
-
from langchain_core.tools import StructuredTool
|
|
163
|
-
|
|
164
|
-
try:
|
|
165
|
-
logger.info(f"Exporting tools to {format.value} format")
|
|
166
|
-
if isinstance(tools, dict):
|
|
167
|
-
self._load_tools_from_tool_config(tools)
|
|
168
|
-
else:
|
|
169
|
-
self._load_tools_from_list(tools)
|
|
170
|
-
|
|
171
|
-
loaded_tools = self.tool_manager.get_tools()
|
|
172
|
-
|
|
173
|
-
if format != ToolFormat.LANGCHAIN:
|
|
174
|
-
return convert_tools(loaded_tools, format)
|
|
175
|
-
|
|
176
|
-
logger.info(f"Exporting {len(loaded_tools)} tools to LangChain format with special handling")
|
|
177
|
-
|
|
178
|
-
langchain_tools = []
|
|
179
|
-
for tool in loaded_tools:
|
|
180
|
-
full_docstring = inspect.getdoc(tool.fn)
|
|
181
|
-
|
|
182
|
-
def create_coroutine(t):
|
|
183
|
-
async def call_tool_wrapper(**arguments: dict[str, Any]):
|
|
184
|
-
logger.debug(
|
|
185
|
-
f"Executing registry-wrapped LangChain tool '{t.name}' with arguments: {arguments}"
|
|
186
|
-
)
|
|
187
|
-
return await self.call_tool(t.name, arguments)
|
|
188
|
-
|
|
189
|
-
return call_tool_wrapper
|
|
190
|
-
|
|
191
|
-
langchain_tool = StructuredTool(
|
|
192
|
-
name=tool.name,
|
|
193
|
-
description=full_docstring or tool.description or "",
|
|
194
|
-
coroutine=create_coroutine(tool),
|
|
195
|
-
response_format="content",
|
|
196
|
-
args_schema=tool.parameters,
|
|
197
|
-
)
|
|
198
|
-
langchain_tools.append(langchain_tool)
|
|
199
|
-
|
|
200
|
-
return langchain_tools
|
|
201
|
-
|
|
202
|
-
except Exception as e:
|
|
203
|
-
logger.error(f"Error exporting tools: {e}")
|
|
204
|
-
return []
|
|
205
|
-
|
|
206
145
|
def _handle_special_output(self, data: Any) -> Any:
|
|
207
146
|
if isinstance(data, dict):
|
|
208
147
|
type_ = data.get("type")
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import inspect
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from functools import wraps
|
|
2
4
|
from typing import Any
|
|
3
5
|
|
|
4
6
|
from loguru import logger
|
|
@@ -12,7 +14,7 @@ def convert_tools(tools: list[Tool], format: ToolFormat) -> list[Any]:
|
|
|
12
14
|
"""Convert a list of Tool objects to a specified format."""
|
|
13
15
|
logger.debug(f"Converting {len(tools)} tools to {format.value} format.")
|
|
14
16
|
if format == ToolFormat.NATIVE:
|
|
15
|
-
return [tool
|
|
17
|
+
return [convert_to_native_tool(tool) for tool in tools]
|
|
16
18
|
if format == ToolFormat.MCP:
|
|
17
19
|
return [convert_tool_to_mcp_tool(tool) for tool in tools]
|
|
18
20
|
if format == ToolFormat.LANGCHAIN:
|
|
@@ -22,6 +24,26 @@ def convert_tools(tools: list[Tool], format: ToolFormat) -> list[Any]:
|
|
|
22
24
|
raise ValueError(f"Invalid format: {format}")
|
|
23
25
|
|
|
24
26
|
|
|
27
|
+
def convert_to_native_tool(tool: Tool) -> Callable[..., Any]:
|
|
28
|
+
"""Decorator to convert a Tool object to a native tool."""
|
|
29
|
+
|
|
30
|
+
def decorator(func):
|
|
31
|
+
if inspect.iscoroutinefunction(func):
|
|
32
|
+
@wraps(func)
|
|
33
|
+
async def wrapper(*args, **kwargs):
|
|
34
|
+
return await func(*args, **kwargs)
|
|
35
|
+
else:
|
|
36
|
+
@wraps(func)
|
|
37
|
+
def wrapper(*args, **kwargs):
|
|
38
|
+
return func(*args, **kwargs)
|
|
39
|
+
|
|
40
|
+
wrapper.__name__ = tool.name
|
|
41
|
+
wrapper.__doc__ = tool.fn.__doc__
|
|
42
|
+
return wrapper
|
|
43
|
+
|
|
44
|
+
return decorator(tool.fn)
|
|
45
|
+
|
|
46
|
+
|
|
25
47
|
def convert_tool_to_mcp_tool(
|
|
26
48
|
tool: Tool,
|
|
27
49
|
):
|
|
@@ -81,8 +103,6 @@ def convert_tool_to_langchain_tool(
|
|
|
81
103
|
|
|
82
104
|
"""Convert an tool to a LangChain tool.
|
|
83
105
|
|
|
84
|
-
NOTE: this tool can be executed only in a context of an active MCP client session.
|
|
85
|
-
|
|
86
106
|
Args:
|
|
87
107
|
tool: Tool to convert
|
|
88
108
|
|
|
@@ -5,23 +5,13 @@ from loguru import logger
|
|
|
5
5
|
|
|
6
6
|
from universal_mcp.applications.application import BaseApplication
|
|
7
7
|
from universal_mcp.tools.tools import Tool
|
|
8
|
-
from universal_mcp.
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
def _get_app_and_tool_name(tool_name: str) -> tuple[str, str]:
|
|
12
|
-
"""Get the app name from a tool name."""
|
|
13
|
-
if TOOL_NAME_SEPARATOR in tool_name:
|
|
14
|
-
app_name = tool_name.split(TOOL_NAME_SEPARATOR, 1)[0]
|
|
15
|
-
tool_name_without_app_name = tool_name.split(TOOL_NAME_SEPARATOR, 1)[1]
|
|
16
|
-
else:
|
|
17
|
-
app_name = DEFAULT_APP_NAME
|
|
18
|
-
tool_name_without_app_name = tool_name
|
|
19
|
-
return app_name, tool_name_without_app_name
|
|
8
|
+
from universal_mcp.tools.utils import get_app_and_tool_name
|
|
9
|
+
from universal_mcp.types import DEFAULT_IMPORTANT_TAG, ToolFormat
|
|
20
10
|
|
|
21
11
|
|
|
22
12
|
def _sanitize_tool_names(tool_names: list[str]) -> list[str]:
|
|
23
13
|
"""Sanitize tool names by removing empty strings and converting to lowercase."""
|
|
24
|
-
return [
|
|
14
|
+
return [get_app_and_tool_name(name)[1].lower() for name in tool_names if name]
|
|
25
15
|
|
|
26
16
|
|
|
27
17
|
def _filter_by_name(tools: list[Tool], tool_names: list[str] | None) -> list[Tool]:
|
|
@@ -114,8 +104,8 @@ class ToolManager:
|
|
|
114
104
|
|
|
115
105
|
def get_tools(
|
|
116
106
|
self,
|
|
117
|
-
tags: list[str] | None = None,
|
|
118
107
|
tool_names: list[str] | None = None,
|
|
108
|
+
tags: list[str] | None = None,
|
|
119
109
|
) -> list[Tool]:
|
|
120
110
|
"""Get a filtered list of registered tools.
|
|
121
111
|
|
|
@@ -151,10 +141,10 @@ class ToolManager:
|
|
|
151
141
|
if self.warn_on_duplicate_tools:
|
|
152
142
|
if existing.fn is not tool.fn:
|
|
153
143
|
logger.warning(
|
|
154
|
-
f"Tool
|
|
144
|
+
f"Tool '{tool.name}' already exists with a different function. Skipping addition of new function."
|
|
155
145
|
)
|
|
156
146
|
else:
|
|
157
|
-
logger.debug(f"Tool '{tool.name}' with the same function
|
|
147
|
+
logger.debug(f"Tool '{tool.name}' already exists with the same function.")
|
|
158
148
|
return existing
|
|
159
149
|
|
|
160
150
|
logger.debug(f"Adding tool: {tool.name}")
|
|
@@ -4,7 +4,9 @@ from typing import Any
|
|
|
4
4
|
from loguru import logger
|
|
5
5
|
|
|
6
6
|
from universal_mcp.applications.application import BaseApplication
|
|
7
|
-
from universal_mcp.tools.
|
|
7
|
+
from universal_mcp.tools.adapters import convert_tools
|
|
8
|
+
from universal_mcp.tools.manager import ToolManager
|
|
9
|
+
from universal_mcp.tools.utils import list_to_tool_config, tool_config_to_list
|
|
8
10
|
from universal_mcp.types import ToolConfig, ToolFormat
|
|
9
11
|
|
|
10
12
|
|
|
@@ -20,6 +22,36 @@ class ToolRegistry(ABC):
|
|
|
20
22
|
self.tool_manager = ToolManager()
|
|
21
23
|
logger.debug(f"{self.__class__.__name__} initialized.")
|
|
22
24
|
|
|
25
|
+
def _load_tools_from_app(self, app_name: str, tool_names: list[str] | None) -> None:
|
|
26
|
+
"""Helper method to load and register tools for an app."""
|
|
27
|
+
logger.info(f"Loading tools for app '{app_name}' (tools: {tool_names or 'default'})")
|
|
28
|
+
try:
|
|
29
|
+
if app_name not in self._app_instances:
|
|
30
|
+
self._app_instances[app_name] = self._create_app_instance(app_name)
|
|
31
|
+
app_instance = self._app_instances[app_name]
|
|
32
|
+
self.tool_manager.register_tools_from_app(app_instance, tool_names=tool_names)
|
|
33
|
+
logger.info(f"Successfully registered tools for app: {app_name}")
|
|
34
|
+
except Exception as e:
|
|
35
|
+
logger.error(f"Failed to load tools for app {app_name}: {e}", exc_info=True)
|
|
36
|
+
|
|
37
|
+
def _load_tools_from_list(self, tools: list[str]) -> None:
|
|
38
|
+
"""Load tools from a list of full tool names (e.g., 'app__tool')."""
|
|
39
|
+
tool_config = list_to_tool_config(tools)
|
|
40
|
+
for app_name, tool_names in tool_config.items():
|
|
41
|
+
self._load_tools_from_app(app_name, tool_names or None)
|
|
42
|
+
|
|
43
|
+
def _load_tools_from_tool_config(self, tool_config: ToolConfig) -> None:
|
|
44
|
+
"""Load tools from a ToolConfig dictionary."""
|
|
45
|
+
logger.debug(f"Loading tools from tool_config: {tool_config}")
|
|
46
|
+
for app_name, tool_names in tool_config.items():
|
|
47
|
+
self._load_tools_from_app(app_name, tool_names or None)
|
|
48
|
+
|
|
49
|
+
# --- Abstract method for subclass implementation ---
|
|
50
|
+
|
|
51
|
+
def _create_app_instance(self, app_name: str) -> BaseApplication:
|
|
52
|
+
"""Create an application instance for a given app name."""
|
|
53
|
+
raise NotImplementedError("Subclasses must implement this method")
|
|
54
|
+
|
|
23
55
|
# --- Abstract methods for the public interface ---
|
|
24
56
|
|
|
25
57
|
@abstractmethod
|
|
@@ -49,54 +81,35 @@ class ToolRegistry(ABC):
|
|
|
49
81
|
"""Search for tools by a query, optionally filtered by an app."""
|
|
50
82
|
pass
|
|
51
83
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
84
|
+
async def load_tools(self, tools: list[str] | ToolConfig | None = None):
|
|
85
|
+
"""Load the tools to be used"""
|
|
86
|
+
if isinstance(tools, list):
|
|
87
|
+
self._load_tools_from_list(tools)
|
|
88
|
+
elif isinstance(tools, dict):
|
|
89
|
+
self._load_tools_from_tool_config(tools)
|
|
90
|
+
else:
|
|
91
|
+
raise ValueError(f"Invalid tools type: {type(tools)}. Expected list or ToolConfig.")
|
|
92
|
+
return self.tool_manager.get_tools()
|
|
93
|
+
|
|
94
|
+
async def export_tools(
|
|
95
|
+
self, tools: list[str] | ToolConfig | None = None, format: ToolFormat = ToolFormat.NATIVE
|
|
96
|
+
) -> list[Any]:
|
|
97
|
+
"""Export the loaded tools as in required format"""
|
|
98
|
+
if tools is not None:
|
|
99
|
+
# Load the tools if they are not already loaded
|
|
100
|
+
await self.load_tools(tools)
|
|
101
|
+
tools_list = tool_config_to_list(tools) if isinstance(tools, dict) else tools
|
|
102
|
+
loaded_tools = self.tool_manager.get_tools(tool_names=tools_list)
|
|
103
|
+
exported_tools = convert_tools(loaded_tools, format)
|
|
104
|
+
logger.info(f"Exported {len(exported_tools)} tools to {format.value} format")
|
|
105
|
+
return exported_tools if isinstance(exported_tools, list) else [exported_tools]
|
|
56
106
|
|
|
57
|
-
@abstractmethod
|
|
58
107
|
async def call_tool(self, tool_name: str, tool_args: dict[str, Any]) -> Any:
|
|
59
108
|
"""Call a tool with the given name and arguments."""
|
|
60
|
-
|
|
109
|
+
result = await self.tool_manager.get_tool(tool_name)
|
|
110
|
+
return await result.run(tool_args)
|
|
61
111
|
|
|
62
112
|
@abstractmethod
|
|
63
113
|
async def list_connected_apps(self) -> list[dict[str, Any]]:
|
|
64
114
|
"""List all apps that the user has connected."""
|
|
65
115
|
pass
|
|
66
|
-
|
|
67
|
-
# --- Abstract method for subclass implementation ---
|
|
68
|
-
|
|
69
|
-
def _create_app_instance(self, app_name: str) -> BaseApplication:
|
|
70
|
-
"""Create an application instance for a given app name."""
|
|
71
|
-
raise NotImplementedError("Subclasses must implement this method")
|
|
72
|
-
|
|
73
|
-
# --- Concrete methods for shared tool loading ---
|
|
74
|
-
|
|
75
|
-
def _load_tools(self, app_name: str, tool_names: list[str] | None) -> None:
|
|
76
|
-
"""Helper method to load and register tools for an app."""
|
|
77
|
-
logger.info(f"Loading tools for app '{app_name}' (tools: {tool_names or 'default'})")
|
|
78
|
-
try:
|
|
79
|
-
if app_name not in self._app_instances:
|
|
80
|
-
self._app_instances[app_name] = self._create_app_instance(app_name)
|
|
81
|
-
app_instance = self._app_instances[app_name]
|
|
82
|
-
self.tool_manager.register_tools_from_app(app_instance, tool_names=tool_names)
|
|
83
|
-
logger.info(f"Successfully registered tools for app: {app_name}")
|
|
84
|
-
except Exception as e:
|
|
85
|
-
logger.error(f"Failed to load tools for app {app_name}: {e}", exc_info=True)
|
|
86
|
-
|
|
87
|
-
def _load_tools_from_list(self, tools: list[str]) -> None:
|
|
88
|
-
"""Load tools from a list of full tool names (e.g., 'app__tool')."""
|
|
89
|
-
logger.debug(f"Loading tools from list: {tools}")
|
|
90
|
-
tools_by_app: dict[str, list[str]] = {}
|
|
91
|
-
for tool_name in tools:
|
|
92
|
-
app_name, _ = _get_app_and_tool_name(tool_name)
|
|
93
|
-
tools_by_app.setdefault(app_name, []).append(tool_name)
|
|
94
|
-
|
|
95
|
-
for app_name, tool_names in tools_by_app.items():
|
|
96
|
-
self._load_tools(app_name, tool_names)
|
|
97
|
-
|
|
98
|
-
def _load_tools_from_tool_config(self, tool_config: ToolConfig) -> None:
|
|
99
|
-
"""Load tools from a ToolConfig dictionary."""
|
|
100
|
-
logger.debug(f"Loading tools from tool_config: {tool_config}")
|
|
101
|
-
for app_name, tool_names in tool_config.items():
|
|
102
|
-
self._load_tools(app_name, tool_names or None)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
from universal_mcp.types import DEFAULT_APP_NAME, TOOL_NAME_SEPARATOR, ToolConfig
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def get_app_and_tool_name(tool_name: str) -> tuple[str, str]:
|
|
5
|
+
"""Get the app and tool name from a tool name."""
|
|
6
|
+
if TOOL_NAME_SEPARATOR in tool_name:
|
|
7
|
+
app_name = tool_name.split(TOOL_NAME_SEPARATOR, 1)[0]
|
|
8
|
+
tool_name_without_app_name = tool_name.split(TOOL_NAME_SEPARATOR, 1)[1]
|
|
9
|
+
else:
|
|
10
|
+
app_name = DEFAULT_APP_NAME
|
|
11
|
+
tool_name_without_app_name = tool_name
|
|
12
|
+
return app_name, tool_name_without_app_name
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def tool_config_to_list(tools: ToolConfig) -> list[str]:
|
|
16
|
+
"""Convert a ToolConfig dictionary to a list of tool names."""
|
|
17
|
+
return [
|
|
18
|
+
f"{app_name}{TOOL_NAME_SEPARATOR}{tool_name}"
|
|
19
|
+
for app_name, tool_names in tools.items()
|
|
20
|
+
for tool_name in tool_names
|
|
21
|
+
]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def list_to_tool_config(tools: list[str]) -> ToolConfig:
|
|
25
|
+
"""Convert a list of tool names to a ToolConfig dictionary."""
|
|
26
|
+
tool_config = {}
|
|
27
|
+
for tool_name in tools:
|
|
28
|
+
app_name, tool_name_without_app_name = get_app_and_tool_name(tool_name)
|
|
29
|
+
tool_config.setdefault(app_name, []).append(tool_name_without_app_name)
|
|
30
|
+
return tool_config
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/agentr/integration.py
RENAMED
|
File without changes
|
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/applications/application.py
RENAMED
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/applications/sample/app.py
RENAMED
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/applications/utils.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/client/token_store.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/integrations/__init__.py
RENAMED
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/integrations/integration.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/tools/docstring_parser.py
RENAMED
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/tools/func_metadata.py
RENAMED
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/tools/local_registry.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/installation.py
RENAMED
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/__inti__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/cli.py
RENAMED
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/docgen.py
RENAMED
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/filters.py
RENAMED
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/openapi.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/openapi/readme.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{universal_mcp-0.1.24rc26 → universal_mcp-0.1.24rc28}/src/universal_mcp/utils/templates/README.md.j2
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|