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.
Files changed (30) hide show
  1. universal_mcp/applications/ahrefs/README.md +76 -0
  2. universal_mcp/applications/ahrefs/__init__.py +0 -0
  3. universal_mcp/applications/ahrefs/app.py +2291 -0
  4. universal_mcp/applications/application.py +67 -0
  5. universal_mcp/applications/calendly/app.py +0 -12
  6. universal_mcp/applications/coda/app.py +0 -33
  7. universal_mcp/applications/e2b/app.py +2 -28
  8. universal_mcp/applications/figma/README.md +74 -0
  9. universal_mcp/applications/figma/__init__.py +0 -0
  10. universal_mcp/applications/figma/app.py +1261 -0
  11. universal_mcp/applications/firecrawl/app.py +2 -32
  12. universal_mcp/applications/google_calendar/app.py +0 -11
  13. universal_mcp/applications/google_docs/app.py +0 -18
  14. universal_mcp/applications/google_drive/app.py +0 -17
  15. universal_mcp/applications/google_mail/app.py +0 -16
  16. universal_mcp/applications/google_sheet/app.py +0 -18
  17. universal_mcp/applications/perplexity/app.py +0 -34
  18. universal_mcp/applications/resend/app.py +0 -18
  19. universal_mcp/applications/serpapi/app.py +2 -28
  20. universal_mcp/applications/tavily/app.py +0 -20
  21. universal_mcp/applications/wrike/app.py +0 -12
  22. universal_mcp/applications/youtube/app.py +0 -18
  23. universal_mcp/integrations/agentr.py +27 -4
  24. universal_mcp/integrations/integration.py +14 -6
  25. universal_mcp/servers/server.py +0 -3
  26. universal_mcp/utils/installation.py +199 -8
  27. {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.8rc4.dist-info}/METADATA +1 -1
  28. {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.8rc4.dist-info}/RECORD +30 -24
  29. {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.8rc4.dist-info}/WHEEL +0 -0
  30. {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.8rc4.dist-info}/entry_points.txt +0 -0
@@ -15,41 +15,11 @@ class FirecrawlApp(APIApplication):
15
15
 
16
16
  def __init__(self, integration: Integration | None = None) -> None:
17
17
  super().__init__(name="firecrawl", integration=integration)
18
- self.api_key: str | None = None
19
-
20
- def _set_api_key(self):
21
- """
22
- Ensures the API key is loaded from the integration.
23
- Raises ValueError if the integration or key is missing/misconfigured.
24
- """
25
- if self.api_key:
26
- return
27
-
28
- if not self.integration:
29
- raise ValueError("Integration is None. Cannot retrieve Firecrawl API Key.")
30
-
31
- credentials = self.integration.get_credentials()
32
- if not credentials:
33
- raise ValueError(
34
- f"Failed to retrieve Firecrawl API Key using integration '{self.integration.name}'. "
35
- f"Check store configuration (e.g., ensure the correct source like environment variable is set)."
36
- )
37
- api_key = (
38
- credentials.get("api_key")
39
- or credentials.get("API_KEY")
40
- or credentials.get("apiKey")
41
- )
42
- if not api_key:
43
- raise ValueError(
44
- f"Failed to retrieve Firecrawl API Key using integration '{self.integration.name}'. "
45
- f"Check store configuration (e.g., ensure the correct environment variable is set)."
46
- )
47
- self.api_key = api_key
48
18
 
49
19
  def _get_client(self) -> FirecrawlApiClient:
50
20
  """Initializes and returns the Firecrawl client after ensuring API key is set."""
51
- self._set_api_key()
52
- return FirecrawlApiClient(api_key=self.api_key)
21
+ api_key = self.integration.get_credentials().get("api_key")
22
+ return FirecrawlApiClient(api_key=api_key)
53
23
 
