universal-mcp 0.1.22rc1__py3-none-any.whl → 0.1.23__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.
@@ -9,7 +9,8 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
9
9
 
10
10
  Supports multi-line descriptions for each section. Recognizes common section
11
11
  headers like 'Args:', 'Returns:', 'Raises:', 'Tags:', etc. Also attempts
12
- to parse key-value pairs within 'Args:' and 'Raises:' sections.
12
+ to parse key-value pairs within 'Args:' and 'Raises:' sections, including
13
+ type information for arguments if present in the docstring.
13
14
 
14
15
  Args:
15
16
  docstring: The docstring string to parse, or None.
@@ -17,7 +18,9 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
17
18
  Returns:
18
19
  A dictionary containing the parsed components:
19
20
  - 'summary': The first paragraph of the docstring.
20
- - 'args': A dictionary mapping argument names to their descriptions.
21
+ - 'args': A dictionary mapping argument names to their details,
22
+ including 'description' and 'type_str' (if found).
23
+ Example: {"param_name": {"description": "desc...", "type_str": "str"}}
21
24
  - 'returns': The description of the return value.
22
25
  - 'raises': A dictionary mapping exception types to their descriptions.
23
26
  - 'tags': A list of strings found in the 'Tags:' section.
@@ -31,7 +34,7 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
31
34
 
32
35
  summary: str = ""
33
36
  summary_lines: list[str] = []
34
- args: dict[str, str] = {}
37
+ args: dict[str, dict[str, str | None]] = {}
35
38
  returns: str = ""
36
39
  raises: dict[str, str] = {}
37
40
  tags: list[str] = []
@@ -39,17 +42,20 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
39
42
  current_section: str | None = None
40
43
  current_key: str | None = None
41
44
  current_desc_lines: list[str] = []
45
+ current_arg_type_str: str | None = None
42
46
 
43
- key_pattern = re.compile(r"^\s*([\w\.]+)\s*(?:\(.*\))?:\s*(.*)")
47
+ key_pattern = re.compile(r"^\s*([\w\.]+)\s*(?:\((.*?)\))?:\s*(.*)")
44
48
 
45
49
  def finalize_current_item():
46
- """Processes the collected current_desc_lines and assigns them."""
47
- nonlocal returns, tags, args, raises
50
+ nonlocal returns, tags, args, raises, current_arg_type_str
48
51
  desc = " ".join(current_desc_lines).strip()
49
52
 
50
53
  if current_section == "args" and current_key:
51
- if desc:
52
- args[current_key] = desc
54
+ if desc or current_arg_type_str:
55
+ args[current_key] = {
56
+ "description": desc,
57
+ "type_str": current_arg_type_str,
58
+ }
53
59
  elif current_section == "raises" and current_key:
54
60
  if desc:
55
61
  raises[current_key] = desc
@@ -61,7 +67,6 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
61
67
  tags.extend([tag.strip() for tag in desc.split(",") if tag.strip()])
62
68
 
63
69
  def check_for_section_header(line: str) -> tuple[bool, str | None, str]:
64
- """Checks if a line is a recognized section header."""
65
70
  stripped_lower = line.strip().lower()
66
71
  section_type: str | None = None
67
72
  header_content = ""
@@ -79,12 +84,11 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
79
84
  parts = re.split(r"[:\s]+", line.strip(), maxsplit=1)
80
85
  if len(parts) > 1:
81
86
  header_content = parts[1].strip()
82
- elif stripped_lower.startswith(("tags",)):
87
+ elif stripped_lower.startswith(("tags",)): # Match "tags" without colon for header content
83
88
  section_type = "tags"
84
89
  parts = re.split(r"[:\s]+", line.strip(), maxsplit=1)
85
90
  if len(parts) > 1:
86
91
  header_content = parts[1].strip()
87
-
88
92
  elif stripped_lower.endswith(":") and stripped_lower[:-1] in (
89
93
  "attributes",
90
94
  "see also",
@@ -97,7 +101,6 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
97
101
  "warnings",
98
102
  ):
99
103
  section_type = "other"
100
-
101
104
  return section_type is not None, section_type, header_content
102
105
 
103
106
  in_summary = True
