universal-mcp 0.1.14__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 (34) 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 +49 -49
  6. universal_mcp/config.py +95 -22
  7. universal_mcp/exceptions.py +8 -0
  8. universal_mcp/integrations/integration.py +18 -2
  9. universal_mcp/logger.py +59 -8
  10. universal_mcp/servers/__init__.py +2 -2
  11. universal_mcp/stores/store.py +2 -12
  12. universal_mcp/tools/__init__.py +14 -2
  13. universal_mcp/tools/adapters.py +25 -0
  14. universal_mcp/tools/func_metadata.py +12 -2
  15. universal_mcp/tools/manager.py +236 -0
  16. universal_mcp/tools/tools.py +5 -249
  17. universal_mcp/utils/common.py +33 -0
  18. universal_mcp/utils/openapi/__inti__.py +0 -0
  19. universal_mcp/utils/{api_generator.py → openapi/api_generator.py} +1 -1
  20. universal_mcp/utils/openapi/openapi.py +930 -0
  21. universal_mcp/utils/openapi/preprocessor.py +1223 -0
  22. universal_mcp/utils/{readme.py → openapi/readme.py} +21 -31
  23. universal_mcp/utils/templates/README.md.j2 +17 -0
  24. {universal_mcp-0.1.14.dist-info → universal_mcp-0.1.15rc7.dist-info}/METADATA +6 -3
  25. universal_mcp-0.1.15rc7.dist-info/RECORD +44 -0
  26. universal_mcp-0.1.15rc7.dist-info/licenses/LICENSE +21 -0
  27. universal_mcp/templates/README.md.j2 +0 -93
  28. universal_mcp/utils/dump_app_tools.py +0 -78
  29. universal_mcp/utils/openapi.py +0 -697
  30. universal_mcp-0.1.14.dist-info/RECORD +0 -39
  31. /universal_mcp/utils/{docgen.py → openapi/docgen.py} +0 -0
  32. /universal_mcp/{templates → utils/templates}/api_client.py.j2 +0 -0
  33. {universal_mcp-0.1.14.dist-info → universal_mcp-0.1.15rc7.dist-info}/WHEEL +0 -0
  34. {universal_mcp-0.1.14.dist-info → universal_mcp-0.1.15rc7.dist-info}/entry_points.txt +0 -0
@@ -1,3 +1,4 @@
1
+ import ast
1
2
  import importlib
2
3
  from pathlib import Path
3
4
 
@@ -6,10 +7,6 @@ from loguru import logger
6
7
 
7
8
 
8
9
  def _import_class(module_path: str, class_name: str):
9
- """
10
- Helper to import a class by name from a module.
11
- Raises ModuleNotFoundError if module or class does not exist.
12
- """
13
10
  try:
14
11
  spec = importlib.util.spec_from_file_location("temp_module", module_path)
15
12
  module = importlib.util.module_from_spec(spec)
@@ -26,35 +23,33 @@ def _import_class(module_path: str, class_name: str):
26
23
  ) from e
27
24
 
28
25
 
29
- def generate_readme(app: Path, class_name: str) -> Path:
30
- """Generate README.md with API documentation.
26
+ def _get_single_class_name(file_path: Path) -> str:
27
+ with open(file_path) as file:
28
+ tree = ast.parse(file.read(), filename=str(file_path))
29
+ class_defs = [
30
+ node.name for node in ast.walk(tree) if isinstance(node, ast.ClassDef)
31
+ ]
32
+ if len(class_defs) == 1:
33
+ logger.info(f"Auto-detected class: {class_defs[0]}")
34
+ return class_defs[0]
35
+ elif len(class_defs) == 0:
36
+ raise ValueError(f"No class found in {file_path}")
37
+ else:
38
+ raise ValueError(f"Multiple classes found in {file_path}; please specify one.")
31
39
 
32
- Args:
33
- app_dir: Directory where the README will be generated
34
- folder_name: Name of the application folder
35
- tools: List of Function objects from the OpenAPI schema
36
40
 
37
- Returns:
38
- Path to the generated README file
39
-
40
- Raises:
41
- FileNotFoundError: If the template directory doesn't exist
42
- TemplateError: If there's an error rendering the template
43
- IOError: If there's an error writing the README file
44
- """
45
-
46
- # Import the app
41
+ def generate_readme(app: Path) -> Path:
42
+ """Generate README.md with API documentation from a file containing one class."""
43
+ class_name = _get_single_class_name(app)
47
44
  app_instance = _import_class(app, class_name)(integration=None)
