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.
- universal_mcp-0.1.15/LICENSE +21 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/PKG-INFO +6 -3
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/pyproject.toml +5 -4
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/test_api_generator.py +1 -1
- universal_mcp-0.1.15/src/tests/test_applications.py +90 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/test_tool.py +24 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/analytics.py +7 -1
- universal_mcp-0.1.15/src/universal_mcp/applications/README.md +122 -0
- universal_mcp-0.1.15/src/universal_mcp/applications/__init__.py +101 -0
- universal_mcp-0.1.15/src/universal_mcp/applications/application.py +442 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/cli.py +49 -49
- universal_mcp-0.1.15/src/universal_mcp/config.py +105 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/exceptions.py +8 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/integrations/integration.py +18 -2
- universal_mcp-0.1.15/src/universal_mcp/logger.py +68 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/servers/__init__.py +2 -2
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/stores/store.py +2 -12
- universal_mcp-0.1.15/src/universal_mcp/tools/__init__.py +15 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/tools/adapters.py +25 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/tools/func_metadata.py +12 -2
- universal_mcp-0.1.15/src/universal_mcp/tools/manager.py +236 -0
- universal_mcp-0.1.15/src/universal_mcp/tools/tools.py +96 -0
- universal_mcp-0.1.15/src/universal_mcp/utils/common.py +33 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/utils/installation.py +8 -8
- {universal_mcp-0.1.13rc14/src/universal_mcp/utils → universal_mcp-0.1.15/src/universal_mcp/utils/openapi}/api_generator.py +1 -1
- universal_mcp-0.1.15/src/universal_mcp/utils/openapi/openapi.py +930 -0
- universal_mcp-0.1.15/src/universal_mcp/utils/openapi/preprocessor.py +1223 -0
- {universal_mcp-0.1.13rc14/src/universal_mcp/utils → universal_mcp-0.1.15/src/universal_mcp/utils/openapi}/readme.py +21 -31
- universal_mcp-0.1.15/src/universal_mcp/utils/templates/README.md.j2 +17 -0
- universal_mcp-0.1.13rc14/src/playground/README.md +0 -65
- universal_mcp-0.1.13rc14/src/playground/__main__.py +0 -36
- universal_mcp-0.1.13rc14/src/playground/agents/react.py +0 -54
- universal_mcp-0.1.13rc14/src/playground/client.py +0 -387
- universal_mcp-0.1.13rc14/src/playground/schema.py +0 -178
- universal_mcp-0.1.13rc14/src/playground/settings.py +0 -24
- universal_mcp-0.1.13rc14/src/playground/streamlit.py +0 -459
- universal_mcp-0.1.13rc14/src/playground/utils.py +0 -76
- universal_mcp-0.1.13rc14/src/tests/test_applications.py +0 -66
- universal_mcp-0.1.13rc14/src/universal_mcp/applications/__init__.py +0 -99
- universal_mcp-0.1.13rc14/src/universal_mcp/applications/application.py +0 -233
- universal_mcp-0.1.13rc14/src/universal_mcp/config.py +0 -32
- universal_mcp-0.1.13rc14/src/universal_mcp/logger.py +0 -17
- universal_mcp-0.1.13rc14/src/universal_mcp/templates/README.md.j2 +0 -93
- universal_mcp-0.1.13rc14/src/universal_mcp/tools/__init__.py +0 -3
- universal_mcp-0.1.13rc14/src/universal_mcp/tools/tools.py +0 -340
- universal_mcp-0.1.13rc14/src/universal_mcp/utils/dump_app_tools.py +0 -78
- universal_mcp-0.1.13rc14/src/universal_mcp/utils/openapi.py +0 -697
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/.gitignore +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/README.md +0 -0
- {universal_mcp-0.1.13rc14/src/playground → universal_mcp-0.1.15/src/tests}/__init__.py +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/conftest.py +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/test_api_integration.py +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/test_localserver.py +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/test_stores.py +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/tests/test_zenquotes.py +0 -0
- {universal_mcp-0.1.13rc14/src/tests → universal_mcp-0.1.15/src/universal_mcp}/__init__.py +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/integrations/README.md +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/integrations/__init__.py +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/py.typed +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/servers/README.md +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/servers/server.py +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/stores/README.md +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/stores/__init__.py +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/tools/README.md +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/utils/__init__.py +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/utils/agentr.py +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/utils/docstring_parser.py +0 -0
- /universal_mcp-0.1.13rc14/src/universal_mcp/__init__.py → /universal_mcp-0.1.15/src/universal_mcp/utils/openapi/__inti__.py +0 -0
- {universal_mcp-0.1.13rc14/src/universal_mcp/utils → universal_mcp-0.1.15/src/universal_mcp/utils/openapi}/docgen.py +0 -0
- {universal_mcp-0.1.13rc14 → universal_mcp-0.1.15}/src/universal_mcp/utils/singleton.py +0 -0
- {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.
|
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.
|
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.
|
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.
|
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]
|
@@ -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,
|
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
|
+
]
|