@@ -113,10 +116,8 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
113
116
  in_summary = False
114
117
  summary = " ".join(summary_lines).strip()
115
118
  summary_lines = []
116
-
117
119
  if not stripped_line:
118
120
  continue
119
-
120
121
  else:
121
122
  summary_lines.append(stripped_line)
122
123
  continue
@@ -133,27 +134,15 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
133
134
  current_section in ["returns", "tags", "other"]
134
135
  and current_desc_lines
135
136
  and original_indentation == 0
136
- and stripped_line
137
+ and stripped_line # Ensure it's not an empty unindented line (handled by rule 2)
137
138
  )
138
139
  ):
139
140
  should_finalize_previous = True
140
- elif (
141
- current_section in ["args", "raises"]
142
- and current_key is not None
143
- or current_section in ["returns", "tags", "other"]
144
- and current_desc_lines
145
- ):
146
- pass
147
141
 
148
142
  if should_finalize_previous:
149
143
  finalize_current_item()
150
- if is_new_section_header or (
151
- current_section in ["args", "raises"]
152
- and current_key is not None
153
- and not key_pattern.match(line)
154
- and (not stripped_line or original_indentation == 0)
155
- ):
156
- current_key = None
144
+ current_key = None
145
+ current_arg_type_str = None
157
146
  current_desc_lines = []
158
147
 
159
148
  if is_new_section_header:
@@ -169,15 +158,14 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
169
158
  match = key_pattern.match(line)
170
159
  if match:
171
160
  current_key = match.group(1)
172
- current_desc_lines = [match.group(2).strip()]
173
- elif current_key is not None:
161
+ current_arg_type_str = match.group(2).strip() if match.group(2) else None
162
+ current_desc_lines = [match.group(3).strip()] # Start new description
163
+ elif current_key is not None: # Continuation line for an existing key
174
164
  current_desc_lines.append(stripped_line)
175
-
176
165
  elif current_section in ["returns", "tags", "other"]:
177
166
  current_desc_lines.append(stripped_line)
178
167
 
179
- finalize_current_item()
180
-
168
+ finalize_current_item() # Finalize any pending item at the end of the docstring
181
169
  if in_summary:
182
170
  summary = " ".join(summary_lines).strip()
183
171
 
@@ -191,29 +179,23 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
191
179
 
192
180
 