48
- # List tools by calling list_tools()
49
45
  tools = app_instance.list_tools()
50
- # Format tools into (name, description) tuples
46
+
51
47
  formatted_tools = []
52
48
  for tool in tools:
53
49
  name = tool.__name__
54
50
  description = tool.__doc__.strip().split("\n")[0]
55
51
  formatted_tools.append((name, description))
56
- # Render the template
57
- # Set up Jinja2 environment
52
+
58
53
  template_dir = Path(__file__).parent.parent / "templates"
59
54
  if not template_dir.exists():
60
55
  logger.error(f"Template directory not found: {template_dir}")
@@ -68,19 +63,14 @@ def generate_readme(app: Path, class_name: str) -> Path:
68
63
  except Exception as e:
69
64
  logger.error(f"Error loading template: {e}")
70
65
  raise TemplateError(f"Error loading template: {e}") from e
71
- # Write the README file
66
+
72
67
  try:
73
68
  readme_content = template.render(name=class_name, tools=formatted_tools)
74
69
  except Exception as e:
75
70
  logger.error(f"Error rendering template: {e}")
76
71
  raise TemplateError(f"Error rendering template: {e}") from e
77
72
 
78
- # Write the README file
79
- app_dir = app.parent
80
- readme_file = app_dir / "README.md"
81
-
82
- # Write the README file
83
- readme_file = app_dir / "README.md"
73
+ readme_file = app.parent / "README.md"
84
74
  try:
85
75
  with open(readme_file, "w") as f:
86
76
  f.write(readme_content)
@@ -0,0 +1,17 @@
1
+ # {{ name }} MCP Server
2
+
3
+ An MCP Server for the {{ name }} API.
4
+
5
+ ## 🛠️ Tool List
6
+
7
+ This is automatically generated from OpenAPI schema for the {{ name }} API.
8
+
9
+ {% if tools %}
10
+ | Tool | Description |
11
+ |------|-------------|
12
+ {%- for tool_name, tool_desc in tools %}
13
+ | `{{ tool_name }}` | {{ tool_desc }} |
14
+ {%- endfor %}
15
+ {% else %}
16
+ No tools with documentation were found in this API client.
17
+ {% endif %}
@@ -1,17 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp
3
- Version: 0.1.14
3
+ Version: 0.1.15rc7
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
7
+ License-File: LICENSE
7
8
  Requires-Python: >=3.11
8
9
  Requires-Dist: cookiecutter>=2.6.0
9
10
  Requires-Dist: gql[all]>=3.5.2
10
11
  Requires-Dist: jinja2>=3.1.3
12
+ Requires-Dist: jsonref>=1.1.0
11
13
  Requires-Dist: keyring>=25.6.0
14
+ Requires-Dist: langchain-mcp-adapters>=0.0.3
12
15
  Requires-Dist: litellm>=1.30.7
13
16
  Requires-Dist: loguru>=0.7.3
14
- Requires-Dist: mcp>=1.6.0
17
+ Requires-Dist: mcp>=1.7.1
15
18
  Requires-Dist: posthog>=3.24.0
16
19
  Requires-Dist: pydantic-settings>=2.8.1
17
20
  Requires-Dist: pydantic>=2.11.1
@@ -26,10 +29,10 @@ Requires-Dist: pytest-asyncio>=0.26.0; extra == 'dev'
26
29
  Requires-Dist: pytest>=8.3.5; extra == 'dev'
27
30
  Requires-Dist: ruff>=0.11.4; extra == 'dev'
28
31
  Provides-Extra: playground
29
- Requires-Dist: langchain-mcp-adapters>=0.0.3; extra == 'playground'
30
32
  Requires-Dist: langchain-openai>=0.3.12; extra == 'playground'
31
33
  Requires-Dist: langgraph-checkpoint-sqlite>=2.0.6; extra == 'playground'
32
34
  Requires-Dist: langgraph>=0.3.24; extra == 'playground'
