universal-mcp 0.1.13rc3__tar.gz → 0.1.13rc7__tar.gz

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 (58) hide show
  1. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/PKG-INFO +1 -1
  2. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/pyproject.toml +1 -41
  3. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/playground/__main__.py +3 -1
  4. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/playground/client.py +40 -41
  5. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/playground/streamlit.py +21 -10
  6. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/tests/test_api_generator.py +3 -4
  7. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/tests/test_api_integration.py +2 -2
  8. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/tests/test_applications.py +1 -1
  9. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/tests/test_stores.py +1 -0
  10. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/applications/__init__.py +20 -6
  11. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/cli.py +14 -16
  12. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/servers/server.py +0 -2
  13. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/stores/store.py +2 -0
  14. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/tools/tools.py +1 -3
  15. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/utils/agentr.py +8 -3
  16. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/utils/api_generator.py +26 -23
  17. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/utils/openapi.py +54 -38
  18. universal_mcp-0.1.13rc3/src/playground/memory/__init__.py +0 -14
  19. universal_mcp-0.1.13rc3/src/playground/memory/sqlite.py +0 -9
  20. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/.gitignore +0 -0
  21. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/README.md +0 -0
  22. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/playground/README.md +0 -0
  23. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/playground/__init__.py +0 -0
  24. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/playground/agents/react.py +0 -0
  25. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/playground/schema.py +0 -0
  26. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/playground/settings.py +0 -0
  27. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/playground/utils.py +0 -0
  28. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/tests/__init__.py +0 -0
  29. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/tests/conftest.py +0 -0
  30. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/tests/test_localserver.py +0 -0
  31. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/tests/test_tool.py +0 -0
  32. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/tests/test_zenquotes.py +0 -0
  33. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/__init__.py +0 -0
  34. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/analytics.py +0 -0
  35. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/applications/application.py +0 -0
  36. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/config.py +0 -0
  37. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/exceptions.py +0 -0
  38. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/integrations/README.md +0 -0
  39. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/integrations/__init__.py +1 -1
  40. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/integrations/integration.py +0 -0
  41. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/logger.py +0 -0
  42. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/py.typed +0 -0
  43. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/servers/README.md +0 -0
  44. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/servers/__init__.py +0 -0
  45. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/stores/README.md +0 -0
  46. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/stores/__init__.py +0 -0
  47. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/templates/README.md.j2 +0 -0
  48. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/templates/api_client.py.j2 +0 -0
  49. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/tools/README.md +0 -0
  50. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/tools/__init__.py +0 -0
  51. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/tools/adapters.py +0 -0
  52. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/tools/func_metadata.py +0 -0
  53. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/utils/__init__.py +0 -0
  54. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/utils/docgen.py +0 -0
  55. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/utils/docstring_parser.py +0 -0
  56. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/utils/dump_app_tools.py +0 -0
  57. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/utils/installation.py +0 -0
  58. {universal_mcp-0.1.13rc3 → universal_mcp-0.1.13rc7}/src/universal_mcp/utils/singleton.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp
3
- Version: 0.1.13rc3
3
+ Version: 0.1.13rc7
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
  License: MIT
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "universal-mcp"
3
- version = "0.1.13-rc3"
3
+ version = "0.1.13-rc7"
4
4
  description = "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
  readme = "README.md"
