universal-mcp 0.1.8rc2__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 (53) hide show
  1. universal_mcp/__init__.py +0 -2
  2. universal_mcp/analytics.py +75 -0
  3. universal_mcp/applications/ahrefs/README.md +76 -0
  4. universal_mcp/applications/ahrefs/__init__.py +0 -0
  5. universal_mcp/applications/ahrefs/app.py +2291 -0
  6. universal_mcp/applications/application.py +94 -5
  7. universal_mcp/applications/calendly/app.py +412 -171
  8. universal_mcp/applications/coda/README.md +133 -0
  9. universal_mcp/applications/coda/__init__.py +0 -0
  10. universal_mcp/applications/coda/app.py +3671 -0
  11. universal_mcp/applications/e2b/app.py +8 -35
  12. universal_mcp/applications/figma/README.md +74 -0
  13. universal_mcp/applications/figma/__init__.py +0 -0
  14. universal_mcp/applications/figma/app.py +1261 -0
  15. universal_mcp/applications/firecrawl/app.py +3 -33
  16. universal_mcp/applications/github/app.py +41 -42
  17. universal_mcp/applications/google_calendar/app.py +20 -31
  18. universal_mcp/applications/google_docs/app.py +21 -46
  19. universal_mcp/applications/google_drive/app.py +53 -76
  20. universal_mcp/applications/google_mail/app.py +40 -56
  21. universal_mcp/applications/google_sheet/app.py +43 -68
  22. universal_mcp/applications/markitdown/app.py +4 -4
  23. universal_mcp/applications/notion/app.py +93 -83
  24. universal_mcp/applications/perplexity/app.py +4 -38
  25. universal_mcp/applications/reddit/app.py +32 -32
  26. universal_mcp/applications/resend/app.py +4 -22
  27. universal_mcp/applications/serpapi/app.py +6 -32
  28. universal_mcp/applications/tavily/app.py +4 -24
  29. universal_mcp/applications/wrike/app.py +565 -237
  30. universal_mcp/applications/youtube/app.py +625 -183
  31. universal_mcp/applications/zenquotes/app.py +3 -3
  32. universal_mcp/exceptions.py +1 -0
  33. universal_mcp/integrations/__init__.py +11 -2
  34. universal_mcp/integrations/agentr.py +27 -4
  35. universal_mcp/integrations/integration.py +14 -6
  36. universal_mcp/logger.py +3 -56
  37. universal_mcp/servers/__init__.py +2 -1
  38. universal_mcp/servers/server.py +73 -77
  39. universal_mcp/stores/store.py +5 -3
  40. universal_mcp/tools/__init__.py +1 -1
  41. universal_mcp/tools/adapters.py +4 -1
  42. universal_mcp/tools/func_metadata.py +5 -6
  43. universal_mcp/tools/tools.py +108 -51
  44. universal_mcp/utils/docgen.py +121 -69
  45. universal_mcp/utils/docstring_parser.py +44 -21
  46. universal_mcp/utils/dump_app_tools.py +33 -23
  47. universal_mcp/utils/installation.py +199 -8
  48. universal_mcp/utils/openapi.py +121 -47
  49. {universal_mcp-0.1.8rc2.dist-info → universal_mcp-0.1.8rc4.dist-info}/METADATA +2 -2
  50. universal_mcp-0.1.8rc4.dist-info/RECORD +81 -0
  51. universal_mcp-0.1.8rc2.dist-info/RECORD +0 -71
  52. {universal_mcp-0.1.8rc2.dist-info → universal_mcp-0.1.8rc4.dist-info}/WHEEL +0 -0
  53. {universal_mcp-0.1.8rc2.dist-info → universal_mcp-0.1.8rc4.dist-info}/entry_points.txt +0 -0
@@ -29,7 +29,7 @@ def create_file_if_not_exists(path: Path) -> None:
29
29
 
30
30
  def get_supported_apps() -> list[str]:
31
31
  """Get list of supported apps"""
32
- return ["claude", "cursor", "windsurf"]
32
+ return ["claude", "cursor", "cline", "continue", "goose", "windsurf", "zed"]
33
33
 
34
34
 
35
35
  def install_claude(api_key: str) -> None:
@@ -99,21 +99,212 @@ def install_cursor(api_key: str) -> None:
99
99
  print("[green]✓[/green] Cursor configuration installed successfully")
100
100
 
101
101
 
