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.
Files changed (71) hide show
  1. universal_mcp/applications/__init__.py +7 -2
  2. universal_mcp/applications/ahrefs/README.md +76 -0
  3. universal_mcp/applications/ahrefs/__init__.py +0 -0
  4. universal_mcp/applications/ahrefs/app.py +2291 -0
  5. universal_mcp/applications/application.py +191 -87
  6. universal_mcp/applications/cal_com_v2/README.md +175 -0
  7. universal_mcp/applications/cal_com_v2/__init__.py +0 -0
  8. universal_mcp/applications/cal_com_v2/app.py +5390 -0
  9. universal_mcp/applications/calendly/app.py +0 -12
  10. universal_mcp/applications/clickup/README.md +160 -0
  11. universal_mcp/applications/clickup/__init__.py +0 -0
  12. universal_mcp/applications/clickup/app.py +5009 -0
  13. universal_mcp/applications/coda/app.py +0 -33
  14. universal_mcp/applications/e2b/app.py +2 -28
  15. universal_mcp/applications/falai/README.md +42 -0
  16. universal_mcp/applications/falai/__init__.py +0 -0
  17. universal_mcp/applications/falai/app.py +332 -0
  18. universal_mcp/applications/figma/README.md +74 -0
  19. universal_mcp/applications/figma/__init__.py +0 -0
  20. universal_mcp/applications/figma/app.py +1261 -0
  21. universal_mcp/applications/firecrawl/app.py +2 -32
  22. universal_mcp/applications/gong/README.md +88 -0
  23. universal_mcp/applications/gong/__init__.py +0 -0
  24. universal_mcp/applications/gong/app.py +2297 -0
  25. universal_mcp/applications/google_calendar/app.py +0 -11
  26. universal_mcp/applications/google_docs/app.py +0 -18
  27. universal_mcp/applications/google_drive/app.py +0 -17
  28. universal_mcp/applications/google_mail/app.py +0 -16
  29. universal_mcp/applications/google_sheet/app.py +0 -18
  30. universal_mcp/applications/hashnode/app.py +81 -0
  31. universal_mcp/applications/hashnode/prompt.md +23 -0
  32. universal_mcp/applications/heygen/README.md +69 -0
  33. universal_mcp/applications/heygen/__init__.py +0 -0
  34. universal_mcp/applications/heygen/app.py +956 -0
  35. universal_mcp/applications/mailchimp/README.md +306 -0
  36. universal_mcp/applications/mailchimp/__init__.py +0 -0
  37. universal_mcp/applications/mailchimp/app.py +10937 -0
  38. universal_mcp/applications/markitdown/app.py +2 -2
  39. universal_mcp/applications/perplexity/app.py +0 -35
  40. universal_mcp/applications/replicate/README.md +65 -0
  41. universal_mcp/applications/replicate/__init__.py +0 -0
  42. universal_mcp/applications/replicate/app.py +980 -0
  43. universal_mcp/applications/resend/app.py +0 -18
  44. universal_mcp/applications/retell_ai/README.md +46 -0
  45. universal_mcp/applications/retell_ai/__init__.py +0 -0
  46. universal_mcp/applications/retell_ai/app.py +333 -0
  47. universal_mcp/applications/rocketlane/README.md +42 -0
  48. universal_mcp/applications/rocketlane/__init__.py +0 -0
  49. universal_mcp/applications/rocketlane/app.py +194 -0
  50. universal_mcp/applications/serpapi/app.py +2 -28
  51. universal_mcp/applications/spotify/README.md +116 -0
  52. universal_mcp/applications/spotify/__init__.py +0 -0
  53. universal_mcp/applications/spotify/app.py +2526 -0
  54. universal_mcp/applications/supabase/README.md +112 -0
  55. universal_mcp/applications/supabase/__init__.py +0 -0
  56. universal_mcp/applications/supabase/app.py +2970 -0
  57. universal_mcp/applications/tavily/app.py +0 -20
  58. universal_mcp/applications/wrike/app.py +0 -12
  59. universal_mcp/applications/youtube/app.py +0 -18
  60. universal_mcp/integrations/agentr.py +27 -4
  61. universal_mcp/integrations/integration.py +14 -6
  62. universal_mcp/servers/server.py +53 -6
  63. universal_mcp/stores/store.py +6 -0
  64. universal_mcp/tools/tools.py +2 -2
  65. universal_mcp/utils/docstring_parser.py +192 -94
  66. universal_mcp/utils/installation.py +199 -8
  67. {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.9.dist-info}/METADATA +6 -1
  68. universal_mcp-0.1.9.dist-info/RECORD +116 -0
  69. universal_mcp-0.1.8rc3.dist-info/RECORD +0 -75
  70. {universal_mcp-0.1.8rc3.dist-info → universal_mcp-0.1.9.dist-info}/WHEEL +0 -0
  71. {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
- 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,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.analytics import analytics
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) -> Application | None:
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) -> Application | None:
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)
@@ -69,6 +69,12 @@ class BaseStore(ABC):
69
69
  """
70
70
  pass
71
71
 
72
+ def __repr__(self):
73
+ return f"{self.__class__.__name__}()"
74
+
75
+ def __str__(self):
76
+ return self.__repr__()
77
+
72
78
 
73
79
  class MemoryStore(BaseStore):
74
80
  """
@@ -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.application import Application
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: Application,
256
+ app: BaseApplication,
257
257
  tools: list[str] | None = None,
258
258
  tags: list[str] | None = None,
259
259
  ) -> None: