universal-mcp 0.1.8rc3__py3-none-any.whl → 0.1.9__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/__init__.py +7 -2
- 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 +191 -87
- universal_mcp/applications/cal_com_v2/README.md +175 -0
- universal_mcp/applications/cal_com_v2/__init__.py +0 -0
- universal_mcp/applications/cal_com_v2/app.py +5390 -0
- universal_mcp/applications/calendly/app.py +0 -12
- universal_mcp/applications/clickup/README.md +160 -0
- universal_mcp/applications/clickup/__init__.py +0 -0
- universal_mcp/applications/clickup/app.py +5009 -0
- universal_mcp/applications/coda/app.py +0 -33
- universal_mcp/applications/e2b/app.py +2 -28
- universal_mcp/applications/falai/README.md +42 -0
- universal_mcp/applications/falai/__init__.py +0 -0
- universal_mcp/applications/falai/app.py +332 -0
- 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/gong/README.md +88 -0
- universal_mcp/applications/gong/__init__.py +0 -0
- universal_mcp/applications/gong/app.py +2297 -0
- 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/hashnode/app.py +81 -0
- universal_mcp/applications/hashnode/prompt.md +23 -0
- universal_mcp/applications/heygen/README.md +69 -0
- universal_mcp/applications/heygen/__init__.py +0 -0
- universal_mcp/applications/heygen/app.py +956 -0
- universal_mcp/applications/mailchimp/README.md +306 -0
- universal_mcp/applications/mailchimp/__init__.py +0 -0
- universal_mcp/applications/mailchimp/app.py +10937 -0
- universal_mcp/applications/markitdown/app.py +2 -2
- universal_mcp/applications/perplexity/app.py +0 -35
- universal_mcp/applications/replicate/README.md +65 -0
- universal_mcp/applications/replicate/__init__.py +0 -0
- universal_mcp/applications/replicate/app.py +980 -0
- universal_mcp/applications/resend/app.py +0 -18
- universal_mcp/applications/retell_ai/README.md +46 -0
- universal_mcp/applications/retell_ai/__init__.py +0 -0
- universal_mcp/applications/retell_ai/app.py +333 -0
- universal_mcp/applications/rocketlane/README.md +42 -0
- universal_mcp/applications/rocketlane/__init__.py +0 -0
- universal_mcp/applications/rocketlane/app.py +194 -0
- universal_mcp/applications/serpapi/app.py +2 -28
- universal_mcp/applications/spotify/README.md +116 -0
- universal_mcp/applications/spotify/__init__.py +0 -0
- universal_mcp/applications/spotify/app.py +2526 -0
- universal_mcp/applications/supabase/README.md +112 -0
- universal_mcp/applications/supabase/__init__.py +0 -0
- universal_mcp/applications/supabase/app.py +2970 -0
- 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 +53 -6
- universal_mcp/stores/store.py +6 -0
- universal_mcp/tools/tools.py +2 -2
- universal_mcp/utils/docstring_parser.py +192 -94
- universal_mcp/utils/installation.py +199 -8
- {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.9.dist-info}/METADATA +6 -1
- universal_mcp-0.1.9.dist-info/RECORD +116 -0
- universal_mcp-0.1.8rc3.dist-info/RECORD +0 -75
- {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.9.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.9.dist-info}/entry_points.txt +0 -0
@@ -7,25 +7,6 @@ class TavilyApp(APIApplication):
|
|
7
7
|
name = "tavily"
|
8
8
|
self.base_url = "https://api.tavily.com"
|
9
9
|
super().__init__(name=name, integration=integration)
|
10
|
-
self.api_key = None
|
11
|
-
|
12
|
-
def _get_headers(self):
|
13
|
-
if not self.api_key:
|
14
|
-
credentials = self.integration.get_credentials()
|
15
|
-
if not credentials:
|
16
|
-
raise ValueError("No credentials found")
|
17
|
-
api_key = (
|
18
|
-
credentials.get("api_key")
|
19
|
-
or credentials.get("API_KEY")
|
20
|
-
or credentials.get("apiKey")
|
21
|
-
)
|
22
|
-
if not api_key:
|
23
|
-
raise ValueError("No API key found")
|
24
|
-
self.api_key = api_key
|
25
|
-
return {
|
26
|
-
"Authorization": f"Bearer {self.api_key}",
|
27
|
-
"Content-Type": "application/json",
|
28
|
-
}
|
29
10
|
|
30
11
|
def search(self, query: str) -> str:
|
31
12
|
"""
|
@@ -44,7 +25,6 @@ class TavilyApp(APIApplication):
|
|
44
25
|
Tags:
|
45
26
|
search, ai, web, query, important, api-client, text-processing
|
46
27
|
"""
|
47
|
-
self.validate()
|
48
28
|
url = f"{self.base_url}/search"
|
49
29
|
payload = {
|
50
30
|
"query": query,
|
@@ -19,18 +19,6 @@ class WrikeApp(APIApplication):
|
|
19
19
|
super().__init__(name="wrike", integration=integration, **kwargs)
|
20
20
|
self.base_url = "https://www.wrike.com/api/v4"
|
21
21
|
|
22
|
-
def _get_headers(self):
|
23
|
-
if not self.integration:
|
24
|
-
raise ValueError("Integration not configured for WrikeApp")
|
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 get_contacts(self, deleted=None, fields=None, metadata=None) -> Any:
|
35
23
|
"""
|
36
24
|
Retrieves a list of contacts from the server, with optional filtering and field selection.
|
@@ -1,9 +1,6 @@
|
|
1
1
|
from typing import Any
|
2
2
|
|
3
|
-
from loguru import logger
|
4
|
-
|
5
3
|
from universal_mcp.applications import APIApplication
|
6
|
-
from universal_mcp.exceptions import NotAuthorizedError
|
7
4
|
from universal_mcp.integrations import Integration
|
8
5
|
|
9
6
|
|
@@ -22,21 +19,6 @@ class YoutubeApp(APIApplication):
|
|
22
19
|
super().__init__(name="youtube", integration=integration, **kwargs)
|
23
20
|
self.base_url = "https://www.googleapis.com/youtube/v3"
|
24
21
|
|
25
|
-
def _get_headers(self):
|
26
|
-
if not self.integration:
|
27
|
-
raise ValueError("Integration not configured for YoutubeApp")
|
28
|
-
credentials = self.integration.get_credentials()
|
29
|
-
if not credentials:
|
30
|
-
logger.warning("No Google credentials found via integration.")
|
31
|
-
action = self.integration.authorize()
|
32
|
-
raise NotAuthorizedError(action)
|
33
|
-
if "headers" in credentials:
|
34
|
-
return credentials["headers"]
|
35
|
-
return {
|
36
|
-
"Authorization": f"Bearer {credentials['access_token']}",
|
37
|
-
"Content-Type": "application/json",
|
38
|
-
}
|
39
|
-
|
40
22
|
def get_jobs_job_reports(
|
41
23
|
self,
|
42
24
|
jobId,
|
@@ -29,7 +29,10 @@ class AgentRIntegration(Integration):
|
|
29
29
|
"API key for AgentR is missing. Please visit https://agentr.dev to create an API key, then set it as AGENTR_API_KEY environment variable."
|
30
30
|
)
|
31
31
|
raise ValueError("AgentR API key required - get one at https://agentr.dev")
|
32
|
-
self.base_url = os.getenv("AGENTR_BASE_URL", "https://api.agentr.dev")
|
32
|
+
self.base_url = os.getenv("AGENTR_BASE_URL", "https://api.agentr.dev").rstrip(
|
33
|
+
"/"
|
34
|
+
)
|
35
|
+
self._credentials = None
|
33
36
|
|
34
37
|
def set_credentials(self, credentials: dict | None = None):
|
35
38
|
"""Set credentials for the integration.
|
@@ -43,9 +46,9 @@ class AgentRIntegration(Integration):
|
|
43
46
|
str: Authorization URL from authorize() method
|
44
47
|
"""
|
45
48
|
return self.authorize()
|
46
|
-
# raise NotImplementedError("AgentR Integration does not support setting credentials. Visit the authorize url to set credentials.")
|
47
49
|
|
48
|
-
|
50
|
+
@property
|
51
|
+
def credentials(self):
|
49
52
|
"""Get credentials for the integration from the AgentR API.
|
50
53
|
|
51
54
|
Makes API request to retrieve stored credentials for this integration.
|
@@ -57,16 +60,36 @@ class AgentRIntegration(Integration):
|
|
57
60
|
NotAuthorizedError: If credentials are not found (404 response)
|
58
61
|
HTTPError: For other API errors
|
59
62
|
"""
|
63
|
+
if self._credentials is not None:
|
64
|
+
return self._credentials
|
60
65
|
response = httpx.get(
|
61
66
|
f"{self.base_url}/api/{self.name}/credentials/",
|
62
67
|
headers={"accept": "application/json", "X-API-KEY": self.api_key},
|
63
68
|
)
|
64
69
|
if response.status_code == 404:
|
70
|
+
logger.warning(
|
71
|
+
f"No credentials found for {self.name}. Requesting authorization..."
|
72
|
+
)
|
65
73
|
action = self.authorize()
|
66
74
|
raise NotAuthorizedError(action)
|
67
75
|
response.raise_for_status()
|
68
76
|
data = response.json()
|
69
|
-
|
77
|
+
self._credentials = data
|
78
|
+
return self._credentials
|
79
|
+
|
80
|
+
def get_credentials(self):
|
81
|
+
"""Get credentials for the integration from the AgentR API.
|
82
|
+
|
83
|
+
Makes API request to retrieve stored credentials for this integration.
|
84
|
+
|
85
|
+
Returns:
|
86
|
+
dict: Credentials data from API response
|
87
|
+
|
88
|
+
Raises:
|
89
|
+
NotAuthorizedError: If credentials are not found (404 response)
|
90
|
+
HTTPError: For other API errors
|
91
|
+
"""
|
92
|
+
return self.credentials
|
70
93
|
|
71
94
|
def authorize(self):
|
72
95
|
"""Get authorization URL for the integration.
|
@@ -91,9 +91,22 @@ class ApiKeyIntegration(Integration):
|
|
91
91
|
"""
|
92
92
|
|
93
93
|
def __init__(self, name: str, store: BaseStore | None = None, **kwargs):
|
94
|
+
self.type = "api_key"
|
94
95
|
sanitized_name = sanitize_api_key_name(name)
|
95
96
|
super().__init__(sanitized_name, store, **kwargs)
|
96
97
|
logger.info(f"Initializing API Key Integration: {name} with store: {store}")
|
98
|
+
self._api_key: str | None = None
|
99
|
+
|
100
|
+
@property
|
101
|
+
def api_key(self) -> str | None:
|
102
|
+
if not self._api_key:
|
103
|
+
try:
|
104
|
+
credentials = self.store.get(self.name)
|
105
|
+
self._api_key = credentials
|
106
|
+
except KeyNotFoundError as e:
|
107
|
+
action = self.authorize()
|
108
|
+
raise NotAuthorizedError(action) from e
|
109
|
+
return self._api_key
|
97
110
|
|
98
111
|
def get_credentials(self) -> dict[str, str]:
|
99
112
|
"""Get API key credentials.
|
@@ -104,12 +117,7 @@ class ApiKeyIntegration(Integration):
|
|
104
117
|
Raises:
|
105
118
|
NotAuthorizedError: If API key is not found.
|
106
119
|
"""
|
107
|
-
|
108
|
-
credentials = self.store.get(self.name)
|
109
|
-
except KeyNotFoundError as e:
|
110
|
-
action = self.authorize()
|
111
|
-
raise NotAuthorizedError(action) from e
|
112
|
-
return {"api_key": credentials}
|
120
|
+
return {"api_key": self.api_key}
|
113
121
|
|
114
122
|
def set_credentials(self, credentials: dict[str, Any]) -> None:
|
115
123
|
"""Set API key credentials.
|
universal_mcp/servers/server.py
CHANGED
@@ -9,8 +9,7 @@ from loguru import logger
|
|
9
9
|
from mcp.server.fastmcp import FastMCP
|
10
10
|
from mcp.types import TextContent
|
11
11
|
|
12
|
-
from universal_mcp.
|
13
|
-
from universal_mcp.applications import Application, app_from_slug
|
12
|
+
from universal_mcp.applications import BaseApplication, app_from_slug
|
14
13
|
from universal_mcp.config import AppConfig, ServerConfig, StoreConfig
|
15
14
|
from universal_mcp.integrations import AgentRIntegration, integration_from_config
|
16
15
|
from universal_mcp.stores import BaseStore, store_from_config
|
@@ -130,7 +129,7 @@ class LocalServer(BaseServer):
|
|
130
129
|
self.add_tool(store.delete)
|
131
130
|
return store
|
132
131
|
|
133
|
-
def _load_app(self, app_config: AppConfig) ->
|
132
|
+
def _load_app(self, app_config: AppConfig) -> BaseApplication | None:
|
134
133
|
"""Load a single application with its integration.
|
135
134
|
|
136
135
|
Args:
|
@@ -145,7 +144,6 @@ class LocalServer(BaseServer):
|
|
145
144
|
if app_config.integration
|
146
145
|
else None
|
147
146
|
)
|
148
|
-
analytics.track_app_loaded(app_config.name) # Track app loading
|
149
147
|
return app_from_slug(app_config.name)(integration=integration)
|
150
148
|
except Exception as e:
|
151
149
|
logger.error(f"Failed to load app {app_config.name}: {e}", exc_info=True)
|
@@ -204,7 +202,7 @@ class AgentRServer(BaseServer):
|
|
204
202
|
logger.error(f"Failed to fetch apps from AgentR: {e}", exc_info=True)
|
205
203
|
raise
|
206
204
|
|
207
|
-
def _load_app(self, app_config: AppConfig) ->
|
205
|
+
def _load_app(self, app_config: AppConfig) -> BaseApplication | None:
|
208
206
|
"""Load a single application with AgentR integration.
|
209
207
|
|
210
208
|
Args:
|
@@ -221,7 +219,6 @@ class AgentRServer(BaseServer):
|
|
221
219
|
if app_config.integration
|
222
220
|
else None
|
223
221
|
)
|
224
|
-
analytics.track_app_loaded(app_config.name) # Track app loading
|
225
222
|
return app_from_slug(app_config.name)(integration=integration)
|
226
223
|
except Exception as e:
|
227
224
|
logger.error(f"Failed to load app {app_config.name}: {e}", exc_info=True)
|
@@ -237,3 +234,53 @@ class AgentRServer(BaseServer):
|
|
237
234
|
except Exception:
|
238
235
|
logger.error("Failed to load apps", exc_info=True)
|
239
236
|
raise
|
237
|
+
|
238
|
+
|
239
|
+
class SingleMCPServer(BaseServer):
|
240
|
+
"""
|
241
|
+
Minimal server implementation hosting a single BaseApplication instance.
|
242
|
+
|
243
|
+
This server type is intended for development and testing of a single
|
244
|
+
application's tools. It does not manage integrations or stores internally
|
245
|
+
beyond initializing the ToolManager and exposing the provided application's tools.
|
246
|
+
The application instance passed to the constructor should already be
|
247
|
+
configured with its appropriate integration (if required).
|
248
|
+
|
249
|
+
Args:
|
250
|
+
config: Server configuration (used for name, description, etc. but ignores 'apps')
|
251
|
+
app_instance: The single BaseApplication instance to host and expose its tools.
|
252
|
+
Can be None, in which case no tools will be registered.
|
253
|
+
**kwargs: Additional keyword arguments passed to FastMCP parent class.
|
254
|
+
"""
|
255
|
+
|
256
|
+
def __init__(
|
257
|
+
self,
|
258
|
+
app_instance: BaseApplication,
|
259
|
+
config: ServerConfig | None = None,
|
260
|
+
**kwargs,
|
261
|
+
):
|
262
|
+
server_config = ServerConfig(
|
263
|
+
type="local",
|
264
|
+
name=f"{app_instance.name.title()} MCP Server for Local Development"
|
265
|
+
if app_instance
|
266
|
+
else "Unnamed MCP Server",
|
267
|
+
description=f"Minimal MCP server for the local {app_instance.name} application."
|
268
|
+
if app_instance
|
269
|
+
else "Minimal MCP server with no application loaded.",
|
270
|
+
)
|
271
|
+
if not config:
|
272
|
+
config = server_config
|
273
|
+
super().__init__(config, **kwargs)
|
274
|
+
|
275
|
+
self.app_instance = app_instance
|
276
|
+
self._load_apps()
|
277
|
+
|
278
|
+
def _load_apps(self) -> None:
|
279
|
+
"""Registers tools from the single provided application instance."""
|
280
|
+
if not self.app_instance:
|
281
|
+
logger.warning("No app_instance provided. No tools registered.")
|
282
|
+
return
|
283
|
+
|
284
|
+
tool_functions = self.app_instance.list_tools()
|
285
|
+
for tool_func in tool_functions:
|
286
|
+
self._tool_manager.add_tool(tool_func)
|
universal_mcp/stores/store.py
CHANGED
universal_mcp/tools/tools.py
CHANGED
@@ -9,7 +9,7 @@ from loguru import logger
|
|
9
9
|
from pydantic import BaseModel, Field
|
10
10
|
|
11
11
|
from universal_mcp.analytics import analytics
|
12
|
-
from universal_mcp.applications
|
12
|
+
from universal_mcp.applications import BaseApplication
|
13
13
|
from universal_mcp.exceptions import NotAuthorizedError, ToolError
|
14
14
|
from universal_mcp.utils.docstring_parser import parse_docstring
|
15
15
|
|
@@ -253,7 +253,7 @@ class ToolManager:
|
|
253
253
|
|
254
254
|
def register_tools_from_app(
|
255
255
|
self,
|
256
|
-
app:
|
256
|
+
app: BaseApplication,
|
257
257
|
tools: list[str] | None = None,
|
258
258
|
tags: list[str] | None = None,
|
259
259
|
) -> None:
|