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.
- universal_mcp/applications/__init__.py +7 -2
- universal_mcp/applications/application.py +151 -114
- 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/clickup/README.md +160 -0
- universal_mcp/applications/clickup/__init__.py +0 -0
- universal_mcp/applications/clickup/app.py +5009 -0
- 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/gong/README.md +88 -0
- universal_mcp/applications/gong/__init__.py +0 -0
- universal_mcp/applications/gong/app.py +2297 -0
- 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 -1
- 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/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/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/integrations/integration.py +1 -1
- universal_mcp/servers/server.py +53 -3
- universal_mcp/stores/store.py +6 -0
- universal_mcp/tools/tools.py +2 -2
- universal_mcp/utils/docstring_parser.py +192 -94
- {universal_mcp-0.1.8rc4.dist-info → universal_mcp-0.1.9rc2.dist-info}/METADATA +6 -1
- {universal_mcp-0.1.8rc4.dist-info → universal_mcp-0.1.9rc2.dist-info}/RECORD +48 -13
- {universal_mcp-0.1.8rc4.dist-info → universal_mcp-0.1.9rc2.dist-info}/WHEEL +0 -0
- {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.
|
105
|
+
self._api_key = credentials
|
106
106
|
except KeyNotFoundError as e:
|
107
107
|
action = self.authorize()
|
108
108
|
raise NotAuthorizedError(action) from e
|
universal_mcp/servers/server.py
CHANGED
@@ -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
|
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) ->
|
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) ->
|
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)
|
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:
|
@@ -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
|
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
|
14
|
-
'
|
15
|
-
'
|
16
|
-
'
|
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 =
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
-
"""
|
37
|
-
nonlocal returns, tags
|
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
|
-
|
53
|
+
if desc:
|
54
|
+
args[current_key] = desc
|
41
55
|
elif current_section == "raises" and current_key:
|
42
|
-
|
56
|
+
if desc:
|
57
|
+
raises[current_key] = desc
|
43
58
|
elif current_section == "returns":
|
44
59
|
returns = desc
|
45
|
-
|
46
|
-
|
47
|
-
tags
|
48
|
-
|
49
|
-
|
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
|
-
|
55
|
-
|
56
|
-
|
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
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
elif
|
66
|
-
|
67
|
-
|
68
|
-
elif
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
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
|
-
|
83
|
-
is_new_section_header = True
|
110
|
+
section_type = "other"
|
84
111
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
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
|
-
|
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
|
-
|
179
|
+
# If finalizing the previous item/section
|
180
|
+
if should_finalize_previous:
|
104
181
|
finalize_current_item()
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
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
|
-
|
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
|
132
|
-
|
133
|
-
|
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.
|
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
|
-
|
264
|
+
Raises:
|
167
265
|
ValueError: If the URL is invalid.
|
168
|
-
|
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.
|
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=
|
9
|
-
universal_mcp/applications/application.py,sha256=
|
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/
|
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=
|
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=
|
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=
|
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=
|
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=
|
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
|
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.
|
79
|
-
universal_mcp-0.1.
|
80
|
-
universal_mcp-0.1.
|
81
|
-
universal_mcp-0.1.
|
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,,
|
File without changes
|
File without changes
|