universal-mcp 0.1.20rc2__py3-none-any.whl → 0.1.21rc2__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.
- universal_mcp/servers/server.py +2 -1
- universal_mcp/tools/func_metadata.py +4 -2
- universal_mcp/utils/openapi/openapi.py +114 -8
- {universal_mcp-0.1.20rc2.dist-info → universal_mcp-0.1.21rc2.dist-info}/METADATA +1 -1
- {universal_mcp-0.1.20rc2.dist-info → universal_mcp-0.1.21rc2.dist-info}/RECORD +8 -8
- {universal_mcp-0.1.20rc2.dist-info → universal_mcp-0.1.21rc2.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.20rc2.dist-info → universal_mcp-0.1.21rc2.dist-info}/entry_points.txt +0 -0
- {universal_mcp-0.1.20rc2.dist-info → universal_mcp-0.1.21rc2.dist-info}/licenses/LICENSE +0 -0
universal_mcp/servers/server.py
CHANGED
@@ -13,6 +13,7 @@ from universal_mcp.exceptions import ConfigurationError, ToolError
|
|
13
13
|
from universal_mcp.integrations import AgentRIntegration, integration_from_config
|
14
14
|
from universal_mcp.stores import BaseStore, store_from_config
|
15
15
|
from universal_mcp.tools import ToolManager
|
16
|
+
from universal_mcp.tools.adapters import ToolFormat
|
16
17
|
from universal_mcp.utils.agentr import AgentrClient
|
17
18
|
|
18
19
|
|
@@ -56,7 +57,7 @@ class BaseServer(FastMCP):
|
|
56
57
|
Returns:
|
57
58
|
List of tool definitions
|
58
59
|
"""
|
59
|
-
return self._tool_manager.list_tools(format=
|
60
|
+
return self._tool_manager.list_tools(format=ToolFormat.MCP)
|
60
61
|
|
61
62
|
def _format_tool_result(self, result: Any) -> list[TextContent]:
|
62
63
|
"""Format tool result into TextContent list.
|
@@ -192,8 +192,10 @@ class FuncMetadata(BaseModel):
|
|
192
192
|
_get_typed_annotation(annotation, globalns),
|
193
193
|
param.default if param.default is not inspect.Parameter.empty else PydanticUndefined,
|
194
194
|
)
|
195
|
-
if not field_info.title
|
196
|
-
field_info.title =
|
195
|
+
if not field_info.title:
|
196
|
+
field_info.title = param.name
|
197
|
+
if not field_info.description and arg_description and arg_description.get(param.name):
|
198
|
+
field_info.description = arg_description.get(param.name)
|
197
199
|
dynamic_pydantic_model_params[param.name] = (
|
198
200
|
field_info.annotation,
|
199
201
|
field_info,
|
@@ -19,6 +19,7 @@ class Parameters(BaseModel):
|
|
19
19
|
required: bool
|
20
20
|
example: str | None = None
|
21
21
|
is_file: bool = False
|
22
|
+
schema: dict = {}
|
22
23
|
|
23
24
|
def __str__(self):
|
24
25
|
return f"{self.name}: ({self.type})"
|
@@ -79,6 +80,61 @@ def convert_to_snake_case(identifier: str) -> str:
|
|
79
80
|
return result.strip("_").lower()
|
80
81
|
|
81
82
|
|
83
|
+
# Added new recursive type mapper
|
84
|
+
def _openapi_type_to_python_type(schema: dict, required: bool = True) -> str:
|
85
|
+
"""
|
86
|
+
Recursively map OpenAPI schema to Python type hints.
|
87
|
+
"""
|
88
|
+
openapi_type = schema.get("type")
|
89
|
+
|
90
|
+
|
91
|
+
if "$ref" in schema and not openapi_type:
|
92
|
+
py_type = "dict[str, Any]"
|
93
|
+
elif openapi_type == "array":
|
94
|
+
items_schema = schema.get("items", {})
|
95
|
+
item_type = _openapi_type_to_python_type(items_schema, required=True)
|
96
|
+
py_type = f"List[{item_type}]"
|
97
|
+
elif openapi_type == "object":
|
98
|
+
|
99
|
+
if schema.get("format") in ["binary", "byte"]:
|
100
|
+
py_type = "bytes"
|
101
|
+
else:
|
102
|
+
|
103
|
+
if "additionalProperties" in schema and isinstance(schema["additionalProperties"], dict):
|
104
|
+
additional_props_schema = schema["additionalProperties"]
|
105
|
+
|
106
|
+
value_type = _openapi_type_to_python_type(additional_props_schema, required=True)
|
107
|
+
py_type = f"dict[str, {value_type}]"
|
108
|
+
elif not schema.get("properties") and not schema.get("allOf") and not schema.get("oneOf") and not schema.get("anyOf"):
|
109
|
+
|
110
|
+
py_type = "dict[str, Any]"
|
111
|
+
else:
|
112
|
+
|
113
|
+
py_type = "dict[str, Any]"
|
114
|
+
elif openapi_type == "integer":
|
115
|
+
py_type = "int"
|
116
|
+
elif openapi_type == "number":
|
117
|
+
py_type = "float"
|
118
|
+
elif openapi_type == "boolean":
|
119
|
+
py_type = "bool"
|
120
|
+
elif openapi_type == "string":
|
121
|
+
if schema.get("format") in ["binary", "byte"]:
|
122
|
+
py_type = "bytes"
|
123
|
+
elif schema.get("format") == "date" or schema.get("format") == "date-time":
|
124
|
+
py_type = "str"
|
125
|
+
else:
|
126
|
+
py_type = "str"
|
127
|
+
else:
|
128
|
+
|
129
|
+
py_type = "Any"
|
130
|
+
|
131
|
+
if not required:
|
132
|
+
if py_type.startswith("Optional[") and py_type.endswith("]"):
|
133
|
+
return py_type
|
134
|
+
return f"Optional[{py_type}]"
|
135
|
+
return py_type
|
136
|
+
|
137
|
+
|
82
138
|
def _sanitize_identifier(name: str | None) -> str:
|
83
139
|
"""Cleans a string to be a valid Python identifier.
|
84
140
|
|
@@ -235,6 +291,7 @@ def _generate_path_params(path: str) -> list[Parameters]:
|
|
235
291
|
where="path",
|
236
292
|
required=True,
|
237
293
|
example=None,
|
294
|
+
schema={"type": "string"}
|
238
295
|
)
|
239
296
|
)
|
240
297
|
except Exception as e:
|
@@ -283,6 +340,7 @@ def _generate_query_params(operation: dict[str, Any]) -> list[Parameters]:
|
|
283
340
|
where=where,
|
284
341
|
required=required,
|
285
342
|
example=str(example_value) if example_value is not None else None,
|
343
|
+
schema=param_schema if param_schema else {"type": type_value}
|
286
344
|
)
|
287
345
|
query_params.append(parameter)
|
288
346
|
return query_params
|
@@ -318,7 +376,8 @@ def _generate_body_params(schema_to_process: dict[str, Any] | None, overall_body
|
|
318
376
|
where="body",
|
319
377
|
required=param_required,
|
320
378
|
example=str(param_example) if param_example is not None else None,
|
321
|
-
is_file=current_is_file
|
379
|
+
is_file=current_is_file,
|
380
|
+
schema=param_schema_details
|
322
381
|
)
|
323
382
|
)
|
324
383
|
# print(f"[DEBUG] Final body_params list generated: {body_params}") # DEBUG
|
@@ -497,21 +556,34 @@ def _generate_method_code(path, method, operation):
|
|
497
556
|
required_args = []
|
498
557
|
optional_args = []
|
499
558
|
|
559
|
+
# Arguments for the function signature with type hints
|
560
|
+
signature_required_args_typed = []
|
561
|
+
signature_optional_args_typed = []
|
562
|
+
|
500
563
|
# Process Path Parameters (Highest Priority)
|
501
564
|
for param in path_params:
|
502
565
|
# Path param names are sanitized but not suffixed by aliasing.
|
503
566
|
if param.name not in required_args: # param.name is the sanitized name
|
504
567
|
required_args.append(param.name)
|
568
|
+
# For signature with types
|
569
|
+
param_py_type = _openapi_type_to_python_type(param.schema, required=True)
|
570
|
+
signature_required_args_typed.append(f"{param.name}: {param_py_type}")
|
505
571
|
|
506
572
|
# Process Query Parameters
|
507
573
|
for param in query_params: # param.name is the potentially aliased name (e.g., id_query)
|
508
574
|
arg_name_for_sig = param.name
|
509
575
|
current_arg_names_set = set(required_args) | {arg.split("=")[0] for arg in optional_args}
|
576
|
+
|
577
|
+
# For signature with types
|
578
|
+
param_py_type = _openapi_type_to_python_type(param.schema, required=param.required)
|
579
|
+
|
510
580
|
if arg_name_for_sig not in current_arg_names_set:
|
511
581
|
if param.required:
|
512
582
|
required_args.append(arg_name_for_sig)
|
583
|
+
signature_required_args_typed.append(f"{arg_name_for_sig}: {param_py_type}")
|
513
584
|
else:
|
514
585
|
optional_args.append(f"{arg_name_for_sig}=None")
|
586
|
+
signature_optional_args_typed.append(f"{arg_name_for_sig}: {param_py_type} = None")
|
515
587
|
|
516
588
|
# Process Body Parameters / Request Body
|
517
589
|
# This list tracks the *final* names of parameters in the signature that come from the request body,
|
@@ -539,10 +611,16 @@ def _generate_method_code(path, method, operation):
|
|
539
611
|
final_array_param_name = f"{array_param_name_base}_body_{counter}"
|
540
612
|
counter += 1
|
541
613
|
|
614
|
+
# For signature with types
|
615
|
+
# The schema for an array body is body_schema_to_use itself
|
616
|
+
array_body_py_type = _openapi_type_to_python_type(body_schema_to_use, required=body_required)
|
617
|
+
|
542
618
|
if body_required:
|
543
619
|
required_args.append(final_array_param_name)
|
620
|
+
signature_required_args_typed.append(f"{final_array_param_name}: {array_body_py_type}")
|
544
621
|
else:
|
545
622
|
optional_args.append(f"{final_array_param_name}=None")
|
623
|
+
signature_optional_args_typed.append(f"{final_array_param_name}: {array_body_py_type} = None")
|
546
624
|
final_request_body_arg_names_for_signature.append(final_array_param_name)
|
547
625
|
|
548
626
|
# New: Handle raw body parameter (if body_params is empty but body is expected and not array/empty JSON)
|
@@ -563,10 +641,22 @@ def _generate_method_code(path, method, operation):
|
|
563
641
|
counter += 1
|
564
642
|
raw_body_param_name = temp_raw_body_name
|
565
643
|
|
644
|
+
# For signature with types
|
645
|
+
# Determine type based on selected_content_type for raw body
|
646
|
+
raw_body_schema_for_type = {"type": "string", "format": "binary"} # Default to bytes
|
647
|
+
if selected_content_type and "text" in selected_content_type:
|
648
|
+
raw_body_schema_for_type = {"type": "string"}
|
649
|
+
elif selected_content_type and selected_content_type.startswith("image/"):
|
650
|
+
raw_body_schema_for_type = {"type": "string", "format": "binary"} # image is bytes
|
651
|
+
|
652
|
+
raw_body_py_type = _openapi_type_to_python_type(raw_body_schema_for_type, required=body_required)
|
653
|
+
|
566
654
|
if body_required: # If the raw body itself is required
|
567
655
|
required_args.append(raw_body_param_name)
|
656
|
+
signature_required_args_typed.append(f"{raw_body_param_name}: {raw_body_py_type}")
|
568
657
|
else:
|
569
658
|
optional_args.append(f"{raw_body_param_name}=None")
|
659
|
+
signature_optional_args_typed.append(f"{raw_body_param_name}: {raw_body_py_type} = None")
|
570
660
|
final_request_body_arg_names_for_signature.append(raw_body_param_name)
|
571
661
|
|
572
662
|
elif body_params: # Object body with discernible properties
|
@@ -575,12 +665,18 @@ def _generate_method_code(path, method, operation):
|
|
575
665
|
|
576
666
|
# Defensive check against already added args
|
577
667
|
current_arg_names_set_loop = set(required_args) | {arg.split("=")[0] for arg in optional_args}
|
668
|
+
|
669
|
+
# For signature with types
|
670
|
+
param_py_type = _openapi_type_to_python_type(param.schema, required=param.required)
|
671
|
+
|
578
672
|
if arg_name_for_sig not in current_arg_names_set_loop:
|
579
673
|
if param.required:
|
580
674
|
required_args.append(arg_name_for_sig)
|
675
|
+
signature_required_args_typed.append(f"{arg_name_for_sig}: {param_py_type}")
|
581
676
|
else:
|
582
677
|
# Parameters model does not store schema 'default'. Optional params default to None.
|
583
678
|
optional_args.append(f"{arg_name_for_sig}=None")
|
679
|
+
signature_optional_args_typed.append(f"{arg_name_for_sig}: {param_py_type} = None")
|
584
680
|
final_request_body_arg_names_for_signature.append(arg_name_for_sig)
|
585
681
|
|
586
682
|
|
@@ -599,17 +695,26 @@ def _generate_method_code(path, method, operation):
|
|
599
695
|
final_empty_body_param_name = f"{empty_body_param_name_base}_body_{counter}"
|
600
696
|
counter += 1
|
601
697
|
|
602
|
-
#
|
698
|
+
# For signature with types
|
699
|
+
# Empty body usually implies an empty JSON object, so dict or Optional[dict]
|
700
|
+
empty_body_py_type = _openapi_type_to_python_type({"type": "object"}, required=False)
|
603
701
|
|
604
702
|
if final_empty_body_param_name not in (set(required_args) | {arg.split("=")[0] for arg in optional_args}):
|
605
703
|
optional_args.append(f"{final_empty_body_param_name}=None")
|
704
|
+
# Add to typed signature list
|
705
|
+
signature_optional_args_typed.append(f"{final_empty_body_param_name}: {empty_body_py_type} = None")
|
706
|
+
|
606
707
|
# Track for docstring, even if it's just 'request_body' or 'request_body_body'
|
607
708
|
if final_empty_body_param_name not in final_request_body_arg_names_for_signature:
|
608
709
|
final_request_body_arg_names_for_signature.append(final_empty_body_param_name)
|
609
710
|
|
610
|
-
# Combine required and optional arguments
|
711
|
+
# Combine required and optional arguments FOR DOCSTRING (as before, without types)
|
611
712
|
args = required_args + optional_args
|
612
|
-
print(f"[DEBUG] Final combined args for
|
713
|
+
print(f"[DEBUG] Final combined args for DOCSTRING: {args}") # DEBUG
|
714
|
+
|
715
|
+
# Combine required and optional arguments FOR SIGNATURE (with types)
|
716
|
+
signature_args_combined_typed = signature_required_args_typed + signature_optional_args_typed
|
717
|
+
print(f"[DEBUG] Final combined args for SIGNATURE: {signature_args_combined_typed}") # DEBUG
|
613
718
|
|
614
719
|
# ----- Build Docstring -----
|
615
720
|
# This section constructs the entire docstring for the generated method,
|
@@ -742,8 +847,9 @@ def _generate_method_code(path, method, operation):
|
|
742
847
|
# ----- End Build Docstring -----
|
743
848
|
|
744
849
|
# --- Construct Method Signature String ---
|
745
|
-
|
746
|
-
|
850
|
+
# Use signature_args_combined_typed for the signature
|
851
|
+
if signature_args_combined_typed:
|
852
|
+
signature = f" def {func_name}(self, {', '.join(signature_args_combined_typed)}) -> {return_type}:"
|
747
853
|
else:
|
748
854
|
signature = f" def {func_name}(self) -> {return_type}:"
|
749
855
|
|
@@ -965,7 +1071,7 @@ def generate_api_client(schema, class_name: str | None = None):
|
|
965
1071
|
|
966
1072
|
# Generate class imports
|
967
1073
|
imports = [
|
968
|
-
"from typing import Any",
|
1074
|
+
"from typing import Any, Optional, List",
|
969
1075
|
"from universal_mcp.applications import APIApplication",
|
970
1076
|
"from universal_mcp.integrations import Integration",
|
971
1077
|
]
|
@@ -1039,4 +1145,4 @@ if __name__ == "__main__":
|
|
1039
1145
|
|
1040
1146
|
schema = load_schema("openapi.yaml")
|
1041
1147
|
code = generate_api_client(schema)
|
1042
|
-
print(code)
|
1148
|
+
print(code)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: universal-mcp
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.21rc2
|
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
|
@@ -13,14 +13,14 @@ universal_mcp/integrations/__init__.py,sha256=X8iEzs02IlXfeafp6GMm-cOkg70QdjnlTR
|
|
13
13
|
universal_mcp/integrations/integration.py,sha256=QvZlq3G5OU4tHPv9uq9Nv5NFe30NdUsJU-Av474n0_o,13154
|
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=
|
16
|
+
universal_mcp/servers/server.py,sha256=bBCds1sLb3FOiIF3m9XjDe_FRyDpmkD1PGOuUV6Xl3g,12883
|
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
22
|
universal_mcp/tools/adapters.py,sha256=OCZuWxLscys6mw1Q5ctQFshv9q3szlikwHhn4j5PMnE,1432
|
23
|
-
universal_mcp/tools/func_metadata.py,sha256=
|
23
|
+
universal_mcp/tools/func_metadata.py,sha256=XvdXSZEzvgbH70bc-Zu0B47CD7f_rm--vblq4en3n0Q,8181
|
24
24
|
universal_mcp/tools/manager.py,sha256=iwywaTjVGvBCJJasfwDWrASUleYqxenm4S-0txdhCF0,8076
|
25
25
|
universal_mcp/tools/tools.py,sha256=qiuuLe0mCWtxXp6E5ISDDaNojCrMLfV1r5L8peFoJfg,3327
|
26
26
|
universal_mcp/utils/__init__.py,sha256=8wi4PGWu-SrFjNJ8U7fr2iFJ1ktqlDmSKj1xYd7KSDc,41
|
@@ -33,13 +33,13 @@ universal_mcp/utils/openapi/__inti__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMp
|
|
33
33
|
universal_mcp/utils/openapi/api_generator.py,sha256=FjtvbnWuI1P8W8wXuKLCirUtsqQ4HI_TuQrhpA4SqTs,4749
|
34
34
|
universal_mcp/utils/openapi/api_splitter.py,sha256=6O2y7fcCo2k3ixLr6_9-aAZx2kas3UAxQhqJy1esNkE,18829
|
35
35
|
universal_mcp/utils/openapi/docgen.py,sha256=DNmwlhg_-TRrHa74epyErMTRjV2nutfCQ7seb_Rq5hE,21366
|
36
|
-
universal_mcp/utils/openapi/openapi.py,sha256=
|
36
|
+
universal_mcp/utils/openapi/openapi.py,sha256=t2iDyJEMF-h3a8ssng0udUmJoF3OHYGkglXV4Kx3s5M,50850
|
37
37
|
universal_mcp/utils/openapi/preprocessor.py,sha256=qLYv4ekors5B2OU_YUvXICYQ7XYhAOEPyAnKtnBvNpM,46699
|
38
38
|
universal_mcp/utils/openapi/readme.py,sha256=R2Jp7DUXYNsXPDV6eFTkLiy7MXbSULUj1vHh4O_nB4c,2974
|
39
39
|
universal_mcp/utils/templates/README.md.j2,sha256=Mrm181YX-o_-WEfKs01Bi2RJy43rBiq2j6fTtbWgbTA,401
|
40
40
|
universal_mcp/utils/templates/api_client.py.j2,sha256=972Im7LNUAq3yZTfwDcgivnb-b8u6_JLKWXwoIwXXXQ,908
|
41
|
-
universal_mcp-0.1.
|
42
|
-
universal_mcp-0.1.
|
43
|
-
universal_mcp-0.1.
|
44
|
-
universal_mcp-0.1.
|
45
|
-
universal_mcp-0.1.
|
41
|
+
universal_mcp-0.1.21rc2.dist-info/METADATA,sha256=I-PuM1PTi5y1lEFg2VRp8OYec60vuKApGAJgz2t3AGs,12125
|
42
|
+
universal_mcp-0.1.21rc2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
43
|
+
universal_mcp-0.1.21rc2.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
|
44
|
+
universal_mcp-0.1.21rc2.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
|
45
|
+
universal_mcp-0.1.21rc2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|