universal-mcp 0.1.22rc1__py3-none-any.whl → 0.1.22rc4__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.
@@ -30,6 +30,8 @@ sys.path.append(str(UNIVERSAL_MCP_HOME))
30
30
  # Name are in the format of "app-name", eg, google-calendar
31
31
  # Class name is NameApp, eg, GoogleCalendarApp
32
32
 
33
+ app_cache: dict[str, type[BaseApplication]] = {}
34
+
33
35
 
34
36
  def _install_or_upgrade_package(package_name: str, repository_path: str):
35
37
  """
@@ -71,6 +73,8 @@ def app_from_slug(slug: str):
71
73
  Dynamically resolve and return the application class for the given slug.
72
74
  Attempts installation from GitHub if the package is not found locally.
73
75
  """
76
+ if slug in app_cache:
77
+ return app_cache[slug]
74
78
  class_name = get_default_class_name(slug)
75
79
  module_path = get_default_module_path(slug)
76
80
  package_name = get_default_package_name(slug)
@@ -81,6 +85,7 @@ def app_from_slug(slug: str):
81
85
  module = importlib.import_module(module_path)
82
86
  class_ = getattr(module, class_name)
83
87
  logger.debug(f"Loaded class '{class_}' from module '{module_path}'")
88
+ app_cache[slug] = class_
84
89
  return class_
85
90
  except ModuleNotFoundError as e:
86
91
  raise ModuleNotFoundError(f"Package '{module_path}' not found locally. Please install it first.") from e
@@ -4,7 +4,6 @@ from typing import Any
4
4
  import httpx
5
5
  from loguru import logger
6
6
  from mcp.server.fastmcp import FastMCP
7
- from mcp.server.fastmcp.server import MCPTool
8
7
  from mcp.types import TextContent
9
8
  from pydantic import ValidationError
10
9
 
@@ -203,23 +202,13 @@ class AgentRServer(BaseServer):
203
202
  """
204
203
 
205
204
  def __init__(self, config: ServerConfig, **kwargs):
206
- # Initialize API key and client before calling super().__init__
205
+ super().__init__(config, **kwargs)
207
206
  self.api_key = config.api_key.get_secret_value() if config.api_key else None
208
207
  if not self.api_key:
209
208
  raise ValueError("API key is required for AgentR server")
210
-
211
209
  logger.info(f"Initializing AgentR server with API key: {self.api_key}")
212
210
  self.client = AgentrClient(api_key=self.api_key)
213
- super().__init__(config, **kwargs)
214
- self.integration = AgentRIntegration(name="agentr", api_key=self.api_key)
215
- # Don't load apps in __init__ for stateless operation
216
- self._apps_loaded = False
217
-
218
- def _ensure_apps_loaded(self) -> None:
219
- """Ensure apps are loaded, loading them if necessary."""
220
- if not self._apps_loaded:
221
- self._load_apps()
222
- self._apps_loaded = True
211
+ self._load_apps()
223
212
 
224
213
  def _fetch_apps(self) -> list[AppConfig]:
225
214
  """Fetch available apps from AgentR API with retry logic.
@@ -256,7 +245,7 @@ class AgentRServer(BaseServer):
256
245
  """
257
246
  try:
258
247
  integration = (
259
- AgentRIntegration(name=app_config.integration.name, api_key=self.client.api_key)
248
+ AgentRIntegration(name=app_config.integration.name, api_key=self.api_key)
260
249
  if app_config.integration
261
250
  else None
262
251
  )
@@ -292,16 +281,6 @@ class AgentRServer(BaseServer):
292
281
  # Don't raise the exception to allow server to start with partial functionality
293
282
  logger.warning("Server will start with limited functionality due to app loading failures")
294
283
 
295
- async def list_tools(self) -> list[MCPTool]:
296
- """List available tools, ensuring apps are loaded first."""
297
- self._ensure_apps_loaded()
298
- return await super().list_tools()
299
-
300
- async def call_tool(self, name: str, arguments: dict) -> list[TextContent]:
301
- """Call a tool by name, ensuring apps are loaded first."""
302
- self._ensure_apps_loaded()
303
- return await super().call_tool(name, arguments)
304
-
305
284
 
306
285
  class SingleMCPServer(BaseServer):
307
286
  """
@@ -19,11 +19,14 @@ def convert_tool_to_mcp_tool(
19
19
  ):
20
20
  from mcp.server.fastmcp.server import MCPTool
21
21
 
