universal-mcp 0.1.13rc14__tar.gz → 0.1.15__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.
Files changed (71) hide show
  1. universal_mcp-0.1.15/LICENSE +21 -0
  2. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/PKG-INFO +6 -3
  3. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/pyproject.toml +5 -4
  4. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/test_api_generator.py +1 -1
  5. universal_mcp-0.1.15/src/tests/test_applications.py +90 -0
  6. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/test_tool.py +24 -0
  7. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/analytics.py +7 -1
  8. universal_mcp-0.1.15/src/universal_mcp/applications/README.md +122 -0
  9. universal_mcp-0.1.15/src/universal_mcp/applications/__init__.py +101 -0
  10. universal_mcp-0.1.15/src/universal_mcp/applications/application.py +442 -0
  11. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/cli.py +49 -49
  12. universal_mcp-0.1.15/src/universal_mcp/config.py +105 -0
  13. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/exceptions.py +8 -0
  14. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/integrations/integration.py +18 -2
  15. universal_mcp-0.1.15/src/universal_mcp/logger.py +68 -0
  16. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/servers/__init__.py +2 -2
  17. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/stores/store.py +2 -12
  18. universal_mcp-0.1.15/src/universal_mcp/tools/__init__.py +15 -0
  19. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/tools/adapters.py +25 -0
  20. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/tools/func_metadata.py +12 -2
  21. universal_mcp-0.1.15/src/universal_mcp/tools/manager.py +236 -0
  22. universal_mcp-0.1.15/src/universal_mcp/tools/tools.py +96 -0
  23. universal_mcp-0.1.15/src/universal_mcp/utils/common.py +33 -0
  24. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/utils/installation.py +8 -8
  25. {universal_mcp-0.1.13rc14/src/universal_mcp/utils → universal_mcp-0.1.15/src/universal_mcp/utils/openapi}/api_generator.py +1 -1
  26. universal_mcp-0.1.15/src/universal_mcp/utils/openapi/openapi.py +930 -0
  27. universal_mcp-0.1.15/src/universal_mcp/utils/openapi/preprocessor.py +1223 -0
  28. {universal_mcp-0.1.13rc14/src/universal_mcp/utils → universal_mcp-0.1.15/src/universal_mcp/utils/openapi}/readme.py +21 -31
  29. universal_mcp-0.1.15/src/universal_mcp/utils/templates/README.md.j2 +17 -0
  30. universal_mcp-0.1.13rc14/src/playground/README.md +0 -65
  31. universal_mcp-0.1.13rc14/src/playground/__main__.py +0 -36
  32. universal_mcp-0.1.13rc14/src/playground/agents/react.py +0 -54
  33. universal_mcp-0.1.13rc14/src/playground/client.py +0 -387
  34. universal_mcp-0.1.13rc14/src/playground/schema.py +0 -178
  35. universal_mcp-0.1.13rc14/src/playground/settings.py +0 -24
  36. universal_mcp-0.1.13rc14/src/playground/streamlit.py +0 -459
  37. universal_mcp-0.1.13rc14/src/playground/utils.py +0 -76
  38. universal_mcp-0.1.13rc14/src/tests/test_applications.py +0 -66
  39. universal_mcp-0.1.13rc14/src/universal_mcp/applications/__init__.py +0 -99
  40. universal_mcp-0.1.13rc14/src/universal_mcp/applications/application.py +0 -233
  41. universal_mcp-0.1.13rc14/src/universal_mcp/config.py +0 -32
  42. universal_mcp-0.1.13rc14/src/universal_mcp/logger.py +0 -17
  43. universal_mcp-0.1.13rc14/src/universal_mcp/templates/README.md.j2 +0 -93
  44. universal_mcp-0.1.13rc14/src/universal_mcp/tools/__init__.py +0 -3
  45. universal_mcp-0.1.13rc14/src/universal_mcp/tools/tools.py +0 -340
  46. universal_mcp-0.1.13rc14/src/universal_mcp/utils/dump_app_tools.py +0 -78
  47. universal_mcp-0.1.13rc14/src/universal_mcp/utils/openapi.py +0 -697
  48. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/.gitignore +0 -0
  49. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/README.md +0 -0
  50. {universal_mcp-0.1.13rc14/src/playground → universal_mcp-0.1.15/src/tests}/__init__.py +0 -0
  51. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/conftest.py +0 -0
  52. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/test_api_integration.py +0 -0
  53. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/test_localserver.py +0 -0
  54. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/test_stores.py +0 -0
  55. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/test_zenquotes.py +0 -0
  56. {universal_mcp-0.1.13rc14/src/tests → universal_mcp-0.1.15/src/universal_mcp}/__init__.py +0 -0
  57. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/integrations/README.md +0 -0
  58. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/integrations/__init__.py +0 -0
  59. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/py.typed +0 -0
  60. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/servers/README.md +0 -0
  61. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/servers/server.py +0 -0
  62. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/stores/README.md +0 -0
  63. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/stores/__init__.py +0 -0
  64. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/tools/README.md +0 -0
  65. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/utils/__init__.py +0 -0
  66. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/utils/agentr.py +0 -0
  67. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/utils/docstring_parser.py +0 -0
  68. /universal_mcp-0.1.13rc14/src/universal_mcp/__init__.py → /universal_mcp-0.1.15/src/universal_mcp/utils/openapi/__inti__.py +0 -0
  69. {universal_mcp-0.1.13rc14/src/universal_mcp/utils → universal_mcp-0.1.15/src/universal_mcp/utils/openapi}/docgen.py +0 -0
  70. {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/utils/singleton.py +0 -0
  71. {universal_mcp-0.1.13rc14/src/universal_mcp → universal_mcp-0.1.15/src/universal_mcp/utils}/templates/api_client.py.j2 +0 -0
@@ -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,17 +1,20 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp
3
- Version: 0.1.13rc14
3
+ Version: 0.1.15
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'
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "universal-mcp"
7
- version = "0.1.13-rc14"
7
+ version = "0.1.15"
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 = [
@@ -16,10 +16,12 @@ dependencies = [
16
16
  "Jinja2>=3.1.3",
17
17
  "cookiecutter>=2.6.0",
18
18
  "gql[all]>=3.5.2",
19
+ "jsonref>=1.1.0",
19
20
  "keyring>=25.6.0",
21
+ "langchain-mcp-adapters>=0.0.3",
20
22
  "litellm>=1.30.7",
21
23
  "loguru>=0.7.3",
22
- "mcp>=1.6.0",
24
+ "mcp>=1.7.1",
23
25
  "posthog>=3.24.0",
24
26
  "pydantic>=2.11.1",
25
27
  "pydantic-settings>=2.8.1",
@@ -38,13 +40,13 @@ dev = [
38
40
  "ruff>=0.11.4",
39
41
  ]
40
42
  playground = [
41
- "langchain-mcp-adapters>=0.0.3",
42
43
  "langchain-openai>=0.3.12",
43
44
  "langgraph>=0.3.24",
44
45
  "langgraph-checkpoint-sqlite>=2.0.6",
45
46
  "python-dotenv>=1.0.1",
46
47
  "streamlit>=1.44.1",
47
48
  "watchdog>=6.0.0",
49
+ "litellm>=1.30.7",
48
50
  ]
49
51
 
50
52
  [project.scripts]
@@ -56,7 +58,6 @@ universal_mcp = "universal_mcp.cli:app"
56
58
  [tool.hatch.build.targets.sdist]
57
59
  include = [
58
60
  "/src",
59
- "/templates", # Important: Include the templates directory!
60
61
  ]
61
62
 
62
63
  [tool.hatch.build.targets.wheel]
@@ -3,7 +3,7 @@ from pathlib import Path
3
3
 
4
4
  import pytest
5
5
 
6
- from universal_mcp.utils.api_generator import generate_api_from_schema
6
+ from universal_mcp.utils.openapi.api_generator import generate_api_from_schema
7
7
 
8
8
 
9
9
  @pytest.fixture
@@ -0,0 +1,90 @@
1
+ import pytest
2
+ from loguru import logger
3
+
4
+ from universal_mcp.applications import app_from_slug
5
+ from universal_mcp.tools.tools import Tool
6
+
7
+ ALL_APPS = [
8
+ "ahrefs",
9
+ "airtable",
10
+ "apollo",
11
+ "asana",
12
+ # "box",
13
+ "braze",
14
+ "cal-com-v2",
15
+ "confluence",
16
+ "calendly",
17
+ "canva",
18
+ "clickup",
19
+ "coda",
20
+ "crustdata",
21
+ "e2b",
22
+ "elevenlabs",
23
+ "exa",
24
+ "falai",
25
+ "figma",
26
+ "firecrawl",
27
+ "github",
28
+ "gong",
29
+ "google-calendar",
30
+ "google-docs",
31
+ "google-drive",
32
+ "google-gemini",
33
+ "google-mail",
34
+ "google-sheet",
35
+ "hashnode",
36
+ "heygen",
37
+ # "jira",
38
+ "klaviyo",
39
+ "mailchimp",
40
+ "markitdown",
41
+ "miro",
42
+ "neon",
43
+ "notion",
44
+ "perplexity",
45
+ "pipedrive",
46
+ "posthog",
47
+ "reddit",
48
+ "replicate",
49
+ "resend",
50
+ "retell",
51
+ "rocketlane",
52
+ "serpapi",
53
+ "shopify",
54
+ "shortcut",
55
+ "spotify",
56
+ "supabase",
57
+ "tavily",
58
+ "trello",
59
+ "whatsapp-business",
60
+ "wrike",
61
+ "youtube",
62
+ "zenquotes",
63
+ ]
64
+
65
+
66
+ @pytest.fixture
67
+ def app(app_name):
68
+ return app_from_slug(app_name)
69
+
70
+
71
+ class TestApplications:
72
+ @pytest.mark.parametrize(
73
+ "app_name",
74
+ ALL_APPS,
75
+ )
76
+ def test_application(self, app, app_name):
77
+ assert app is not None
78
+ app_instance = app(integration=None)
79
+ assert app_instance.name == app_name
80
+ tools = app_instance.list_tools()
81
+ logger.info(f"Tools for {app_name}: {len(tools)}")
82
+ assert len(tools) > 0, f"No tools found for {app_name}"
83
+ tools = [Tool.from_function(tool) for tool in tools]
84
+ important_tools = []
85
+ for tool in tools:
86
+ assert tool.name is not None
87
+ assert tool.description is not None
88
+ if "important" in tool.tags:
89
+ important_tools.append(tool.name)
90
+ assert len(important_tools) > 0, f"No important tools found for {app_name}"
@@ -3,6 +3,7 @@ from typing import Annotated
3
3
  from pydantic import Field
4
4
 
5
5
  from universal_mcp.tools.func_metadata import FuncMetadata
6
+ from universal_mcp.tools.tools import Tool
6
7
  from universal_mcp.utils.docstring_parser import parse_docstring
7
8
 
8
9
 
@@ -23,6 +24,29 @@ def test_func_metadata_annotated():
23
24
  }
24
25
 
25
26
 
27
+ def test_func_metadata_no_annotated():
28
+ def func(a: int, b: int):
29
+ """Test function with no annotated args
30
+
31
+ Args:
32
+ a: The first integer
33
+ b: The second integer
34
+ """
35
+ return a + b
36
+
37
+ tool = Tool.from_function(func)
38
+ meta = tool.fn_metadata
39
+ assert meta.arg_model.model_json_schema() == {
40
+ "type": "object",
41
+ "title": "funcArguments",
42
+ "properties": {
43
+ "a": {"type": "integer", "title": "The first integer"},
44
+ "b": {"type": "integer", "title": "The second integer"},
45
+ },
46
+ "required": ["a", "b"],
47
+ }
48
+
49
+
26
50
  def test_func_metadata_no_args():
27
51
  def func():
28
52
  """Test function with no args"""
@@ -48,7 +48,12 @@ class Analytics:
48
48
  logger.error(f"Failed to track app_loaded event: {e}")
49
49
 
50
50
  def track_tool_called(
51
- self, tool_name: str, status: str, error: str = None, user_id=None
51
+ self,
52
+ tool_name: str,
53
+ app_name: str,
54
+ status: str,
55
+ error: str = None,
56
+ user_id=None,
52
57
  ):
53
58
  """Track when a tool is called
54
59
 
@@ -63,6 +68,7 @@ class Analytics:
63
68
  try:
64
69
  properties = {
65
70
  "tool_name": tool_name,
71
+ "app_name": app_name,
66
72
  "status": status,
67
73
  "error": error,
68
74
  "version": self.get_version(),
@@ -0,0 +1,122 @@
1
+ # Universal MCP Applications Module
2
+
3
+ This module provides the core functionality for managing and integrating applications within the Universal MCP system. It offers a flexible framework for creating, managing, and interacting with various types of applications through a unified interface.
4
+
5
+ ## Overview
6
+
7
+ The applications module provides three main base classes for building application integrations:
8
+
9
+ 1. `BaseApplication`: The abstract base class that defines the common interface for all applications
10
+ 2. `APIApplication`: A concrete implementation for applications that communicate via HTTP APIs
11
+ 3. `GraphQLApplication`: A specialized implementation for applications that use GraphQL APIs
12
+
13
+ ## Key Features
14
+
15
+ - **Dynamic Application Loading**: Applications can be loaded dynamically from external packages
16
+ - **Unified Credential Management**: Centralized handling of application credentials
17
+ - **HTTP API Support**: Built-in support for RESTful API interactions
18
+ - **GraphQL Support**: Specialized support for GraphQL-based applications
19
+ - **Automatic Package Installation**: Automatic installation of application packages from GitHub
20
+
21
+ ## Base Classes
22
+
23
+ ### BaseApplication
24
+
25
+ The foundation class for all applications, providing:
26
+ - Basic initialization
27
+ - Credential management
28
+ - Tool listing interface
29
+
30
+ ### APIApplication
31
+
32
+ Extends BaseApplication to provide:
33
+ - HTTP client management
34
+ - Authentication handling
35
+ - Common HTTP methods (GET, POST, PUT, DELETE, PATCH)
36
+ - Request/response handling
37
+
38
+ ### GraphQLApplication
39
+
40
+ Specialized for GraphQL-based applications, offering:
41
+ - GraphQL client management
42
+ - Query and mutation execution
43
+ - Authentication handling
44
+
45
+ ## Usage
46
+
47
+ ### Creating a New Application
48
+
49
+ 1. Create a new package following the naming convention: `universal_mcp_<app_name>`
50
+ 2. Implement your application class inheriting from one of the base classes
51
+ 3. Name your class following the convention: `<AppName>App`
52
+
53
+ Example:
54
+ ```python
55
+ from universal_mcp.applications import APIApplication
56
+
57
+ class MyApp(APIApplication):
58
+ def __init__(self, name: str, integration=None, **kwargs):
59
+ super().__init__(name, integration, **kwargs)
60
+ self.base_url = "https://api.example.com"
61
+
62
+ def list_tools(self):
63
+ return [self.my_tool]
64
+
65
+ def my_tool(self):
66
+ # Implementation here
67
+ pass
68
+ ```
69
+
70
+ ### Loading an Application
71
+
72
+ ```python
73
+ from universal_mcp.applications import app_from_slug
74
+
75
+ # The system will automatically install the package if needed
76
+ MyApp = app_from_slug("my-app")
77
+ app = MyApp("my-app-instance")
78
+ ```
79
+
80
+ ## Authentication
81
+
82
+ The module supports various authentication methods:
83
+ - API Keys
84
+ - Access Tokens
85
+ - Custom Headers
86
+ - Bearer Tokens
87
+
88
+ Credentials are managed through the integration system and can be accessed via the `credentials` property.
89
+
90
+ ## Error Handling
91
+
92
+ The module includes comprehensive error handling for:
93
+ - Package installation failures
94
+ - Import errors
95
+ - API request failures
96
+ - Authentication issues
97
+
98
+ ## Logging
99
+
100
+ All operations are logged using the `loguru` logger, providing detailed information about:
101
+ - Application initialization
102
+ - API requests
103
+ - Authentication attempts
104
+ - Package installation
105
+ - Error conditions
106
+
107
+ ## Requirements
108
+
109
+ - Python 3.8+
110
+ - httpx
111
+ - gql
112
+ - loguru
113
+ - uv (for package installation)
114
+
115
+ ## Contributing
116
+
117
+ To contribute a new application:
118
+ 1. Create a new package following the naming conventions
119
+ 2. Implement the application class
120
+ 3. Add proper error handling and logging
121
+ 4. Include comprehensive documentation
122
+ 5. Submit a pull request to the Universal MCP repository
@@ -0,0 +1,101 @@
1
+ import importlib
2
+ import os
3
+ import subprocess
4
+ import sys
5
+ from importlib.metadata import version
6
+
7
+ from loguru import logger
8
+
9
+ from universal_mcp.applications.application import (
10
+ APIApplication,
11
+ BaseApplication,
12
+ GraphQLApplication,
13
+ )
14
+ from universal_mcp.utils.common import (
15
+ get_default_class_name,
16
+ get_default_module_path,
17
+ get_default_package_name,
18
+ get_default_repository_path,
19
+ )
20
+
21
+ UNIVERSAL_MCP_HOME = os.path.join(os.path.expanduser("~"), ".universal-mcp", "packages")
22
+
23
+ if not os.path.exists(UNIVERSAL_MCP_HOME):
24
+ os.makedirs(UNIVERSAL_MCP_HOME)
25
+
26
+ # set python path to include the universal-mcp home directory
27
+ sys.path.append(UNIVERSAL_MCP_HOME)
28
+
29
+
30
+ # Name are in the format of "app-name", eg, google-calendar
31
+ # Class name is NameApp, eg, GoogleCalendarApp
32
+
33
+
34
+ def _install_or_upgrade_package(package_name: str, repository_path: str):
35
+ """
36
+ Helper to install a package via pip from the universal-mcp GitHub repository.
37
+ """
38
+ try:
39
+ current_version = version(package_name)
40
+ logger.info(f"Current version of {package_name} is {current_version}")
41
+ except ImportError:
42
+ current_version = None
43
+ if current_version is not None:
44
+ return
45
+ cmd = [
46
+ "uv",
47
+ "pip",
48
+ "install",
49
+ "--upgrade",
50
+ repository_path,
51
+ "--target",
52
+ UNIVERSAL_MCP_HOME,
53
+ ]
54
+ logger.debug(f"Installing package '{package_name}' with command: {' '.join(cmd)}")
55
+ try:
56
+ subprocess.check_call(cmd)
57
+ except subprocess.CalledProcessError as e:
58
+ logger.error(f"Installation failed for '{package_name}': {e}")
59
+ raise ModuleNotFoundError(
60
+ f"Installation failed for package '{package_name}'"
61
+ ) from e
62
+ else:
63
+ logger.debug(f"Package {package_name} installed successfully")
64
+
65
+
66
+ def app_from_slug(slug: str):
67
+ """
68
+ Dynamically resolve and return the application class for the given slug.
69
+ Attempts installation from GitHub if the package is not found locally.
70
+ """
71
+ class_name = get_default_class_name(slug)
72
+ module_path = get_default_module_path(slug)
73
+ package_name = get_default_package_name(slug)
74
+ repository_path = get_default_repository_path(slug)
75
+ logger.debug(
76
+ f"Resolving app for slug '{slug}' → module '{module_path}', class '{class_name}'"
77
+ )
78
+ try:
79
+ _install_or_upgrade_package(package_name, repository_path)
80
+ module = importlib.import_module(module_path)
81
+ class_ = getattr(module, class_name)
82
+ logger.debug(f"Loaded class '{class_}' from module '{module_path}'")
83
+ return class_
84
+ except ModuleNotFoundError as e:
85
+ raise ModuleNotFoundError(
86
+ f"Package '{module_path}' not found locally. Please install it first."
87
+ ) from e
88
+ except AttributeError as e:
89
+ raise AttributeError(
90
+ f"Class '{class_name}' not found in module '{module_path}'"
91
+ ) from e
92
+ except Exception as e:
93
+ raise Exception(f"Error importing module '{module_path}': {e}") from e
94
+
95
+
96
+ __all__ = [
97
+ "app_from_slug",
98
+ "BaseApplication",
99
+ "APIApplication",
100
+ "GraphQLApplication",
101
+ ]