universal-mcp 0.1.19__tar.gz → 0.1.20__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.
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/PKG-INFO +2 -2
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/pyproject.toml +2 -2
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/tests/test_applications.py +2 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/cli.py +4 -5
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/servers/server.py +1 -1
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/tools/adapters.py +1 -1
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/tools/manager.py +4 -2
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/tools/tools.py +1 -1
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/openapi/openapi.py +19 -42
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/.gitignore +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/LICENSE +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/README.md +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/tests/__init__.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/tests/conftest.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/tests/test_api_generator.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/tests/test_api_integration.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/tests/test_localserver.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/tests/test_stores.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/tests/test_tool.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/tests/test_tool_manager.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/tests/test_zenquotes.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/__init__.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/analytics.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/applications/README.md +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/applications/__init__.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/applications/application.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/config.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/exceptions.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/integrations/README.md +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/integrations/__init__.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/integrations/integration.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/logger.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/py.typed +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/servers/README.md +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/servers/__init__.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/stores/README.md +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/stores/__init__.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/stores/store.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/tools/README.md +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/tools/__init__.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/tools/func_metadata.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/__init__.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/agentr.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/common.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/docstring_parser.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/installation.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/openapi/__inti__.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/openapi/api_generator.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/openapi/api_splitter.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/openapi/docgen.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/openapi/preprocessor.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/openapi/readme.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/singleton.py +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/templates/README.md.j2 +0 -0
- {universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/templates/api_client.py.j2 +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: universal-mcp
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.20
|
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
|
@@ -14,7 +14,7 @@ Requires-Dist: keyring>=25.6.0
|
|
14
14
|
Requires-Dist: langchain-mcp-adapters>=0.0.3
|
15
15
|
Requires-Dist: litellm>=1.30.7
|
16
16
|
Requires-Dist: loguru>=0.7.3
|
17
|
-
Requires-Dist: mcp>=1.
|
17
|
+
Requires-Dist: mcp>=1.9.0
|
18
18
|
Requires-Dist: posthog>=3.24.0
|
19
19
|
Requires-Dist: pydantic-settings>=2.8.1
|
20
20
|
Requires-Dist: pydantic>=2.11.1
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "universal-mcp"
|
7
|
-
version = "0.1.
|
7
|
+
version = "0.1.20"
|
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 = [
|
@@ -21,7 +21,7 @@ dependencies = [
|
|
21
21
|
"langchain-mcp-adapters>=0.0.3",
|
22
22
|
"litellm>=1.30.7",
|
23
23
|
"loguru>=0.7.3",
|
24
|
-
"mcp>=1.
|
24
|
+
"mcp>=1.9.0",
|
25
25
|
"posthog>=3.24.0",
|
26
26
|
"pydantic>=2.11.1",
|
27
27
|
"pydantic-settings>=2.8.1",
|
@@ -34,6 +34,7 @@ ALL_APPS = [
|
|
34
34
|
"google-sheet",
|
35
35
|
"hashnode",
|
36
36
|
"heygen",
|
37
|
+
"hubspot",
|
37
38
|
"jira",
|
38
39
|
"klaviyo",
|
39
40
|
"mailchimp",
|
@@ -84,6 +85,7 @@ class TestApplications:
|
|
84
85
|
important_tools = []
|
85
86
|
for tool in tools:
|
86
87
|
assert tool.name is not None
|
88
|
+
assert len(tool.name) > 0 and len(tool.name) < 64
|
87
89
|
assert tool.description is not None
|
88
90
|
if "important" in tool.tags:
|
89
91
|
important_tools.append(tool.name)
|
@@ -59,8 +59,8 @@ def generate(
|
|
59
59
|
else:
|
60
60
|
# Handle the error case from api_generator if validation fails
|
61
61
|
if isinstance(app_file_data, dict) and "error" in app_file_data:
|
62
|
-
|
63
|
-
|
62
|
+
console.print(f"[red]{app_file_data['error']}[/red]")
|
63
|
+
raise typer.Exit(1)
|
64
64
|
else:
|
65
65
|
console.print("[red]Unexpected return value from API generator.[/red]")
|
66
66
|
raise typer.Exit(1)
|
@@ -119,9 +119,8 @@ def run(
|
|
119
119
|
from universal_mcp.logger import setup_logger
|
120
120
|
from universal_mcp.servers import server_from_config
|
121
121
|
|
122
|
-
setup_logger()
|
123
|
-
|
124
122
|
config = ServerConfig.model_validate_json(config_path.read_text()) if config_path else ServerConfig()
|
123
|
+
setup_logger(level=config.log_level)
|
125
124
|
server = server_from_config(config)
|
126
125
|
server.run(transport=config.transport)
|
127
126
|
|
@@ -291,7 +290,7 @@ def split_api(
|
|
291
290
|
console.print(f"[green]Successfully split {input_app_file} into {output_dir}[/green]")
|
292
291
|
except Exception as e:
|
293
292
|
console.print(f"[red]Error splitting API client: {e}[/red]")
|
294
|
-
|
293
|
+
|
295
294
|
raise typer.Exit(1) from e
|
296
295
|
|
297
296
|
|
@@ -329,4 +329,4 @@ class SingleMCPServer(BaseServer):
|
|
329
329
|
description=f"Minimal MCP server for the local {app_instance.name} application.",
|
330
330
|
)
|
331
331
|
super().__init__(config, **kwargs)
|
332
|
-
self._tool_manager.register_tools_from_app(app_instance)
|
332
|
+
self._tool_manager.register_tools_from_app(app_instance, tags="all")
|
@@ -33,6 +33,10 @@ def _filter_by_name(tools: list[Tool], tool_names: list[str] | None) -> list[Too
|
|
33
33
|
|
34
34
|
|
35
35
|
def _filter_by_tags(tools: list[Tool], tags: list[str] | None) -> list[Tool]:
|
36
|
+
logger.debug(f"Filtering tools by tags: {tags}")
|
37
|
+
if "all" in tags:
|
38
|
+
return tools
|
39
|
+
|
36
40
|
if not tags:
|
37
41
|
return tools
|
38
42
|
return [tool for tool in tools if any(tag in tool.tags for tag in tags)]
|
@@ -197,11 +201,9 @@ class ToolManager:
|
|
197
201
|
|
198
202
|
if tool_names:
|
199
203
|
tools = _filter_by_name(tools, tool_names)
|
200
|
-
|
201
204
|
# If no tool names or tags are provided, use the default important tag
|
202
205
|
if not tool_names and not tags:
|
203
206
|
tools = _filter_by_tags(tools, [DEFAULT_IMPORTANT_TAG])
|
204
|
-
|
205
207
|
self.register_tools(tools)
|
206
208
|
return
|
207
209
|
|
@@ -643,43 +643,28 @@ def _generate_method_code(path, method, operation):
|
|
643
643
|
param_details[param_obj.name] = param_obj
|
644
644
|
|
645
645
|
# Fetch request body example
|
646
|
-
|
646
|
+
example_data = None # Initialize example_data here for wider scope
|
647
|
+
|
647
648
|
if has_body:
|
648
649
|
try:
|
649
650
|
json_content = operation["requestBody"]["content"]["application/json"]
|
650
|
-
|
651
|
+
#From direct content definition
|
651
652
|
if "example" in json_content:
|
652
653
|
example_data = json_content["example"]
|
653
654
|
elif "examples" in json_content and json_content["examples"]:
|
654
655
|
first_example_key = list(json_content["examples"].keys())[0]
|
655
656
|
example_data = json_content["examples"][first_example_key].get("value")
|
656
|
-
|
657
|
-
if example_data is
|
658
|
-
|
659
|
-
example_json = json.dumps(example_data, indent=2)
|
660
|
-
indented_example = textwrap.indent(example_json, " " * 8) # 8 spaces
|
661
|
-
request_body_example_str = f"\n Example:\n ```json\n{indented_example}\n ```"
|
662
|
-
except TypeError:
|
663
|
-
request_body_example_str = f"\n Example: {example_data}"
|
657
|
+
#If not found directly, try from resolved body schema (for nested/referenced examples)
|
658
|
+
if example_data is None and body_schema_to_use and "example" in body_schema_to_use:
|
659
|
+
example_data = body_schema_to_use["example"]
|
664
660
|
except KeyError:
|
665
|
-
pass # No example found
|
666
|
-
|
667
|
-
# Identify the last argument related to the request body
|
668
|
-
last_body_arg_name = None
|
669
|
-
# request_body_params contains the names as they appear in the signature
|
670
|
-
if final_request_body_arg_names_for_signature: # Use the new list with final aliased names
|
671
|
-
# Find which of these appears last in the combined args list
|
672
|
-
body_args_in_signature = [
|
673
|
-
a.split("=")[0] for a in args if a.split("=")[0] in final_request_body_arg_names_for_signature
|
674
|
-
]
|
675
|
-
if body_args_in_signature:
|
676
|
-
last_body_arg_name = body_args_in_signature[-1]
|
661
|
+
pass # No example found or application/json content not present
|
677
662
|
|
678
663
|
if signature_arg_names:
|
679
664
|
args_doc_lines.append("Args:")
|
680
665
|
for arg_signature_str in args:
|
681
666
|
arg_name = arg_signature_str.split("=")[0]
|
682
|
-
example_str = None # Initialize example_str here
|
667
|
+
example_str = None # Initialize example_str here for each argument
|
683
668
|
detail = param_details.get(arg_name)
|
684
669
|
if detail:
|
685
670
|
desc = detail.description or "No description provided."
|
@@ -692,26 +677,22 @@ def _generate_method_code(path, method, operation):
|
|
692
677
|
if detail.example and not detail.is_file: # Don't show schema example for file inputs
|
693
678
|
example_str = repr(detail.example)
|
694
679
|
arg_line += f" Example: {example_str}."
|
695
|
-
|
696
|
-
|
697
|
-
|
698
|
-
|
699
|
-
|
700
|
-
|
701
|
-
|
702
|
-
|
703
|
-
f" Example: {example_str}.", ""
|
704
|
-
) # Remove with or without trailing period
|
705
|
-
arg_line += request_body_example_str # Append the formatted JSON example
|
680
|
+
# Fallback for body parameters if no direct example was found
|
681
|
+
elif not example_str and detail.where == "body" and example_data and isinstance(example_data, dict) and detail.identifier in example_data:
|
682
|
+
current_body_param_example = example_data[detail.identifier]
|
683
|
+
if current_body_param_example is not None: # Ensure the extracted part is not None
|
684
|
+
try:
|
685
|
+
arg_line += f" Example: {repr(current_body_param_example)}."
|
686
|
+
except Exception: # Fallback if repr fails
|
687
|
+
arg_line += " Example: [Could not represent example]."
|
706
688
|
|
707
689
|
args_doc_lines.append(arg_line)
|
708
|
-
|
690
|
+
|
691
|
+
elif arg_name == final_empty_body_param_name and has_empty_body:
|
709
692
|
args_doc_lines.append(
|
710
693
|
f" {arg_name} (dict | None): Optional dictionary for an empty JSON request body (e.g., {{}})."
|
711
694
|
)
|
712
|
-
|
713
|
-
args_doc_lines[-1] += request_body_example_str
|
714
|
-
elif arg_name == raw_body_param_name: # Docstring for raw body parameter
|
695
|
+
elif arg_name == raw_body_param_name:
|
715
696
|
raw_body_type_hint = "bytes"
|
716
697
|
raw_body_desc = "Raw binary content for the request body."
|
717
698
|
if selected_content_type and "text" in selected_content_type:
|
@@ -720,13 +701,9 @@ def _generate_method_code(path, method, operation):
|
|
720
701
|
elif selected_content_type and selected_content_type.startswith("image/"):
|
721
702
|
raw_body_type_hint = "bytes (image data)"
|
722
703
|
raw_body_desc = f"Raw image content ({selected_content_type}) for the request body."
|
723
|
-
|
724
704
|
args_doc_lines.append(
|
725
705
|
f" {arg_name} ({raw_body_type_hint} | None): {raw_body_desc}"
|
726
706
|
)
|
727
|
-
# Example for raw body is harder to give generically, but if present in spec, could be added.
|
728
|
-
if ( arg_name == last_body_arg_name and request_body_example_str ):
|
729
|
-
args_doc_lines[-1] += request_body_example_str
|
730
707
|
|
731
708
|
if args_doc_lines:
|
732
709
|
docstring_parts.append("\n".join(args_doc_lines))
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/openapi/api_generator.py
RENAMED
File without changes
|
{universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/openapi/api_splitter.py
RENAMED
File without changes
|
File without changes
|
{universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/openapi/preprocessor.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
{universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/templates/README.md.j2
RENAMED
File without changes
|
{universal_mcp-0.1.19 → universal_mcp-0.1.20}/src/universal_mcp/utils/templates/api_client.py.j2
RENAMED
File without changes
|