universal-mcp 0.1.8rc4__py3-none-any.whl → 0.1.9rc2__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 (48) hide show
  1. universal_mcp/applications/__init__.py +7 -2
  2. universal_mcp/applications/application.py +151 -114
  3. universal_mcp/applications/cal_com_v2/README.md +175 -0
  4. universal_mcp/applications/cal_com_v2/__init__.py +0 -0
  5. universal_mcp/applications/cal_com_v2/app.py +5390 -0
  6. universal_mcp/applications/clickup/README.md +160 -0
  7. universal_mcp/applications/clickup/__init__.py +0 -0
  8. universal_mcp/applications/clickup/app.py +5009 -0
  9. universal_mcp/applications/falai/README.md +42 -0
  10. universal_mcp/applications/falai/__init__.py +0 -0
  11. universal_mcp/applications/falai/app.py +332 -0
  12. universal_mcp/applications/gong/README.md +88 -0
  13. universal_mcp/applications/gong/__init__.py +0 -0
  14. universal_mcp/applications/gong/app.py +2297 -0
  15. universal_mcp/applications/hashnode/app.py +81 -0
  16. universal_mcp/applications/hashnode/prompt.md +23 -0
  17. universal_mcp/applications/heygen/README.md +69 -0
  18. universal_mcp/applications/heygen/__init__.py +0 -0
  19. universal_mcp/applications/heygen/app.py +956 -0
  20. universal_mcp/applications/mailchimp/README.md +306 -0
  21. universal_mcp/applications/mailchimp/__init__.py +0 -0
  22. universal_mcp/applications/mailchimp/app.py +10937 -0
  23. universal_mcp/applications/markitdown/app.py +2 -2
  24. universal_mcp/applications/perplexity/app.py +0 -1
  25. universal_mcp/applications/replicate/README.md +65 -0
  26. universal_mcp/applications/replicate/__init__.py +0 -0
  27. universal_mcp/applications/replicate/app.py +980 -0
  28. universal_mcp/applications/retell_ai/README.md +46 -0
  29. universal_mcp/applications/retell_ai/__init__.py +0 -0
  30. universal_mcp/applications/retell_ai/app.py +333 -0
  31. universal_mcp/applications/rocketlane/README.md +42 -0
  32. universal_mcp/applications/rocketlane/__init__.py +0 -0
  33. universal_mcp/applications/rocketlane/app.py +194 -0
  34. universal_mcp/applications/spotify/README.md +116 -0
  35. universal_mcp/applications/spotify/__init__.py +0 -0
  36. universal_mcp/applications/spotify/app.py +2526 -0
  37. universal_mcp/applications/supabase/README.md +112 -0
  38. universal_mcp/applications/supabase/__init__.py +0 -0
  39. universal_mcp/applications/supabase/app.py +2970 -0
  40. universal_mcp/integrations/integration.py +1 -1
  41. universal_mcp/servers/server.py +53 -3
  42. universal_mcp/stores/store.py +6 -0
  43. universal_mcp/tools/tools.py +2 -2
  44. universal_mcp/utils/docstring_parser.py +192 -94
  45. {universal_mcp-0.1.8rc4.dist-info → universal_mcp-0.1.9rc2.dist-info}/METADATA +6 -1
  46. {universal_mcp-0.1.8rc4.dist-info → universal_mcp-0.1.9rc2.dist-info}/RECORD +48 -13
  47. {universal_mcp-0.1.8rc4.dist-info → universal_mcp-0.1.9rc2.dist-info}/WHEEL +0 -0
  48. {universal_mcp-0.1.8rc4.dist-info → universal_mcp-0.1.9rc2.dist-info}/entry_points.txt +0 -0
@@ -102,7 +102,7 @@ class ApiKeyIntegration(Integration):
102
102
  if not self._api_key:
103
103
  try:
104
104
  credentials = self.store.get(self.name)
105
- self.api_key = credentials
105
+ self._api_key = credentials
106
106
  except KeyNotFoundError as e:
107
107
  action = self.authorize()
108
108
  raise NotAuthorizedError(action) from e
@@ -9,7 +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.applications import Application, app_from_slug
12
+ from universal_mcp.applications import BaseApplication, app_from_slug
13
13
  from universal_mcp.config import AppConfig, ServerConfig, StoreConfig
14
14
  from universal_mcp.integrations import AgentRIntegration, integration_from_config
15
15
  from universal_mcp.stores import BaseStore, store_from_config
@@ -129,7 +129,7 @@ class LocalServer(BaseServer):
129
129
  self.add_tool(store.delete)
130
130
  return store
131
131
 
132
- def _load_app(self, app_config: AppConfig) -> Application | None:
132
+ def _load_app(self, app_config: AppConfig) -> BaseApplication | None:
133
133
  """Load a single application with its integration.
134
134
 
135
135
  Args:
@@ -202,7 +202,7 @@ class AgentRServer(BaseServer):
202
202
  logger.error(f"Failed to fetch apps from AgentR: {e}", exc_info=True)
203
203
  raise
204
204
 
205
- def _load_app(self, app_config: AppConfig) -> Application | None:
205
+ def _load_app(self, app_config: AppConfig) -> BaseApplication | None:
206
206
  """Load a single application with AgentR integration.