193
181
  docstring_example = """
194
- Starts a crawl job for a given URL using Firecrawl.
195
- Returns the job ID immediately.
182
+ Creates a new product in the CRM product library to manage the collection of goods and services offered by the company.
196
183
 
197
- Args:
198
- url: The starting URL for the crawl.
199
- It can be a very long url that spans multiple lines if needed.
200
- params: Optional dictionary of parameters to customize the crawl.
201
- See API docs for details.
202
- idempotency_key: Optional unique key to prevent duplicate jobs.
184
+ Args:
185
+ associations (array): associations
186
+ properties (object): No description provided. Example: "{'description': 'Onboarding service for data product', 'name': '1 year implementation consultation', 'price': '6000.00', 'hs_sku': '191902', 'hs_cost_of_goods_sold': '600.00', 'hs_recurring_billing_period': 'P24M', 'city': 'Cambridge', 'phone': '(877) 929-0687', 'state': 'Massachusetts', 'domain': 'biglytics.net', 'industry': 'Technology', 'amount': '1500.00', 'dealname': 'Custom data integrations', 'pipeline': 'default', 'closedate': '2019-12-07T16:50:06.678Z', 'dealstage': 'presentationscheduled', 'hubspot_owner_id': '910901', 'email': 'bcooper@biglytics.net', 'company': 'Biglytics', 'website': 'biglytics.net', 'lastname': 'Cooper', 'firstname': 'Bryan', 'subject': 'troubleshoot report', 'hs_pipeline': 'support_pipeline', 'hs_pipeline_stage': 'open', 'hs_ticket_priority': 'HIGH', 'quantity': '2', 'hs_product_id': '191902', 'recurringbillingfrequency': 'monthly'}".
203
187
 
204
- Returns:
205
- A dictionary containing the job initiation response on success,
206
- or a string containing an error message on failure. This description
207
- can also span multiple lines.
188
+ Returns:
189
+ dict[str, Any]: successful operation
208
190
 
209
191
  Raises:
210
- ValueError: If the URL is invalid.
211
- ConnectionError: If connection fails.
192
+ HTTPError: Raised when the API request fails (e.g., non-2XX status code).
193
+ JSONDecodeError: Raised if the response body cannot be parsed as JSON.
212
194
 
213
- Tags:
214
- crawl, async_job, start, api, long_tag_example , another
215
- , final_tag
216
- """
195
+ Tags:
196
+ Basic, Another Tag
197
+ Yet Another Tag
198
+ """
217
199
 
218
200
  if __name__ == "__main__":
219
201
  import json
@@ -15,17 +15,21 @@ class APISegmentBase:
15
15
  def _get(self, url: str, params: dict = None, **kwargs):
16
16
  return self.main_app_client._get(url, params=params, **kwargs)
17
17
 
18
- def _post(self, url: str, data: Any = None, files: Any = None, params: dict = None, content_type: str = None, **kwargs):
19
- return self.main_app_client._post(url, data=data, files=files, params=params, content_type=content_type, **kwargs)
18
+ def _post(self, url: str, data: Any = None, params: dict = None, content_type: str = None, files: Any = None, **kwargs):
19
+ return self.main_app_client._post(url, data=data, params=params, content_type=content_type, files=files, **kwargs)
20
20
 
21
- def _put(self, url: str, data: Any = None, files: Any = None, params: dict = None, content_type: str = None, **kwargs):
22
- return self.main_app_client._put(url, data=data, files=files, params=params, content_type=content_type, **kwargs)
21
+ def _put(self, url: str, data: Any = None, params: dict = None, content_type: str = None, files: Any = None, **kwargs):
22
+ return self.main_app_client._put(url, data=data, params=params, content_type=content_type, files=files, **kwargs)
23
23
 
24
24
  def _patch(self, url: str, data: Any = None, params: dict = None, **kwargs):
25
25
  return self.main_app_client._patch(url, data=data, params=params, **kwargs)
26
26
 
27
27
  def _delete(self, url: str, params: dict = None, **kwargs):
28
28
  return self.main_app_client._delete(url, params=params, **kwargs)
29
+
30
+ def _handle_response(self, response):
31
+ return self.main_app_client._handle_response(response)
32
+
29
33
  """
30
34
 
31
35
 
@@ -150,7 +154,8 @@ class MethodTransformer(ast.NodeTransformer):
150
154
  return self.generic_visit(node)
151
155
 
152
156
 
153
- def split_generated_app_file(input_app_file: Path, output_dir: Path):
157
+ def split_generated_app_file(input_app_file: Path, output_dir: Path, package_name: str = None):
158
+
154
159
  content = input_app_file.read_text()
155
160
  tree = ast.parse(content)
156
161
 
@@ -500,7 +505,7 @@ def split_generated_app_file(input_app_file: Path, output_dir: Path):
500
505
  # Adjust import path for segments subfolder