35
+ Requires-Dist: litellm>=1.30.7; extra == 'playground'
33
36
  Requires-Dist: python-dotenv>=1.0.1; extra == 'playground'
34
37
  Requires-Dist: streamlit>=1.44.1; extra == 'playground'
35
38
  Requires-Dist: watchdog>=6.0.0; extra == 'playground'
@@ -0,0 +1,44 @@
1
+ universal_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ universal_mcp/analytics.py,sha256=Dkv8mkc_2T2t5NxLSZzcr3BlmOispj1RKtbB86V1i4M,2306
3
+ universal_mcp/cli.py,sha256=fqB4CzcA2CAg1m4tzhK0k9X4otHWxaAy2edP1QQYUIQ,8974
4
+ universal_mcp/config.py,sha256=xqz5VNxtk6Clepjw-aK-HrgMFQLzFuxiDb1fuHGpbxE,3717
5
+ universal_mcp/exceptions.py,sha256=2A_aIzcwjB99tR4xCFjPkWPf10rBK_785FerJI7Tzns,564
6
+ universal_mcp/logger.py,sha256=JtAC8ImO74lvt5xepV3W5BIz-u3nZOAY1ecdhmQhub0,2081
7
+ universal_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
+ universal_mcp/applications/README.md,sha256=eqbizxaTxKH2O1tyIJR2yI0Db5TQxtgPd_vbpWyCa2Y,3527
9
+ universal_mcp/applications/__init__.py,sha256=zyV_0GyunGBA5OEO1FQsTOsj34SYjnERu5dDJLxd6c0,3133
10
+ universal_mcp/applications/application.py,sha256=9PfmvVx1csmy0RzaTC71S4DkMD8VxvY3yIsMAt3dQLw,14425
11
+ universal_mcp/integrations/README.md,sha256=lTAPXO2nivcBe1q7JT6PRa6v9Ns_ZersQMIdw-nmwEA,996
12
+ universal_mcp/integrations/__init__.py,sha256=tg6Yk59AEhwPsrTp0hZQ3NBfmJuYGu2sNCOXuph-h9k,922
13
+ universal_mcp/integrations/integration.py,sha256=4SRcD78ZF9-m65aTxkoF-CZl7XKUsfSr1wppKz_ZgiA,13078
14
+ universal_mcp/servers/README.md,sha256=ytFlgp8-LO0oogMrHkMOp8SvFTwgsKgv7XhBVZGNTbM,2284
15
+ universal_mcp/servers/__init__.py,sha256=etFrqXFODIl9oGeqQS-aUxzw4J6pjzYjHl4VPvQaR3A,508
16
+ universal_mcp/servers/server.py,sha256=0oJQQUiwPdG2q79tzsVv3WPMV5YIFbF14PRvBF-SxMQ,9395
17
+ universal_mcp/stores/README.md,sha256=jrPh_ow4ESH4BDGaSafilhOVaN8oQ9IFlFW-j5Z5hLA,2465
18
+ universal_mcp/stores/__init__.py,sha256=quvuwhZnpiSLuojf0NfmBx2xpaCulv3fbKtKaSCEmuM,603
19
+ universal_mcp/stores/store.py,sha256=mxnmOVlDNrr8OKhENWDtCIfK7YeCBQcGdS6I2ogRCsU,6756
20
+ universal_mcp/tools/README.md,sha256=RuxliOFqV1ZEyeBdj3m8UKfkxAsfrxXh-b6V4ZGAk8I,2468
21
+ universal_mcp/tools/__init__.py,sha256=Fatza_R0qYWmNF1WQSfUZZKQFu5qf-16JhZzdmyx3KY,333
22
+ universal_mcp/tools/adapters.py,sha256=gz_sNDc_bseMHWmpQmqhOq65veE-DuK_kJYXGIx0Wi8,1427
23
+ universal_mcp/tools/func_metadata.py,sha256=4LrOeQ81RnfWjPp9N-tOkw-nr6XosRw6P0EUriNhpu4,8328
24
+ universal_mcp/tools/manager.py,sha256=OArAbckBay5Lbpu2-oI38zEEbJyM8E_WLB8985B6a70,7718
25
+ universal_mcp/tools/tools.py,sha256=lg1rM9644OLAmi-yXar-olPCAfsu1ol2Aw9XRuIN7Fk,3383
26
+ universal_mcp/utils/__init__.py,sha256=8wi4PGWu-SrFjNJ8U7fr2iFJ1ktqlDmSKj1xYd7KSDc,41
27
+ universal_mcp/utils/agentr.py,sha256=3sobve7Odk8pIAZm3RHTX4Rc21rkBClcXQgXXslbSUA,3490
28
+ universal_mcp/utils/common.py,sha256=F2Teso1Cs4E1dgMq0ESLJOWJ0n9arLX-YuawfYfZg3I,911
29
+ universal_mcp/utils/docstring_parser.py,sha256=j7aE-LLnBOPTJI0qXayf0NlYappzxICv5E_hUPNmAlc,11459
30
+ universal_mcp/utils/installation.py,sha256=1n5X_aIiuY8WNQn6Oji_gZ-aiRmNXxrg-qYRv-pGjxw,10195
31
+ universal_mcp/utils/singleton.py,sha256=kolHnbS9yd5C7z-tzaUAD16GgI-thqJXysNi3sZM4No,733
32
+ universal_mcp/utils/openapi/__inti__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
33
+ universal_mcp/utils/openapi/api_generator.py,sha256=aNzrcglYXL6mQizFQUuiFFKTPuEPgTC1unr6NT_U0VM,4771
34
+ universal_mcp/utils/openapi/docgen.py,sha256=zPmZ-b-fK6xk-dwHEx2hwShN-iquPD_O15CGuPwlj2k,21870
35
+ universal_mcp/utils/openapi/openapi.py,sha256=PeCj01skdvBaYvGedSShgUrzZ2GhEpPX8IOyTvq-ubk,37284
36
+ universal_mcp/utils/openapi/preprocessor.py,sha256=zMCsHv9lNIrIlAMyF39cIm0-c2vydrtbtQjJ0rjcLL8,48765
37
+ universal_mcp/utils/openapi/readme.py,sha256=wTg2D0sPKppPmGn7b-vtD5hAGRv8DLEAuxMNzqBeuEs,3032
38
+ universal_mcp/utils/templates/README.md.j2,sha256=Mrm181YX-o_-WEfKs01Bi2RJy43rBiq2j6fTtbWgbTA,401
39
+ universal_mcp/utils/templates/api_client.py.j2,sha256=972Im7LNUAq3yZTfwDcgivnb-b8u6_JLKWXwoIwXXXQ,908
40
+ universal_mcp-0.1.15rc7.dist-info/METADATA,sha256=Ij-KJ9juKlrmOEiRiJkUK6_rAye0RYrXxjbTZWAz3_w,9886
41
+ universal_mcp-0.1.15rc7.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
42
+ universal_mcp-0.1.15rc7.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
43
+ universal_mcp-0.1.15rc7.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
44
+ universal_mcp-0.1.15rc7.dist-info/RECORD,,
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Agentr
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -1,93 +0,0 @@
1
- # {{ name }} MCP Server
2
-
3
- An MCP Server for the {{ name }} API.
4
-
5
- ## 📋 Prerequisites
6
-
7
- Before you begin, ensure you have met the following requirements:
8
- * Python 3.11+ (Recommended)
9
- * [uv](https://github.com/astral-sh/uv) installed globally (`pip install uv`)
10
-
11
- ## 🛠️ Setup Instructions
12
-
13
- Follow these steps to get the development environment up and running:
14
-
15
- ### 1. Sync Project Dependencies
16
- Navigate to the project root directory (where `pyproject.toml` is located).
17
- ```bash
18
- uv sync
19
- ```
20
- This command uses `uv` to install all dependencies listed in `pyproject.toml` into a virtual environment (`.venv`) located in the project root.
21
-
22
- ### 2. Activate the Virtual Environment
23
- Activating the virtual environment ensures that you are using the project's specific dependencies and Python interpreter.
24
- - On **Linux/macOS**:
25
- ```bash
26
- source .venv/bin/activate
27
- ```
28
- - On **Windows**:
29
- ```bash
30
- .venv\\Scripts\\activate
31
- ```
32
-
33
- ### 3. Start the MCP Inspector
34
- Use the MCP CLI to start the application in development mode.
35
- ```bash
36
- mcp dev src/{{ name.lower() }}/mcp.py
37
- ```
38
- The MCP inspector should now be running. Check the console output for the exact address and port.
39
-
40
- ## 🔌 Supported Integrations
41
-
42
- - AgentR
43
- - API Key (Coming Soon)
44
- - OAuth (Coming Soon)
45
-
46
- ## 🛠️ Tool List
47
-
48
- This is automatically generated from OpenAPI schema for the {{ name }} API.
49
-
50
- {% if tools %}
51
- | Tool | Description |
52
- |------|-------------|
53
- {%- for tool_name, tool_desc in tools %}
54
- | `{{ tool_name }}` | {{ tool_desc }} |
55
- {%- endfor %}
56
- {% else %}
57
- No tools with documentation were found in this API client.
58
- {% endif %}
59
-
60
- ## 📁 Project Structure
61
-
62
- The generated project has a standard layout:
63
- ```
64
- .
65
- ├── src/ # Source code directory
66
- │ └── {{ name.lower() }}/
67
- │ ├── __init__.py
68
- │ └── mcp.py # Server is launched here
69
- │ └── app.py # Application tools are defined here
70
- ├── tests/ # Directory for project tests
71
- ├── .env # Environment variables (for local development)
72
- ├── pyproject.toml # Project dependencies managed by uv
73
- ├── README.md # This file
74
- ```
75
-
76
- ## 📝 License
77
-
78
- This project is licensed under the MIT License.
79
-
80
- ---
81
-
82
- _This project was generated using **MCP CLI** — Happy coding! 🚀_
83
-
84
- ## Usage
85
-
86
- - Login to AgentR
87
- - Follow the quickstart guide to setup MCP Server for your client
88
- - Visit Apps Store and enable the {{ name }} app
89
- - Restart the MCP Server
90
-
91
- ### Local Development
92
-
93
- - Follow the README to test with the local MCP Server
@@ -1,78 +0,0 @@
1
- import csv
2
- from pathlib import Path
3
-
4
- from universal_mcp.applications import app_from_slug
5
-
6
-
7
- def discover_available_app_slugs():
8
- apps_dir = Path(__file__).resolve().parent.parent / "applications"
9
- app_slugs = []
10
-
11
- for item in apps_dir.iterdir():
12
- if not item.is_dir() or item.name.startswith("_"):
13
- continue
14
-
15
- if (item / "app.py").exists():
16
- slug = item.name.replace("_", "-")
17
- app_slugs.append(slug)
18
-
19
- return app_slugs
20
-
21
-
22
- def extract_app_tools(app_slugs):
23
- all_apps_tools = []
24
-
25
- for slug in app_slugs:
26
- try:
27
- print(f"Loading app: {slug}")
28
- app_class = app_from_slug(slug)
29
-
30
- app_instance = app_class(integration=None)
31
-
32
- tools = app_instance.list_tools()
33
-
34
- for tool in tools:
35
- tool_name = tool.__name__
36
- description = (
37
- tool.__doc__.strip().split("\n")[0]
38
- if tool.__doc__
39
- else "No description"
40
- )
41
-
42
- all_apps_tools.append(
43
- {
44
- "app_name": slug,
45
- "tool_name": tool_name,
46
- "description": description,
47
- }
48
- )
49
-
50
- except Exception as e:
51
- print(f"Error loading app {slug}: {e}")
52
-
53
- return all_apps_tools
54
-
55
-
56
- def write_to_csv(app_tools, output_file="app_tools.csv"):
57
- fieldnames = ["app_name", "tool_name", "description"]
58
-
59
- with open(output_file, "w", newline="") as csvfile:
60
- writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
61
- writer.writeheader()
62
- writer.writerows(app_tools)
63
-
64
- print(f"CSV file created: {output_file}")
65
-
66
-
67
- def main():
68
- app_slugs = discover_available_app_slugs()
69
- print(f"Found {len(app_slugs)} app slugs: {', '.join(app_slugs)}")
70
-
71
- app_tools = extract_app_tools(app_slugs)
72
- print(f"Extracted {len(app_tools)} tools from all apps")
73
-
74
- write_to_csv(app_tools)
75
-
76
-
77
- if __name__ == "__main__":
78
- main()