universal-mcp 0.1.8rc3__py3-none-any.whl → 0.1.8rc4__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.
- universal_mcp/applications/ahrefs/README.md +76 -0
- universal_mcp/applications/ahrefs/__init__.py +0 -0
- universal_mcp/applications/ahrefs/app.py +2291 -0
- universal_mcp/applications/application.py +67 -0
- universal_mcp/applications/calendly/app.py +0 -12
- universal_mcp/applications/coda/app.py +0 -33
- universal_mcp/applications/e2b/app.py +2 -28
- universal_mcp/applications/figma/README.md +74 -0
- universal_mcp/applications/figma/__init__.py +0 -0
- universal_mcp/applications/figma/app.py +1261 -0
- universal_mcp/applications/firecrawl/app.py +2 -32
- universal_mcp/applications/google_calendar/app.py +0 -11
- universal_mcp/applications/google_docs/app.py +0 -18
- universal_mcp/applications/google_drive/app.py +0 -17
- universal_mcp/applications/google_mail/app.py +0 -16
- universal_mcp/applications/google_sheet/app.py +0 -18
- universal_mcp/applications/perplexity/app.py +0 -34
- universal_mcp/applications/resend/app.py +0 -18
- universal_mcp/applications/serpapi/app.py +2 -28
- universal_mcp/applications/tavily/app.py +0 -20
- universal_mcp/applications/wrike/app.py +0 -12
- universal_mcp/applications/youtube/app.py +0 -18
- universal_mcp/integrations/agentr.py +27 -4
- universal_mcp/integrations/integration.py +14 -6
- universal_mcp/servers/server.py +0 -3
- universal_mcp/utils/installation.py +199 -8
- {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.8rc4.dist-info}/METADATA +1 -1
- {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.8rc4.dist-info}/RECORD +30 -24
- {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.8rc4.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.8rc4.dist-info}/entry_points.txt +0 -0
@@ -3,6 +3,7 @@ from abc import ABC, abstractmethod
|
|
3
3
|
import httpx
|
4
4
|
from loguru import logger
|
5
5
|
|
6
|
+
from universal_mcp.analytics import analytics
|
6
7
|
from universal_mcp.exceptions import NotAuthorizedError
|
7
8
|
from universal_mcp.integrations import Integration
|
8
9
|
|
@@ -14,6 +15,8 @@ class Application(ABC):
|
|
14
15
|
|
15
16
|
def __init__(self, name: str, **kwargs):
|
16
17
|
self.name = name
|
18
|
+
logger.debug(f"Initializing Application '{name}' with kwargs: {kwargs}")
|
19
|
+
analytics.track_app_loaded(name) # Track app loading
|
17
20
|
|
18
21
|
@abstractmethod
|
19
22
|
def list_tools(self):
|
@@ -29,17 +32,58 @@ class APIApplication(Application):
|
|
29
32
|
super().__init__(name, **kwargs)
|
30
33
|
self.default_timeout = 30
|
31
34
|
self.integration = integration
|
35
|
+
logger.debug(
|
36
|
+
f"Initializing APIApplication '{name}' with integration: {integration}"
|
37
|
+
)
|
32
38
|
|
33
39
|
def _get_headers(self):
|
40
|
+
if not self.integration:
|
41
|
+
logger.debug("No integration configured, returning empty headers")
|
42
|
+
return {}
|
43
|
+
credentials = self.integration.get_credentials()
|
44
|
+
logger.debug(f"Got credentials for integration: {credentials.keys()}")
|
45
|
+
|
46
|
+
# Check if direct headers are provided
|
47
|
+
headers = credentials.get("headers")
|
48
|
+
if headers:
|
49
|
+
logger.debug("Using direct headers from credentials")
|
50
|
+
return headers
|
51
|
+
|
52
|
+
# Check if api key is provided
|
53
|
+
api_key = (
|
54
|
+
credentials.get("api_key")
|
55
|
+
or credentials.get("API_KEY")
|
56
|
+
or credentials.get("apiKey")
|
57
|
+
)
|
58
|
+
if api_key:
|
59
|
+
logger.debug("Using API key from credentials")
|
60
|
+
return {
|
61
|
+
"Authorization": f"Bearer {api_key}",
|
62
|
+
"Content-Type": "application/json",
|
63
|
+
}
|
64
|
+
|
65
|
+
# Check if access token is provided
|
66
|
+
access_token = credentials.get("access_token")
|
67
|
+
if access_token:
|
68
|
+
logger.debug("Using access token from credentials")
|
69
|
+
return {
|
70
|
+
"Authorization": f"Bearer {access_token}",
|
71
|
+
"Content-Type": "application/json",
|
72
|
+
}
|
73
|
+
logger.debug("No authentication found in credentials, returning empty headers")
|
34
74
|
return {}
|
35
75
|
|
36
76
|
def _get(self, url, params=None):
|
37
77
|
try:
|
38
78
|
headers = self._get_headers()
|
79
|
+
logger.debug(f"Making GET request to {url} with params: {params}")
|
39
80
|
response = httpx.get(
|
40
81
|
url, headers=headers, params=params, timeout=self.default_timeout
|
41
82
|
)
|
42
83
|
response.raise_for_status()
|
84
|
+
logger.debug(
|
85
|
+
f"GET request successful with status code: {response.status_code}"
|
86
|
+
)
|
43
87
|
return response
|
44
88
|
except NotAuthorizedError as e:
|
45
89
|
logger.warning(f"Authorization needed: {e.message}")
|
@@ -51,6 +95,9 @@ class APIApplication(Application):
|
|
51
95
|
def _post(self, url, data, params=None):
|
52
96
|
try:
|
53
97
|
headers = self._get_headers()
|
98
|
+
logger.debug(
|
99
|
+
f"Making POST request to {url} with params: {params} and data: {data}"
|
100
|
+
)
|
54
101
|
response = httpx.post(
|
55
102
|
url,
|
56
103
|
headers=headers,
|
@@ -59,12 +106,16 @@ class APIApplication(Application):
|
|
59
106
|
timeout=self.default_timeout,
|
60
107
|
)
|
61
108
|
response.raise_for_status()
|
109
|
+
logger.debug(
|
110
|
+
f"POST request successful with status code: {response.status_code}"
|
111
|
+
)
|
62
112
|
return response
|
63
113
|
except NotAuthorizedError as e:
|
64
114
|
logger.warning(f"Authorization needed: {e.message}")
|
65
115
|
raise e
|
66
116
|
except httpx.HTTPStatusError as e:
|
67
117
|
if e.response.status_code == 429:
|
118
|
+
logger.warning(f"Rate limit exceeded for {url}")
|
68
119
|
return e.response.text or "Rate limit exceeded. Please try again later."
|
69
120
|
else:
|
70
121
|
raise e
|
@@ -75,6 +126,9 @@ class APIApplication(Application):
|
|
75
126
|
def _put(self, url, data, params=None):
|
76
127
|
try:
|
77
128
|
headers = self._get_headers()
|
129
|
+
logger.debug(
|
130
|
+
f"Making PUT request to {url} with params: {params} and data: {data}"
|
131
|
+
)
|
78
132
|
response = httpx.put(
|
79
133
|
url,
|
80
134
|
headers=headers,
|
@@ -83,6 +137,9 @@ class APIApplication(Application):
|
|
83
137
|
timeout=self.default_timeout,
|
84
138
|
)
|
85
139
|
response.raise_for_status()
|
140
|
+
logger.debug(
|
141
|
+
f"PUT request successful with status code: {response.status_code}"
|
142
|
+
)
|
86
143
|
return response
|
87
144
|
except NotAuthorizedError as e:
|
88
145
|
logger.warning(f"Authorization needed: {e.message}")
|
@@ -94,10 +151,14 @@ class APIApplication(Application):
|
|
94
151
|
def _delete(self, url, params=None):
|
95
152
|
try:
|
96
153
|
headers = self._get_headers()
|
154
|
+
logger.debug(f"Making DELETE request to {url} with params: {params}")
|
97
155
|
response = httpx.delete(
|
98
156
|
url, headers=headers, params=params, timeout=self.default_timeout
|
99
157
|
)
|
100
158
|
response.raise_for_status()
|
159
|
+
logger.debug(
|
160
|
+
f"DELETE request successful with status code: {response.status_code}"
|
161
|
+
)
|
101
162
|
return response
|
102
163
|
except NotAuthorizedError as e:
|
103
164
|
logger.warning(f"Authorization needed: {e.message}")
|
@@ -109,6 +170,9 @@ class APIApplication(Application):
|
|
109
170
|
def _patch(self, url, data, params=None):
|
110
171
|
try:
|
111
172
|
headers = self._get_headers()
|
173
|
+
logger.debug(
|
174
|
+
f"Making PATCH request to {url} with params: {params} and data: {data}"
|
175
|
+
)
|
112
176
|
response = httpx.patch(
|
113
177
|
url,
|
114
178
|
headers=headers,
|
@@ -117,6 +181,9 @@ class APIApplication(Application):
|
|
117
181
|
timeout=self.default_timeout,
|
118
182
|
)
|
119
183
|
response.raise_for_status()
|
184
|
+
logger.debug(
|
185
|
+
f"PATCH request successful with status code: {response.status_code}"
|
186
|
+
)
|
120
187
|
return response
|
121
188
|
except NotAuthorizedError as e:
|
122
189
|
logger.warning(f"Authorization needed: {e.message}")
|
@@ -19,18 +19,6 @@ class CalendlyApp(APIApplication):
|
|
19
19
|
super().__init__(name="calendly", integration=integration, **kwargs)
|
20
20
|
self.base_url = "https://api.calendly.com"
|
21
21
|
|
22
|
-
def _get_headers(self):
|
23
|
-
if not self.integration:
|
24
|
-
raise ValueError("Integration not configured for CalendlyApp")
|
25
|
-
credentials = self.integration.get_credentials()
|
26
|
-
|
27
|
-
if "headers" in credentials:
|
28
|
-
return credentials["headers"]
|
29
|
-
return {
|
30
|
-
"Authorization": f"Bearer {credentials['access_token']}",
|
31
|
-
"Content-Type": "application/json",
|
32
|
-
}
|
33
|
-
|
34
22
|
def list_event_invitees(
|
35
23
|
self, uuid, status=None, sort=None, email=None, page_token=None, count=None
|
36
24
|
) -> dict[str, Any]:
|
@@ -1,7 +1,5 @@
|
|
1
1
|
from typing import Any
|
2
2
|
|
3
|
-
from loguru import logger
|
4
|
-
|
5
3
|
from universal_mcp.applications import APIApplication
|
6
4
|
from universal_mcp.integrations import Integration
|
7
5
|
|
@@ -10,37 +8,6 @@ class CodaApp(APIApplication):
|
|
10
8
|
def __init__(self, integration: Integration = None, **kwargs) -> None:
|
11
9
|
super().__init__(name="coda", integration=integration, **kwargs)
|
12
10
|
self.base_url = "https://coda.io/apis/v1"
|
13
|
-
self.api_key: str | None = None
|
14
|
-
|
15
|
-
def _set_api_key(self):
|
16
|
-
if self.api_key:
|
17
|
-
return
|
18
|
-
|
19
|
-
if not self.integration:
|
20
|
-
raise ValueError("Integration is None. Cannot retrieve Coda API Key.")
|
21
|
-
|
22
|
-
credentials = self.integration.get_credentials()
|
23
|
-
if not credentials:
|
24
|
-
raise ValueError(
|
25
|
-
f"Failed to retrieve Coda API Key using integration '{self.integration.name}'. "
|
26
|
-
)
|
27
|
-
api_key = (
|
28
|
-
credentials.get("api_key")
|
29
|
-
or credentials.get("API_KEY")
|
30
|
-
or credentials.get("apiKey")
|
31
|
-
)
|
32
|
-
if not api_key:
|
33
|
-
raise ValueError("Coda API Key not found in credentials.")
|
34
|
-
self.api_key = api_key
|
35
|
-
|
36
|
-
def _get_headers(self) -> dict[str, str]:
|
37
|
-
self._set_api_key()
|
38
|
-
logger.info(f"Coda API Key: {self.api_key}")
|
39
|
-
return {
|
40
|
-
"Authorization": f"Bearer {self.api_key}",
|
41
|
-
"Content-Type": "application/json",
|
42
|
-
"Accept": "application/json",
|
43
|
-
}
|
44
11
|
|
45
12
|
def list_categories(
|
46
13
|
self,
|
@@ -1,7 +1,6 @@
|
|
1
1
|
from typing import Annotated
|
2
2
|
|
3
3
|
from e2b_code_interpreter import Sandbox
|
4
|
-
from loguru import logger
|
5
4
|
|
6
5
|
from universal_mcp.applications.application import APIApplication
|
7
6
|
from universal_mcp.integrations import Integration
|
@@ -15,31 +14,6 @@ class E2BApp(APIApplication):
|
|
15
14
|
|
16
15
|
def __init__(self, integration: Integration | None = None) -> None:
|
17
16
|
super().__init__(name="e2b", integration=integration)
|
18
|
-
self.api_key: str | None = None
|
19
|
-
|
20
|
-
def _set_api_key(self):
|
21
|
-
if self.api_key:
|
22
|
-
return
|
23
|
-
|
24
|
-
if not self.integration:
|
25
|
-
raise ValueError("Integration is None. Cannot retrieve E2B API Key.")
|
26
|
-
|
27
|
-
credentials = self.integration.get_credentials()
|
28
|
-
if not credentials:
|
29
|
-
raise ValueError(
|
30
|
-
f"Invalid credential format received for E2B API Key via integration '{self.integration.name}'. "
|
31
|
-
)
|
32
|
-
api_key = (
|
33
|
-
credentials.get("api_key")
|
34
|
-
or credentials.get("API_KEY")
|
35
|
-
or credentials.get("apiKey")
|
36
|
-
)
|
37
|
-
if not api_key:
|
38
|
-
raise ValueError(
|
39
|
-
f"Invalid credential format received for E2B API Key via integration '{self.integration.name}'. "
|
40
|
-
)
|
41
|
-
self.api_key = api_key
|
42
|
-
logger.info("E2B API Key successfully retrieved via integration.")
|
43
17
|
|
44
18
|
def _format_execution_output(self, logs) -> str:
|
45
19
|
"""Helper function to format the E2B execution logs nicely."""
|
@@ -79,8 +53,8 @@ class E2BApp(APIApplication):
|
|
79
53
|
Tags:
|
80
54
|
execute, sandbox, code-execution, security, important
|
81
55
|
"""
|
82
|
-
self.
|
83
|
-
with Sandbox(api_key=
|
56
|
+
api_key = self.integration.get_credentials().get("api_key")
|
57
|
+
with Sandbox(api_key=api_key) as sandbox:
|
84
58
|
execution = sandbox.run_code(code=code)
|
85
59
|
result = self._format_execution_output(execution.logs)
|
86
60
|
return result
|
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
# Figma MCP Server
|
3
|
+
|
4
|
+
An MCP Server for the Figma API.
|
5
|
+
|
6
|
+
## Supported Integrations
|
7
|
+
|
8
|
+
- AgentR
|
9
|
+
- API Key (Coming Soon)
|
10
|
+
- OAuth (Coming Soon)
|
11
|
+
|
12
|
+
## Tools
|
13
|
+
|
14
|
+
This is automatically generated from OpenAPI schema for the Figma API.
|
15
|
+
|
16
|
+
## Supported Integrations
|
17
|
+
|
18
|
+
This tool can be integrated with any service that supports HTTP requests.
|
19
|
+
|
20
|
+
## Tool List
|
21
|
+
|
22
|
+
| Tool | Description |
|
23
|
+
|------|-------------|
|
24
|
+
| get_file | Retrieves file metadata and content from the API using the specified file key and optional query parameters. |
|
25
|
+
| get_file_nodes | Fetches node data for specified IDs from a file, supporting optional filters such as version, depth, geometry, and plugin data. |
|
26
|
+
| get_images | Retrieves image assets from a remote service for specified node IDs within a given file, with optional image format and export options. |
|
27
|
+
| get_image_fills | Retrieves image fill data for a given file key from the API. |
|
28
|
+
| get_team_projects | Retrieves the list of projects associated with a specified team. |
|
29
|
+
| get_project_files | Retrieves the list of files associated with a specified project, optionally filtered by branch data. |
|
30
|
+
| get_file_versions | Retrieves a paginated list of version history for a specified file. |
|
31
|
+
| get_comments | Retrieves comments for a specified file, optionally formatting the output as Markdown. |
|
32
|
+
| post_comment | Posts a comment to a specified file. |
|
33
|
+
| delete_comment | Deletes a specific comment from a file identified by its key and comment ID. |
|
34
|
+
| get_comment_reactions | Retrieves the reactions associated with a specific comment in a file. |
|
35
|
+
| post_comment_reaction | Posts a reaction emoji to a specific comment on a file. |
|
36
|
+
| delete_comment_reaction | Removes a specific emoji reaction from a comment in a file. |
|
37
|
+
| get_me | Retrieves information about the authenticated user from the API. |
|
38
|
+
| get_team_components | Retrieves a paginated list of components associated with a specified team. |
|
39
|
+
| get_file_components | Retrieves the component information for a specified file from the API. |
|
40
|
+
| get_component | Retrieves a component's details by its key from the API. |
|
41
|
+
| get_team_component_sets | Retrieves a paginated list of component sets for a specified team. |
|
42
|
+
| get_file_component_sets | Retrieves the list of component sets associated with a specific file key. |
|
43
|
+
| get_component_set | Retrieves a component set resource by its key from the server. |
|
44
|
+
| get_team_styles | Retrieves a list of styles for a specified team, with optional pagination controls. |
|
45
|
+
| get_file_styles | Retrieves the style definitions for a specified file from the API. |
|
46
|
+
| get_style | Retrieves a style resource identified by the given key from the API. |
|
47
|
+
| post_webhook | Registers a new webhook for a specified event type and team. |
|
48
|
+
| get_webhook | Retrieves the details of a specific webhook by its unique identifier. |
|
49
|
+
| put_webhook | Update an existing webhook's configuration with new settings such as event type, endpoint, passcode, status, or description. |
|
50
|
+
| delete_webhook | Deletes a webhook with the specified webhook ID. |
|
51
|
+
| get_team_webhooks | Retrieves the list of webhooks configured for a given team. |
|
52
|
+
| get_webhook_requests | Retrieves the list of requests for a specified webhook. |
|
53
|
+
| get_activity_logs | Retrieves activity logs from the API with optional filters for events, time range, limit, and order. |
|
54
|
+
| get_payments | Retrieves a list of payments based on the provided filter criteria. |
|
55
|
+
| get_local_variables | Retrieves the local variables associated with a specific file identified by file_key. |
|
56
|
+
| get_published_variables | Retrieves the published variables associated with a specified file key from the server. |
|
57
|
+
| post_variables | Posts or updates variable data for a specified file by sending a POST request to the variables endpoint. |
|
58
|
+
| get_dev_resources | Retrieves development resources for a specific file. |
|
59
|
+
| post_dev_resources | Submits developer resources to the API and returns the parsed JSON response. |
|
60
|
+
| put_dev_resources | Updates the development resources by sending a PUT request to the dev_resources endpoint. |
|
61
|
+
| delete_dev_resource | Deletes a specific development resource associated with a file. |
|
62
|
+
|
63
|
+
|
64
|
+
|
65
|
+
## Usage
|
66
|
+
|
67
|
+
- Login to AgentR
|
68
|
+
- Follow the quickstart guide to setup MCP Server for your client
|
69
|
+
- Visit Apps Store and enable the Figma app
|
70
|
+
- Restart the MCP Server
|
71
|
+
|
72
|
+
### Local Development
|
73
|
+
|
74
|
+
- Follow the README to test with the local MCP Server
|
File without changes
|