universal-mcp 0.1.15rc5__py3-none-any.whl → 0.1.16__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 (37) hide show
  1. universal_mcp/analytics.py +7 -1
  2. universal_mcp/applications/README.md +122 -0
  3. universal_mcp/applications/__init__.py +51 -56
  4. universal_mcp/applications/application.py +255 -82
  5. universal_mcp/cli.py +27 -43
  6. universal_mcp/config.py +16 -48
  7. universal_mcp/exceptions.py +8 -0
  8. universal_mcp/integrations/__init__.py +1 -3
  9. universal_mcp/integrations/integration.py +18 -2
  10. universal_mcp/logger.py +31 -29
  11. universal_mcp/servers/server.py +6 -18
  12. universal_mcp/stores/store.py +2 -12
  13. universal_mcp/tools/__init__.py +12 -1
  14. universal_mcp/tools/adapters.py +11 -0
  15. universal_mcp/tools/func_metadata.py +11 -15
  16. universal_mcp/tools/manager.py +163 -117
  17. universal_mcp/tools/tools.py +6 -13
  18. universal_mcp/utils/agentr.py +2 -6
  19. universal_mcp/utils/common.py +33 -0
  20. universal_mcp/utils/docstring_parser.py +4 -13
  21. universal_mcp/utils/installation.py +67 -184
  22. universal_mcp/utils/openapi/__inti__.py +0 -0
  23. universal_mcp/utils/{api_generator.py → openapi/api_generator.py} +2 -4
  24. universal_mcp/utils/{docgen.py → openapi/docgen.py} +17 -54
  25. universal_mcp/utils/openapi/openapi.py +882 -0
  26. universal_mcp/utils/openapi/preprocessor.py +1093 -0
  27. universal_mcp/utils/{readme.py → openapi/readme.py} +21 -37
  28. universal_mcp-0.1.16.dist-info/METADATA +282 -0
  29. universal_mcp-0.1.16.dist-info/RECORD +44 -0
  30. universal_mcp-0.1.16.dist-info/licenses/LICENSE +21 -0
  31. universal_mcp/utils/openapi.py +0 -646
  32. universal_mcp-0.1.15rc5.dist-info/METADATA +0 -245
  33. universal_mcp-0.1.15rc5.dist-info/RECORD +0 -39
  34. /universal_mcp/{templates → utils/templates}/README.md.j2 +0 -0
  35. /universal_mcp/{templates → utils/templates}/api_client.py.j2 +0 -0
  36. {universal_mcp-0.1.15rc5.dist-info → universal_mcp-0.1.16.dist-info}/WHEEL +0 -0
  37. {universal_mcp-0.1.15rc5.dist-info → universal_mcp-0.1.16.dist-info}/entry_points.txt +0 -0
@@ -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
@@ -1,7 +1,7 @@
1
1
  import importlib
2
- import os
3
2
  import subprocess
4
3
  import sys
4
+ from pathlib import Path
5
5
 
6
6
  from loguru import logger
7
7
 
@@ -10,55 +10,56 @@ from universal_mcp.applications.application import (
10
10
  BaseApplication,
11
11
  GraphQLApplication,
12
12
  )
13
+ from universal_mcp.utils.common import (
14
+ get_default_class_name,
15
+ get_default_module_path,
16
+ get_default_package_name,
17
+ get_default_repository_path,
18
+ )
13
19
 
14
- UNIVERSAL_MCP_HOME = os.path.join(os.path.expanduser("~"), ".universal-mcp", "packages")
20
+ UNIVERSAL_MCP_HOME = Path.home() / ".universal-mcp" / "packages"
15
21
 
16
- if not os.path.exists(UNIVERSAL_MCP_HOME):
17
- os.makedirs(UNIVERSAL_MCP_HOME)
22
+ if not UNIVERSAL_MCP_HOME.exists():
23
+ UNIVERSAL_MCP_HOME.mkdir(parents=True, exist_ok=True)
18
24
 
19
25
  # set python path to include the universal-mcp home directory
20
- sys.path.append(UNIVERSAL_MCP_HOME)
26
+ sys.path.append(str(UNIVERSAL_MCP_HOME))
21
27
 
22
28
 
23
29
  # Name are in the format of "app-name", eg, google-calendar
24
30
  # Class name is NameApp, eg, GoogleCalendarApp
25
31
 
26
32
 