6
6
  authors = [
@@ -199,43 +199,3 @@ docstring-code-line-length = "dynamic"
199
199
  [tool.pytest.ini_options]
200
200
  asyncio_mode = "strict"
201
201
  asyncio_default_fixture_loop_scope = "function"
202
-
203
- [tool.uv.sources]
204
- universal-mcp-ahrefs = { git = "https://github.com/universal-mcp/ahrefs" }
205
- universal-mcp-cal-com-v2 = { git = "https://github.com/universal-mcp/cal-com-v2" }
206
- universal-mcp-calendly = { git = "https://github.com/universal-mcp/calendly" }
207
- universal-mcp-clickup = { git = "https://github.com/universal-mcp/clickup" }
208
- universal-mcp-coda = { git = "https://github.com/universal-mcp/coda" }
209
- universal-mcp-crustdata = { git = "https://github.com/universal-mcp/crustdata" }
210
- universal-mcp-e2b = { git = "https://github.com/universal-mcp/e2b" }
211
- universal-mcp-elevenlabs = { git = "https://github.com/universal-mcp/elevenlabs" }
212
- universal-mcp-falai = { git = "https://github.com/universal-mcp/falai" }
213
- universal-mcp-figma = { git = "https://github.com/universal-mcp/figma" }
214
- universal-mcp-firecrawl = { git = "https://github.com/universal-mcp/firecrawl" }
215
- universal-mcp-github = { git = "https://github.com/universal-mcp/github" }
216
- universal-mcp-gong = { git = "https://github.com/universal-mcp/gong" }
217
- universal-mcp-google-calendar = { git = "https://github.com/universal-mcp/google-calendar" }
218
- universal-mcp-google-docs = { git = "https://github.com/universal-mcp/google-docs" }
219
- universal-mcp-google-drive = { git = "https://github.com/universal-mcp/google-drive" }
220
- universal-mcp-google-mail = { git = "https://github.com/universal-mcp/google-mail" }
221
- universal-mcp-google-sheet = { git = "https://github.com/universal-mcp/google-sheet" }
222
- universal-mcp-hashnode = { git = "https://github.com/universal-mcp/hashnode" }
223
- universal-mcp-heygen = { git = "https://github.com/universal-mcp/heygen" }
224
- universal-mcp-mailchimp = { git = "https://github.com/universal-mcp/mailchimp" }
225
- universal-mcp-markitdown = { git = "https://github.com/universal-mcp/markitdown" }
226
- universal-mcp-neon = { git = "https://github.com/universal-mcp/neon" }
227
- universal-mcp-notion = { git = "https://github.com/universal-mcp/notion" }
228
- universal-mcp-perplexity = { git = "https://github.com/universal-mcp/perplexity" }
229
- universal-mcp-reddit = { git = "https://github.com/universal-mcp/reddit" }
230
- universal-mcp-replicate = { git = "https://github.com/universal-mcp/replicate" }
231
- universal-mcp-resend = { git = "https://github.com/universal-mcp/resend" }
232
- universal-mcp-retell = { git = "https://github.com/universal-mcp/retell" }
233
- universal-mcp-rocketlane = { git = "https://github.com/universal-mcp/rocketlane" }
234
- universal-mcp-serpapi = { git = "https://github.com/universal-mcp/serpapi" }
235
- universal-mcp-shortcut = { git = "https://github.com/universal-mcp/shortcut" }
236
- universal-mcp-spotify = { git = "https://github.com/universal-mcp/spotify" }
237
- universal-mcp-supabase = { git = "https://github.com/universal-mcp/supabase" }
238
- universal-mcp-tavily = { git = "https://github.com/universal-mcp/tavily" }
239
- universal-mcp-wrike = { git = "https://github.com/universal-mcp/wrike" }
240
- universal-mcp-youtube = { git = "https://github.com/universal-mcp/youtube" }
241
- universal-mcp-zenquotes = { git = "https://github.com/universal-mcp/zenquotes" }
@@ -11,7 +11,9 @@ def main():
11
11
  # Ask the user if they want to run the MCP server
12
12
  run_mcp_server = input("Do you want to run the MCP server? (y/n): ")
13
13
  if run_mcp_server == "y":
14
- mcp_process = subprocess.Popen(["universal_mcp", "run", "-c", "local_config.json"])
14
+ mcp_process = subprocess.Popen(
15
+ ["universal_mcp", "run", "-c", "local_config.json"]
16
+ )
15
17
  processes.append(mcp_process)
16
18
  time.sleep(6) # Give MCP server time to start
17
19
  logger.info("MCP server started")
@@ -1,35 +1,29 @@
1
- from contextlib import asynccontextmanager
2
1
  import json
3
- import os
4
2
  from collections.abc import AsyncGenerator, Generator
3
+ from contextlib import asynccontextmanager
5
4
  from typing import Any
6
- from uuid import UUID, uuid4
5
+ from uuid import uuid4
7
6
 
8
7
  from langchain_core.messages import HumanMessage
9
8
  from langchain_core.runnables import RunnableConfig
9
+ from langgraph.types import Command
10
10
 
11
11
  from playground.agents.react import create_agent
12
- from playground.memory import initialize_database
13
12
  from playground.schema import (
14
13
  ChatHistory,
15
- ChatHistoryInput,
16
14
  ChatMessage,
17
- StreamInput,
18
- UserInput,
19
15
  )
20
16
  from playground.utils import (
21
17
  convert_message_content_to_string,
22
18
  langchain_to_chat_message,
23
19
  remove_tool_calls,
24
20
  )
25
- from langgraph.types import Command
21
+
26
22
 
27
23
  @asynccontextmanager
28
24
  async def create_agent_client():
29
- async with create_agent() as react_agent, initialize_database() as saver:
30
- await saver.setup()
25
+ async with create_agent() as react_agent:
31
26
  agent = react_agent
32
- agent.checkpointer = saver
33
27
  client = AgentClient(agent=agent)
34
28
  yield client
35
29
 
@@ -41,10 +35,7 @@ class AgentClientError(Exception):
41
35
  class AgentClient:
42
36
  """Client for interacting with the agent directly."""
43
37
 
44
- def __init__(
45
- self,
46
- agent
47
- ) -> None:
38
+ def __init__(self, agent) -> None:
48
39
  """
49
40
  Initialize the client.
50
41
 
@@ -82,7 +73,9 @@ class AgentClient:
82
73
  configurable = {"thread_id": thread_id}
83
74
  if agent_config:
84
75
  if overlap := configurable.keys() & agent_config.keys():
85
- raise AgentClientError(f"agent_config contains reserved keys: {overlap}")
76
+ raise AgentClientError(
77
+ f"agent_config contains reserved keys: {overlap}"
78
+ )
86
79
  configurable.update(agent_config)
87
80
 
88
81
  kwargs = {
@@ -92,7 +85,7 @@ class AgentClient:
92
85
  run_id=run_id,
93
86
  ),
94
87
  }
95
-
88
+
96
89
  try:
97
90
  response = await self.agent.ainvoke(**kwargs)
98
91
  output = langchain_to_chat_message(response["messages"][-1])
@@ -124,7 +117,9 @@ class AgentClient:
124
117
  configurable = {"thread_id": thread_id}
125
118
  if agent_config:
126
119
  if overlap := configurable.keys() & agent_config.keys():
127
- raise AgentClientError(f"agent_config contains reserved keys: {overlap}")
120
+ raise AgentClientError(
121
+ f"agent_config contains reserved keys: {overlap}"
122
+ )
128
123
  configurable.update(agent_config)
129
124
 
130
125
  kwargs = {
@@ -134,7 +129,7 @@ class AgentClient:
134
129
  run_id=run_id,
135
130
  ),
136
131
  }
137
-
132
+
138
133
  try:
139
134
  response = self.agent.invoke(**kwargs)
140
135
  output = langchain_to_chat_message(response["messages"][-1])
@@ -146,7 +141,7 @@ class AgentClient:
146
141
  def _parse_stream_line(self, line: str) -> ChatMessage | str | None:
147
142
  """
148
143
  Parse a line from the stream.
149
-
144
+
150
145
  This method is kept for compatibility but is no longer used
151
146
  since we're directly streaming from the agent.
152
147
  """
@@ -201,7 +196,9 @@ class AgentClient:
201
196
  configurable = {"thread_id": thread_id}
202
197
  if agent_config:
203
198
  if overlap := configurable.keys() & agent_config.keys():
204
- raise AgentClientError(f"agent_config contains reserved keys: {overlap}")
199
+ raise AgentClientError(
200
+ f"agent_config contains reserved keys: {overlap}"
201
+ )
205
202
  configurable.update(agent_config)
206
203
 
207
204
  kwargs = {
@@ -211,7 +208,7 @@ class AgentClient:
211
208
  run_id=run_id,
212
209
  ),
213
210
  }
214
-
211
+
215
212
  try:
216
213
  for event in self.agent.stream_events(**kwargs, version="v2"):
217
214
  if not event:
@@ -226,12 +223,16 @@ class AgentClient:
226
223
  and any(t.startswith("graph:step:") for t in event.get("tags", []))
227
224
  ):
228
225
  if isinstance(event["data"]["output"], Command):
229
- new_messages = event["data"]["output"].update.get("messages", [])
226
+ new_messages = event["data"]["output"].update.get(
227
+ "messages", []
228
+ )
230
229
  elif "messages" in event["data"]["output"]:
231
230
  new_messages = event["data"]["output"]["messages"]
232
231
 
233
232
  # Also yield intermediate messages from agents.utils.CustomData.adispatch().
234
- if event["event"] == "on_custom_event" and "custom_data_dispatch" in event.get(
233
+ if event[
234
+ "event"
235
+ ] == "on_custom_event" and "custom_data_dispatch" in event.get(
235
236
  "tags", []
236
237
  ):
237
238
  new_messages = [event["data"]]
@@ -240,14 +241,11 @@ class AgentClient:
240
241
  try:
241
242
  chat_message = langchain_to_chat_message(message)
242
243
  chat_message.run_id = str(run_id)
243
- except Exception as e:
244
+ except Exception:
244
245
  yield f"data: {json.dumps({'type': 'error', 'content': 'Unexpected error'})}\n\n"
245
246
  continue
246
247
  # LangGraph re-sends the input message, which feels weird, so drop it
247
- if (
248
- chat_message.type == "human"
249
- and chat_message.content == message
250
- ):
248
+ if chat_message.type == "human" and chat_message.content == message:
251
249
  continue
252
250
  yield chat_message
253
251
 
@@ -296,7 +294,9 @@ class AgentClient:
296
294
  configurable = {"thread_id": thread_id}
297
295
  if agent_config:
298
296
  if overlap := configurable.keys() & agent_config.keys():
299
- raise AgentClientError(f"agent_config contains reserved keys: {overlap}")
297
+ raise AgentClientError(
298
+ f"agent_config contains reserved keys: {overlap}"
299
+ )
300
300
  configurable.update(agent_config)
301
301
 
302
302
  kwargs = {
@@ -306,7 +306,7 @@ class AgentClient:
306
306
  run_id=run_id,
307
307
  ),
308
308
  }
309
-
309
+
310
310
  try:
311
311
  async for event in self.agent.astream_events(**kwargs, version="v2"):
312
312
  if not event:
@@ -321,12 +321,16 @@ class AgentClient:
321
321
  and any(t.startswith("graph:step:") for t in event.get("tags", []))
322
322
  ):
323
323
  if isinstance(event["data"]["output"], Command):
324
- new_messages = event["data"]["output"].update.get("messages", [])
324
+ new_messages = event["data"]["output"].update.get(
325
+ "messages", []
326
+ )
325
327
  elif "messages" in event["data"]["output"]:
326
328
  new_messages = event["data"]["output"]["messages"]
327
329
 
328
330
  # Also yield intermediate messages from agents.utils.CustomData.adispatch().
329
- if event["event"] == "on_custom_event" and "custom_data_dispatch" in event.get(
331
+ if event[
332
+ "event"
333
+ ] == "on_custom_event" and "custom_data_dispatch" in event.get(
330
334
  "tags", []
331
335
  ):
332
336
  new_messages = [event["data"]]
@@ -335,14 +339,11 @@ class AgentClient:
335
339
  try:
336
340
  chat_message = langchain_to_chat_message(message)
337
341
  chat_message.run_id = str(run_id)
338
- except Exception as e:
342
+ except Exception:
339
343
  yield f"data: {json.dumps({'type': 'error', 'content': 'Unexpected error'})}\n\n"
340
344
  continue
341
345
  # LangGraph re-sends the input message, which feels weird, so drop it
342
- if (
343
- chat_message.type == "human"
344
- and chat_message.content == message
345
- ):
346
+ if chat_message.type == "human" and chat_message.content == message:
346
347
  continue
347
348
  yield chat_message
348
349
 
@@ -380,9 +381,7 @@ class AgentClient:
380
381
  )
381
382
  )
382
383
  messages = state_snapshot.values["messages"]
383
- chat_messages = [
384
- langchain_to_chat_message(m) for m in messages
385
- ]
384
+ chat_messages = [langchain_to_chat_message(m) for m in messages]
386
385
  return ChatHistory(messages=chat_messages)
387
386
  except Exception as e:
388
387
  raise AgentClientError(f"Error getting history: {e}") from e
@@ -20,6 +20,7 @@ APP_TITLE = "MCP Playground"
20
20
  APP_ICON = "🧰"
21
21
  use_streaming = True
22
22
 
23
+
23
24
  # --- Function to handle unique filename generation ---
24
25
  def get_unique_filepath(upload_dir: Path, filename: str) -> Path:
25
26
  """Checks if a file exists and returns a unique path if needed."""
@@ -37,6 +38,7 @@ def get_unique_filepath(upload_dir: Path, filename: str) -> Path:
37
38
  return new_filepath
38
39
  counter += 1
39
40
 
41
+
40
42
  async def main() -> None:
41
43
  # Configure page layout
42
44
  st.set_page_config(
@@ -48,7 +50,8 @@ async def main() -> None:
48
50
  )
49
51
 
50
52
  # Custom CSS for chat layout and styling
51
- st.markdown("""
53
+ st.markdown(
54
+ """
52
55
  <style>
53
56
  /* Hide Streamlit default header and footer */
54
57
  #MainMenu, header, footer {
@@ -157,7 +160,9 @@ async def main() -> None:
157
160
  border-bottom-left-radius: 0.25rem;
158
161
  }
159
162
  </style>
160
- """, unsafe_allow_html=True)
163
+ """,
164
+ unsafe_allow_html=True,
165
+ )
161
166
 
162
167
  # Minimal toolbar
163
168
  if st.get_option("client.toolbarMode") != "minimal":
@@ -217,7 +222,7 @@ async def main() -> None:
217
222
  "Upload a file to process",
218
223
  type=None,
219
224
  key="file_uploader",
220
- help="Upload any file to process with the AI assistant"
225
+ help="Upload any file to process with the AI assistant",
221
226
  )
222
227
 
223
228
  # Handle file upload state
@@ -233,7 +238,8 @@ async def main() -> None:
233
238
  icon="📄",
234
239
  )
235
240
  elif (
236
- uploaded_file is None and st.session_state.get("uploaded_file_obj") is not None
241
+ uploaded_file is None
242
+ and st.session_state.get("uploaded_file_obj") is not None
237
243
  ):
238
244
  st.session_state.uploaded_file_obj = None
239
245
  st.session_state.file_processed = False
@@ -259,19 +265,21 @@ async def main() -> None:
259
265
  yield m
260
266
 
261
267
  await draw_messages(amessage_iter())
262
- st.markdown('</div>', unsafe_allow_html=True)
268
+ st.markdown("</div>", unsafe_allow_html=True)
263
269
 
264
270
  # Chat input area
265
271
  st.markdown('<div class="chat-input-area">', unsafe_allow_html=True)
266
272
  if user_input := st.chat_input(
267
- "Enter message or upload a file and describe task...",
268
- key="chat_input"
273
+ "Enter message or upload a file and describe task...", key="chat_input"
269
274
  ):
270
275
  final_message_content = user_input
271
276
  display_content = user_input
272
277
 
273
278
  # --- Handle File Upload Integration ---
274
- if st.session_state.uploaded_file_obj and not st.session_state.file_processed:
279
+ if (
280
+ st.session_state.uploaded_file_obj
281
+ and not st.session_state.file_processed
282
+ ):
275
283
  uploaded_file_obj = st.session_state.uploaded_file_obj
276
284
  original_filename = uploaded_file_obj.name
277
285
  try:
@@ -321,10 +329,11 @@ async def main() -> None:
321
329
  st.error(
322
330
  f"An unexpected error occurred during agent communication: {e}"
323
331
  )
324
- st.markdown('</div>', unsafe_allow_html=True)
332
+ st.markdown("</div>", unsafe_allow_html=True)
325
333
 
326
334
  # End chat app wrapper
327
- st.markdown('</div>', unsafe_allow_html=True)
335
+ st.markdown("</div>", unsafe_allow_html=True)
336
+
328
337
 
329
338
  async def draw_messages(
330
339
  messages_agen: AsyncGenerator[ChatMessage | str, None],
@@ -418,6 +427,7 @@ async def draw_messages(
418
427
  st.write(msg)
419
428
  st.stop()
420
429
 
430
+
421
431
  async def handle_feedback() -> None:
422
432
  """Draws a feedback widget and records feedback from the user."""
423
433
  if "last_feedback" not in st.session_state:
@@ -443,6 +453,7 @@ async def handle_feedback() -> None:
443
453
  st.session_state.last_feedback = (latest_run_id, feedback)
444
454
  st.toast("Feedback recorded", icon="⭐")
445
455
 
456
+
446
457
  if __name__ == "__main__":
447
458
  asyncio.run(main())
448
459
  # End of Selectio
@@ -53,7 +53,7 @@ def sample_schema(temp_dir):
53
53
  @pytest.mark.asyncio
54
54
  async def test_generate_api_without_output(sample_schema):
55
55
  """Test API generation without output file (return code only)."""
56
- result =generate_api_from_schema(
56
+ result = generate_api_from_schema(
57
57
  schema_path=sample_schema, output_path=None, add_docstrings=False
58
58
  )
59
59
 
@@ -76,8 +76,8 @@ async def test_generate_api_with_output(sample_schema, temp_dir):
76
76
  schema_path=sample_schema, output_path=output_path, add_docstrings=True
77
77
  )
78
78
 
79
- assert "app_file" is not None
80
- assert "readme_file" is not None
79
+ assert "app_file" != None
80
+ assert "readme_file" != None
81
81
 
82
82
  assert app_file.exists()
83
83
  content = app_file.read_text()
@@ -138,7 +138,6 @@ async def test_generate_api_with_docstrings(sample_schema, temp_dir):
138
138
  assert "Tags:" in content
139
139
 
140
140
 
141
-
142
141
  @pytest.mark.asyncio
143
142
  async def test_generate_api_without_docstrings(sample_schema, temp_dir):
144
143
  """Test API generation without docstring generation."""
@@ -1,6 +1,6 @@
1
1
  import pytest
2
2
 
3
- from universal_mcp.applications.perplexity.app import PerplexityApp
3
+ from universal_mcp.applications import app_from_slug
4
4
  from universal_mcp.exceptions import NotAuthorizedError
5
5
  from universal_mcp.integrations import ApiKeyIntegration
6
6
  from universal_mcp.stores import MemoryStore
@@ -14,7 +14,7 @@ def test_perplexity_api_no_key():
14
14
  integration = ApiKeyIntegration("PERPLEXITY", store=store)
15
15
 
16
16
  # Create Perplexity app with the integration
17
- app = PerplexityApp(integration=integration)
17
+ app = app_from_slug("perplexity")(integration=integration)
18
18
 
19
19
  # Try to make a chat request without setting API key
20
20
  with pytest.raises(NotAuthorizedError) as exc_info:
@@ -59,4 +59,4 @@ def test_application(app_name):
59
59
  assert isinstance(tools[0], Callable)
60
60
  for tool in tools:
61
61
  assert tool.__name__ is not None
62
- assert tool.__doc__ is not None
62
+ assert tool.__doc__ is not None
@@ -67,6 +67,7 @@ class TestEnvironmentStore:
67
67
 
68
68
 
69
69
  # Test KeyringStore
70
+ @pytest.mark.skip(reason="Skipping KeyringStore tests")
70
71
  class TestKeyringStore:
71
72
  @pytest.fixture
72
73
  def store(self):
@@ -1,4 +1,5 @@
1
1
  import importlib
2
+ import subprocess
2
3
 
3
4
  from loguru import logger
4
5
 
@@ -7,12 +8,12 @@ from universal_mcp.applications.application import (
7
8
  BaseApplication,
8
9
  GraphQLApplication,
9
10
  )
10
- import subprocess
11
11
 
12
12
  # Name are in the format of "app-name", eg, google-calendar
13
13
  # Folder name is "app_name", eg, google_calendar
14
14
  # Class name is NameApp, eg, GoogleCalendarApp
15
15
 
16
+
16
17
  def _import_class(module_path: str, class_name: str):
17
18
  """
18
19
  Helper to import a class by name from a module.
@@ -27,7 +28,10 @@ def _import_class(module_path: str, class_name: str):
27
28
  return getattr(module, class_name)
28
29
  except AttributeError as e:
29
30
  logger.error(f"Class '{class_name}' not found in module '{module_path}'")
30
- raise ModuleNotFoundError(f"Class '{class_name}' not found in module '{module_path}'") from e
31
+ raise ModuleNotFoundError(
32
+ f"Class '{class_name}' not found in module '{module_path}'"
33
+ ) from e
34
+
31
35
 
32
36
  def _install_package(slug_clean: str):
33
37
  """
@@ -40,10 +44,13 @@ def _install_package(slug_clean: str):
40
44
  subprocess.check_call(cmd)
41
45
  except subprocess.CalledProcessError as e:
42
46
  logger.error(f"Installation failed for '{slug_clean}': {e}")
43
- raise ModuleNotFoundError(f"Installation failed for package '{slug_clean}'") from e
47
+ raise ModuleNotFoundError(
48
+ f"Installation failed for package '{slug_clean}'"
49
+ ) from e
44
50
  else:
45
51
  logger.info(f"Package '{slug_clean}' installed successfully")
46
52
 
53
+
47
54
  def app_from_slug(slug: str):
48
55
  """
49
56
  Dynamically resolve and return the application class for the given slug.
@@ -54,19 +61,26 @@ def app_from_slug(slug: str):
54
61
  package_prefix = f"universal_mcp_{slug_clean.replace('-', '_')}"
55
62
  module_path = f"{package_prefix}.app"
56
63
 
57
- logger.info(f"Resolving app for slug '{slug}' → module '{module_path}', class '{class_name}'")
64
+ logger.info(
65
+ f"Resolving app for slug '{slug}' → module '{module_path}', class '{class_name}'"
66
+ )
58
67
  try:
59
68
  return _import_class(module_path, class_name)
60
69
  except ModuleNotFoundError as orig_err:
61
- logger.warning(f"Module '{module_path}' not found locally: {orig_err}. Installing...")
70
+ logger.warning(
71
+ f"Module '{module_path}' not found locally: {orig_err}. Installing..."
72
+ )
62
73
  _install_package(slug_clean)
63
74
  # Retry import after installation
64
75
  try:
65
76
  return _import_class(module_path, class_name)
66
77
  except ModuleNotFoundError as retry_err:
67
- logger.error(f"Still cannot import '{module_path}' after installation: {retry_err}")
78
+ logger.error(
79
+ f"Still cannot import '{module_path}' after installation: {retry_err}"
80
+ )
68
81
  raise
69
82
 
83
+
70
84
  __all__ = [
71
85
  "app_from_slug",
72
86
  "BaseApplication",
@@ -1,11 +1,9 @@
1
- import asyncio
2
- import os
1
+ import re
3
2
  from pathlib import Path
4
3
 
5
4
  import typer
6
5
  from rich import print as rprint
7
6
  from rich.panel import Panel
8
- import re
9
7
 
10
8
  from universal_mcp.utils.installation import (
11
9
  get_supported_apps,
@@ -41,9 +39,9 @@ def generate(
41
39
  try:
42
40
  # Run the async function in the event loop
43
41
  result = generate_api_from_schema(
44
- schema_path=schema_path,
45
- output_path=output_path,
46
- )
42
+ schema_path=schema_path,
43
+ output_path=output_path,
44
+ )
47
45
 
48
46
  if not output_path:
49
47
  # Print to stdout if no output path
@@ -68,7 +66,6 @@ def docgen(
68
66
  "-m",
69
67
  help="Model to use for generating docstrings",
70
68
  ),
71
-
72
69
  ):
73
70
  """Generate docstrings for Python files using LLMs.
74
71
 
@@ -154,6 +151,7 @@ def install(app_name: str = typer.Argument(..., help="Name of app to install")):
154
151
  typer.echo(f"Error installing app: {e}", err=True)
155
152
  raise typer.Exit(1) from e
156
153
 
154
+
157
155
  @app.command()
158
156
  def init(
159
157
  output_dir: Path | None = typer.Option(
@@ -162,13 +160,13 @@ def init(
162
160
  "-o",
163
161
  help="Output directory for the project (must exist)",
164
162
  ),
165
- app_name: str|None = typer.Option(
163
+ app_name: str | None = typer.Option(
166
164
  None,
167
165
  "--app-name",
168
166
  "-a",
169
167
  help="App name (letters, numbers, hyphens, underscores only)",
170
168
  ),
171
- integration_type: str|None = typer.Option(
169
+ integration_type: str | None = typer.Option(
172
170
  None,
173
171
  "--integration-type",
174
172
  "-i",
@@ -195,7 +193,7 @@ def init(
195
193
  app_name = typer.prompt(
196
194
  "Enter the app name",
197
195
  default="app_name",
198
- prompt_suffix=" (e.g., reddit, youtube): "
196
+ prompt_suffix=" (e.g., reddit, youtube): ",
199
197
  ).strip()
200
198
  validate_pattern(app_name, "app name")
201
199
 
@@ -203,10 +201,10 @@ def init(
203
201
  path_str = typer.prompt(
204
202
  "Enter the output directory for the project",
205
203
  default=str(Path.cwd()),
206
- prompt_suffix=": "
204
+ prompt_suffix=": ",
207
205
  ).strip()
208
206
  output_dir = Path(path_str)
209
-
207
+
210
208
  if not output_dir.exists():
211
209
  try:
212
210
  output_dir.mkdir(parents=True, exist_ok=True)
@@ -219,7 +217,7 @@ def init(
219
217
  f"❌ Failed to create output directory '{output_dir}': {e}",
220
218
  fg=typer.colors.RED,
221
219
  )
222
- raise typer.Exit(code=1)
220
+ raise typer.Exit(code=1) from e
223
221
  elif not output_dir.is_dir():
224
222
  typer.secho(
225
223
  f"❌ Output path '{output_dir}' exists but is not a directory.",
@@ -232,7 +230,7 @@ def init(
232
230
  integration_type = typer.prompt(
233
231
  "Choose the integration type",
234
232
  default="agentr",
235
- prompt_suffix=" (api_key, oauth, agentr, none): "
233
+ prompt_suffix=" (api_key, oauth, agentr, none): ",
236
234
  ).lower()
237
235
  if integration_type not in ("api_key", "oauth", "agentr", "none"):
238
236
  typer.secho(
@@ -240,7 +238,6 @@ def init(
240
238
  fg=typer.colors.RED,
241
239
  )
242
240
  raise typer.Exit(code=1)
243
-
244
241
 
245
242
  typer.secho("🚀 Generating project using cookiecutter...", fg=typer.colors.BLUE)
246
243
  try:
@@ -255,10 +252,11 @@ def init(
255
252
  )
256
253
  except Exception as exc:
257
254
  typer.secho(f"❌ Project generation failed: {exc}", fg=typer.colors.RED)
258
- raise typer.Exit(code=1)
255
+ raise typer.Exit(code=1) from exc
259
256
 
260
257
  project_dir = output_dir / f"universal-mcp-{app_name}"
261
258
  typer.secho(f"✅ Project created at {project_dir}", fg=typer.colors.GREEN)
262
259
 
260
+
263
261
  if __name__ == "__main__":
264
262
  app()
@@ -1,8 +1,6 @@
1
- import os
2
1
  from abc import ABC, abstractmethod
3
2
  from collections.abc import Callable
4
3
  from typing import Any
5
- from urllib.parse import urlparse
6
4
 
7
5
  import httpx
8
6
  from loguru import logger
@@ -11,11 +11,13 @@ class StoreError(Exception):
11
11
 
12
12
  pass
13
13
 
14
+
14
15
  class KeyNotFoundError(StoreError):
15
16
  """Exception raised when a key is not found in the store."""
16
17
 
17
18
  pass
18
19
 
20
+
19
21
  class BaseStore(ABC):
20
22
  """
21
23
  Abstract base class defining the interface for credential stores.
@@ -260,9 +260,7 @@ class ToolManager:
260
260
  try:
261
261
  available_tool_functions = app.list_tools()
262
262
  except TypeError as e:
263
- logger.error(
264
- f"Error calling list_tools for app '{app.name}'. Error: {e}"
265
- )
263
+ logger.error(f"Error calling list_tools for app '{app.name}'. Error: {e}")
266
264
  return
267
265
  except Exception as e:
268
266
  logger.error(f"Failed to get tool list from app '{app.name}': {e}")
@@ -1,9 +1,13 @@
1
- from loguru import logger
2
1
  import os
2
+
3
3
  import httpx
4
+ from loguru import logger
5
+
4
6
  from universal_mcp.config import AppConfig
7
+ from universal_mcp.exceptions import NotAuthorizedError
5
8
  from universal_mcp.utils.singleton import Singleton
6
9
 
10
+
7
11
  class AgentrClient(metaclass=Singleton):
8
12
  """Helper class for AgentR API operations.
9
13
 
@@ -22,7 +26,9 @@ class AgentrClient(metaclass=Singleton):
22
26
  "API key for AgentR is missing. Please visit https://agentr.dev to create an API key, then set it as AGENTR_API_KEY environment variable."
23
27
  )
24
28
  raise ValueError("AgentR API key required - get one at https://agentr.dev")
25
- self.base_url = (base_url or os.getenv("AGENTR_BASE_URL", "https://api.agentr.dev")).rstrip("/")
29
+ self.base_url = (
30
+ base_url or os.getenv("AGENTR_BASE_URL", "https://api.agentr.dev")
31
+ ).rstrip("/")
26
32
 
27
33
  def get_credentials(self, integration_name: str) -> dict:
28
34
  """Get credentials for an integration from the AgentR API.
@@ -87,4 +93,3 @@ class AgentrClient(metaclass=Singleton):
87
93
  response.raise_for_status()
88
94
  data = response.json()
89
95
  return [AppConfig.model_validate(app) for app in data]
90
-
@@ -1,10 +1,11 @@
1
+ import importlib.util
1
2
  import inspect
2
3
  import os
3
- from pathlib import Path
4
- from loguru import logger
5
4
  import shutil
6
- import importlib.util
5
+ from pathlib import Path
6
+
7
7
  from jinja2 import Environment, FileSystemLoader, TemplateError, select_autoescape
8
+ from loguru import logger
8
9
 
9
10
  from universal_mcp.utils.openapi import generate_api_client, load_schema
10
11
 
@@ -26,6 +27,7 @@ def validate_and_load_schema(schema_path: Path) -> dict:
26
27
  echo(f"Error loading schema: {e}", err=True)
27
28
  raise
28
29
 
30
+
29
31
  def get_class_info(module: any) -> tuple[str | None, any]:
30
32
  """Find the main class in the generated module."""
31
33
  for name, obj in inspect.getmembers(module):
@@ -33,19 +35,18 @@ def get_class_info(module: any) -> tuple[str | None, any]:
33
35
  return name, obj
34
36
  return None, None
35
37
 
36
- def generate_readme(
37
- app_dir: Path, folder_name: str, tools: list
38
- ) -> Path:
38
+
39
+ def generate_readme(app_dir: Path, folder_name: str, tools: list) -> Path:
39
40
  """Generate README.md with API documentation.
40
-
41
+
41
42
  Args:
42
43
  app_dir: Directory where the README will be generated
43
44
  folder_name: Name of the application folder
44
45
  tools: List of Function objects from the OpenAPI schema
45
-
46
+
46
47
  Returns:
47
48
  Path to the generated README file
48
-
49
+
49
50
  Raises:
50
51
  FileNotFoundError: If the template directory doesn't exist
51
52
  TemplateError: If there's an error rendering the template
@@ -69,23 +70,19 @@ def generate_readme(
69
70
 
70
71
  try:
71
72
  env = Environment(
72
- loader=FileSystemLoader(template_dir),
73
- autoescape=select_autoescape()
73
+ loader=FileSystemLoader(template_dir), autoescape=select_autoescape()
74
74
  )
75
75
  template = env.get_template("README.md.j2")
76
76
  except Exception as e:
77
77
  logger.error(f"Error loading template: {e}")
78
- raise TemplateError(f"Error loading template: {e}")
78
+ raise TemplateError(f"Error loading template: {e}") from e
79
79
 
80
80
  # Render the template
81
81
  try:
82
- readme_content = template.render(
83
- name=app,
84
- tools=formatted_tools
85
- )
82
+ readme_content = template.render(name=app, tools=formatted_tools)
86
83
  except Exception as e:
87
84
  logger.error(f"Error rendering template: {e}")
88
- raise TemplateError(f"Error rendering template: {e}")
85
+ raise TemplateError(f"Error rendering template: {e}") from e
89
86
 
90
87
  # Write the README file
91
88
  readme_file = app_dir / "README.md"
@@ -95,10 +92,11 @@ def generate_readme(
95
92
  logger.info(f"Documentation generated at: {readme_file}")
96
93
  except Exception as e:
97
94
  logger.error(f"Error writing README file: {e}")
98
- raise IOError(f"Error writing README file: {e}")
95
+ raise OSError(f"Error writing README file: {e}") from e
99
96
 
100
97
  return readme_file
101
98
 
99
+
102
100
  def test_correct_output(gen_file: Path):
103
101
  # Check file is non-empty
104
102
  if gen_file.stat().st_size == 0:
@@ -137,7 +135,6 @@ def generate_api_from_schema(
137
135
  """
138
136
  # Local imports for logging and file operations
139
137
 
140
-
141
138
  logger.info("Starting API generation for schema: %s", schema_path)
142
139
 
143
140
  # 1. Parse and validate schema
@@ -174,10 +171,15 @@ def generate_api_from_schema(
174
171
  f.write(code)
175
172
 
176
173
  if not test_correct_output(gen_file):
177
- logger.error("Generated code validation failed for '%s'. Aborting generation.", gen_file)
174
+ logger.error(
175
+ "Generated code validation failed for '%s'. Aborting generation.", gen_file
176
+ )
178
177
  logger.info("Next steps:")
179
178
  logger.info(" 1) Review your OpenAPI schema for potential mismatches.")
180
- logger.info(" 2) Inspect '%s' for syntax or logic errors in the generated code.", gen_file)
179
+ logger.info(
180
+ " 2) Inspect '%s' for syntax or logic errors in the generated code.",
181
+ gen_file,
182
+ )
181
183
  logger.info(" 3) Correct the issues and re-run the command.")
182
184
  return {"error": "Validation failed. See logs above for detailed instructions."}
183
185
 
@@ -210,13 +212,14 @@ def generate_api_from_schema(
210
212
  client = cls()
211
213
  tools = client.list_tools()
212
214
  except Exception as e:
213
- logger.warning("Failed to instantiate '%s' or list tools: %s", class_name, e)
215
+ logger.warning(
216
+ "Failed to instantiate '%s' or list tools: %s", class_name, e
217
+ )
214
218
  else:
215
219
  logger.warning("No generated class found in module 'temp_module'")
216
220
  readme_file = generate_readme(target_dir, output_path.stem, tools)
217
221
  logger.info("README generated at: %s", readme_file)
218
222
 
219
-
220
223
  # Cleanup intermediate file
221
224
  try:
222
225
  os.remove(gen_file)
@@ -1,15 +1,16 @@
1
1
  import json
2
2
  import re
3
+ from dataclasses import dataclass
3
4
  from pathlib import Path
4
- from typing import Any, Dict, List, Literal
5
- from loguru import logger
5
+ from typing import Any, Literal
6
6
 
7
7
  import yaml
8
8
  from jinja2 import Environment, FileSystemLoader, select_autoescape
9
- from dataclasses import dataclass
9
+ from loguru import logger
10
+
10
11
 
11
12
  def convert_to_snake_case(identifier: str) -> str:
12
- """
13
+ """
13
14
  Convert a camelCase or PascalCase identifier to snake_case.
14
15
 
15
16
  Args:
@@ -140,15 +141,13 @@ def generate_api_client(schema):
140
141
  # Set up Jinja2 environment
141
142
  env = Environment(
142
143
  loader=FileSystemLoader(Path(__file__).parent.parent / "templates"),
143
- autoescape=select_autoescape()
144
+ autoescape=select_autoescape(),
144
145
  )
145
146
  template = env.get_template("api_client.py.j2")
146
147
 
147
148
  # Render the template
148
149
  class_code = template.render(
149
- class_name=class_name,
150
- base_url=base_url,
151
- methods=methods
150
+ class_name=class_name, base_url=base_url, methods=methods
152
151
  )
153
152
 
154
153
  return class_code
@@ -158,10 +157,10 @@ def generate_api_client(schema):
158
157
  class Function:
159
158
  name: str
160
159
  type: Literal["get", "post", "put", "delete", "patch", "options", "head"]
161
- args: Dict[str, str]
160
+ args: dict[str, str]
162
161
  return_type: str
163
162
  description: str
164
- tags: List[str]
163
+ tags: list[str]
165
164
  implementation: str
166
165
 
167
166
  @property
@@ -171,10 +170,7 @@ class Function:
171
170
 
172
171
 
173
172
  def generate_method_code(
174
- path: str,
175
- method: str,
176
- operation: dict[str, Any],
177
- full_schema: dict[str, Any]
173
+ path: str, method: str, operation: dict[str, Any], full_schema: dict[str, Any]
178
174
  ) -> Function:
179
175
  """
180
176
  Generate a Function object for a single API method.
@@ -205,7 +201,6 @@ def generate_method_code(
205
201
  return "dict[str, Any]"
206
202
  return "Any"
207
203
 
208
-
209
204
  # Determine function name
210
205
  if op_id := operation.get("operationId"):
211
206
  cleaned_id = op_id.replace(".", "_").replace("-", "_")
@@ -240,10 +235,12 @@ def generate_method_code(
240
235
  # Analyze requestBody
241
236
  has_body = "requestBody" in operation
242
237
  body_required = bool(has_body and operation["requestBody"].get("required"))
243
- content = (operation.get("requestBody", {}) or {}).get("content", {}) if has_body else {}
238
+ content = (
239
+ (operation.get("requestBody", {}) or {}).get("content", {}) if has_body else {}
240
+ )
244
241
  is_array_body = False
245
- request_props: Dict[str, Any] = {}
246
- required_fields: List[str] = []
242
+ request_props: dict[str, Any] = {}
243
+ required_fields: list[str] = []
247
244
  if has_body and content:
248
245
  for mime, info in content.items():
249
246
  if not mime.startswith("application/json") or "schema" not in info:
@@ -258,10 +255,12 @@ def generate_method_code(
258
255
  request_props = schema.get("properties", {}) or {}
259
256
  for name, prop_schema in list(request_props.items()):
260
257
  if pre := prop_schema.get("$ref"):
261
- request_props[name] = resolve_schema_reference(pre, full_schema) or prop_schema
258
+ request_props[name] = (
259
+ resolve_schema_reference(pre, full_schema) or prop_schema
260
+ )
262
261
 
263
262
  # Build function arguments with Annotated[type, description]
264
- arg_defs: Dict[str, str] = {}
263
+ arg_defs: dict[str, str] = {}
265
264
  for p in path_params:
266
265
  name = p["name"]
267
266
  ty = map_type(p.get("schema", {}))
@@ -304,7 +303,7 @@ def generate_method_code(
304
303
  # Assemble description
305
304
  summary = operation.get("summary", "")
306
305
  operation_desc = operation.get("description", "")
307
- desc_parts: List[str] = []
306
+ desc_parts: list[str] = []
308
307
  if summary:
309
308
  desc_parts.append(summary)
310
309
  if operation_desc:
@@ -315,25 +314,33 @@ def generate_method_code(
315
314
 
316
315
  # Generate implementation code
317
316
  implementation_lines = []
318
-
317
+
319
318
  # Add parameter validation for required fields
320
319
  for param in path_params + query_params:
321
320
  if param.get("required"):
322
321
  name = param["name"]
323
322
  implementation_lines.append(f"if {name} is None:")
324
- implementation_lines.append(f" raise ValueError(\"Missing required parameter '{name}'\")")
325
-
323
+ implementation_lines.append(
324
+ f" raise ValueError(\"Missing required parameter '{name}'\")"
325
+ )
326
+
326
327
  if has_body and body_required:
327
328
  if is_array_body:
328
329
  implementation_lines.append("if items is None:")
329
- implementation_lines.append(" raise ValueError(\"Missing required parameter 'items'\")")
330
+ implementation_lines.append(
331
+ " raise ValueError(\"Missing required parameter 'items'\")"
332
+ )
330
333
  elif request_props:
331
334
  for prop in required_fields:
332
335
  implementation_lines.append(f"if {prop} is None:")
333
- implementation_lines.append(f" raise ValueError(\"Missing required parameter '{prop}'\")")
336
+ implementation_lines.append(
337
+ f" raise ValueError(\"Missing required parameter '{prop}'\")"
338
+ )
334
339
  else:
335
340
  implementation_lines.append("if request_body is None:")
336
- implementation_lines.append(" raise ValueError(\"Missing required parameter 'request_body'\")")
341
+ implementation_lines.append(
342
+ " raise ValueError(\"Missing required parameter 'request_body'\")"
343
+ )
337
344
 
338
345
  # Build request body
339
346
  if has_body:
@@ -342,35 +349,43 @@ def generate_method_code(
342
349
  elif request_props:
343
350
  implementation_lines.append("request_body = {")
344
351
  for prop in request_props:
345
- implementation_lines.append(f" \"{prop}\": {prop},")
352
+ implementation_lines.append(f' "{prop}": {prop},')
346
353
  implementation_lines.append("}")
347
- implementation_lines.append("request_body = {k: v for k, v in request_body.items() if v is not None}")
354
+ implementation_lines.append(
355
+ "request_body = {k: v for k, v in request_body.items() if v is not None}"
356
+ )
348
357
  else:
349
358
  implementation_lines.append("request_body = request_body")
350
359
 
351
360
  # Build URL with path parameters
352
- path = "/".join([path_params["name"] for path_params in path_params]) or '\"\"'
353
- url = '\"{self.base_url}{path}\"'
354
- implementation_lines.append(f'path = {path}')
355
- implementation_lines.append(f'url = f{url}')
361
+ path = "/".join([path_params["name"] for path_params in path_params]) or '""'
362
+ url = '"{self.base_url}{path}"'
363
+ implementation_lines.append(f"path = {path}")
364
+ implementation_lines.append(f"url = f{url}")
356
365
 
357
366
  # Build query parameters
358
367
  if query_params:
359
368
  implementation_lines.append("query_params = {")
360
369
  for param in query_params:
361
370
  name = param["name"]
362
- implementation_lines.append(f" \"{name}\": {name},")
371
+ implementation_lines.append(f' "{name}": {name},')
363
372
  implementation_lines.append(" }")
364
- implementation_lines.append("query_params = {k: v for k, v in query_params.items() if v is not None}")
373
+ implementation_lines.append(
374
+ "query_params = {k: v for k, v in query_params.items() if v is not None}"
375
+ )
365
376
  else:
366
377
  implementation_lines.append("query_params = {}")
367
378
 
368
379
  # Make the request using the appropriate method
369
380
  http_method = method.lower()
370
381
  if has_body:
371
- implementation_lines.append(f"response = self._{http_method}(url, data=request_body, params=query_params)")
382
+ implementation_lines.append(
383
+ f"response = self._{http_method}(url, data=request_body, params=query_params)"
384
+ )
372
385
  else:
373
- implementation_lines.append(f"response = self._{http_method}(url, params=query_params)")
386
+ implementation_lines.append(
387
+ f"response = self._{http_method}(url, params=query_params)"
388
+ )
374
389
 
375
390
  # Handle response
376
391
  implementation_lines.append("response.raise_for_status()")
@@ -386,12 +401,13 @@ def generate_method_code(
386
401
  return_type=return_type,
387
402
  description=description_text,
388
403
  tags=tags,
389
- implementation=implementation
404
+ implementation=implementation,
390
405
  )
391
406
 
392
407
  logger.debug(f"Generated function: {function}")
393
408
  return function
394
409
 
410
+
395
411
  # Example usage
396
412
  if __name__ == "__main__":
397
413
  # Sample OpenAPI schema
@@ -1,14 +0,0 @@
1
- from langgraph.checkpoint.base import BaseCheckpointSaver
2
-
3
- from playground.memory.sqlite import get_sqlite_saver
4
-
5
-
6
- def initialize_database() -> BaseCheckpointSaver:
7
- """
8
- Initialize the appropriate database checkpointer based on configuration.
9
- Returns an initialized AsyncCheckpointer instance.
10
- """
11
- return get_sqlite_saver()
12
-
13
-
14
- __all__ = ["initialize_database"]
@@ -1,9 +0,0 @@
1
- from langgraph.checkpoint.base import BaseCheckpointSaver
2
- from langgraph.checkpoint.sqlite.aio import AsyncSqliteSaver
3
-
4
- from playground.settings import settings
5
-
6
-
7
- def get_sqlite_saver() -> BaseCheckpointSaver:
8
- """Initialize and return a SQLite saver instance."""
9
- return AsyncSqliteSaver.from_conn_string(settings.SQLITE_DB_PATH)
@@ -1,9 +1,9 @@
1
1
  from universal_mcp.config import IntegrationConfig
2
2
  from universal_mcp.integrations.integration import (
3
+ AgentRIntegration,
3
4
  ApiKeyIntegration,
4
5
  Integration,
5
6
  OAuthIntegration,
6
- AgentRIntegration,
7
7
  )
8
8
  from universal_mcp.stores.store import BaseStore
9
9