207
207
 
208
208
  Args:
@@ -234,3 +234,53 @@ class AgentRServer(BaseServer):
234
234
  except Exception:
235
235
  logger.error("Failed to load apps", exc_info=True)
236
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:
@@ -4,16 +4,23 @@ from typing import Any
4
4
 
5
5
  def parse_docstring(docstring: str | None) -> dict[str, Any]:
6
6
  """
7
- Parses a standard Python docstring into summary, args, returns, raises, and tags.
7
+ Parses a Python docstring into structured components: summary, arguments,
8
+ return value, raised exceptions, and custom tags.
9
+
10
+ Supports multi-line descriptions for each section. Recognizes common section
11
+ headers like 'Args:', 'Returns:', 'Raises:', 'Tags:', etc. Also attempts
12
+ to parse key-value pairs within 'Args:' and 'Raises:' sections.
8
13
 
9
14
  Args:
10
- docstring: The docstring to parse.
15
+ docstring: The docstring string to parse, or None.
11
16
 
12
17
  Returns:
13
- A dictionary with keys 'summary', 'args', 'returns', 'raises', 'tags'.
14
- 'args' is a dict mapping arg names to descriptions.
15
- 'raises' is a dict mapping exception type names to descriptions.
16
- 'tags' is a list of strings extracted from the 'Tags:' section, comma-separated.
18
+ A dictionary containing the parsed components:
19
+ - 'summary': The first paragraph of the docstring.
20
+ - 'args': A dictionary mapping argument names to their descriptions.
21
+ - 'returns': The description of the return value.
22
+ - 'raises': A dictionary mapping exception types to their descriptions.
23
+ - 'tags': A list of strings found in the 'Tags:' section.
17
24
  """
18
25
  if not docstring:
19
26
  return {"summary": "", "args": {}, "returns": "", "raises": {}, "tags": []}
@@ -22,123 +29,213 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
22
29
  if not lines:
23
30
  return {"summary": "", "args": {}, "returns": "", "raises": {}, "tags": []}
24
31
 
25
- summary = lines[0].strip()
26
- args = {}
27
- returns = ""
28
- raises = {}
29
- tags: list[str] = [] # Final list of parsed tags
30
- current_section = None
31
- current_key = None
32
- current_desc_lines = [] # Accumulator for multi-line descriptions/tag content
32
+ summary: str = ""
33
+ summary_lines: list[str] = []
34
+ args: dict[str, str] = {}
35
+ returns: str = ""
36
+ raises: dict[str, str] = {}
37
+ tags: list[str] = []
38
+
39
+ current_section: str | None = None
40
+ current_key: str | None = None
41
+ current_desc_lines: list[str] = []
42
+
43
+ # Pattern to capture item key and the start of its description
44
+ # Matches "key:" or "key (type):" followed by description
33
45
  key_pattern = re.compile(r"^\s*([\w\.]+)\s*(?:\(.*\))?:\s*(.*)")
34
46
 
35
47
  def finalize_current_item():
36
- """Helper function to finalize the currently parsed item."""
37
- nonlocal returns, tags # Allow modification of outer scope variables
48
+ """Processes the collected current_desc_lines and assigns them."""
49
+ nonlocal returns, tags, args, raises
38
50
  desc = " ".join(current_desc_lines).strip()
51
+
39
52
  if current_section == "args" and current_key:
40
- args[current_key] = desc
53
+ if desc:
54
+ args[current_key] = desc
41
55
  elif current_section == "raises" and current_key:
42
- raises[current_key] = desc
56
+ if desc:
57
+ raises[current_key] = desc
43
58
  elif current_section == "returns":
44
59
  returns = desc
45
- # SIM102 applied: Combine nested if
46
- elif current_section == "tags" and desc: # Only process if there's content
47
- tags = [tag.strip() for tag in desc.split(",") if tag.strip()]
48
-
49
- # B007 applied: Rename unused loop variable i to _
50
- for _, line in enumerate(lines[1:]):
51
- stripped_line = line.strip()
52
- original_indentation = len(line) - len(line.lstrip(" "))
60
+ elif current_section == "tags":
61
+ # Tags section content is treated as a comma-separated list
62
+ tags.clear() # Clear existing tags in case of multiple tag sections (unlikely but safe)
63
+ tags.extend([tag.strip() for tag in desc.split(",") if tag.strip()])
64
+ # 'other' sections are ignored in the final output
53
65
 
54
- section_line = stripped_line.lower()
55
- is_new_section_header = False
56
- new_section_type = None
66
+ def check_for_section_header(line: str) -> tuple[bool, str | None, str]:
67
+ """Checks if a line is a recognized section header."""
68
+ stripped_lower = line.strip().lower()
69
+ section_type: str | None = None
57
70
  header_content = ""
58
71
 
