universal-mcp 0.1.15rc5__py3-none-any.whl → 0.1.15rc7__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.
Files changed (29) hide show
  1. universal_mcp/analytics.py +7 -1
  2. universal_mcp/applications/README.md +122 -0
  3. universal_mcp/applications/__init__.py +48 -46
  4. universal_mcp/applications/application.py +249 -40
  5. universal_mcp/cli.py +17 -14
  6. universal_mcp/exceptions.py +8 -0
  7. universal_mcp/integrations/integration.py +18 -2
  8. universal_mcp/stores/store.py +2 -12
  9. universal_mcp/tools/__init__.py +12 -1
  10. universal_mcp/tools/adapters.py +11 -0
  11. universal_mcp/tools/func_metadata.py +11 -1
  12. universal_mcp/tools/manager.py +165 -109
  13. universal_mcp/tools/tools.py +3 -3
  14. universal_mcp/utils/common.py +33 -0
  15. universal_mcp/utils/openapi/__inti__.py +0 -0
  16. universal_mcp/utils/{api_generator.py → openapi/api_generator.py} +1 -1
  17. universal_mcp/utils/openapi/openapi.py +930 -0
  18. universal_mcp/utils/openapi/preprocessor.py +1223 -0
  19. universal_mcp/utils/{readme.py → openapi/readme.py} +21 -31
  20. {universal_mcp-0.1.15rc5.dist-info → universal_mcp-0.1.15rc7.dist-info}/METADATA +5 -3
  21. universal_mcp-0.1.15rc7.dist-info/RECORD +44 -0
  22. universal_mcp-0.1.15rc7.dist-info/licenses/LICENSE +21 -0
  23. universal_mcp/utils/openapi.py +0 -646
  24. universal_mcp-0.1.15rc5.dist-info/RECORD +0 -39
  25. /universal_mcp/utils/{docgen.py → openapi/docgen.py} +0 -0
  26. /universal_mcp/{templates → utils/templates}/README.md.j2 +0 -0
  27. /universal_mcp/{templates → utils/templates}/api_client.py.j2 +0 -0
  28. {universal_mcp-0.1.15rc5.dist-info → universal_mcp-0.1.15rc7.dist-info}/WHEEL +0 -0
  29. {universal_mcp-0.1.15rc5.dist-info → universal_mcp-0.1.15rc7.dist-info}/entry_points.txt +0 -0