102
- def install_windsurf() -> None:
102
+ def install_cline(api_key: str) -> None:
103
+ """Install Cline"""
104
+ print("[bold blue]Installing Cline configuration...[/bold blue]")
105
+ # Set up Cline config path
106
+ config_path = Path.home() / ".config/cline/mcp.json"
107
+
108
+ # Create config directory if it doesn't exist
109
+ create_file_if_not_exists(config_path)
110
+
111
+ try:
112
+ config = json.loads(config_path.read_text())
113
+ except json.JSONDecodeError:
114
+ print(
115
+ "[yellow]Config file was empty or invalid, creating new configuration[/yellow]"
116
+ )
117
+ config = {}
118
+
119
+ if "mcpServers" not in config:
120
+ config["mcpServers"] = {}
121
+ config["mcpServers"]["universal_mcp"] = {
122
+ "command": get_uvx_path(),
123
+ "args": ["universal_mcp[all]@latest", "run"],
124
+ "env": {"AGENTR_API_KEY": api_key},
125
+ }
126
+
127
+ with open(config_path, "w") as f:
128
+ json.dump(config, f, indent=4)
129
+ print("[green]✓[/green] Cline configuration installed successfully")
130
+
131
+
132
+ def install_continue(api_key: str) -> None:
133
+ """Install Continue"""
134
+ print("[bold blue]Installing Continue configuration...[/bold blue]")
135
+
136
+ # Determine platform-specific config path
137
+ if sys.platform == "darwin": # macOS
138
+ config_path = Path.home() / "Library/Application Support/Continue/mcp.json"
139
+ elif sys.platform == "win32": # Windows
140
+ config_path = Path.home() / "AppData/Roaming/Continue/mcp.json"
141
+ else: # Linux and others
142
+ config_path = Path.home() / ".config/continue/mcp.json"
143
+
144
+ # Create config directory if it doesn't exist
145
+ create_file_if_not_exists(config_path)
146
+
147
+ try:
148
+ config = json.loads(config_path.read_text())
149
+ except json.JSONDecodeError:
150
+ print(
151
+ "[yellow]Config file was empty or invalid, creating new configuration[/yellow]"
152
+ )
153
+ config = {}
154
+
155
+ if "mcpServers" not in config:
156
+ config["mcpServers"] = {}
157
+ config["mcpServers"]["universal_mcp"] = {
158
+ "command": get_uvx_path(),
159
+ "args": ["universal_mcp[all]@latest", "run"],
160
+ "env": {"AGENTR_API_KEY": api_key},
161
+ }
162
+
163
+ with open(config_path, "w") as f:
164
+ json.dump(config, f, indent=4)
165
+ print("[green]✓[/green] Continue configuration installed successfully")
166
+
167
+
168
+ def install_goose(api_key: str) -> None:
169
+ """Install Goose"""
170
+ print("[bold blue]Installing Goose configuration...[/bold blue]")
171
+
172
+ # Determine platform-specific config path
173
+ if sys.platform == "darwin": # macOS
174
+ config_path = Path.home() / "Library/Application Support/Goose/mcp-config.json"
175
+ elif sys.platform == "win32": # Windows
176
+ config_path = Path.home() / "AppData/Roaming/Goose/mcp-config.json"
177
+ else: # Linux and others
178
+ config_path = Path.home() / ".config/goose/mcp-config.json"
179
+
180
+ # Create config directory if it doesn't exist
181
+ create_file_if_not_exists(config_path)
182
+
183
+ try:
184
+ config = json.loads(config_path.read_text())
185
+ except json.JSONDecodeError:
186
+ print(
187
+ "[yellow]Config file was empty or invalid, creating new configuration[/yellow]"
188
+ )
189
+ config = {}
190
+
191
+ if "mcpServers" not in config:
192
+ config["mcpServers"] = {}
193
+ config["mcpServers"]["universal_mcp"] = {
194
+ "command": get_uvx_path(),
195
+ "args": ["universal_mcp[all]@latest", "run"],
196
+ "env": {"AGENTR_API_KEY": api_key},
197
+ }
198
+
199
+ with open(config_path, "w") as f:
200
+ json.dump(config, f, indent=4)
201
+ print("[green]✓[/green] Goose configuration installed successfully")
202
+
203
+
204
+ def install_windsurf(api_key: str) -> None:
103
205
  """Install Windsurf"""