27
- def _import_class(module_path: str, class_name: str):
28
- """
29
- Helper to import a class by name from a module.
30
- Raises ModuleNotFoundError if module or class does not exist.
31
- """
32
- try:
33
- module = importlib.import_module(module_path)
34
- except ModuleNotFoundError as e:
35
- logger.debug(f"Import failed for module '{module_path}': {e}")
36
- raise
37
- try:
38
- return getattr(module, class_name)
39
- except AttributeError as e:
40
- logger.error(f"Class '{class_name}' not found in module '{module_path}'")
41
- raise ModuleNotFoundError(
42
- f"Class '{class_name}' not found in module '{module_path}'"
43
- ) from e
44
-
45
-
46
- def _install_package(slug_clean: str):
33
+ def _install_or_upgrade_package(package_name: str, repository_path: str):
47
34
  """
48
35
  Helper to install a package via pip from the universal-mcp GitHub repository.
49
36
  """
50
- repo_url = f"git+https://github.com/universal-mcp/{slug_clean}"
51
- cmd = ["uv", "pip", "install", repo_url, "--target", UNIVERSAL_MCP_HOME]
52
- logger.info(f"Installing package '{slug_clean}' with command: {' '.join(cmd)}")
37
+ cmd = [
38
+ "uv",
39
+ "pip",
40
+ "install",
41
+ "--upgrade",
42
+ repository_path,
43
+ "--target",
44
+ str(UNIVERSAL_MCP_HOME),
45
+ ]
46
+ logger.debug(f"Installing package '{package_name}' with command: {' '.join(cmd)}")
53
47
  try:
54
- subprocess.check_call(cmd)
48
+ result = subprocess.run(cmd, capture_output=True, text=True)
49
+ if result.stdout:
50
+ logger.info(f"Command stdout: {result.stdout}")
51
+ if result.stderr:
52
+ logger.info(f"Command stderr: {result.stderr}")
53
+ result.check_returncode()
55
54
  except subprocess.CalledProcessError as e:
56
- logger.error(f"Installation failed for '{slug_clean}': {e}")
57
- raise ModuleNotFoundError(
58
- f"Installation failed for package '{slug_clean}'"
59
- ) from e
55
+ logger.error(f"Installation failed for '{package_name}': {e}")
56
+ if e.stdout:
57
+ logger.error(f"Command stdout: {e.stdout}")
58
+ if e.stderr:
59
+ logger.error(f"Command stderr: {e.stderr}")
60
+ raise ModuleNotFoundError(f"Installation failed for package '{package_name}'") from e
60
61
  else:
61
- logger.info(f"Package '{slug_clean}' installed successfully")
62
+ logger.debug(f"Package {package_name} installed successfully")
62
63
 
63
64
 
64
65
  def app_from_slug(slug: str):
@@ -66,29 +67,23 @@ def app_from_slug(slug: str):
66
67
  Dynamically resolve and return the application class for the given slug.
67
68
  Attempts installation from GitHub if the package is not found locally.
68
69
  """
69
- slug_clean = slug.strip().lower()
70
- class_name = "".join(part.capitalize() for part in slug_clean.split("-")) + "App"
71
- package_prefix = f"universal_mcp_{slug_clean.replace('-', '_')}"
72
- module_path = f"{package_prefix}.app"
73
-
74
- logger.info(
75
- f"Resolving app for slug '{slug}' → module '{module_path}', class '{class_name}'"
76
- )
70
+ class_name = get_default_class_name(slug)
71
+ module_path = get_default_module_path(slug)
72
+ package_name = get_default_package_name(slug)
73
+ repository_path = get_default_repository_path(slug)
74
+ logger.debug(f"Resolving app for slug '{slug}' → module '{module_path}', class '{class_name}'")
77
75
  try:
78
- return _import_class(module_path, class_name)
79
- except ModuleNotFoundError as orig_err:
80
- logger.warning(
81
- f"Module '{module_path}' not found locally: {orig_err}. Installing..."
82
- )
83
- _install_package(slug_clean)
84
- # Retry import after installation
85
- try:
86
- return _import_class(module_path, class_name)
87
- except ModuleNotFoundError as retry_err:
88
- logger.error(
89
- f"Still cannot import '{module_path}' after installation: {retry_err}"
90
- )
91
- raise
76
+ _install_or_upgrade_package(package_name, repository_path)
77
+ module = importlib.import_module(module_path)
78
+ class_ = getattr(module, class_name)
79
+ logger.debug(f"Loaded class '{class_}' from module '{module_path}'")
80
+ return class_
81
+ except ModuleNotFoundError as e:
82
+ raise ModuleNotFoundError(f"Package '{module_path}' not found locally. Please install it first.") from e
83
+ except AttributeError as e:
84
+ raise AttributeError(f"Class '{class_name}' not found in module '{module_path}'") from e
85
+ except Exception as e:
86
+ raise Exception(f"Error importing module '{module_path}': {e}") from e
92
87
 
93
88
 
94
89
  __all__ = [