@@ -1,646 +0,0 @@
1
- import json
2
- import re
3
- from pathlib import Path
4
- from typing import Any, Literal
5
-
6
- import yaml
7
- from pydantic import BaseModel
8
-
9
-
10
- class Parameters(BaseModel):
11
- name: str
12
- identifier: str
13
- description: str = ""
14
- type: str = "string"
15
- where: Literal["path", "query", "header", "body"]
16
- required: bool
17
- example: str | None = None
18
-
19
- def __str__(self):
20
- return f"{self.name}: ({self.type})"
21
-
22
-
23
- class Method(BaseModel):
24
- name: str
25
- summary: str
26
- tags: list[str]
27
- path: str
28
- method: str
29
- path_params: list[Parameters]
30
- query_params: list[Parameters]
31
- body_params: list[Parameters]
32
- return_type: str
33
-
34
- def deduplicate_params(self):
35
- """
36
- Deduplicate parameters by name.
37
- Sometimes the same parameter is defined in multiple places, we only want to include it once.
38
- """
39
- # TODO: Implement this
40
- pass
41
-
42
- def render(self, template_dir: str, template_name: str = "method.jinja2") -> str:
43
- """
44
- Render this Method instance into source code using a Jinja2 template.
45
-
46
- Args:
47
- template_dir (str): Directory where the Jinja2 templates are located.
48
- template_name (str): Filename of the method template.
49
-
50
- Returns:
51
- str: The rendered method source code.
52
- """
53
- from jinja2 import Environment, FileSystemLoader
54
-
55
- env = Environment(
56
- loader=FileSystemLoader(template_dir),
57
- trim_blocks=True,
58
- lstrip_blocks=True,
59
- )
60
- template = env.get_template(template_name)
61
- return template.render(method=self)
62
-
63
-
64
- def convert_to_snake_case(identifier: str) -> str:
65
- """
66
- Convert a camelCase or PascalCase identifier to snake_case.
67
-
68
- Args:
69
- identifier (str): The string to convert
70
-
71
- Returns:
72
- str: The converted snake_case string
73
- """
74
- if not identifier:
75
- return identifier
76
- # Add underscore between lowercase and uppercase letters
77
- result = re.sub(r"([a-z0-9])([A-Z])", r"\1_\2", identifier)
78
- # Convert to lowercase
79
- return result.lower()
80
-
81
-
82
- def _load_and_resolve_references(path: Path):
83
- from jsonref import replace_refs
84
-
85
- # Load the schema
86
- type = "yaml" if path.suffix == ".yaml" else "json"
87
- with open(path) as f:
88
- schema = yaml.safe_load(f) if type == "yaml" else json.load(f)
89
- # Resolve references
90
- return replace_refs(schema)
91
-
92
-
93
- def _determine_return_type(operation: dict[str, Any]) -> str:
94
- """
95
- Determine the return type from the response schema.
96
-
97
- Args:
98
- operation (dict): The operation details from the schema.
99
-
100
- Returns:
101
- str: The appropriate return type annotation (list[Any], dict[str, Any], or Any)
102
- """
103
- responses = operation.get("responses", {})
104
- # Find successful response (2XX)
105
- success_response = None
106
- for code in responses:
107
- if code.startswith("2"):
108
- success_response = responses[code]
109
- break
110
-
111
- if not success_response:
112
- return "Any" # Default to Any if no success response
113
-
114
- # Check if there's content with schema
115
- if "content" in success_response:
116
- for content_type, content_info in success_response["content"].items():
117
- if content_type.startswith("application/json") and "schema" in content_info:
118
- schema = content_info["schema"]
119
-
120
- # Only determine if it's a list, dict, or unknown (Any)
121
- if schema.get("type") == "array":
122
- return "list[Any]"
123
- elif schema.get("type") == "object" or "$ref" in schema:
124
- return "dict[str, Any]"
125
-
126
- # Default to Any if unable to determine
127
- return "Any"
128
-
129
-
130
- def _determine_function_name(operation: dict[str, Any], path: str, method: str) -> str:
131
- """
132
- Determine the function name from the operation.
133
- """
134
- # Determine function name
135
- if "operationId" in operation:
136
- raw_name = operation["operationId"]
137
- cleaned_name = raw_name.replace(".", "_").replace("-", "_")
138
- func_name = convert_to_snake_case(cleaned_name)
139
- else:
140
- # Generate name from path and method
141
- path_parts = path.strip("/").split("/")
142
- name_parts = [method]
143
- for part in path_parts:
144
- if part.startswith("{") and part.endswith("}"):
145
- name_parts.append("by_" + part[1:-1])
146
- else:
147
- name_parts.append(part)
148
- func_name = "_".join(name_parts).replace("-", "_").lower()
149
-
150
- # Only fix isolated 'a' and 'an' as articles, not when they're part of words
151
- func_name = re.sub(
152
- r"_a([^_a-z])", r"_a_\1", func_name
153
- ) # Fix for patterns like retrieve_ablock -> retrieve_a_block
154
- func_name = re.sub(
155
- r"_a$", r"_a", func_name
156
- ) # Don't change if 'a' is at the end of the name
157
- func_name = re.sub(
158
- r"_an([^_a-z])", r"_an_\1", func_name
159
- ) # Fix for patterns like create_anitem -> create_an_item
160
- func_name = re.sub(
161
- r"_an$", r"_an", func_name
162
- ) # Don't change if 'an' is at the end of the name
163
- return func_name
164
-
165
-
166
- def _generate_path_params(path: str) -> list[Parameters]:
167
- path_params_in_url = re.findall(r"{([^}]+)}", path)
168
- parameters = []
169
- for param in path_params_in_url:
170
- try:
171
- parameters.append(
172
- Parameters(
173
- name=param.replace("-", "_"),
174
- identifier=param,
175
- description=param,
176
- type="string",
177
- where="path",
178
- required=True,
179
- )
180
- )
181
- except Exception as e:
182
- print(f"Error generating path parameters {param}: {e}")
183
- raise e
184
- return parameters
185
-
186
-
187
- def _generate_url(path: str, path_params: list[Parameters]):
188
- formatted_path = path
189
- for param in path_params:
190
- formatted_path = formatted_path.replace(
191
- f"{{{param.identifier}}}", f"{{{param.name}}}"
192
- )
193
- return formatted_path
194
-
195
-
196
- def _generate_query_params(operation: dict[str, Any]) -> list[Parameters]:
197
- query_params = []
198
- for param in operation.get("parameters", []):
199
- name = param.get("name")
200
- description = param.get("description", "")
201
- type = param.get("type")
202
- where = param.get("in")
203
- required = param.get("required")
204
- if where == "query":
205
- parameter = Parameters(
206
- name=name.replace("-", "_"),
207
- identifier=name,
208
- description=description,
209
- type=type,
210
- where=where,
211
- required=required,
212
- )
213
- query_params.append(parameter)
214
- return query_params
215
-
216
-
217
- def _generate_body_params(operation: dict[str, Any]) -> list[Parameters]:
218
- body_params = []
219
- request_body = operation.get("requestBody", {})
220
- required = request_body.get("required", False)
221
- content = request_body.get("content", {})
222
- json_content = content.get("application/json", {})
223
- schema = json_content.get("schema", {})
224
- properties = schema.get("properties", {})
225
- for param in properties:
226
- body_params.append(
227
- Parameters(
228
- name=param,
229
- identifier=param,
230
- description=param,
231
- type="string",
232
- where="body",
233
- required=required,
234
- )
235
- )
236
- return body_params
237
-
238
-
239
- def _generate_method_code(path, method, operation):
240
- """
241
- Generate the code for a single API method.
242
-
243
- Args:
244
- path (str): The API path (e.g., '/users/{user_id}').
245
- method (str): The HTTP method (e.g., 'get').
246
- operation (dict): The operation details from the schema.
247
- full_schema (dict): The complete OpenAPI schema, used for reference resolution.
248
- tool_name (str, optional): The name of the tool/app to prefix the function name with.
249
-
250
- Returns:
251
- tuple: (method_code, func_name) - The Python code for the method and its name.
252
- """
253
-
254
- func_name = _determine_function_name(operation, path, method)
255
- operation.get("summary", "")
256
- operation.get("tags", [])
257
- # Extract path parameters from the URL path
258
- path_params = _generate_path_params(path)
259
- query_params = _generate_query_params(operation)
260
- _generate_body_params(operation)
261
- return_type = _determine_return_type(operation)
262
- # gen_method = Method(name=func_name, summary=summary, tags=tags, path=path, method=method, path_params=path_params, query_params=query_params, body_params=body_params, return_type=return_type)
263
- # logger.info(f"Generated method: {gen_method.model_dump()}")
264
- # return method.render(template_dir="templates", template_name="method.jinja2")
265
-
266
- has_body = "requestBody" in operation
267
- body_required = has_body and operation["requestBody"].get("required", False)
268
-
269
- # Check if the requestBody has actual content or is empty
270
- has_empty_body = False
271
- if has_body:
272
- request_body_content = operation["requestBody"].get("content", {})
273
- if not request_body_content or all(
274
- not content for content_type, content in request_body_content.items()
275
- ):
276
- has_empty_body = True
277
- else:
278
- # Handle empty properties with additionalProperties:true
279
- for content_type, content in request_body_content.items():
280
- if content_type.startswith("application/json") and "schema" in content:
281
- schema = content["schema"]
282
-
283
- # Check if properties is empty and additionalProperties is true
284
- if (
285
- schema.get("type") == "object"
286
- and schema.get("additionalProperties", False) is True
287
- ):
288
- properties = schema.get("properties", {})
289
- if not properties or len(properties) == 0:
290
- has_empty_body = True
291
-
292
- # Extract request body schema properties and required fields
293
- required_fields = []
294
- request_body_properties = {}
295
- is_array_body = False
296
-
297
- if has_body:
298
- for content_type, content in (
299
- operation["requestBody"].get("content", {}).items()
300
- ):
301
- if content_type.startswith("application/json") and "schema" in content:
302
- schema = content["schema"]
303
-
304
- # Check if the schema is an array type
305
- if schema.get("type") == "array":
306
- is_array_body = True
307
- schema.get("items", {})
308
-
309
- else:
310
- # Extract required fields from schema
311
- if "required" in schema:
312
- required_fields = schema["required"]
313
- # Extract properties from schema
314
- if "properties" in schema:
315
- request_body_properties = schema["properties"]
316
-
317
- # Handle schemas with empty properties but additionalProperties: true
318
- # by treating them similar to empty bodies
319
- if (
320
- not request_body_properties or len(request_body_properties) == 0
321
- ) and schema.get("additionalProperties") is True:
322
- has_empty_body = True
323
-
324
- # Build function arguments
325
- required_args = []
326
- optional_args = []
327
-
328
- # Add path parameters
329
- for param in path_params:
330
- if param.name not in required_args:
331
- required_args.append(param.name)
332
-
333
- for param in query_params:
334
- param_name = param["name"]
335
- # Handle parameters with square brackets and hyphens by converting to valid Python identifiers
336
- param_identifier = (
337
- param_name.replace("[", "_").replace("]", "").replace("-", "_")
338
- )
339
- if param_identifier not in required_args and param_identifier not in [
340
- p.split("=")[0] for p in optional_args
341
- ]:
342
- if param.get("required", False):
343
- required_args.append(param_identifier)
344
- else:
345
- optional_args.append(f"{param_identifier}=None")
346
-
347
- # Handle array type request body differently
348
- request_body_params = []
349
- if has_body:
350
- if is_array_body:
351
- # For array request bodies, add a single parameter for the entire array
352
- array_param_name = "items"
353
- # Try to get a better name from the operation or path
354
- if func_name.endswith("_list_input"):
355
- array_param_name = func_name.replace("_list_input", "")
356
- elif "List" in func_name:
357
- array_param_name = func_name.split("List")[0].lower() + "_list"
358
-
359
- # Make the array parameter required if the request body is required
360
- if body_required:
361
- required_args.append(array_param_name)
362
- else:
363
- optional_args.append(f"{array_param_name}=None")
364
-
365
- # Remember this is an array param
366
- request_body_params = [array_param_name]
367
- elif request_body_properties:
368
- # For object request bodies, add individual properties as parameters
369
- for prop_name in request_body_properties:
370
- if prop_name in required_fields:
371
- request_body_params.append(prop_name)
372
- if prop_name not in required_args:
373
- required_args.append(prop_name)
374
- else:
375
- request_body_params.append(prop_name)
376
- if f"{prop_name}=None" not in optional_args:
377
- optional_args.append(f"{prop_name}=None")
378
-
379
- # If request body is present but empty (content: {}), add a generic request_body parameter
380
- if has_empty_body and "request_body=None" not in optional_args:
381
- optional_args.append("request_body=None")
382
-
383
- # Combine required and optional arguments
384
- args = required_args + optional_args
385
-
386
- # Determine return type
387
- return_type = _determine_return_type(operation)
388
- if args:
389
- signature = f" def {func_name}(self, {', '.join(args)}) -> {return_type}:"
390
- else:
391
- signature = f" def {func_name}(self) -> {return_type}:"
392
-
393
- # Build method body
394
- body_lines = []
395
-
396
- for param in path_params:
397
- body_lines.append(f" if {param.name} is None:")
398
- body_lines.append(
399
- f" raise ValueError(\"Missing required parameter '{param.identifier}'\")" # Use original name in error
400
- )
401
-
402
- # Build request body (handle array and object types differently)
403
- if has_body:
404
- if is_array_body:
405
- # For array request bodies, use the array parameter directly
406
- body_lines.append(" # Use items array directly as request body")
407
- body_lines.append(f" request_body = {request_body_params[0]}")
408
- elif request_body_properties:
409
- # For object request bodies, build the request body from individual parameters
410
-
411
- body_lines.append(" request_body = {")
412
-
413
- for prop_name in request_body_params:
414
- # Only include non-None values in the request body
415
- body_lines.append(f" '{prop_name}': {prop_name},")
416
-
417
- body_lines.append(" }")
418
-
419
- body_lines.append(
420
- " request_body = {k: v for k, v in request_body.items() if v is not None}"
421
- )
422
-
423
- # Format URL directly with path parameters
424
- url = _generate_url(path, path_params)
425
- url_line = f' url = f"{{self.base_url}}{url}"'
426
- body_lines.append(url_line)
427
-
428
- # Build query parameters, handling square brackets in parameter names
429
- if query_params:
430
- query_params_items = []
431
- for param in query_params:
432
- param_name = param.name
433
- param_identifier = (
434
- param_name.replace("[", "_").replace("]", "").replace("-", "_")
435
- )
436
- query_params_items.append(f"('{param_name}', {param_identifier})")
437
- body_lines.append(
438
- f" query_params = {{k: v for k, v in [{', '.join(query_params_items)}] if v is not None}}"
439
- )
440
- else:
441
- body_lines.append(" query_params = {}")
442
-
443
- # Make HTTP request using the proper method
444
- method_lower = method.lower()
445
-
446
- # Determine what to use as the request body argument
447
- if has_empty_body:
448
- request_body_arg = "request_body"
449
- elif not has_body:
450
- request_body_arg = "{}"
451
- else:
452
- request_body_arg = "request_body"
453
-
454
- if method_lower == "get":
455
- body_lines.append(" response = self._get(url, params=query_params)")
456
- elif method_lower == "post":
457
- body_lines.append(
458
- f" response = self._post(url, data={request_body_arg}, params=query_params)"
459
- )
460
- elif method_lower == "put":
461
- body_lines.append(
462
- f" response = self._put(url, data={request_body_arg}, params=query_params)"
463
- )
464
- elif method_lower == "patch":
465
- body_lines.append(
466
- f" response = self._patch(url, data={request_body_arg}, params=query_params)"
467
- )
468
- elif method_lower == "delete":
469
- body_lines.append(" response = self._delete(url, params=query_params)")
470
- else:
471
- body_lines.append(
472
- f" response = self._{method_lower}(url, data={request_body_arg}, params=query_params)"
473
- )
474
-
475
- # Handle response
476
- body_lines.append(" response.raise_for_status()")
477
- body_lines.append(" return response.json()")
478
-
479
- method_code = signature + "\n" + "\n".join(body_lines)
480
- return method_code, func_name
481
-
482
-
483
- def load_schema(path: Path):
484
- return _load_and_resolve_references(path)
485
-
486
-
487
- def generate_api_client(schema, class_name: str | None = None):
488
- """
489
- Generate a Python API client class from an OpenAPI schema.
490
-
491
- Args:
492
- schema (dict): The OpenAPI schema as a dictionary.
493
-
494
- Returns:
495
- str: A string containing the Python code for the API client class.
496
- """
497
- methods = []
498
- method_names = []
499
-
500
- # Extract API info for naming and base URL
501
- info = schema.get("info", {})
502
- api_title = info.get("title", "API")
503
-
504
- # Get base URL from servers array if available
505
- base_url = ""
506
- servers = schema.get("servers", [])
507
- if servers and isinstance(servers, list) and "url" in servers[0]:
508
- base_url = servers[0]["url"].rstrip("/")
509
-
510
- # Create a clean class name from API title
511
- if api_title:
512
- # Convert API title to a clean class name
513
- if class_name:
514
- clean_name = (
515
- class_name.capitalize()[:-3]
516
- if class_name.endswith("App")
517
- else class_name.capitalize()
518
- )
519
- else:
520
- base_name = "".join(word.capitalize() for word in api_title.split())
521
- clean_name = "".join(c for c in base_name if c.isalnum())
522
- class_name = f"{clean_name}App"
523
-
524
- # Extract tool name - remove spaces and convert to lowercase
525
- tool_name = api_title.lower()
526
-
527
- # Remove version numbers (like 3.0, v1, etc.)
528
- tool_name = re.sub(r"\s*v?\d+(\.\d+)*", "", tool_name)
529
-
530
- # Remove common words that aren't needed
531
- common_words = ["api", "openapi", "open", "swagger", "spec", "specification"]
532
- for word in common_words:
533
- tool_name = tool_name.replace(word, "")
534
-
535
- # Remove spaces, hyphens, underscores
536
- tool_name = tool_name.replace(" ", "").replace("-", "").replace("_", "")
537
-
538
- # Remove any non-alphanumeric characters
539
- tool_name = "".join(c for c in tool_name if c.isalnum())
540
-
541
- # If empty (after cleaning), use generic name
542
- if not tool_name:
543
- tool_name = "api"
544
- else:
545
- class_name = "APIClient"
546
- tool_name = "api"
547
-
548
- # Iterate over paths and their operations
549
- for path, path_info in schema.get("paths", {}).items():
550
- for method in path_info:
551
- if method in ["get", "post", "put", "delete", "patch", "options", "head"]:
552
- operation = path_info[method]
553
- method_code, func_name = _generate_method_code(path, method, operation)
554
- methods.append(method_code)
555
- method_names.append(func_name)
556
-
557
- # Generate list_tools method with all the function names
558
- tools_list = ",\n ".join([f"self.{name}" for name in method_names])
559
- list_tools_method = f""" def list_tools(self):
560
- return [
561
- {tools_list}
562
- ]"""
563
-
564
- # Generate class imports
565
- imports = [
566
- "from typing import Any",
567
- "from universal_mcp.applications import APIApplication",
568
- "from universal_mcp.integrations import Integration",
569
- ]
570
-
571
- # Construct the class code
572
- class_code = (
573
- "\n".join(imports) + "\n\n"
574
- f"class {class_name}(APIApplication):\n"
575
- f" def __init__(self, integration: Integration = None, **kwargs) -> None:\n"
576
- f" super().__init__(name='{class_name.lower()}', integration=integration, **kwargs)\n"
577
- f' self.base_url = "{base_url}"\n\n'
578
- + "\n\n".join(methods)
579
- + "\n\n"
580
- + list_tools_method
581
- + "\n"
582
- )
583
- return class_code
584
-
585
-
586
- # Example usage
587
- if __name__ == "__main__":
588
- # Sample OpenAPI schema
589
- schema = {
590
- "paths": {
591
- "/users": {
592
- "get": {
593
- "summary": "Get a list of users",
594
- "parameters": [
595
- {
596
- "name": "limit",
597
- "in": "query",
598
- "required": False,
599
- "schema": {"type": "integer"},
600
- }
601
- ],
602
- "responses": {
603
- "200": {
604
- "description": "A list of users",
605
- "content": {
606
- "application/json": {"schema": {"type": "array"}}
607
- },
608
- }
609
- },
610
- },
611
- "post": {
612
- "summary": "Create a user",
613
- "requestBody": {
614
- "required": True,
615
- "content": {
616
- "application/json": {
617
- "schema": {
618
- "type": "object",
619
- "properties": {"name": {"type": "string"}},
620
- }
621
- }
622
- },
623
- },
624
- "responses": {"201": {"description": "User created"}},
625
- },
626
- },
627
- "/users/{user_id}": {
628
- "get": {
629
- "summary": "Get a user by ID",
630
- "parameters": [
631
- {
632
- "name": "user_id",
633
- "in": "path",
634
- "required": True,
635
- "schema": {"type": "string"},
636
- }
637
- ],
638
- "responses": {"200": {"description": "User details"}},
639
- }
640
- },
641
- }
642
- }
643
-
644
- schema = load_schema("openapi.yaml")
645
- code = generate_api_client(schema)
646
- print(code)
@@ -1,39 +0,0 @@
1
- universal_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
- universal_mcp/analytics.py,sha256=aGCg0Okpcy06W70qCA9I8_ySOiCgAtzJAIWAdhBsOeA,2212
3
- universal_mcp/cli.py,sha256=XujnWHxz5NVxQjwu2BRHUcGC--Vp98M32n8hTsEqayI,8689
4
- universal_mcp/config.py,sha256=xqz5VNxtk6Clepjw-aK-HrgMFQLzFuxiDb1fuHGpbxE,3717
5
- universal_mcp/exceptions.py,sha256=WApedvzArNujD0gZfUofYBxjQo97ZDJLqDibtLWZoRk,373
6
- universal_mcp/logger.py,sha256=JtAC8ImO74lvt5xepV3W5BIz-u3nZOAY1ecdhmQhub0,2081
7
- universal_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- universal_mcp/applications/__init__.py,sha256=ZjV0P55Ej0vjIQ_bCSNatWTX-VphDJ6OGePWBu3bu3U,3196
9
- universal_mcp/applications/application.py,sha256=0eC9D4HHRwIGpuFusaCxTZ0u64U68VbBpRSxjxGB5y8,8152
10
- universal_mcp/integrations/README.md,sha256=lTAPXO2nivcBe1q7JT6PRa6v9Ns_ZersQMIdw-nmwEA,996
11
- universal_mcp/integrations/__init__.py,sha256=tg6Yk59AEhwPsrTp0hZQ3NBfmJuYGu2sNCOXuph-h9k,922
12
- universal_mcp/integrations/integration.py,sha256=genBiaWuzCs-XCf3UD1j8PQYyGU3GiVO4uupSdJRHnA,12601
13
- universal_mcp/servers/README.md,sha256=ytFlgp8-LO0oogMrHkMOp8SvFTwgsKgv7XhBVZGNTbM,2284
14
- universal_mcp/servers/__init__.py,sha256=etFrqXFODIl9oGeqQS-aUxzw4J6pjzYjHl4VPvQaR3A,508
15
- universal_mcp/servers/server.py,sha256=0oJQQUiwPdG2q79tzsVv3WPMV5YIFbF14PRvBF-SxMQ,9395
16
- universal_mcp/stores/README.md,sha256=jrPh_ow4ESH4BDGaSafilhOVaN8oQ9IFlFW-j5Z5hLA,2465
17
- universal_mcp/stores/__init__.py,sha256=quvuwhZnpiSLuojf0NfmBx2xpaCulv3fbKtKaSCEmuM,603
18
- universal_mcp/stores/store.py,sha256=lYaFd-9YKC404BPeqzNw_Xm3ziQjksZyvQtaW1yd9FM,6900
19
- universal_mcp/templates/README.md.j2,sha256=Mrm181YX-o_-WEfKs01Bi2RJy43rBiq2j6fTtbWgbTA,401
20
- universal_mcp/templates/api_client.py.j2,sha256=972Im7LNUAq3yZTfwDcgivnb-b8u6_JLKWXwoIwXXXQ,908
21
- universal_mcp/tools/README.md,sha256=RuxliOFqV1ZEyeBdj3m8UKfkxAsfrxXh-b6V4ZGAk8I,2468
22
- universal_mcp/tools/__init__.py,sha256=GgK8CAxskkoif4WCaXsjs4zTgqN9VcVMyQa2G2LaYJ4,92
23
- universal_mcp/tools/adapters.py,sha256=oCF042vsOKP9bUT6000YbYEmOpOiroXEnMdDMs847CU,1235
24
- universal_mcp/tools/func_metadata.py,sha256=bxcwwKkHcITZQXt1eal7rFr5NGCrC0SLSGZwOsyCVKo,7985
25
- universal_mcp/tools/manager.py,sha256=Ne37cLlyk0oku0iUXbc6BmzNH-3a98oWcpe_nlyZRtU,7516
26
- universal_mcp/tools/tools.py,sha256=2ddZdi618xTs36iM062Pc6cnIbVP17L4NRBMNiFuv1k,3306
27
- universal_mcp/utils/__init__.py,sha256=8wi4PGWu-SrFjNJ8U7fr2iFJ1ktqlDmSKj1xYd7KSDc,41
28
- universal_mcp/utils/agentr.py,sha256=3sobve7Odk8pIAZm3RHTX4Rc21rkBClcXQgXXslbSUA,3490
29
- universal_mcp/utils/api_generator.py,sha256=x3LkJm3tXgl2qVQq-ZQW86w7IqbErEdFTfwBP3aOwyI,4763
30
- universal_mcp/utils/docgen.py,sha256=zPmZ-b-fK6xk-dwHEx2hwShN-iquPD_O15CGuPwlj2k,21870
31
- universal_mcp/utils/docstring_parser.py,sha256=j7aE-LLnBOPTJI0qXayf0NlYappzxICv5E_hUPNmAlc,11459
32
- universal_mcp/utils/installation.py,sha256=1n5X_aIiuY8WNQn6Oji_gZ-aiRmNXxrg-qYRv-pGjxw,10195
33
- universal_mcp/utils/openapi.py,sha256=fd4rfiT_hFmEHtzGg-tM3FMSAn-AT0EskKfOc71--jE,23317
34
- universal_mcp/utils/readme.py,sha256=Q4E3RidWVg0ngYBGZCKcoA4eX6wlkCpvU-clU0E7Q20,3305
35
- universal_mcp/utils/singleton.py,sha256=kolHnbS9yd5C7z-tzaUAD16GgI-thqJXysNi3sZM4No,733
36
- universal_mcp-0.1.15rc5.dist-info/METADATA,sha256=OXwhLXpEIbkAsrFWmwx6aO_nhtHjjhif2yN-2kthT_M,9833
37
- universal_mcp-0.1.15rc5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
38
- universal_mcp-0.1.15rc5.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
39
- universal_mcp-0.1.15rc5.dist-info/RECORD,,
File without changes