universal-mcp 0.1.24rc22__tar.gz → 0.1.24rc24__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.
Files changed (64) hide show
  1. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/PKG-INFO +1 -1
  2. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/pyproject.toml +1 -1
  3. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/agentr/README.md +30 -34
  4. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/agentr/client.py +6 -4
  5. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/agentr/registry.py +18 -4
  6. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/applications/application.py +4 -1
  7. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/tools/adapters.py +3 -1
  8. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/tools/registry.py +8 -3
  9. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/.gitignore +0 -0
  10. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/LICENSE +0 -0
  11. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/README.md +0 -0
  12. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/tests/__init__.py +0 -0
  13. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/tests/conftest.py +0 -0
  14. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/tests/test_api_generator.py +0 -0
  15. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/tests/test_apps.py +0 -0
  16. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/tests/test_local_registry.py +0 -0
  17. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/tests/test_localserver.py +0 -0
  18. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/tests/test_stores.py +0 -0
  19. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/tests/test_tool.py +0 -0
  20. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/tests/test_tool_manager.py +0 -0
  21. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/agentr/__init__.py +0 -0
  22. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/agentr/integration.py +0 -0
  23. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/agentr/server.py +0 -0
  24. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/applications/sample/app.py +0 -0
  25. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/applications/utils.py +0 -0
  26. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/cli.py +1 -1
  27. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/client/oauth.py +0 -0
  28. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/client/token_store.py +0 -0
  29. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/client/transport.py +0 -0
  30. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/config.py +0 -0
  31. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/exceptions.py +0 -0
  32. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/integrations/__init__.py +0 -0
  33. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/integrations/integration.py +0 -0
  34. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/logger.py +0 -0
  35. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/py.typed +0 -0
  36. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/servers/__init__.py +0 -0
  37. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/servers/server.py +0 -0
  38. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/stores/__init__.py +0 -0
  39. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/stores/store.py +0 -0
  40. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/tools/__init__.py +0 -0
  41. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/tools/docstring_parser.py +0 -0
  42. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/tools/func_metadata.py +0 -0
  43. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/tools/local_registry.py +0 -0
  44. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/tools/manager.py +0 -0
  45. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/tools/tools.py +0 -0
  46. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/types.py +0 -0
  47. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/__init__.py +0 -0
  48. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/installation.py +0 -0
  49. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/openapi/__inti__.py +0 -0
  50. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/openapi/api_generator.py +0 -0
  51. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/openapi/api_splitter.py +0 -0
  52. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/openapi/cli.py +0 -0
  53. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/openapi/docgen.py +0 -0
  54. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/openapi/filters.py +0 -0
  55. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/openapi/openapi.py +0 -0
  56. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/openapi/postprocessor.py +0 -0
  57. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/openapi/preprocessor.py +0 -0
  58. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/openapi/readme.py +0 -0
  59. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/openapi/test_generator.py +0 -0
  60. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/prompts.py +0 -0
  61. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/singleton.py +0 -0
  62. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/templates/README.md.j2 +0 -0
  63. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/src/universal_mcp/utils/templates/api_client.py.j2 +0 -0
  64. {universal_mcp-0.1.24rc22 → universal_mcp-0.1.24rc24}/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.24rc22