54
24
  def scrape_url(
55
25
  self, url: str, params: dict[str, Any] | None = None
@@ -11,17 +11,6 @@ class GoogleCalendarApp(APIApplication):
11
11
  super().__init__(name="google-calendar", integration=integration)
12
12
  self.base_api_url = "https://www.googleapis.com/calendar/v3/calendars/primary"
13
13
 
14
- def _get_headers(self):
15
- if not self.integration:
16
- raise ValueError("Integration not configured for GoogleCalendarApp")
17
- credentials = self.integration.get_credentials()
18
- if "headers" in credentials:
19
- return credentials["headers"]
20
- return {
21
- "Authorization": f"Bearer {credentials['access_token']}",
22
- "Accept": "application/json",
23
- }
24
-
25
14
  def _format_datetime(self, dt_string: str) -> str:
26
15
  """Format a datetime string from ISO format to a human-readable format.
27
16
 
@@ -1,9 +1,6 @@
1
1
  from typing import Any
2
2
 
3
- from loguru import logger
4
-
5
3
  from universal_mcp.applications.application import APIApplication
6
- from universal_mcp.exceptions import NotAuthorizedError
7
4
  from universal_mcp.integrations import Integration
8
5
 
9
6
 
@@ -12,21 +9,6 @@ class GoogleDocsApp(APIApplication):
12
9
  super().__init__(name="google-docs", integration=integration)
13
10
  self.base_api_url = "https://docs.googleapis.com/v1/documents"
14
11
 
15
- def _get_headers(self):
16
- if not self.integration:
17
- raise ValueError("Integration not configured for GoogleDocsApp")
18
- credentials = self.integration.get_credentials()
19
- if not credentials:
20
- logger.warning("No Google credentials found via integration.")
21
- action = self.integration.authorize()
22
- raise NotAuthorizedError(action)
23
- if "headers" in credentials:
24
- return credentials["headers"]
25
- return {
26
- "Authorization": f"Bearer {credentials['access_token']}",
27
- "Content-Type": "application/json",
28
- }
29
-
30
12
  def create_document(self, title: str) -> dict[str, Any]:
31
13
  """
32
14
  Creates a new blank Google Document with the specified title and returns the API response.
@@ -4,7 +4,6 @@ import httpx
4
4
  from loguru import logger
5
5
 
6
6
  from universal_mcp.applications.application import APIApplication
7
- from universal_mcp.exceptions import NotAuthorizedError
8
7
  from universal_mcp.integrations import Integration
9
8
 
10
9
 
@@ -18,22 +17,6 @@ class GoogleDriveApp(APIApplication):
18
17
  super().__init__(name="google-drive", integration=integration)
19
18
  self.base_url = "https://www.googleapis.com/drive/v3"
20
19
 
21
- def _get_headers(self):
22
- if not self.integration:
23
- raise ValueError("Integration not configured for GoogleDriveApp")
24
- credentials = self.integration.get_credentials()
25
- if not credentials:
26
- logger.warning("No Google Drive credentials found via integration.")
27
- action = self.integration.authorize()
28
- raise NotAuthorizedError(action)
29
-
30
- if "headers" in credentials:
31
- return credentials["headers"]
32
- return {
33
- "Authorization": f"Bearer {credentials['access_token']}",
34
- "Content-Type": "application/json",
35
- }
36
-
37
20
  def get_drive_info(self) -> dict[str, Any]:
38
21
  """
39
22
  Retrieves detailed information about the user's Google Drive storage and account.
@@ -13,22 +13,6 @@ class GoogleMailApp(APIApplication):
13
13
  super().__init__(name="google-mail", integration=integration)
14
14
  self.base_api_url = "https://gmail.googleapis.com/gmail/v1/users/me"
15
15
 
16
- def _get_headers(self):
17
- if not self.integration:
18
- raise ValueError("Integration not configured for GmailApp")
19
- credentials = self.integration.get_credentials()
20
- if not credentials:
21
- logger.warning("No Gmail credentials found via integration.")
22
- action = self.integration.authorize()
23
- raise NotAuthorizedError(action)
24
-
25
- if "headers" in credentials:
26
- return credentials["headers"]
27
- return {
28
- "Authorization": f"Bearer {credentials['access_token']}",
29
- "Content-Type": "application/json",
30
- }
31
-
32
16
  def send_email(self, to: str, subject: str, body: str) -> str:
33
17
  """
34
18
  Sends an email using the Gmail API and returns a confirmation or error message.
@@ -1,9 +1,6 @@
1
1
  from typing import Any
2
2
 
3
- from loguru import logger
4
-
5
3
  from universal_mcp.applications.application import APIApplication
6
- from universal_mcp.exceptions import NotAuthorizedError
7
4
  from universal_mcp.integrations import Integration
8
5
 
9
6
 
@@ -17,21 +14,6 @@ class GoogleSheetApp(APIApplication):
17
14
  super().__init__(name="google-sheet", integration=integration)
18
15
  self.base_api_url = "https://sheets.googleapis.com/v4/spreadsheets"
19
16
 
20
- def _get_headers(self):
21
- if not self.integration:
22
- raise ValueError("Integration not configured for GoogleSheetsApp")
23
- credentials = self.integration.get_credentials()
24
- if not credentials:
25
- logger.warning("No Google credentials found via integration.")
26
- action = self.integration.authorize()
27
- raise NotAuthorizedError(action)
28
- if "headers" in credentials:
29
- return credentials["headers"]
30
- return {
31
- "Authorization": f"Bearer {credentials['access_token']}",
32
- "Content-Type": "application/json",
33
- }
34
-
35
17
  def create_spreadsheet(self, title: str) -> dict[str, Any]:
36
18
  """
37
19
  Creates a new blank Google Spreadsheet with the specified title and returns the API response.
@@ -1,7 +1,5 @@
1
1
  from typing import Any, Literal
2
2
 
3
- from loguru import logger
4
-
5
3
  from universal_mcp.applications.application import APIApplication
6
4
  from universal_mcp.integrations import Integration
7
5
 
@@ -12,38 +10,6 @@ class PerplexityApp(APIApplication):
12
10
  self.api_key: str | None = None
13
11
  self.base_url = "https://api.perplexity.ai"
14
12
 
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 Perplexity API Key.")
21
-
22
- credentials = self.integration.get_credentials()
23
- if not credentials:
24
- raise ValueError(
25
- f"Failed to retrieve Perplexity 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(
34
- f"Invalid credential format received for Perplexity API Key via integration '{self.integration.name}'. "
35
- )
36
- self.api_key = api_key
37
-
38
- def _get_headers(self) -> dict[str, str]:
39
- self._set_api_key()
40
- logger.debug(f"Perplexity API Key: {self.api_key}")
41
- return {
42
- "Authorization": f"Bearer {self.api_key}",
43
- "Content-Type": "application/json",
44
- "Accept": "application/json",
45
- }
46
-
47
13
  def chat(
48
14
  self,
49
15
  query: str,
@@ -5,24 +5,6 @@ from universal_mcp.integrations import Integration
5
5
  class ResendApp(APIApplication):
6
6
  def __init__(self, integration: Integration) -> None:
7
7
  super().__init__(name="resend", integration=integration)
8
- self.api_key = None
9
-
10
- def _get_headers(self):
11
- if not self.api_key:
12
- credentials = self.integration.get_credentials()
13
- if not credentials:
14
- raise ValueError("No credentials found")
15
- api_key = (
16
- credentials.get("api_key")
17
- or credentials.get("API_KEY")
18
- or credentials.get("apiKey")
19
- )
20
- if not api_key:
21
- raise ValueError("No API key found")
22
- self.api_key = api_key
23
- return {
24
- "Authorization": f"Bearer {self.api_key}",
25
- }
26
8
 
27
9
  def send_email(self, to: str, subject: str, content: str) -> str:
28
10
  """
@@ -1,5 +1,4 @@
1
1
  import httpx
2
- from loguru import logger
3
2
  from serpapi import SerpApiClient as SerpApiSearch
4
3
 
5
4
  from universal_mcp.applications.application import APIApplication
@@ -8,31 +7,6 @@ from universal_mcp.applications.application import APIApplication
8
7
  class SerpapiApp(APIApplication):
9
8
  def __init__(self, **kwargs):
10
9
  super().__init__(name="serpapi", **kwargs)
11
- self.api_key: str | None = None
12
-
13
- def _set_api_key(self):
14
- if self.api_key is not None:
15
- return
16
- if not self.integration:
17
- raise ValueError("Integration is None. Cannot retrieve SERP API Key.")
18
-
19
- credentials = self.integration.get_credentials()
20
- if not credentials:
21
- raise ValueError(
22
- f"Failed to retrieve SERP API Key using integration '{self.integration.name}'. "
23
- f"Check store configuration (e.g., ensure the correct environment variable is set)."
24
- )
25
- api_key = (
26
- credentials.get("api_key")
27
- or credentials.get("API_KEY")
28
- or credentials.get("apiKey")
29
- )
30
- if not api_key:
31
- raise ValueError(
32
- f"Invalid credential format received for SERP API Key via integration '{self.integration.name}'. "
33
- )
34
- self.api_key = api_key
35
- logger.info("SERP API Key successfully retrieved via integration.")
36
10
 
37
11
  async def search(self, params: dict[str, any] = None) -> str:
38
12
  """
@@ -53,9 +27,9 @@ class SerpapiApp(APIApplication):
53
27
  """
54
28
  if params is None:
55
29
  params = {}
56
- self._set_api_key()
30
+ api_key = self.integration.get_credentials().get("api_key")
57
31
  params = {
58
- "api_key": self.api_key,
32
+ "api_key": api_key,
59
33
  "engine": "google_light", # Fastest engine by default
60
34
  **params, # Include any additional parameters
61
35
  }
@@ -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
- def get_credentials(self):
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
- return data
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
- try:
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.
@@ -9,7 +9,6 @@ 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.analytics import analytics
13
12
  from universal_mcp.applications import Application, app_from_slug
14
13
  from universal_mcp.config import AppConfig, ServerConfig, StoreConfig
15
14
  from universal_mcp.integrations import AgentRIntegration, integration_from_config
@@ -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)
@@ -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)