501
506
  final_main_module_imports.append(
502
507
  ast.ImportFrom(
503
- module=f".{segments_foldername}.{seg_detail['module_name']}",
508
+ module=f"universal_mcp_{package_name}.{segments_foldername}.{seg_detail['module_name']}",
504
509
  names=[ast.alias(name=seg_detail["class_name"])],
505
510
  level=0,
506
511
  )
@@ -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("/", "_").replace("@", "at")
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.
@@ -845,8 +847,7 @@ def _generate_method_code(path, method, operation):
845
847
 
846
848
  raises_section_lines = [
847
849
  "Raises:",
848
- " HTTPError: Raised when the API request fails (e.g., non-2XX status code).",
849
- " JSONDecodeError: Raised if the response body cannot be parsed as JSON.",
850
+ " HTTPStatusError: Raised when the API request fails with detailed error information including status code and response body.",
850
851
  ]
851
852
  docstring_parts.append("\n".join(raises_section_lines))
852
853
 
@@ -967,9 +968,11 @@ def _generate_method_code(path, method, operation):
967
968
  # --- Make HTTP Request ---
968
969
  # This section generates the actual HTTP call
969
970
  # using the prepared URL, query parameters, request body data, files, and content type.
971
+ # Use convenience methods that automatically handle responses and errors
970
972
 
971
973
  if method_lower == "get":
972
974
  body_lines.append(" response = self._get(url, params=query_params)")
975
+ body_lines.append(" return self._handle_response(response)")
973
976
  elif method_lower == "post":
974
977
  if selected_content_type == "multipart/form-data":
975
978
  body_lines.append(
@@ -979,6 +982,7 @@ def _generate_method_code(path, method, operation):
979
982
  body_lines.append(
980
983
  f" response = self._post(url, data=request_body_data, params=query_params, content_type='{final_content_type_for_api_call}')"
981
984
  )
985
+ body_lines.append(" return self._handle_response(response)")
982
986
  elif method_lower == "put":
983
987
  if selected_content_type == "multipart/form-data":
984
988
  body_lines.append(
@@ -988,21 +992,16 @@ def _generate_method_code(path, method, operation):
988
992
  body_lines.append(
989
993
  f" response = self._put(url, data=request_body_data, params=query_params, content_type='{final_content_type_for_api_call}')"
990
994
  )
995
+ body_lines.append(" return self._handle_response(response)")
991
996
  elif method_lower == "patch":
992
997
  body_lines.append(" response = self._patch(url, data=request_body_data, params=query_params)")
998
+ body_lines.append(" return self._handle_response(response)")
993
999
  elif method_lower == "delete":
994
1000
  body_lines.append(" response = self._delete(url, params=query_params)")
1001
+ body_lines.append(" return self._handle_response(response)")
995
1002
  else:
996
1003
  body_lines.append(f" response = self._{method_lower}(url, data=request_body_data, params=query_params)")
997
-
998
- # --- Handle Response ---
999
- body_lines.append(" response.raise_for_status()")
1000
- body_lines.append(" if response.status_code == 204 or not response.content or not response.text.strip():")
1001
- body_lines.append(" return None")
1002
- body_lines.append(" try:")
1003
- body_lines.append(" return response.json()")
1004
- body_lines.append(" except ValueError:")
1005
- body_lines.append(" return None")
1004
+ body_lines.append(" return self._handle_response(response)")
1006
1005
 
1007
1006
  # --- Combine Signature, Docstring, and Body for Final Method Code ---
1008
1007
  method_code = signature + formatted_docstring + "\n" + "\n".join(body_lines)
@@ -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.23
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
@@ -15,7 +15,7 @@ Requires-Dist: keyring>=25.6.0
15
15
  Requires-Dist: langchain-mcp-adapters>=0.0.3
16
16
  Requires-Dist: litellm>=1.30.7
17
17
  Requires-Dist: loguru>=0.7.3
18
- Requires-Dist: mcp>=1.9.0
18
+ Requires-Dist: mcp>=1.9.3
19
19
  Requires-Dist: posthog>=3.24.0
20
20
  Requires-Dist: pydantic-settings>=2.8.1
21
21
  Requires-Dist: pydantic>=2.11.1
@@ -1,45 +1,51 @@
1
1
  universal_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
2
  universal_mcp/analytics.py,sha256=Dkv8mkc_2T2t5NxLSZzcr3BlmOispj1RKtbB86V1i4M,2306
3
- universal_mcp/cli.py,sha256=-MVcqDEL_0AefmEgjq3ZCa2tySxXwi-Tfoat2kboX_U,10311
4
- universal_mcp/config.py,sha256=HaAZvf-XzQZpqGGWUuT5zojWloO8GL5Acfa5_0sDs_Q,3321
3
+ universal_mcp/cli.py,sha256=Ndc1I4BtmvDCM6xNtXaO6roUvNabRlclI3tLDeSHKAw,10453
4
+ universal_mcp/config.py,sha256=9yofM9MCyBiriojFVcMqRwc9njce6lgwX5r_EfvG0NE,5185
5
5
  universal_mcp/exceptions.py,sha256=-pbeZhpNieJfnSd2-WM80pU8W8mK8VHXcSjky0BHwdk,665
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
10
- universal_mcp/applications/application.py,sha256=3cQ5BVWmC2gU4fgpM5wZ3ByTe7iGbQriNPVSWxclaiU,17744
9
+ universal_mcp/applications/__init__.py,sha256=l19_sMs5766VFWU_7O2niamvvvfQOteysqylbqvjjGQ,3500
10
+ universal_mcp/applications/application.py,sha256=_fcnfAeT5Lm2PJpHjK5ax8pCsxlDGJIKyRqnlCdEg_w,18758
11
+ universal_mcp/client/__main__.py,sha256=72e4WyJRiLoCtHlpIuvMZcDV98LvZclVaUGJmuCvpIE,886
12
+ universal_mcp/client/agent.py,sha256=20N0aZ55ge9PBg-R9gHxit2NSY1ppSz2ez8ZXTiO5kc,3803
13
+ universal_mcp/client/client.py,sha256=rx-meuWZuurjaTbjumpf5NO9zIh4wH9nMYUAp2uAejU,8393
14
+ universal_mcp/client/oauth.py,sha256=H7goK8FcVuITiwdXR30Am-qAWcgNylsrvt9EJHZMayA,4144
15
+ universal_mcp/client/token_store.py,sha256=eoZJbVcmkR-mAGhwk1tmDXXZnDKvjoqOVZG3nXfI8f8,1234
11
16
  universal_mcp/integrations/README.md,sha256=lTAPXO2nivcBe1q7JT6PRa6v9Ns_ZersQMIdw-nmwEA,996
12
17
  universal_mcp/integrations/__init__.py,sha256=X8iEzs02IlXfeafp6GMm-cOkg70QdjnlTRuFo24KEfo,916
13
- universal_mcp/integrations/integration.py,sha256=uKucut4AKTN2M-K8Aqsm2qtchLqlQRWMU8L287X7VyQ,13043
18
+ universal_mcp/integrations/integration.py,sha256=WtW92Awr111Vb_i_vKBvyyYvsNton3N2cX7wQ3ZXy2Y,13105
14
19
  universal_mcp/servers/README.md,sha256=ytFlgp8-LO0oogMrHkMOp8SvFTwgsKgv7XhBVZGNTbM,2284
15
20
  universal_mcp/servers/__init__.py,sha256=eBZCsaZjiEv6ZlRRslPKgurQxmpHLQyiXv2fTBygHnM,532
16
- universal_mcp/servers/server.py,sha256=7IiivuINr1CjZ6gX8xMgsoTHOevbiHa9U1pUDJGNRIE,13251
21
+ universal_mcp/servers/server.py,sha256=FqAdAAXh23_ZL7ghZvXR5quAidXxQ6m-vDQrim40tag,12429
17
22
  universal_mcp/stores/README.md,sha256=jrPh_ow4ESH4BDGaSafilhOVaN8oQ9IFlFW-j5Z5hLA,2465
18
23
  universal_mcp/stores/__init__.py,sha256=quvuwhZnpiSLuojf0NfmBx2xpaCulv3fbKtKaSCEmuM,603
19
24
  universal_mcp/stores/store.py,sha256=mxnmOVlDNrr8OKhENWDtCIfK7YeCBQcGdS6I2ogRCsU,6756
20
25
  universal_mcp/tools/README.md,sha256=RuxliOFqV1ZEyeBdj3m8UKfkxAsfrxXh-b6V4ZGAk8I,2468
21
26
  universal_mcp/tools/__init__.py,sha256=Fatza_R0qYWmNF1WQSfUZZKQFu5qf-16JhZzdmyx3KY,333
22
- universal_mcp/tools/adapters.py,sha256=n06Nz18L_4dq3iwBchNq9vHsX7whqFZ0g5iuz_0gyaM,2076
23
- universal_mcp/tools/func_metadata.py,sha256=XvdXSZEzvgbH70bc-Zu0B47CD7f_rm--vblq4en3n0Q,8181
27
+ universal_mcp/tools/adapters.py,sha256=rnicV_WBgNe0WqVG60ms0o92BoRTRqd_C9jMMndx47c,3461
28
+ universal_mcp/tools/func_metadata.py,sha256=7kUWArtUDa2Orr7VGzpwPVfyf2LM3UFA_9arMpl7Zn8,10838
24
29
  universal_mcp/tools/manager.py,sha256=ao_ovTyca8HR4uwHdL_lTWNdquxcqRx6FaLA4U1lZvQ,11242
25
- universal_mcp/tools/tools.py,sha256=qiuuLe0mCWtxXp6E5ISDDaNojCrMLfV1r5L8peFoJfg,3327
30
+ universal_mcp/tools/tools.py,sha256=8S_KzARYbG9xbyqhZcI4Wk46tXiZcWlcAMgjChXNEI4,3698
26
31
  universal_mcp/utils/__init__.py,sha256=8wi4PGWu-SrFjNJ8U7fr2iFJ1ktqlDmSKj1xYd7KSDc,41
27
- universal_mcp/utils/agentr.py,sha256=a7D3I4oKvdovoumeSdChhHBcZNrEtKHHb4L4O8Gm_l0,2870
32
+ universal_mcp/utils/agentr.py,sha256=-brwvgCZgPjvF7wPXw0QfpEsl1ekXQxmcF07-1AQMR4,3663
28
33
  universal_mcp/utils/common.py,sha256=HEZC2Mhilb8DrGXQG2tboAIw1r4veGilGWjfnPF1lyA,888
29
- universal_mcp/utils/docstring_parser.py,sha256=SKIfAiFHiqxqageayYFlpsexipy8tN7N4RLT6GIzfoQ,7672
34
+ universal_mcp/utils/docstring_parser.py,sha256=efEOE-ME7G5Jbbzpn7pN2xNuyu2M5zfZ1Tqu1lRB0Gk,8392
30
35
  universal_mcp/utils/installation.py,sha256=ItOfBFhKOh4DLz237jgAz_Fn0uOMdrKXw0n5BaUZZNs,7286
31
36
  universal_mcp/utils/singleton.py,sha256=kolHnbS9yd5C7z-tzaUAD16GgI-thqJXysNi3sZM4No,733
37
+ universal_mcp/utils/testing.py,sha256=0znYkuFi8-WjOdbwrTbNC-UpMqG3EXcGOE0wxlERh_A,1464
32
38
  universal_mcp/utils/openapi/__inti__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
39
  universal_mcp/utils/openapi/api_generator.py,sha256=FjtvbnWuI1P8W8wXuKLCirUtsqQ4HI_TuQrhpA4SqTs,4749
34
- universal_mcp/utils/openapi/api_splitter.py,sha256=S-rT3wsJWUVhU_Tv_ibNNAlQ79SfWOcU6qaa_rFfd5o,20806
40
+ universal_mcp/utils/openapi/api_splitter.py,sha256=0hoq6KMcDVA5NynDBrFjjqjDugZvaWGBdfLzvyLkFAM,20968
35
41
  universal_mcp/utils/openapi/docgen.py,sha256=DNmwlhg_-TRrHa74epyErMTRjV2nutfCQ7seb_Rq5hE,21366
36
- universal_mcp/utils/openapi/openapi.py,sha256=3DWpxVZk9LGGDLrpE7BfSjurd_FJg30oWUiaEv3wjm0,51105
42
+ universal_mcp/utils/openapi/openapi.py,sha256=8TV7L1Wm9_mi_1iy0ccf_4H39XLuZjIinBnJ56H-ggw,51197
37
43
  universal_mcp/utils/openapi/preprocessor.py,sha256=PPIM3Uu8DYi3dRKdqi9thr9ufeUgkr2K08ri1BwKpoQ,60835
38
44
  universal_mcp/utils/openapi/readme.py,sha256=R2Jp7DUXYNsXPDV6eFTkLiy7MXbSULUj1vHh4O_nB4c,2974
39
45
  universal_mcp/utils/templates/README.md.j2,sha256=Mrm181YX-o_-WEfKs01Bi2RJy43rBiq2j6fTtbWgbTA,401
40
46
  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,,
47
+ universal_mcp-0.1.23.dist-info/METADATA,sha256=DWTYTl64ic14aYdgvi7EMsQZLvCjF79u07v6FvQY9HA,12151
48
+ universal_mcp-0.1.23.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
49
+ universal_mcp-0.1.23.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
50
+ universal_mcp-0.1.23.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
51
+ universal_mcp-0.1.23.dist-info/RECORD,,