59
- if section_line in ("args:", "arguments:", "parameters:"):
60
- new_section_type = "args"
61
- is_new_section_header = True
62
- elif section_line in ("returns:", "yields:"):
63
- new_section_type = "returns"
64
- is_new_section_header = True
65
- elif section_line.startswith(("raises ", "raises:", "errors:", "exceptions:")):
66
- new_section_type = "raises"
67
- is_new_section_header = True
68
- elif section_line.startswith(
69
- ("tags:", "tags")
70
- ): # Match "Tags:" or "Tags" potentially followed by content
71
- new_section_type = "tags"
72
- is_new_section_header = True
73
- if ":" in stripped_line:
74
- header_content = stripped_line.split(":", 1)[1].strip()
75
- elif section_line.endswith(":") and section_line[:-1] in (
72
+ if stripped_lower in ("args:", "arguments:", "parameters:"):
73
+ section_type = "args"
74
+ elif stripped_lower in ("returns:", "yields:"):
75
+ section_type = "returns"
76
+ elif stripped_lower in ("raises:", "errors:", "exceptions:"):
77
+ section_type = "raises"
78
+ elif stripped_lower in ("tags:",):
79
+ section_type = "tags"
80
+ # Allow "Raises Description:" or "Tags content:"
81
+ elif stripped_lower.startswith(("raises ", "errors ", "exceptions ")):
82
+ section_type = "raises"
83
+ # Capture content after header word and potential colon/space
84
+ parts = re.split(
85
+ r"[:\s]+", line.strip(), maxsplit=1
86
+ ) # B034: Use keyword maxsplit
87
+ if len(parts) > 1:
88
+ header_content = parts[1].strip()
89
+ elif stripped_lower.startswith(("tags",)):
90
+ section_type = "tags"
91
+ # Capture content after header word and potential colon/space
92
+ parts = re.split(
93
+ r"[:\s]+", line.strip(), maxsplit=1
94
+ ) # B034: Use keyword maxsplit
95
+ if len(parts) > 1:
96
+ header_content = parts[1].strip()
97
+
98
+ # Identify other known sections, but don't store their content
99
+ elif stripped_lower.endswith(":") and stripped_lower[:-1] in (
76
100
  "attributes",
77
101
  "see also",
78
102
  "example",
79
103
  "examples",
80
104
  "notes",
105
+ "todo",
106
+ "fixme",
107
+ "warning",
108
+ "warnings",
81
109
  ):
82
- new_section_type = "other"
83
- is_new_section_header = True
110
+ section_type = "other"
84
111
 
85
- finalize_previous = False
86
- if is_new_section_header:
87
- finalize_previous = True
88
- elif current_section in ["args", "raises"] and current_key:
89
- if key_pattern.match(line) or (original_indentation == 0 and stripped_line):
90
- finalize_previous = True
91
- elif current_section in ["returns", "tags"] and current_desc_lines:
92
- if original_indentation == 0 and stripped_line:
93
- finalize_previous = True
94
- # SIM102 applied: Combine nested if/elif
95
- elif (
96
- not stripped_line
97
- and current_desc_lines
98
- and current_section in ["args", "raises", "returns", "tags"]
99
- and (current_section not in ["args", "raises"] or current_key)
112
+ return section_type is not None, section_type, header_content
113
+
114
+ in_summary = True
115
+
116
+ for line in lines:
117
+ stripped_line = line.strip()
118
+ original_indentation = len(line) - len(line.lstrip(" "))
119
+
120
+ is_new_section_header, new_section_type_this_line, header_content_this_line = (
121
+ check_for_section_header(line)
122
+ )
123
+
124
+ should_finalize_previous = False
125
+
126
+ # --- Summary Handling ---
127
+ if in_summary:
128
+ if not stripped_line or is_new_section_header:
129
+ # Empty line or section header marks the end of the summary
130
+ in_summary = False
131
+ summary = " ".join(summary_lines).strip()
132
+ summary_lines = [] # Clear summary_lines after finalizing summary
133
+
134
+ if not stripped_line:
135
+ # If the line was just empty, continue to the next line
136
+ # The new_section_header check will happen on the next iteration if it exists
137
+ continue
138
+ # If it was a header, fall through to section handling below
139
+
140
+ else:
141
+ # Still in summary, append line
142
+ summary_lines.append(stripped_line)
143
+ continue # Process next line
144
+
145
+ # --- Section and Item Handling ---
146
+
147
+ # Decide if the previous item/section block should be finalized BEFORE processing the current line
148
+ # Finalize if:
149
+ # 1. A new section header is encountered.
150
+ # 2. An empty line is encountered AFTER we've started collecting content for an item or section.
151
+ # 3. In 'args' or 'raises', we encounter a line that looks like a new key: value pair, or a non-indented line.
152
+ # 4. In 'returns', 'tags', or 'other', we encounter a non-indented line after collecting content.
153
+ if (
154
+ is_new_section_header
155
+ or (not stripped_line and (current_desc_lines or current_key is not None))
156
+ or (
157
+ current_section in ["args", "raises"]
158
+ and current_key is not None
159
+ and (
160
+ key_pattern.match(line)
161
+ or (original_indentation == 0 and stripped_line)
162
+ )
163
+ )
164
+ or (
165
+ current_section in ["returns", "tags", "other"]
166
+ and current_desc_lines
167
+ and original_indentation == 0
168
+ and stripped_line
169
+ )
100
170
  ):
101
- finalize_previous = True
171
+ should_finalize_previous = True
172
+ elif current_section in ["args", "raises"] and current_key is not None:
173
+ # Inside args/raises, processing an item (current_key is set)
174
+ pass # Logic moved to the combined if statement
175
+ elif current_section in ["returns", "tags", "other"] and current_desc_lines:
176
+ # Inside returns/tags/other, collecting description lines
177
+ pass # Logic moved to the combined if statement
102
178
 
103
- if finalize_previous:
179
+ # If finalizing the previous item/section
180
+ if should_finalize_previous:
104
181
  finalize_current_item()
105
- current_key = None
106
- current_desc_lines = []
107
- if not is_new_section_header or new_section_type == "other":
108
- current_section = None
109
-
110
- if is_new_section_header and new_section_type != "other":
111
- current_section = new_section_type
112
- # If Tags header had content, start accumulating it
113
- if new_section_type == "tags" and header_content:
114
- current_desc_lines.append(header_content)
115
- # Don't process the header line itself further
116
- continue
182
+ # Reset state after finalizing the previous item/section block
183
+ # If it was a new section header, reset everything
184
+ # If it was an end-of-item/block signal within a section, reset key and description lines
185
+ # (The condition for resetting key here is complex but matches the original logic)
186
+ if is_new_section_header or (
187
+ current_section in ["args", "raises"]
188
+ and current_key is not None
189
+ and not key_pattern.match(line)
190
+ and (not stripped_line or original_indentation == 0)
191
+ ):
192
+ current_key = None
193
+ current_desc_lines = [] # Always clear description lines
194
+
195
+ # --- Process the current line ---
117
196
 
197
+ # If the current line is a section header
198
+ if is_new_section_header:
199
+ current_section = new_section_type_this_line
200
+ if header_content_this_line:
201
+ # Add content immediately following the header on the same line
202
+ current_desc_lines.append(header_content_this_line)
203
+ continue # Move to the next line, header is processed
204
+
205
+ # If the line is empty, and not a section header (handled above), skip it
118
206
  if not stripped_line:
119
207
  continue
120
208
 
209
+ # If we are inside a section, process the line's content
121
210
  if current_section == "args" or current_section == "raises":
122
211
  match = key_pattern.match(line)
123
212
  if match:
213
+ # Found a new key: value item within args/raises
124
214
  current_key = match.group(1)
125
215
  current_desc_lines = [match.group(2).strip()] # Start new description
126
- elif (
127
- current_key and original_indentation > 0
128
- ): # Check for indentation for continuation
216
+ elif current_key is not None:
217
+ # Not a new key, but processing an existing item - append to description
129
218
  current_desc_lines.append(stripped_line)
219
+ # Lines that don't match key_pattern and occur when current_key is None
220
+ # within args/raises are effectively ignored by this block, which seems
221
+ # consistent with needing a key: description format.
130
222
 
131
- elif current_section == "returns":
132
- if not current_desc_lines or original_indentation > 0:
133
- current_desc_lines.append(stripped_line)
134
-
135
- elif current_section == "tags":
136
- if (
137
- original_indentation > 0 or not current_desc_lines
138
- ): # Indented or first line
139
- current_desc_lines.append(stripped_line)
223
+ elif current_section in ["returns", "tags", "other"]:
224
+ # In these sections, all non-empty, non-header lines are description lines
225
+ current_desc_lines.append(stripped_line)
140
226
 
227
+ # --- Finalization after loop ---
228
+ # Finalize any pending item/section block that was being collected
141
229
  finalize_current_item()
230
+
231
+ # If the docstring only had a summary (no empty line or section header)
232
+ # ensure the summary is captured. This check is technically redundant
233
+ # because summary is finalized upon hitting the first empty line or header,
234
+ # or falls through to the final finalize call if neither occurs.
235
+ # Keeping it for clarity, though the logic flow should cover it.
236
+ if in_summary:
237
+ summary = " ".join(summary_lines).strip()
238
+
142
239
  return {
143
240
  "summary": summary,
144
241
  "args": args,
@@ -149,7 +246,8 @@ def parse_docstring(docstring: str | None) -> dict[str, Any]:
149
246
 
150
247
 
151
248
  docstring_example = """
152
- Starts a crawl job for a given URL using Firecrawl. Returns the job ID immediately.
249
+ Starts a crawl job for a given URL using Firecrawl.
250
+ Returns the job ID immediately.
153
251
 
154
252
  Args:
155
253
  url: The starting URL for the crawl.
@@ -163,17 +261,17 @@ docstring_example = """
163
261
  or a string containing an error message on failure. This description
164
262
  can also span multiple lines.
165
263
 
166
- Raises:
264
+ Raises:
167
265
  ValueError: If the URL is invalid.
168
- requests.exceptions.ConnectionError: If connection fails.
266
+ ConnectionError: If connection fails.
169
267
 
170
268
  Tags:
171
269
  crawl, async_job, start, api, long_tag_example , another
172
270
  , final_tag
173
- """
271
+ """
174
272
 
175
273
  if __name__ == "__main__":
176
- parsed = parse_docstring(docstring_example)
177
274
  import json
178
275
 
276
+ parsed = parse_docstring(docstring_example)
179
277
  print(json.dumps(parsed, indent=4))
@@ -1,10 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp
3
- Version: 0.1.8rc4
3
+ Version: 0.1.9rc2
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
  Requires-Python: >=3.11
7
+ Requires-Dist: gql[all]>=3.5.2
7
8
  Requires-Dist: keyring>=25.6.0
9
+ Requires-Dist: langchain-cerebras>=0.5.0
10
+ Requires-Dist: langchain-google-genai>=2.1.3
8
11
  Requires-Dist: litellm>=1.30.7
9
12
  Requires-Dist: loguru>=0.7.3
10
13
  Requires-Dist: mcp>=1.6.0
@@ -22,6 +25,8 @@ Requires-Dist: pytest>=8.3.5; extra == 'dev'
22
25
  Requires-Dist: ruff>=0.11.4; extra == 'dev'
23
26
  Provides-Extra: e2b
24
27
  Requires-Dist: e2b-code-interpreter>=1.2.0; extra == 'e2b'
28
+ Provides-Extra: fal-ai
29
+ Requires-Dist: fal-client>=0.5.9; extra == 'fal-ai'
25
30
  Provides-Extra: firecrawl
26
31
  Requires-Dist: firecrawl-py>=1.15.0; extra == 'firecrawl'
27
32
  Provides-Extra: markitdown
@@ -5,19 +5,28 @@ universal_mcp/config.py,sha256=sJaPI4q51CDPPG0z32rMJiE7a64eaa9nxbjJgYnaFA4,838
5
5
  universal_mcp/exceptions.py,sha256=WApedvzArNujD0gZfUofYBxjQo97ZDJLqDibtLWZoRk,373
6
6
  universal_mcp/logger.py,sha256=D947u1roUf6WqlcEsPpvmWDqGc8L41qF3MO1suK5O1Q,308
7
7
  universal_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- universal_mcp/applications/__init__.py,sha256=qeWnbdIudyMR7ST4XTc0gpEM9o6TsM1ZnZ92dMAPSBA,754
9
- universal_mcp/applications/application.py,sha256=CUNUUfPl4JVmnFPBuYYhhlwRo63_SRvVw1HuKZ3u0js,6772
8
+ universal_mcp/applications/__init__.py,sha256=7s5c2j3Cbgdmqp7J1QKdehUQmlzSE0NLfkDE1U8Dl1A,825
9
+ universal_mcp/applications/application.py,sha256=0eC9D4HHRwIGpuFusaCxTZ0u64U68VbBpRSxjxGB5y8,8152
10
10
  universal_mcp/applications/ahrefs/README.md,sha256=bQQ5AmPFxM52gL-tllIFWC_64f7KYkBiD1tYfdTwDu4,5370
11
11
  universal_mcp/applications/ahrefs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
12
  universal_mcp/applications/ahrefs/app.py,sha256=8iYYmQ5bD6nd_JmHOk4bcEPqG162FtQ14WrnJPeTrBQ,91468
13
+ universal_mcp/applications/cal_com_v2/README.md,sha256=iSgL5yZygDTV19Kh6me7mqaDyjO88Ka9TjE5rDrT8x4,20773
14
+ universal_mcp/applications/cal_com_v2/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ universal_mcp/applications/cal_com_v2/app.py,sha256=5yLDB0jJSSXud8gkwwEt73qMkgZ53hi9H5LuiNHo8x0,235353
13
16
  universal_mcp/applications/calendly/README.md,sha256=85m3XXLPhQ99oghl8SeazCZ2fjBR81-f1y_mjjhx2B0,5911
14
17
  universal_mcp/applications/calendly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
18
  universal_mcp/applications/calendly/app.py,sha256=ANuUfiXBwDZjQC-xfB06JoVu5ebQPEy12COBO5LY3DI,49874
19
+ universal_mcp/applications/clickup/README.md,sha256=A7q0j9IXm2jKBxGtWa4SUuTYF5_nJYEht4xeIbKhLHI,15482
20
+ universal_mcp/applications/clickup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
+ universal_mcp/applications/clickup/app.py,sha256=byJFbwbzt0Pg7CQ2Eyqnrdqi_iFfF2aINBKQ46tbE1I,209269
16
22
  universal_mcp/applications/coda/README.md,sha256=4i6o_R-qtTuxfS1A7VoIb8_85FHAj-WVb8YG5fNvwL4,11411
17
23
  universal_mcp/applications/coda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
24
  universal_mcp/applications/coda/app.py,sha256=47ZmtYF5A2Cn0rh3Dpc3VtkIHR1Xu2PCYe1JDH8kJbY,155862
19
25
  universal_mcp/applications/e2b/README.md,sha256=S4lTp-vEZ8VTCKPXqjUXu5nYlUMAF8lw8CQyBGPgxjs,700
20
26
  universal_mcp/applications/e2b/app.py,sha256=4cMuGHm_QY4uh0JMh3HYzhaqtnfnXajRKFhoAGmRnBE,2252
27
+ universal_mcp/applications/falai/README.md,sha256=fc31zlKe09FsOw6W5KY7VipxvKhon4KQoWjTdoMlPfc,1449
28
+ universal_mcp/applications/falai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
29
+ universal_mcp/applications/falai/app.py,sha256=S6gaXLWBgTm_EBqH8aBUKih5iM2EL6gbRSAJ5Xmcfzg,12323
21
30
  universal_mcp/applications/figma/README.md,sha256=qA9UMf5PsPhfJrnteGVQOudhLuevwZ4-D_1xM6gAjgQ,4393
22
31
  universal_mcp/applications/figma/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
32
  universal_mcp/applications/figma/app.py,sha256=sr-ednZinHdIcXmDWuWAA_Ri21iBbAYPRZ0-uLeiEkM,50392
@@ -25,6 +34,9 @@ universal_mcp/applications/firecrawl/README.md,sha256=KAWe_TQbrc9eA6bSyde5dapMP1
25
34
  universal_mcp/applications/firecrawl/app.py,sha256=qO3XNH9nT2G-9yC1eN4ADZJCE_bxF0qQ3S_qtYoOa2o,8902
26
35
  universal_mcp/applications/github/README.md,sha256=6ID-__gUJ5ZxzAS_OjzmoUAag1LamSvEB75DHcj3m-g,1294
27
36
  universal_mcp/applications/github/app.py,sha256=73Y5ceM2BGRcLUO__xO0RO1NNf6Gf3ROtqTlFI5k0Fg,18162
37
+ universal_mcp/applications/gong/README.md,sha256=R-jcQOi7rRodvcwkxN8YpFRIDhR15G20AeNDQ0PzjAw,6700
38
+ universal_mcp/applications/gong/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
+ universal_mcp/applications/gong/app.py,sha256=C-DQffWMxZuskfsyIuyVVXZZ0u2dSAuCjAY8wIjnDnI,101011
28
40
  universal_mcp/applications/google_calendar/app.py,sha256=o2Mtto4zOIDtCUdXdEgXWhWsKRfzbHC7DAUuvyjUei4,19342
29
41
  universal_mcp/applications/google_docs/README.md,sha256=SyOgJG-XU0FXhrVukvg9mxwin73mpqaCOT6rDJ64Klk,909
30
42
  universal_mcp/applications/google_docs/app.py,sha256=f31nJ3tr9XF-1AbRI3DNVXgMXT4Y33gWMRlBoA-t630,3436
@@ -34,18 +46,41 @@ universal_mcp/applications/google_mail/README.md,sha256=LL6TjjmwEqyuRVvIfCh-I_Pl
34
46
  universal_mcp/applications/google_mail/app.py,sha256=1XI1hve2FXOqkzgJNYu2ki5J1yGKfeMx3cO_Qyflp_o,27286
35
47
  universal_mcp/applications/google_sheet/README.md,sha256=yW1b_qlb_pbIJzCxZc58581kKzC5vyP8Mj4iwXgidtQ,1108
36
48
  universal_mcp/applications/google_sheet/app.py,sha256=O6g8P697ve93CsljLK9ejWbIyzGbZ-_ThK_A_3cTpIk,6310
37
- universal_mcp/applications/markitdown/app.py,sha256=j1AidXGmDwEV4sBw0A-mEe8O4V4Yln4Atrqnk29AHiI,1836
49
+ universal_mcp/applications/hashnode/app.py,sha256=v0OR00kkMqyL-NcclCuriCdGErE7PRvHoWScfvAeEXQ,2538
50
+ universal_mcp/applications/hashnode/prompt.md,sha256=K-LPmn2wchYEX2ytANDS4-fsKrhPphKPV62oYZ_n-M4,1742
51
+ universal_mcp/applications/heygen/README.md,sha256=8YW_JYxPMXIFqNlhp-IYtRN8CrPAR0Uh1q3FFzlPni0,4304
52
+ universal_mcp/applications/heygen/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
+ universal_mcp/applications/heygen/app.py,sha256=2H0RI_zs-L2uT8TiCzCFtuLmuH6O2mDtmVIvNUu4qNA,35256
54
+ universal_mcp/applications/mailchimp/README.md,sha256=9p7rVwf0aX2lClVeMliUz8rFWtXNhi55AAKj3hbHSng,33688
55
+ universal_mcp/applications/mailchimp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
+ universal_mcp/applications/mailchimp/app.py,sha256=_ceKe-ZM6KTgi17WABs9I45dum1cTj0ZlnccJ9BQwuE,466711
57
+ universal_mcp/applications/markitdown/app.py,sha256=FeJONdT-Jwe5mJBelOfgpM1A3T-hIDfFGf1PCR5x1gA,1832
38
58
  universal_mcp/applications/notion/README.md,sha256=45NmPOmSQv99qBvWdwmnV5vbaYc9_8vq8I-FA7veVAA,2600
39
59
  universal_mcp/applications/notion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
60
  universal_mcp/applications/notion/app.py,sha256=nc-p531L-L6gMFqOOkYu5Irn9SReWAYRmJ8ZOIv5LrQ,20834
41
61
  universal_mcp/applications/perplexity/README.md,sha256=QGV1iReH5p-Np7vvkZsVHxxDKQ0YaitHEwomNmGEyQs,732
42
- universal_mcp/applications/perplexity/app.py,sha256=W2wXe2ltQKqKM6hKcVW0DczsULdmPhcsJdjx93wSXaM,2673
62
+ universal_mcp/applications/perplexity/app.py,sha256=WAFDLH6jkH5eo9LLQfD_MRhpJdgZgHXchqs_ARJe3oI,2633
43
63
  universal_mcp/applications/reddit/README.md,sha256=YVbJ1RN6NWlB-P6w2LxCk_DuUWl7mwaKZScY-mIMnNc,1271
44
64
  universal_mcp/applications/reddit/app.py,sha256=Jd-Pr-IMhROun82kuLf0mNJ3P-LDfGfvj1bn_8qNIAI,15748
65
+ universal_mcp/applications/replicate/README.md,sha256=8_PT_Mft6ZvkamU50lVbF7XBQ_jBb-X78ALMCAokna8,3718
66
+ universal_mcp/applications/replicate/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
67
+ universal_mcp/applications/replicate/app.py,sha256=--agxFi0OunJkdzjsah10V660Xz9sDHet8t-PT6OMeI,38818
45
68
  universal_mcp/applications/resend/README.md,sha256=k-sb2UwbFvDPEz6qQPLWd2cJj8hDx5f3NW7dz2jAfjI,719
46
69
  universal_mcp/applications/resend/app.py,sha256=dWhijrx73hw2OLMAC01keVj7hVgu4CUZsURyRjxD7ew,1370
70
+ universal_mcp/applications/retell_ai/README.md,sha256=XKBvMeA-wQAZYGMXogWMyvcqclhLGVnK72G4q2ZdfVM,1882
71
+ universal_mcp/applications/retell_ai/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
72
+ universal_mcp/applications/retell_ai/app.py,sha256=qhXAdXQhOYMNirJ08xU_R5TnJa_d97lc1Hue9xWrX3U,13406
73
+ universal_mcp/applications/rocketlane/README.md,sha256=WtWkCt5nRN-SESaQXpYxgZwGEcxOXIcn9aQB7dDY8vM,1245
74
+ universal_mcp/applications/rocketlane/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
75
+ universal_mcp/applications/rocketlane/app.py,sha256=EvHFiIi_hAuU_UqLCxkdXE3Cb9uNHZmunsUaNjiyfwU,6795
47
76
  universal_mcp/applications/serpapi/README.md,sha256=hX4VeT2iL_67ZsMhKd60DAujQCh9K3IdHroHIq808RY,691
48
77
  universal_mcp/applications/serpapi/app.py,sha256=krx9STkJI0vLarXo34emySv3fs9o9lmQ2qfjWbzxtg4,2918
78
+ universal_mcp/applications/spotify/README.md,sha256=iT8nx2_1jcpNqf6Xn3E0-oxmimg1Z1CYFAJg9qNR_4g,9626
79
+ universal_mcp/applications/spotify/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
80
+ universal_mcp/applications/spotify/app.py,sha256=x8QxvW9gjKtxjM58ECta1MN-_0lwPUGpnmW443a2OM0,105444
81
+ universal_mcp/applications/supabase/README.md,sha256=CqCTwOGJWwOgwPQSblgUxYMWHBn5jZn6gOxB568sVrs,9743
82
+ universal_mcp/applications/supabase/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
83
+ universal_mcp/applications/supabase/app.py,sha256=WRacy6_gqPsoTjtjHu-N2vhned2X-rW77IBj0IyGHh4,135842
49
84
  universal_mcp/applications/tavily/README.md,sha256=cNg4EwX5wBbkDpPtNBNC3A_GxglfSVhdAJuweSrXN20,721
50
85
  universal_mcp/applications/tavily/app.py,sha256=rU9IRyhzYkchjs8rqQMU89hkBQy13W8yeQqpQhPCCFA,1924
51
86
  universal_mcp/applications/wrike/README.md,sha256=4EHVPlA8B_dzTA1-HQQqp89z6QL37RTyD2l6DD7vG9E,5156
@@ -59,23 +94,23 @@ universal_mcp/applications/zenquotes/app.py,sha256=xp_nlW4LFi0Mw1GRWIde4k8eexXUJ
59
94
  universal_mcp/integrations/README.md,sha256=lTAPXO2nivcBe1q7JT6PRa6v9Ns_ZersQMIdw-nmwEA,996
60
95
  universal_mcp/integrations/__init__.py,sha256=YY8Uw0XGNUpAQ1j-qgCOrwHTcuSew4W92cEtYXMxry4,963
61
96
  universal_mcp/integrations/agentr.py,sha256=Bap4PA2-K4BkBhscgAVsBdvXNN19dkuCLO82sQFRvUM,3952
62
- universal_mcp/integrations/integration.py,sha256=33i1-E4EMTZOhCcbLXSrJfnGUCmRROk7m7DxXUVCt5c,9985
97
+ universal_mcp/integrations/integration.py,sha256=imzxq1jZJAjq9odF6ttCUS2-yQW7qqG8NNUj1LudT9U,9986
63
98
  universal_mcp/servers/__init__.py,sha256=dDtvvMzbWskABlobTZHztrWMb3hbzgidza3BmEmIAD8,474
64
- universal_mcp/servers/server.py,sha256=azsUnzMwJXttNDBs4C6_yuds0A3gEsXkf0Dyc-vHeI8,7941
99
+ universal_mcp/servers/server.py,sha256=bm1gprc_6TPB8iShI6H7Kzp8CWsW5A51vb8DHW8AiUA,9916
65
100
  universal_mcp/stores/__init__.py,sha256=quvuwhZnpiSLuojf0NfmBx2xpaCulv3fbKtKaSCEmuM,603
66
- universal_mcp/stores/store.py,sha256=8Hobd55WCXGXTJeADn7d_Qe-U8VkC6X3QDTgaKr3Saw,6774
101
+ universal_mcp/stores/store.py,sha256=lYaFd-9YKC404BPeqzNw_Xm3ziQjksZyvQtaW1yd9FM,6900
67
102
  universal_mcp/tools/__init__.py,sha256=hVL-elJLwD_K87Gpw_s2_o43sQRPyRNOnxlzt0_Pfn8,72
68
103
  universal_mcp/tools/adapters.py,sha256=2HvpyFiI0zg9dp0XshnG7t6KrVqFHM7hgtmgY1bsHN0,927
69
104
  universal_mcp/tools/func_metadata.py,sha256=f_5LdDNsOu1DpXvDUeZYiJswVmwGZz6IMPtpJJ5B2-Y,7975
70
- universal_mcp/tools/tools.py,sha256=27PkcxoxdADoUqQHUyvKX1GJCOYb1lrOtqcR24tR7fs,12982
105
+ universal_mcp/tools/tools.py,sha256=PbvTi8A1hT7F8UXiiP291aP_oZmIPBfhfcgqR8XeSqo,12978
71
106
  universal_mcp/utils/__init__.py,sha256=8wi4PGWu-SrFjNJ8U7fr2iFJ1ktqlDmSKj1xYd7KSDc,41
72
107
  universal_mcp/utils/api_generator.py,sha256=-wRBpLVfJQXy1R-8FpDNs6b8_eeekVDuPc_uwjSGgiY,8883
73
108
  universal_mcp/utils/docgen.py,sha256=yGBcBIr7dz3mNMGrCb_-JFsDf-ShmCKWWiPpuEj2SIU,21878
74
- universal_mcp/utils/docstring_parser.py,sha256=-QVW9u9i1_FlAMwSkFil6ZNaKIkXKI6gqOYtURdp5ak,6745
109
+ universal_mcp/utils/docstring_parser.py,sha256=j7aE-LLnBOPTJI0qXayf0NlYappzxICv5E_hUPNmAlc,11459
75
110
  universal_mcp/utils/dump_app_tools.py,sha256=9bQePJ4ZKzGtcIYrBgLxbKDOZmL7ajIAHhXljT_AlyA,2041
76
111
  universal_mcp/utils/installation.py,sha256=KPBojDlt2YfFY2DfJ9pUr5evFJ9QQGp99KQUsRkz9GQ,10235
77
112
  universal_mcp/utils/openapi.py,sha256=AgmcyntPyovic2mRqr-a7P4kEc7hU-yk9gRVIsO4078,20673
78
- universal_mcp-0.1.8rc4.dist-info/METADATA,sha256=ird6s_0F2lBSsDljkytljaU7rRwLPgx_mGjeLDEfyrk,11271
79
- universal_mcp-0.1.8rc4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
80
- universal_mcp-0.1.8rc4.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
81
- universal_mcp-0.1.8rc4.dist-info/RECORD,,
113
+ universal_mcp-0.1.9rc2.dist-info/METADATA,sha256=_jBOv89dqpJ40o3e40nZytj_2vDbCqpJkSWBy6FoaSk,11463
114
+ universal_mcp-0.1.9rc2.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
115
+ universal_mcp-0.1.9rc2.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
116
+ universal_mcp-0.1.9rc2.dist-info/RECORD,,