104
- print("[yellow]Windsurf installation not yet implemented[/yellow]")
105
- pass
206
+ print("[bold blue]Installing Windsurf configuration...[/bold blue]")
207
+
208
+ # Determine platform-specific config path
209
+ if sys.platform == "darwin": # macOS
210
+ config_path = Path.home() / "Library/Application Support/Windsurf/mcp.json"
211
+ elif sys.platform == "win32": # Windows
212
+ config_path = Path.home() / "AppData/Roaming/Windsurf/mcp.json"
213
+ else: # Linux and others
214
+ config_path = Path.home() / ".config/windsurf/mcp.json"
215
+
216
+ # Create config directory if it doesn't exist
217
+ create_file_if_not_exists(config_path)
218
+
219
+ try:
220
+ config = json.loads(config_path.read_text())
221
+ except json.JSONDecodeError:
222
+ print(
223
+ "[yellow]Config file was empty or invalid, creating new configuration[/yellow]"
224
+ )
225
+ config = {}
226
+
227
+ if "mcpServers" not in config:
228
+ config["mcpServers"] = {}
229
+ config["mcpServers"]["universal_mcp"] = {
230
+ "command": get_uvx_path(),
231
+ "args": ["universal_mcp[all]@latest", "run"],
232
+ "env": {"AGENTR_API_KEY": api_key},
233
+ }
234
+
235
+ with open(config_path, "w") as f:
236
+ json.dump(config, f, indent=4)
237
+ print("[green]✓[/green] Windsurf configuration installed successfully")
238
+
239
+
240
+ def install_zed(api_key: str) -> None:
241
+ """Install Zed"""
242
+ print("[bold blue]Installing Zed configuration...[/bold blue]")
243
+
244
+ # Set up Zed config path
245
+ config_dir = Path.home() / ".config/zed"
246
+ config_path = config_dir / "mcp_servers.json"
247
+
248
+ # Create config directory if it doesn't exist
249
+ create_file_if_not_exists(config_path)
250
+
251
+ try:
252
+ config = json.loads(config_path.read_text())
253
+ except json.JSONDecodeError:
254
+ print(
255
+ "[yellow]Config file was empty or invalid, creating new configuration[/yellow]"
256
+ )
257
+ config = {}
258
+
259
+ if not isinstance(config, list):
260
+ config = []
261
+
262
+ # Check if universal_mcp is already in the config
263
+ existing_config = False
264
+ for server in config:
265
+ if server.get("name") == "universal_mcp":
266
+ existing_config = True
267
+ server.update(
268
+ {
269
+ "command": get_uvx_path(),
270
+ "args": ["universal_mcp[all]@latest", "run"],
271
+ "env": {"AGENTR_API_KEY": api_key},
272
+ }
273
+ )
274
+ break
275
+
276
+ if not existing_config:
277
+ config.append(
278
+ {
279
+ "name": "universal_mcp",
280
+ "command": get_uvx_path(),
281
+ "args": ["universal_mcp[all]@latest", "run"],
282
+ "env": {"AGENTR_API_KEY": api_key},
283
+ }
284
+ )
285
+
286
+ with open(config_path, "w") as f:
287
+ json.dump(config, f, indent=4)
288
+ print("[green]✓[/green] Zed configuration installed successfully")
106
289
 
107
290
 
108
- def install_app(app_name: str) -> None:
291
+ def install_app(app_name: str, api_key: str) -> None:
109
292
  """Install an app"""
110
293
  print(f"[bold]Installing {app_name}...[/bold]")
111
294
  if app_name == "claude":
112
- install_claude()
295
+ install_claude(api_key)
113
296
  elif app_name == "cursor":
114
- install_cursor()
297
+ install_cursor(api_key)
298
+ elif app_name == "cline":
299
+ install_cline(api_key)
300
+ elif app_name == "continue":
301
+ install_continue(api_key)
302
+ elif app_name == "goose":
303
+ install_goose(api_key)
115
304
  elif app_name == "windsurf":
116
- install_windsurf()
305
+ install_windsurf(api_key)
306
+ elif app_name == "zed":
307
+ install_zed(api_key)
117
308
  else:
118
309
  print(f"[red]Error: App '{app_name}' not supported[/red]")
119
310
  raise ValueError(f"App '{app_name}' not supported")
@@ -73,27 +73,27 @@ def determine_return_type(operation: dict[str, Any]) -> str:
73
73
  def resolve_schema_reference(reference, schema):
74
74
  """
75
75
  Resolve a JSON schema reference to its target schema.
76
-
76
+
77
77
  Args:
78
78
  reference (str): The reference string (e.g., '#/components/schemas/User')
79
79
  schema (dict): The complete OpenAPI schema that contains the reference
80
-
80
+
81
81
  Returns:
82
82
  dict: The resolved schema, or None if not found
83
83
  """
84
- if not reference.startswith('#/'):
84
+ if not reference.startswith("#/"):
85
85
  return None
86
-
86
+
87
87
  # Split the reference path and navigate through the schema
88
- parts = reference[2:].split('/')
88
+ parts = reference[2:].split("/")
89
89
  current = schema
90
-
90
+
91
91
  for part in parts:
92
92
  if part in current:
93
93
  current = current[part]
94
94
  else:
95
95
  return None
96
-
96
+
97
97
  return current
98
98
 
99
99
 
@@ -206,8 +206,8 @@ def generate_method_code(path, method, operation, full_schema, tool_name=None):
206
206
  tuple: (method_code, func_name) - The Python code for the method and its name.