22
- return MCPTool(
22
+ logger.debug(f"Converting tool '{tool.name}' to MCP format")
23
+ mcp_tool = MCPTool(
23
24
  name=tool.name[:63],
24
25
  description=tool.description or "",
25
26
  inputSchema=tool.parameters,
26
27
  )
28
+ logger.debug(f"Successfully converted tool '{tool.name}' to MCP format")
29
+ return mcp_tool
27
30
 
28
31
 
29
32
  def format_to_mcp_result(result: any) -> list[TextContent]:
@@ -35,9 +38,12 @@ def format_to_mcp_result(result: any) -> list[TextContent]:
35
38
  Returns:
36
39
  List of TextContent objects
37
40
  """
41
+ logger.debug(f"Formatting result to MCP format, type: {type(result)}")
38
42
  if isinstance(result, str):
43
+ logger.debug("Result is string, wrapping in TextContent")
39
44
  return [TextContent(type="text", text=result)]
40
45
  elif isinstance(result, list) and all(isinstance(item, TextContent) for item in result):
46
+ logger.debug("Result is already list of TextContent objects")
41
47
  return result
42
48
  else:
43
49
  logger.warning(f"Tool returned unexpected type: {type(result)}. Wrapping in TextContent.")
@@ -60,26 +66,33 @@ def convert_tool_to_langchain_tool(
60
66
  a LangChain tool
61
67
  """
62
68
 
69
+ logger.debug(f"Converting tool '{tool.name}' to LangChain format")
70
+
63
71
  async def call_tool(
64
72
  **arguments: dict[str, any],
65
73
  ):
74
+ logger.debug(f"Executing LangChain tool '{tool.name}' with arguments: {arguments}")
66
75
  call_tool_result = await tool.run(arguments)
76
+ logger.debug(f"Tool '{tool.name}' execution completed")
67
77
  return call_tool_result
68
78
 
69
- return StructuredTool(
79
+ langchain_tool = StructuredTool(
70
80
  name=tool.name,
71
81
  description=tool.description or "",
72
82
  coroutine=call_tool,
73
83
  response_format="content",
74
84
  args_schema=tool.parameters,
75
85
  )
86
+ logger.debug(f"Successfully converted tool '{tool.name}' to LangChain format")
87
+ return langchain_tool
76
88
 
77
89
 
78
90
  def convert_tool_to_openai_tool(
79
91
  tool: Tool,
80
92
  ):
81
93
  """Convert a Tool object to an OpenAI function."""
82
- return {
94
+ logger.debug(f"Converting tool '{tool.name}' to OpenAI format")
95
+ openai_tool = {
83
96
  "type": "function",
84
97
  "function": {
85
98
  "name": tool.name,
@@ -87,3 +100,5 @@ def convert_tool_to_openai_tool(
87
100
  "parameters": tool.parameters,
88
101
  },
89
102
  }
103
+ logger.debug(f"Successfully converted tool '{tool.name}' to OpenAI format")
104
+ return openai_tool
@@ -55,7 +55,7 @@ class Tool(BaseModel):
55
55
 
56
56
  return cls(
57
57
  fn=fn,
58
- name=func_name[:48],
58
+ name=func_name,
59
59
  description=parsed_doc["summary"],
60
60
  args_description=parsed_doc["args"],
61
61
  returns_description=parsed_doc["returns"],
@@ -78,3 +78,27 @@ class AgentrClient:
78
78
  response.raise_for_status()
79
79
  data = response.json()
80
80
  return [AppConfig.model_validate(app) for app in data]
81
+
82
+ def list_all_apps(self) -> list:
83
+ """List all apps from AgentR API.
84
+
85
+ Returns:
86
+ List of app names
87
+ """
88
+ response = self.client.get("/apps/")
89
+ response.raise_for_status()
90
+ return response.json()
91
+
92
+ def list_actions(self, app_name: str):
93
+ """List actions for an app.
94
+
95
+ Args:
96
+ app_name (str): Name of the app to list actions for
97
+
98
+ Returns:
99
+ List of action configurations
100
+ """
101
+
102
+ response = self.client.get(f"/apps/{app_name}/actions/")
103
+ response.raise_for_status()
104
+ return response.json()
@@ -149,7 +149,9 @@ def _sanitize_identifier(name: str | None) -> str:
149
149
  return ""
150
150
 
151
151
  # Initial replacements for common non-alphanumeric characters
152
- sanitized = name.replace("-", "_").replace(".", "_").replace("[", "_").replace("]", "").replace("$", "_")
152
+ sanitized = (
153
+ name.replace("-", "_").replace(".", "_").replace("[", "_").replace("]", "").replace("$", "_").replace("/", "_")
154
+ )
153
155
 
154
156
  # Remove leading underscores, but preserve a single underscore if the name (after initial replace)
155
157
  # consisted only of underscores.
@@ -0,0 +1,31 @@
1
+ from loguru import logger
2
+
3
+ from universal_mcp.tools.tools import Tool
4
+
5
+
6
+ def check_application_instance(app_instance, app_name):
7
+ assert app_instance is not None, f"Application object is None for {app_name}"
8
+ assert (
9
+ app_instance.name == app_name
10
+ ), f"Application instance name '{app_instance.name}' does not match expected name '{app_name}'"
11
+
12
+ tools = app_instance.list_tools()
13
+ logger.info(f"Tools for {app_name}: {len(tools)}")
14
+ assert len(tools) > 0, f"No tools found for {app_name}"
15
+
16
+ tools = [Tool.from_function(tool) for tool in tools]
17
+ seen_names = set()
18
+ important_tools = []
19
+
20
+ for tool in tools:
21
+ assert tool.name is not None, f"Tool name is None for a tool in {app_name}"
22
+ assert (
23
+ 0 < len(tool.name) <= 48
24
+ ), f"Tool name '{tool.name}' for {app_name} has invalid length (must be between 1 and 47 characters)"
25
+ assert tool.description is not None, f"Tool description is None for tool '{tool.name}' in {app_name}"
26
+ # assert 0 < len(tool.description) <= 255, f"Tool description for '{tool.name}' in {app_name} has invalid length (must be between 1 and 255 characters)"
27
+ assert tool.name not in seen_names, f"Duplicate tool name: '{tool.name}' found for {app_name}"
28
+ seen_names.add(tool.name)
29
+ if "important" in tool.tags:
30
+ important_tools.append(tool.name)
31
+ assert len(important_tools) > 0, f"No important tools found for {app_name}"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp
3
- Version: 0.1.22rc1
3
+ Version: 0.1.22rc4
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
@@ -6,40 +6,41 @@ universal_mcp/exceptions.py,sha256=-pbeZhpNieJfnSd2-WM80pU8W8mK8VHXcSjky0BHwdk,6
6
6
  universal_mcp/logger.py,sha256=VmH_83efpErLEDTJqz55Dp0dioTXfGvMBLZUx5smOLc,2116
7
7
  universal_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
8
  universal_mcp/applications/README.md,sha256=eqbizxaTxKH2O1tyIJR2yI0Db5TQxtgPd_vbpWyCa2Y,3527
9
- universal_mcp/applications/__init__.py,sha256=No_tptWkYFcbE6Z3tMEpqUbpHXlaCOa8__yqWB5sTjY,3360
9
+ universal_mcp/applications/__init__.py,sha256=l19_sMs5766VFWU_7O2niamvvvfQOteysqylbqvjjGQ,3500
10
10
  universal_mcp/applications/application.py,sha256=3cQ5BVWmC2gU4fgpM5wZ3ByTe7iGbQriNPVSWxclaiU,17744
11
11
  universal_mcp/integrations/README.md,sha256=lTAPXO2nivcBe1q7JT6PRa6v9Ns_ZersQMIdw-nmwEA,996
12
12
  universal_mcp/integrations/__init__.py,sha256=X8iEzs02IlXfeafp6GMm-cOkg70QdjnlTRuFo24KEfo,916
13
13
  universal_mcp/integrations/integration.py,sha256=uKucut4AKTN2M-K8Aqsm2qtchLqlQRWMU8L287X7VyQ,13043
14
14
  universal_mcp/servers/README.md,sha256=ytFlgp8-LO0oogMrHkMOp8SvFTwgsKgv7XhBVZGNTbM,2284
15
15
  universal_mcp/servers/__init__.py,sha256=eBZCsaZjiEv6ZlRRslPKgurQxmpHLQyiXv2fTBygHnM,532
16
- universal_mcp/servers/server.py,sha256=7IiivuINr1CjZ6gX8xMgsoTHOevbiHa9U1pUDJGNRIE,13251
16
+ universal_mcp/servers/server.py,sha256=K7sPdCixYgJmQRxOL1icscL7-52sVsghpRX_D_uREu4,12329
17
17
  universal_mcp/stores/README.md,sha256=jrPh_ow4ESH4BDGaSafilhOVaN8oQ9IFlFW-j5Z5hLA,2465
18
18
  universal_mcp/stores/__init__.py,sha256=quvuwhZnpiSLuojf0NfmBx2xpaCulv3fbKtKaSCEmuM,603
19
19
  universal_mcp/stores/store.py,sha256=mxnmOVlDNrr8OKhENWDtCIfK7YeCBQcGdS6I2ogRCsU,6756
20
20
  universal_mcp/tools/README.md,sha256=RuxliOFqV1ZEyeBdj3m8UKfkxAsfrxXh-b6V4ZGAk8I,2468
21
21
  universal_mcp/tools/__init__.py,sha256=Fatza_R0qYWmNF1WQSfUZZKQFu5qf-16JhZzdmyx3KY,333
22
- universal_mcp/tools/adapters.py,sha256=n06Nz18L_4dq3iwBchNq9vHsX7whqFZ0g5iuz_0gyaM,2076
22
+ universal_mcp/tools/adapters.py,sha256=nMoZ9jnv1uKhfq6NmBJ5-a6uwdB_H8RqkdNLIacCRfM,2978
23
23
  universal_mcp/tools/func_metadata.py,sha256=XvdXSZEzvgbH70bc-Zu0B47CD7f_rm--vblq4en3n0Q,8181
24
24
  universal_mcp/tools/manager.py,sha256=ao_ovTyca8HR4uwHdL_lTWNdquxcqRx6FaLA4U1lZvQ,11242
25
- universal_mcp/tools/tools.py,sha256=qiuuLe0mCWtxXp6E5ISDDaNojCrMLfV1r5L8peFoJfg,3327
25
+ universal_mcp/tools/tools.py,sha256=8YBTaJCM38Nhan9Al6Vlq4FtSULrKlxg1q_o8OL1_FM,3322
26
26
  universal_mcp/utils/__init__.py,sha256=8wi4PGWu-SrFjNJ8U7fr2iFJ1ktqlDmSKj1xYd7KSDc,41
27
- universal_mcp/utils/agentr.py,sha256=a7D3I4oKvdovoumeSdChhHBcZNrEtKHHb4L4O8Gm_l0,2870
27
+ universal_mcp/utils/agentr.py,sha256=Rzfou8dfS705lNrRwKLXkpNlo4zJs1oPP_2QY46l4Uo,3486
28
28
  universal_mcp/utils/common.py,sha256=HEZC2Mhilb8DrGXQG2tboAIw1r4veGilGWjfnPF1lyA,888
29
29
  universal_mcp/utils/docstring_parser.py,sha256=SKIfAiFHiqxqageayYFlpsexipy8tN7N4RLT6GIzfoQ,7672
30
30
  universal_mcp/utils/installation.py,sha256=ItOfBFhKOh4DLz237jgAz_Fn0uOMdrKXw0n5BaUZZNs,7286
31
31
  universal_mcp/utils/singleton.py,sha256=kolHnbS9yd5C7z-tzaUAD16GgI-thqJXysNi3sZM4No,733
32
+ universal_mcp/utils/testing.py,sha256=0znYkuFi8-WjOdbwrTbNC-UpMqG3EXcGOE0wxlERh_A,1464
32
33
  universal_mcp/utils/openapi/__inti__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
34
  universal_mcp/utils/openapi/api_generator.py,sha256=FjtvbnWuI1P8W8wXuKLCirUtsqQ4HI_TuQrhpA4SqTs,4749
34
35
  universal_mcp/utils/openapi/api_splitter.py,sha256=S-rT3wsJWUVhU_Tv_ibNNAlQ79SfWOcU6qaa_rFfd5o,20806
35
36
  universal_mcp/utils/openapi/docgen.py,sha256=DNmwlhg_-TRrHa74epyErMTRjV2nutfCQ7seb_Rq5hE,21366
36
- universal_mcp/utils/openapi/openapi.py,sha256=3DWpxVZk9LGGDLrpE7BfSjurd_FJg30oWUiaEv3wjm0,51105
37
+ universal_mcp/utils/openapi/openapi.py,sha256=sfHJWBaC2c-9ZCjTODH7Gt-un_agixOr_ylx9Xld8N4,51139
37
38
  universal_mcp/utils/openapi/preprocessor.py,sha256=PPIM3Uu8DYi3dRKdqi9thr9ufeUgkr2K08ri1BwKpoQ,60835
38
39
  universal_mcp/utils/openapi/readme.py,sha256=R2Jp7DUXYNsXPDV6eFTkLiy7MXbSULUj1vHh4O_nB4c,2974
39
40
  universal_mcp/utils/templates/README.md.j2,sha256=Mrm181YX-o_-WEfKs01Bi2RJy43rBiq2j6fTtbWgbTA,401
40
41
  universal_mcp/utils/templates/api_client.py.j2,sha256=972Im7LNUAq3yZTfwDcgivnb-b8u6_JLKWXwoIwXXXQ,908
41
- universal_mcp-0.1.22rc1.dist-info/METADATA,sha256=VAB3kMzP5KOKpWoBC12I7i3gW2JmSJLvx92Go9qINv8,12154
42
- universal_mcp-0.1.22rc1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
43
- universal_mcp-0.1.22rc1.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
44
- universal_mcp-0.1.22rc1.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
45
- universal_mcp-0.1.22rc1.dist-info/RECORD,,
42
+ universal_mcp-0.1.22rc4.dist-info/METADATA,sha256=3poFZB1sv1psL8m_KwV8FjNCWsA1tiGzB_X4QdYBaPY,12154
43
+ universal_mcp-0.1.22rc4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
44
+ universal_mcp-0.1.22rc4.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
45
+ universal_mcp-0.1.22rc4.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
46
+ universal_mcp-0.1.22rc4.dist-info/RECORD,,