3
+ Version: 0.1.24rc24
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-rc22"
7
+ version = "0.1.24-rc24"
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 = [
@@ -14,29 +14,30 @@ pip install universal-mcp
14
14
 
15
15
  ## Usage
16
16
 
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.
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 `AgentrRegistry` class, which provides a high-level interface for loading and listing tools.
18
18
 
19
- ### High-Level Client (`Agentr`)
19
+ ### High-Level Client (`AgentrRegistry`)
20
20
 
21
- This is the recommended way to get started. It abstracts away the details of the registry and tool management.
21
+ This is the recommended way to get started. It abstracts away the details of the tool management.
22
22
 
23
23
  ```python
24
24
  import os
25
- from universal_mcp.agentr import Agentr
25
+ from universal_mcp.agentr import AgentrRegistry
26
26
  from universal_mcp.tools import ToolFormat
27
27
 
28
28
  # Initialize the main client
29
29
  # It reads from environment variables by default (AGENTR_API_KEY, AGENTR_BASE_URL)
30
- agentr = Agentr(
30
+ registry = AgentrRegistry(
31
31
  api_key=os.environ.get("AGENTR_API_KEY")
32
32
  )
33
33
 
34
34
  # Load specific tools from the AgentR server into the tool manager
35
- agentr.load_tools(["reddit__search_subreddits", "google-drive__list_files"])
36
-
37
- # List the tools that are now loaded and ready to be used
38
- # You can specify a format compatible with your LLM (e.g., OPENAI)
39
- tools = agentr.list_tools(format=ToolFormat.OPENAI)
35
+ # The export_tools method loads the tools and then exports them to the specified format.
36
+ tools_config = {
37
+ "reddit": ["search_subreddits"],
38
+ "google-drive": ["list_files"]
39
+ }
40
+ tools = await registry.export_tools(tools_config, format=ToolFormat.OPENAI)
40
41
  print(tools)
41
42
  ```
42
43
 
@@ -59,7 +60,7 @@ client = AgentrClient(
59
60
  )
60
61
 
61
62
  # Fetch a list of available applications from the AgentR server
62
- apps = client.list_apps()
63
+ apps = client.list_all_apps()
63
64
  print("Available Apps:", apps)
64
65
 
65
66
  # Get credentials for a specific app by its ID (e.g., 'reddit')
@@ -71,7 +72,7 @@ except NotAuthorizedError as e:
71
72
  print(e) # "Please ask the user to visit the following url to authorize..."
72
73
 
73
74
  # List all available tools globally
74
- all_tools = client.list_tools()
75
+ all_tools = client.list_all_tools()
75
76
  print("All Available Tools:", all_tools)
76
77
 
77
78
  # Example of fetching a single app and a single tool
@@ -80,14 +81,14 @@ if apps:
80
81
  app_id = apps[0]['id']
81
82
 
82
83
  # Fetch a single app's details
83
- app_details = client.get_app(app_id)
84
+ app_details = client.get_app_details(app_id)
84
85
  print(f"Fetched details for app '{app_id}':", app_details)
85
86
 
86
87
  if all_tools:
87
88
  tool_id = all_tools[0]['id']
88
89
 
89
90
  # Fetch a single tool's details
90
- tool_details = client.get_tool(tool_id)
91
+ tool_details = client.get_tool_details(tool_id)
91
92
  print(f"Fetched details for tool '{tool_id}':", tool_details)
92
93
  ```
93
94
 
@@ -134,7 +135,7 @@ registry = AgentrRegistry(client=client)
134
135
 
135
136
  async def main():
136
137
  # List all apps available on the AgentR platform
137
- available_apps = await registry.list_apps()
138
+ available_apps = await registry.list_all_apps()
138
139
  print(available_apps)
139
140
 
140
141
  if available_apps:
@@ -143,14 +144,6 @@ async def main():
143
144
  app_details = await registry.get_app_details(app_id)
144
145
  print(f"Details for {app_id}:", app_details)
145
146
 
146
- # The load_tools method is used internally by the high-level Agentr client
147
- # but can be called directly if needed.
148
- # from universal_mcp.tools import ToolManager
149
- # tool_manager = ToolManager()
150
- # registry.load_tools(["reddit_search_subreddits"], tool_manager)
151
- # print(tool_manager.list_tools())
152
-
153
-
154
147
  if __name__ == "__main__":
155
148
  asyncio.run(main())
156
149
  ```
@@ -179,27 +172,30 @@ print(tool_manager.list_tools())
179
172
 
180
173
  ## Executing Tools
181
174
 
182
- Once tools are loaded, you can execute them using the `call_tool` method on the `ToolManager` instance, which is available via `agentr.manager`.
175
+ Once tools are loaded, you can execute them using the `call_tool` method on the `AgentrRegistry` instance.
183
176
 
184
177
  ```python
185
178
  import os
186
179
  import asyncio
187
- from universal_mcp.agentr import Agentr
180
+ from universal_mcp.agentr import AgentrRegistry
181
+ from universal_mcp.tools import ToolFormat
188
182
 
189
183
  async def main():
190
- # 1. Initialize Agentr
191
- agentr = Agentr(api_key=os.environ.get("AGENTR_API_KEY"))
184
+ # 1. Initialize AgentrRegistry
185
+ registry = AgentrRegistry(api_key=os.environ.get("AGENTR_API_KEY"))
192
186
 
193
- # 2. Load the tool(s) you want to use
187
+ # 2. Load the tool(s) you want to use by exporting them
194
188
  tool_name = "reddit__search_subreddits"
195
- agentr.load_tools([tool_name])
189
+ tools_config = {"reddit": ["search_subreddits"]}
190
+ await registry.export_tools(tools_config, format=ToolFormat.OPENAI)
191
+
196
192
 
197
- # 3. Execute the tool using the tool manager
193
+ # 3. Execute the tool using the registry's call_tool method
198
194
  try:
199
195
  # Note the 'await' since call_tool is an async method
200
- result = await agentr.manager.call_tool(
201
- name=tool_name,
202
- arguments={"query": "elon musk", "limit": 5, "sort": "relevance"}
196
+ result = await registry.call_tool(
197
+ tool_name=tool_name,
198
+ tool_args={"query": "elon musk", "limit": 5, "sort": "relevance"}
203
199
  )
204
200
  print("Execution result:", result)
205
201
  except Exception as e:
@@ -207,4 +203,4 @@ async def main():
207
203
 
208
204
  if __name__ == "__main__":
209
205
  asyncio.run(main())
210
- ```
206
+ ```
@@ -183,7 +183,7 @@ class AgentrClient:
183
183
  response.raise_for_status()
184
184
  return response.json()
185
185
 
186
- def search_all_apps(self, query: str, limit: int = 2):
186
+ def search_all_apps(self, query: str, limit: int = 2, distance_threshold: float = 0.6):
187
187
  """Search for apps from the AgentR API.
188
188
 
189
189
  Args:
@@ -193,11 +193,13 @@ class AgentrClient:
193
193
  Returns:
194
194
  List[Dict[str, Any]]: A list of app data dictionaries.
195
195
  """
196
- response = self.client.get("/apps/", params={"search": query, "limit": limit})
196
+ response = self.client.get(
197
+ "/apps/", params={"search": query, "limit": limit, "distance_threshold": distance_threshold}
198
+ )
197
199
  response.raise_for_status()
198
200
  return response.json().get("items", [])
199
201
 
200
- def search_all_tools(self, query: str, limit: int = 2, app_id: str | None = None):
202
+ def search_all_tools(self, query: str, limit: int = 2, app_id: str | None = None, distance_threshold: float = 0.6):
201
203
  """Search for tools from the AgentR API.
202
204
 
203
205
  Args:
@@ -205,7 +207,7 @@ class AgentrClient:
205
207
  limit (int, optional): The number of tools to return. Defaults to 2.
206
208
  app_id (str, optional): The ID of the app to search tools for.
207
209
  """
208
- params = {"search": query, "limit": limit}
210
+ params = {"search": query, "limit": limit, "distance_threshold": distance_threshold}
209
211
  if app_id:
210
212
  params["app_id"] = app_id
211
213
  response = self.client.get("/tools/", params=params)
@@ -82,6 +82,7 @@ class AgentrRegistry(ToolRegistry):
82
82
  self,
83
83
  query: str,
84
84
  limit: int = 10,
85
+ distance_threshold: float = 0.6,
85
86
  ) -> list[dict[str, Any]]:
86
87
  """Search for apps by a query.
87
88
 
@@ -93,7 +94,7 @@ class AgentrRegistry(ToolRegistry):
93
94
  List of app dictionaries matching the query
94
95
  """
95
96
  try:
96
- apps = self.client.search_all_apps(query=query, limit=limit)
97
+ apps = self.client.search_all_apps(query=query, limit=limit, distance_threshold=distance_threshold)
97
98
  return apps
98
99
  except Exception as e:
99
100
  logger.error(f"Error searching apps from AgentR: {e}")
@@ -123,6 +124,7 @@ class AgentrRegistry(ToolRegistry):
123
124
  query: str,
124
125
  limit: int = 2,
125
126
  app_id: str | None = None,
127
+ distance_threshold: float = 0.6,
126
128
  ) -> list[dict[str, Any]]:
127
129
  """Search for tools by a query.
128
130
 
@@ -134,7 +136,9 @@ class AgentrRegistry(ToolRegistry):
134
136
  List of tool dictionaries matching the query
135
137
  """
136
138
  try:
137
- tools = self.client.search_all_tools(query=query, limit=limit, app_id=app_id)
139
+ tools = self.client.search_all_tools(
140
+ query=query, limit=limit, app_id=app_id, distance_threshold=distance_threshold
141
+ )
138
142
  return tools
139
143
  except Exception as e:
140
144
  logger.error(f"Error searching tools from AgentR: {e}")
@@ -157,8 +161,6 @@ class AgentrRegistry(ToolRegistry):
157
161
  from langchain_core.tools import StructuredTool
158
162
 
159
163
  try:
160
- # Clear tools from tool manager before loading new tools
161
- self.tool_manager.clear_tools()
162
164
  logger.info(f"Exporting tools to {format.value} format")
163
165
  if isinstance(tools, dict):
164
166
  self._load_tools_from_tool_config(tools)
@@ -233,3 +235,15 @@ class AgentrRegistry(ToolRegistry):
233
235
  async def list_connected_apps(self) -> list[dict[str, Any]]:
234
236
  """List all apps that the user has connected."""
235
237
  return self.client.list_my_connections()
238
+
239
+ async def authorise_app(self, app_id: str) -> str:
240
+ """Authorise an app to connect to the user's account.
241
+
242
+ Args:
243
+ app_id: The ID of the app to authorise
244
+
245
+ Returns:
246
+ String containing authorisation url
247
+ """
248
+ url = self.client.get_authorization_url(app_id=app_id)
249
+ return url
@@ -11,6 +11,8 @@ from loguru import logger
11
11
 
12
12
  from universal_mcp.integrations.integration import Integration
13
13
 
14
+ DEFAULT_API_TIMEOUT = 30 # seconds
15
+
14
16
 
15
17
  class BaseApplication(ABC):
16
18
  """Defines the foundational structure for applications in Universal MCP.
@@ -90,7 +92,7 @@ class APIApplication(BaseApplication):
90
92
  BaseApplication.
91
93
  """
92
94
  super().__init__(name, **kwargs)
93
- self.default_timeout: int = 180
95
+ self.default_timeout: int = DEFAULT_API_TIMEOUT
94
96
  self.integration = integration
95
97
  logger.debug(f"Initializing APIApplication '{name}' with integration: {integration}")
96
98
  self._client: httpx.Client | None = client
@@ -452,6 +454,7 @@ class GraphQLApplication(BaseApplication):
452
454
  super().__init__(name, **kwargs)
453
455
  self.base_url = base_url
454
456
  self.integration = integration
457
+ self.default_timeout: float = DEFAULT_API_TIMEOUT
455
458
  logger.debug(f"Initializing Application '{name}' with kwargs: {kwargs}")
456
459
  self._client: GraphQLClient | None = client
457
460
 
@@ -1,3 +1,4 @@
1
+ import inspect
1
2
  from typing import Any
2
3
 
3
4
  from loguru import logger
@@ -90,6 +91,7 @@ def convert_tool_to_langchain_tool(
90
91
  """
91
92
 
92
93
  logger.debug(f"Converting tool '{tool.name}' to LangChain format")
94
+ full_docstring = inspect.getdoc(tool.fn)
93
95
 
94
96
  async def call_tool(
95
97
  **arguments: dict[str, Any],
@@ -101,7 +103,7 @@ def convert_tool_to_langchain_tool(
101
103
 
102
104
  langchain_tool = StructuredTool(
103
105
  name=tool.name,
104
- description=tool.description or "",
106
+ description=full_docstring or tool.description or "",
105
107
  coroutine=call_tool,
106
108
  response_format="content",
107
109
  args_schema=tool.parameters,
@@ -16,6 +16,7 @@ class ToolRegistry(ABC):
16
16
 
17
17
  def __init__(self):
18
18
  """Initializes the registry and its internal tool manager."""
19
+ self._app_instances = {}
19
20
  self.tool_manager = ToolManager()
20
21
  logger.debug(f"{self.__class__.__name__} initialized.")
21
22
 
@@ -32,7 +33,7 @@ class ToolRegistry(ABC):
32
33
  pass
33
34
 
34
35
  @abstractmethod
35
- async def search_apps(self, query: str, limit: int = 2) -> list[dict[str, Any]]:
36
+ async def search_apps(self, query: str, limit: int = 2, distance_threshold: float = 0.6) -> list[dict[str, Any]]:
36
37
  """Search for apps by a query."""
37
38
  pass
38
39
 
@@ -42,7 +43,9 @@ class ToolRegistry(ABC):
42
43
  pass
43
44
 
44
45
  @abstractmethod
45
- async def search_tools(self, query: str, limit: int = 2, app_id: str | None = None) -> list[dict[str, Any]]:
46
+ async def search_tools(
47
+ self, query: str, limit: int = 2, app_id: str | None = None, distance_threshold: float = 0.6
48
+ ) -> list[dict[str, Any]]:
46
49
  """Search for tools by a query, optionally filtered by an app."""
47
50
  pass
48
51
 
@@ -73,7 +76,9 @@ class ToolRegistry(ABC):
73
76
  """Helper method to load and register tools for an app."""
74
77
  logger.info(f"Loading tools for app '{app_name}' (tools: {tool_names or 'default'})")
75
78
  try:
76
- app_instance = self._create_app_instance(app_name)
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]
77
82
  self.tool_manager.register_tools_from_app(app_instance, tool_names=tool_names)
78
83
  logger.info(f"Successfully registered tools for app: {app_name}")
79
84
  except Exception as e:
@@ -3,8 +3,8 @@ from pathlib import Path
3
3
  import typer
4
4
  from rich.console import Console
5
5
  from rich.panel import Panel
6
-
7
6
  from universal_mcp.agents.cli import app as client_app
7
+
8
8
  from universal_mcp.utils.installation import (
9
9
  get_supported_apps,
10
10
  install_app,