207
207
  """
208
208
  # Extract path parameters from the URL path
209
- path_params_in_url = re.findall(r'{([^}]+)}', path)
210
-
209
+ path_params_in_url = re.findall(r"{([^}]+)}", path)
210
+
211
211
  # Determine function name
212
212
  if "operationId" in operation:
213
213
  raw_name = operation["operationId"]
@@ -223,16 +223,40 @@ def generate_method_code(path, method, operation, full_schema, tool_name=None):
223
223
  else:
224
224
  name_parts.append(part)
225
225
  func_name = "_".join(name_parts).replace("-", "_").lower()
226
-
226
+
227
227
  # Only fix isolated 'a' and 'an' as articles, not when they're part of words
228
- func_name = re.sub(r'_a([^_a-z])', r'_a_\1', func_name) # Fix for patterns like retrieve_ablock -> retrieve_a_block
229
- func_name = re.sub(r'_a$', r'_a', func_name) # Don't change if 'a' is at the end of the name
230
- func_name = re.sub(r'_an([^_a-z])', r'_an_\1', func_name) # Fix for patterns like create_anitem -> create_an_item
231
- func_name = re.sub(r'_an$', r'_an', func_name) # Don't change if 'an' is at the end of the name
228
+ func_name = re.sub(
229
+ r"_a([^_a-z])", r"_a_\1", func_name
230
+ ) # Fix for patterns like retrieve_ablock -> retrieve_a_block
231
+ func_name = re.sub(
232
+ r"_a$", r"_a", func_name
233
+ ) # Don't change if 'a' is at the end of the name
234
+ func_name = re.sub(
235
+ r"_an([^_a-z])", r"_an_\1", func_name
236
+ ) # Fix for patterns like create_anitem -> create_an_item
237
+ func_name = re.sub(
238
+ r"_an$", r"_an", func_name
239
+ ) # Don't change if 'an' is at the end of the name
232
240
 
233
241
  # Get parameters and request body
234
- # Filter out header parameters
235
- parameters = [param for param in operation.get("parameters", []) if param.get("in") != "header"]
242
+ # Resolve parameter references before processing
243
+ resolved_parameters = []
244
+ for param in operation.get("parameters", []):
245
+ if "$ref" in param:
246
+ # Resolve reference to actual parameter object
247
+ ref_param = resolve_schema_reference(param["$ref"], full_schema)
248
+ if ref_param:
249
+ resolved_parameters.append(ref_param)
250
+ else:
251
+ print(
252
+ f"Warning: Could not resolve parameter reference: {param['$ref']}"
253
+ )
254
+ else:
255
+ resolved_parameters.append(param)
256
+
257
+ # Filter out header parameters from the resolved parameters
258
+ parameters = [param for param in resolved_parameters if param.get("in") != "header"]
259
+
236
260
  has_body = "requestBody" in operation
237
261
  body_required = has_body and operation["requestBody"].get("required", False)
238
262
 
@@ -240,34 +264,61 @@ def generate_method_code(path, method, operation, full_schema, tool_name=None):
240
264
  has_empty_body = False
241
265
  if has_body:
242
266
  request_body_content = operation["requestBody"].get("content", {})
243
- if not request_body_content or all(not content for content_type, content in request_body_content.items()):
267
+ if not request_body_content or all(
268
+ not content for content_type, content in request_body_content.items()
269
+ ):
244
270
  has_empty_body = True
245
- has_body = False # Treat it as if it doesn't have a body for property extraction
271
+ else:
272
+ # Handle empty properties with additionalProperties:true
273
+ for content_type, content in request_body_content.items():
274
+ if content_type.startswith("application/json") and "schema" in content:
275
+ schema = content["schema"]
276
+
277
+ # Resolve schema reference if present
278
+ if "$ref" in schema:
279
+ ref_schema = resolve_schema_reference(
280
+ schema["$ref"], full_schema
281
+ )
282
+ if ref_schema:
283
+ schema = ref_schema
284
+
285
+ # Check if properties is empty and additionalProperties is true
286
+ if (
287
+ schema.get("type") == "object"
288
+ and schema.get("additionalProperties", False) is True
289
+ ):
290
+ properties = schema.get("properties", {})
291
+ if not properties or len(properties) == 0:
292
+ has_empty_body = True
246
293
 
247
294
  # Extract request body schema properties and required fields
248
295
  required_fields = []
249
296
  request_body_properties = {}
250
297
  is_array_body = False
251
298
  array_items_schema = None
252
-
299
+
253
300
  if has_body:
254
- for content_type, content in operation["requestBody"].get("content", {}).items():
301
+ for content_type, content in (
302
+ operation["requestBody"].get("content", {}).items()
303
+ ):
255
304
  if content_type.startswith("application/json") and "schema" in content:
256
305
  schema = content["schema"]
257
-
306
+
258
307
  # Resolve schema reference if present
259
308
  if "$ref" in schema:
260
309
  ref_schema = resolve_schema_reference(schema["$ref"], full_schema)
261
310
  if ref_schema:
262
311
  schema = ref_schema
263
-
312
+
264
313
  # Check if the schema is an array type
265
314
  if schema.get("type") == "array":
266
315
  is_array_body = True
267
316
  array_items_schema = schema.get("items", {})
268
317
  # Try to resolve any reference in items
269
318
  if "$ref" in array_items_schema:
270
- array_items_schema = resolve_schema_reference(array_items_schema["$ref"], full_schema)
319
+ array_items_schema = resolve_schema_reference(
320
+ array_items_schema["$ref"], full_schema
321
+ )
271
322
  else:
272
323
  # Extract required fields from schema
273
324
  if "required" in schema:
@@ -275,19 +326,27 @@ def generate_method_code(path, method, operation, full_schema, tool_name=None):
275
326
  # Extract properties from schema
276
327
  if "properties" in schema:
277
328
  request_body_properties = schema["properties"]
278
-
329
+
279
330
  # Check for nested references in properties
280
331
  for prop_name, prop_schema in request_body_properties.items():
281
332
  if "$ref" in prop_schema:
282
- ref_prop_schema = resolve_schema_reference(prop_schema["$ref"], full_schema)
333
+ ref_prop_schema = resolve_schema_reference(
334
+ prop_schema["$ref"], full_schema
335
+ )
283
336
  if ref_prop_schema:
284
337
  request_body_properties[prop_name] = ref_prop_schema
285
- break
338
+
339
+ # Handle schemas with empty properties but additionalProperties: true
340
+ # by treating them similar to empty bodies
341
+ if (
342
+ not request_body_properties or len(request_body_properties) == 0
343
+ ) and schema.get("additionalProperties") is True:
344
+ has_empty_body = True
286
345
 
287
346
  # Build function arguments
288
347
  required_args = []
289
348
  optional_args = []
290
-
349
+
291
350
  # Add path parameters
292
351
  for param_name in path_params_in_url:
293
352
  if param_name not in required_args:
@@ -296,12 +355,12 @@ def generate_method_code(path, method, operation, full_schema, tool_name=None):
296
355
  # Add query parameters
297
356
  for param in parameters:
298
357
  param_name = param["name"]
299
- if param_name not in required_args:
358
+ if param_name not in required_args:
300
359
  if param.get("required", False):
301
360
  required_args.append(param_name)
302
361
  else:
303
362
  optional_args.append(f"{param_name}=None")
304
-
363
+
305
364
  # Handle array type request body differently
306
365
  request_body_params = []
307
366
  if has_body:
@@ -313,13 +372,13 @@ def generate_method_code(path, method, operation, full_schema, tool_name=None):
313
372
  array_param_name = func_name.replace("_list_input", "")
314
373
  elif "List" in func_name:
315
374
  array_param_name = func_name.split("List")[0].lower() + "_list"
316
-
375
+
317
376
  # Make the array parameter required if the request body is required
318
377
  if body_required:
319
378
  required_args.append(array_param_name)
320
379
  else:
321
380
  optional_args.append(f"{array_param_name}=None")
322
-
381
+
323
382
  # Remember this is an array param
324
383
  request_body_params = [array_param_name]
325
384
  elif request_body_properties:
@@ -333,9 +392,9 @@ def generate_method_code(path, method, operation, full_schema, tool_name=None):
333
392
  request_body_params.append(prop_name)
334
393
  if f"{prop_name}=None" not in optional_args:
335
394
  optional_args.append(f"{prop_name}=None")
336
-
395
+
337
396
  # If request body is present but empty (content: {}), add a generic request_body parameter
338
- if has_empty_body:
397
+ if has_empty_body and "request_body=None" not in optional_args:
339
398
  optional_args.append("request_body=None")
340
399
 
341
400
  # Combine required and optional arguments
@@ -364,18 +423,19 @@ def generate_method_code(path, method, operation, full_schema, tool_name=None):
364
423
  body_lines.append(f" request_body = {request_body_params[0]}")
365
424
  elif request_body_properties:
366
425
  # For object request bodies, build the request body from individual parameters
367
-
426
+
368
427
  body_lines.append(" request_body = {")
369
-
428
+
370
429
  for prop_name in request_body_params:
371
430
  # Only include non-None values in the request body
372
431
  body_lines.append(f" '{prop_name}': {prop_name},")
373
-
432
+
374
433
  body_lines.append(" }")
375
-
376
-
377
- body_lines.append(" request_body = {k: v for k, v in request_body.items() if v is not None}")
378
-
434
+
435
+ body_lines.append(
436
+ " request_body = {k: v for k, v in request_body.items() if v is not None}"
437
+ )
438
+
379
439
  # Format URL directly with path parameters
380
440
  url_line = f' url = f"{{self.base_url}}{path}"'
381
441
  body_lines.append(url_line)
@@ -394,21 +454,35 @@ def generate_method_code(path, method, operation, full_schema, tool_name=None):
394
454
 
395
455
  # Make HTTP request using the proper method
396
456
  method_lower = method.lower()
397
- # For empty request bodies, use the request_body parameter directly if provided
398
- request_body_arg = "request_body" if has_empty_body else "{}" if not has_body else "request_body"
399
-
457
+
458
+ # Determine what to use as the request body argument
459
+ if has_empty_body:
460
+ request_body_arg = "request_body"
461
+ elif not has_body:
462
+ request_body_arg = "{}"
463
+ else:
464
+ request_body_arg = "request_body"
465
+
400
466
  if method_lower == "get":
401
467
  body_lines.append(" response = self._get(url, params=query_params)")
402
468
  elif method_lower == "post":
403
- body_lines.append(f" response = self._post(url, data={request_body_arg}, params=query_params)")
469
+ body_lines.append(
470
+ f" response = self._post(url, data={request_body_arg}, params=query_params)"
471
+ )
404
472
  elif method_lower == "put":
405
- body_lines.append(f" response = self._put(url, data={request_body_arg}, params=query_params)")
473
+ body_lines.append(
474
+ f" response = self._put(url, data={request_body_arg}, params=query_params)"
475
+ )
406
476
  elif method_lower == "patch":
407
- body_lines.append(f" response = self._patch(url, data={request_body_arg}, params=query_params)")
477
+ body_lines.append(
478
+ f" response = self._patch(url, data={request_body_arg}, params=query_params)"
479
+ )
408
480
  elif method_lower == "delete":
409
481
  body_lines.append(" response = self._delete(url, params=query_params)")
410
482
  else:
411
- body_lines.append(f" response = self._{method_lower}(url, data={request_body_arg}, params=query_params)")
483
+ body_lines.append(
484
+ f" response = self._{method_lower}(url, data={request_body_arg}, params=query_params)"
485
+ )
412
486
 
413
487
  # Handle response
414
488
  body_lines.append(" response.raise_for_status()")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp
3
- Version: 0.1.8rc2
3
+ Version: 0.1.8rc4
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
@@ -28,13 +28,13 @@ Provides-Extra: markitdown
28
28
  Requires-Dist: markitdown[all]>=0.1.1; extra == 'markitdown'
29
29
  Provides-Extra: playground
30
30
  Requires-Dist: fastapi[standard]>=0.115.12; extra == 'playground'
31
- Requires-Dist: langchain-anthropic>=0.3.10; extra == 'playground'
32
31
  Requires-Dist: langchain-mcp-adapters>=0.0.3; extra == 'playground'
33
32
  Requires-Dist: langchain-openai>=0.3.12; extra == 'playground'
34
33
  Requires-Dist: langgraph-checkpoint-sqlite>=2.0.6; extra == 'playground'
35
34
  Requires-Dist: langgraph>=0.3.24; extra == 'playground'
36
35
  Requires-Dist: python-dotenv>=1.0.1; extra == 'playground'
37
36
  Requires-Dist: streamlit>=1.44.1; extra == 'playground'
37
+ Requires-Dist: watchdog>=6.0.0; extra == 'playground'
38
38
  Provides-Extra: serpapi
39
39
  Requires-Dist: google-search-results>=2.4.2; extra == 'serpapi'
40
40
  Description-Content-Type: text/markdown
@@ -0,0 +1,81 @@
1
+ universal_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ universal_mcp/analytics.py,sha256=aGCg0Okpcy06W70qCA9I8_ySOiCgAtzJAIWAdhBsOeA,2212
3
+ universal_mcp/cli.py,sha256=DG-Qxc5vQIdbhAIQuU7bKKJuRGzwyOigjfCKSWBRhBI,5258
4
+ universal_mcp/config.py,sha256=sJaPI4q51CDPPG0z32rMJiE7a64eaa9nxbjJgYnaFA4,838
5
+ universal_mcp/exceptions.py,sha256=WApedvzArNujD0gZfUofYBxjQo97ZDJLqDibtLWZoRk,373
6
+ universal_mcp/logger.py,sha256=D947u1roUf6WqlcEsPpvmWDqGc8L41qF3MO1suK5O1Q,308
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
10
+ universal_mcp/applications/ahrefs/README.md,sha256=bQQ5AmPFxM52gL-tllIFWC_64f7KYkBiD1tYfdTwDu4,5370
11
+ universal_mcp/applications/ahrefs/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
+ universal_mcp/applications/ahrefs/app.py,sha256=8iYYmQ5bD6nd_JmHOk4bcEPqG162FtQ14WrnJPeTrBQ,91468
13
+ universal_mcp/applications/calendly/README.md,sha256=85m3XXLPhQ99oghl8SeazCZ2fjBR81-f1y_mjjhx2B0,5911
14
+ universal_mcp/applications/calendly/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
+ universal_mcp/applications/calendly/app.py,sha256=ANuUfiXBwDZjQC-xfB06JoVu5ebQPEy12COBO5LY3DI,49874
16
+ universal_mcp/applications/coda/README.md,sha256=4i6o_R-qtTuxfS1A7VoIb8_85FHAj-WVb8YG5fNvwL4,11411
17
+ universal_mcp/applications/coda/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
18
+ universal_mcp/applications/coda/app.py,sha256=47ZmtYF5A2Cn0rh3Dpc3VtkIHR1Xu2PCYe1JDH8kJbY,155862
19
+ universal_mcp/applications/e2b/README.md,sha256=S4lTp-vEZ8VTCKPXqjUXu5nYlUMAF8lw8CQyBGPgxjs,700
20
+ universal_mcp/applications/e2b/app.py,sha256=4cMuGHm_QY4uh0JMh3HYzhaqtnfnXajRKFhoAGmRnBE,2252
21
+ universal_mcp/applications/figma/README.md,sha256=qA9UMf5PsPhfJrnteGVQOudhLuevwZ4-D_1xM6gAjgQ,4393
22
+ universal_mcp/applications/figma/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
+ universal_mcp/applications/figma/app.py,sha256=sr-ednZinHdIcXmDWuWAA_Ri21iBbAYPRZ0-uLeiEkM,50392
24
+ universal_mcp/applications/firecrawl/README.md,sha256=KAWe_TQbrc9eA6bSyde5dapMP1CNvarVItV_YJH3d_0,1430
25
+ universal_mcp/applications/firecrawl/app.py,sha256=qO3XNH9nT2G-9yC1eN4ADZJCE_bxF0qQ3S_qtYoOa2o,8902
26
+ universal_mcp/applications/github/README.md,sha256=6ID-__gUJ5ZxzAS_OjzmoUAag1LamSvEB75DHcj3m-g,1294
27
+ universal_mcp/applications/github/app.py,sha256=73Y5ceM2BGRcLUO__xO0RO1NNf6Gf3ROtqTlFI5k0Fg,18162
28
+ universal_mcp/applications/google_calendar/app.py,sha256=o2Mtto4zOIDtCUdXdEgXWhWsKRfzbHC7DAUuvyjUei4,19342
29
+ universal_mcp/applications/google_docs/README.md,sha256=SyOgJG-XU0FXhrVukvg9mxwin73mpqaCOT6rDJ64Klk,909
30
+ universal_mcp/applications/google_docs/app.py,sha256=f31nJ3tr9XF-1AbRI3DNVXgMXT4Y33gWMRlBoA-t630,3436
31
+ universal_mcp/applications/google_drive/README.md,sha256=YlN8IT12oO8zVMib1MlTYRBGNP7rzW_KyVAZyyxKvME,1192
32
+ universal_mcp/applications/google_drive/app.py,sha256=4cJYvT_RSlqL2Vfm4cbrxJYl58gHWdNytVAQVu1amY8,11253
33
+ universal_mcp/applications/google_mail/README.md,sha256=LL6TjjmwEqyuRVvIfCh-I_PlWp9ciCZOdJC0LafGZYc,1185
34
+ universal_mcp/applications/google_mail/app.py,sha256=1XI1hve2FXOqkzgJNYu2ki5J1yGKfeMx3cO_Qyflp_o,27286
35
+ universal_mcp/applications/google_sheet/README.md,sha256=yW1b_qlb_pbIJzCxZc58581kKzC5vyP8Mj4iwXgidtQ,1108
36
+ universal_mcp/applications/google_sheet/app.py,sha256=O6g8P697ve93CsljLK9ejWbIyzGbZ-_ThK_A_3cTpIk,6310
37
+ universal_mcp/applications/markitdown/app.py,sha256=j1AidXGmDwEV4sBw0A-mEe8O4V4Yln4Atrqnk29AHiI,1836
38
+ universal_mcp/applications/notion/README.md,sha256=45NmPOmSQv99qBvWdwmnV5vbaYc9_8vq8I-FA7veVAA,2600
39
+ universal_mcp/applications/notion/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
+ universal_mcp/applications/notion/app.py,sha256=nc-p531L-L6gMFqOOkYu5Irn9SReWAYRmJ8ZOIv5LrQ,20834
41
+ universal_mcp/applications/perplexity/README.md,sha256=QGV1iReH5p-Np7vvkZsVHxxDKQ0YaitHEwomNmGEyQs,732
42
+ universal_mcp/applications/perplexity/app.py,sha256=W2wXe2ltQKqKM6hKcVW0DczsULdmPhcsJdjx93wSXaM,2673
43
+ universal_mcp/applications/reddit/README.md,sha256=YVbJ1RN6NWlB-P6w2LxCk_DuUWl7mwaKZScY-mIMnNc,1271
44
+ universal_mcp/applications/reddit/app.py,sha256=Jd-Pr-IMhROun82kuLf0mNJ3P-LDfGfvj1bn_8qNIAI,15748
45
+ universal_mcp/applications/resend/README.md,sha256=k-sb2UwbFvDPEz6qQPLWd2cJj8hDx5f3NW7dz2jAfjI,719
46
+ universal_mcp/applications/resend/app.py,sha256=dWhijrx73hw2OLMAC01keVj7hVgu4CUZsURyRjxD7ew,1370
47
+ universal_mcp/applications/serpapi/README.md,sha256=hX4VeT2iL_67ZsMhKd60DAujQCh9K3IdHroHIq808RY,691
48
+ universal_mcp/applications/serpapi/app.py,sha256=krx9STkJI0vLarXo34emySv3fs9o9lmQ2qfjWbzxtg4,2918
49
+ universal_mcp/applications/tavily/README.md,sha256=cNg4EwX5wBbkDpPtNBNC3A_GxglfSVhdAJuweSrXN20,721
50
+ universal_mcp/applications/tavily/app.py,sha256=rU9IRyhzYkchjs8rqQMU89hkBQy13W8yeQqpQhPCCFA,1924
51
+ universal_mcp/applications/wrike/README.md,sha256=4EHVPlA8B_dzTA1-HQQqp89z6QL37RTyD2l6DD7vG9E,5156
52
+ universal_mcp/applications/wrike/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
53
+ universal_mcp/applications/wrike/app.py,sha256=j0sfbVaRSBoWN0dnpscifg_mwwSsKulX6dy-2ac3R68,60259
54
+ universal_mcp/applications/youtube/README.md,sha256=NHqIm6QvXQK7I2oZ8hUwfjLDS4_eSK9NPeFbuGIbmhg,5405
55
+ universal_mcp/applications/youtube/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
56
+ universal_mcp/applications/youtube/app.py,sha256=EQgeLNJS8Spm8xB82hSIBANJ4HR2-ktcurgW9Cn_ebU,56885
57
+ universal_mcp/applications/zenquotes/README.md,sha256=wA3hjqjrkrczQaffpwyolSKq6gXmkLgeHx6_EQrYEOY,709
58
+ universal_mcp/applications/zenquotes/app.py,sha256=xp_nlW4LFi0Mw1GRWIde4k8eexXUJx1wNqNExJ0oeKA,1085
59
+ universal_mcp/integrations/README.md,sha256=lTAPXO2nivcBe1q7JT6PRa6v9Ns_ZersQMIdw-nmwEA,996
60
+ universal_mcp/integrations/__init__.py,sha256=YY8Uw0XGNUpAQ1j-qgCOrwHTcuSew4W92cEtYXMxry4,963
61
+ universal_mcp/integrations/agentr.py,sha256=Bap4PA2-K4BkBhscgAVsBdvXNN19dkuCLO82sQFRvUM,3952
62
+ universal_mcp/integrations/integration.py,sha256=33i1-E4EMTZOhCcbLXSrJfnGUCmRROk7m7DxXUVCt5c,9985
63
+ universal_mcp/servers/__init__.py,sha256=dDtvvMzbWskABlobTZHztrWMb3hbzgidza3BmEmIAD8,474
64
+ universal_mcp/servers/server.py,sha256=azsUnzMwJXttNDBs4C6_yuds0A3gEsXkf0Dyc-vHeI8,7941
65
+ universal_mcp/stores/__init__.py,sha256=quvuwhZnpiSLuojf0NfmBx2xpaCulv3fbKtKaSCEmuM,603
66
+ universal_mcp/stores/store.py,sha256=8Hobd55WCXGXTJeADn7d_Qe-U8VkC6X3QDTgaKr3Saw,6774
67
+ universal_mcp/tools/__init__.py,sha256=hVL-elJLwD_K87Gpw_s2_o43sQRPyRNOnxlzt0_Pfn8,72
68
+ universal_mcp/tools/adapters.py,sha256=2HvpyFiI0zg9dp0XshnG7t6KrVqFHM7hgtmgY1bsHN0,927
69
+ universal_mcp/tools/func_metadata.py,sha256=f_5LdDNsOu1DpXvDUeZYiJswVmwGZz6IMPtpJJ5B2-Y,7975
70
+ universal_mcp/tools/tools.py,sha256=27PkcxoxdADoUqQHUyvKX1GJCOYb1lrOtqcR24tR7fs,12982
71
+ universal_mcp/utils/__init__.py,sha256=8wi4PGWu-SrFjNJ8U7fr2iFJ1ktqlDmSKj1xYd7KSDc,41
72
+ universal_mcp/utils/api_generator.py,sha256=-wRBpLVfJQXy1R-8FpDNs6b8_eeekVDuPc_uwjSGgiY,8883
73
+ universal_mcp/utils/docgen.py,sha256=yGBcBIr7dz3mNMGrCb_-JFsDf-ShmCKWWiPpuEj2SIU,21878
74
+ universal_mcp/utils/docstring_parser.py,sha256=-QVW9u9i1_FlAMwSkFil6ZNaKIkXKI6gqOYtURdp5ak,6745
75
+ universal_mcp/utils/dump_app_tools.py,sha256=9bQePJ4ZKzGtcIYrBgLxbKDOZmL7ajIAHhXljT_AlyA,2041
76
+ universal_mcp/utils/installation.py,sha256=KPBojDlt2YfFY2DfJ9pUr5evFJ9QQGp99KQUsRkz9GQ,10235
77
+ 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,,