universal-mcp 0.1.24rc17__py3-none-any.whl → 0.1.24rc19__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.
@@ -1,3 +1,4 @@
1
+ import io
1
2
  import os
2
3
  from typing import Any
3
4
 
@@ -25,6 +26,7 @@ class AgentrClient:
25
26
  base_url = base_url or os.getenv("AGENTR_BASE_URL", "https://api.agentr.dev")
26
27
  self.base_url = f"{base_url.rstrip('/')}/v1"
27
28
  api_key = api_key or os.getenv("AGENTR_API_KEY")
29
+ self.user_id = None
28
30
  if api_key:
29
31
  self.client = httpx.Client(
30
32
  base_url=self.base_url,
@@ -34,6 +36,7 @@ class AgentrClient:
34
36
  verify=False,
35
37
  )
36
38
  me_data = self.me()
39
+ self.user_id = me_data["id"]
37
40
  logger.debug(f"Client initialized with user: {me_data['email']}")
38
41
  elif auth_token:
39
42
  logger.debug("Initializing client with auth token")
@@ -45,6 +48,7 @@ class AgentrClient:
45
48
  verify=False,
46
49
  )
47
50
  me_data = self.me()
51
+ self.user_id = me_data["id"]
48
52
  logger.debug(f"Client initialized with user: {me_data['email']}")
49
53
  else:
50
54
  raise ValueError("No API key or auth token provided")
@@ -207,3 +211,10 @@ class AgentrClient:
207
211
  response = self.client.get("/tools/", params=params)
208
212
  response.raise_for_status()
209
213
  return response.json().get("items", [])
214
+
215
+ def _upload_file(self, file_name: str, mime_type: str, base64_data: str) -> str:
216
+ """Upload a file to the server."""
217
+ files = {"file": (file_name, io.BytesIO(base64_data), mime_type)}
218
+ reponse = self.client.post("/files/upload", files=files)
219
+ reponse.raise_for_status()
220
+ return reponse.json()
@@ -1,15 +1,34 @@
1
+ import base64
1
2
  from typing import Any
2
3
 
3
4
  from loguru import logger
4
5
 
5
6
  from universal_mcp.agentr.client import AgentrClient
6
7
  from universal_mcp.applications.utils import app_from_slug
8
+ from universal_mcp.exceptions import ToolError
7
9
  from universal_mcp.tools.manager import ToolManager, _get_app_and_tool_name
8
10
  from universal_mcp.tools.registry import ToolRegistry
9
11
  from universal_mcp.types import ToolConfig, ToolFormat
10
12
 
11
13
  from .integration import AgentrIntegration
12
14
 
15
+ MARKDOWN_INSTRUCTIONS = """Always render the URL in markdown format for images and media files. Here are examples:
16
+ The url is provided in the response as "signed_url".
17
+ For images:
18
+ - Use markdown image syntax: ![alt text](url)
19
+ - Example: ![Generated sunset image](https://example.com/image.png)
20
+ - Always include descriptive alt text that explains what the image shows
21
+
22
+ For audio files:
23
+ - Use markdown link syntax with audio description: [🔊 Audio file description](url)
24
+ - Example: [🔊 Generated speech audio](https://example.com/audio.mp3)
25
+
26
+ For other media:
27
+ - Use descriptive link text: [📄 File description](url)
28
+ - Example: [📄 Generated document](https://example.com/document.pdf)
29
+
30
+ Always make the links clickable and include relevant context about what the user will see or hear when they access the URL."""
31
+
13
32
 
14
33
  class AgentrRegistry(ToolRegistry):
15
34
  """Platform manager implementation for AgentR platform."""
@@ -181,7 +200,23 @@ class AgentrRegistry(ToolRegistry):
181
200
 
182
201
  async def call_tool(self, tool_name: str, tool_args: dict[str, Any]) -> dict[str, Any]:
183
202
  """Call a tool with the given name and arguments."""
184
- return await self.tool_manager.call_tool(tool_name, tool_args)
203
+ data = await self.tool_manager.call_tool(tool_name, tool_args)
204
+ logger.debug(f"Tool {tool_name} called with args {tool_args} and returned {data}")
205
+ if isinstance(data, dict):
206
+ type_ = data.get("type")
207
+ if type_ == "image" or type_ == "audio":
208
+ # Special handling for images and audio
209
+ base64_data = data.get("data")
210
+ mime_type = data.get("mime_type")
211
+ file_name = data.get("file_name")
212
+ if not mime_type or not file_name:
213
+ raise ToolError("Mime type or file name is missing")
214
+ bytes_data = base64.b64decode(base64_data)
215
+ response = self.client._upload_file(file_name, mime_type, bytes_data)
216
+ # Hard code instructions for llm
217
+ response = {**response, "instructions": MARKDOWN_INSTRUCTIONS}
218
+ return response
219
+ return data
185
220
 
186
221
  async def list_connected_apps(self) -> list[str]:
187
222
  """List all apps that the user has connected."""
@@ -1,22 +1,25 @@
1
1
  import datetime
2
2
 
3
3
  import httpx
4
+ from loguru import logger
4
5
 
5
6
  from universal_mcp.applications.application import BaseApplication
6
7
 
7
8
 
8
- class SampleToolApp(BaseApplication):
9
+ class SampleApp(BaseApplication):
9
10
  """A sample application providing basic utility tools."""
10
11
 
11
- def __init__(self):
12
+ def __init__(self, **kwargs):
12
13
  """Initializes the SampleToolApp with the name 'sample_tool_app'."""
13
- super().__init__(name="sample_tool_app")
14
+ super().__init__(name="sample")
14
15
 
15
16
  def get_current_time(self):
16
17
  """Get the current system time as a formatted string.
17
18
 
18
19
  Returns:
19
20
  str: The current time in the format 'YYYY-MM-DD HH:MM:SS'.
21
+ Tags:
22
+ time, date, current, system, utility, important
20
23
  """
21
24
  return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
22
25
 
@@ -25,6 +28,8 @@ class SampleToolApp(BaseApplication):
25
28
 
26
29
  Returns:
27
30
  str: The current date in the format 'YYYY-MM-DD'.
31
+ Tags:
32
+ time, date, current, system, utility, important
28
33
  """
29
34
  return datetime.datetime.now().strftime("%Y-%m-%d")
30
35
 
@@ -36,6 +41,8 @@ class SampleToolApp(BaseApplication):
36
41
 
37
42
  Returns:
38
43
  str: The result of the calculation, or an error message if evaluation fails.
44
+ Tags:
45
+ math, calculation, utility, important
39
46
  """
40
47
  try:
41
48
  # Safe evaluation of mathematical expressions
@@ -44,29 +51,41 @@ class SampleToolApp(BaseApplication):
44
51
  except Exception as e:
45
52
  return f"Error in calculation: {str(e)}"
46
53
 
47
- def file_operations(self, operation: str, filename: str, content: str = ""):
48
- """Perform file read or write operations.
54
+ def read_file(self, filename: str):
55
+ """Read content from a file.
49
56
 
50
57
  Args:
51
- operation (str): The operation to perform, either 'read' or 'write'.
52
- filename (str): The name of the file to operate on.
53
- content (str, optional): The content to write to the file (used only for 'write'). Defaults to "".
58
+ filename (str): The name of the file to read from.
54
59
 
55
60
  Returns:
56
- str: The result of the file operation, or an error message if the operation fails.
61
+ str: The content of the file, or an error message if the operation fails.
62
+ Tags:
63
+ file, read, utility, important
57
64
  """
58
65
  try:
59
- if operation == "read":
60
- with open(filename) as f:
61
- return f"File content:\n{f.read()}"
62
- elif operation == "write":
63
- with open(filename, "w") as f:
64
- f.write(content)
65
- return f"Successfully wrote to {filename}"
66
- else:
67
- return "Invalid operation. Use 'read' or 'write'"
66
+ with open(filename) as f:
67
+ return f"File content:\n{f.read()}"
68
68
  except Exception as e:
69
- return f"File operation error: {str(e)}"
69
+ return f"File read error: {str(e)}"
70
+
71
+ def write_file(self, filename: str, content: str):
72
+ """Write content to a file.
73
+
74
+ Args:
75
+ filename (str): The name of the file to write to.
76
+ content (str): The content to write to the file.
77
+
78
+ Returns:
79
+ str: Success message or an error message if the operation fails.
80
+ Tags:
81
+ file, write, utility, important
82
+ """
83
+ try:
84
+ with open(filename, "w") as f:
85
+ f.write(content)
86
+ return f"Successfully wrote to {filename}"
87
+ except Exception as e:
88
+ return f"File write error: {str(e)}"
70
89
 
71
90
  def get_weather(
72
91
  self,
@@ -230,6 +249,44 @@ class SampleToolApp(BaseApplication):
230
249
  except Exception as e:
231
250
  return {"error": str(e)}
232
251
 
252
+ def generate_image(self, prompt: str):
253
+ """Generate an image based on a prompt.
254
+ A
255
+ Args:
256
+ prompt (str): The prompt to generate an image from.
257
+
258
+ Returns:
259
+ dict: The generated image.
260
+ Tags:
261
+ image, generate, utility, important
262
+ """
263
+ import base64
264
+ import io
265
+
266
+ from PIL import Image, ImageDraw, ImageFont
267
+
268
+ # Create a simple placeholder image
269
+ img = Image.new("RGB", (600, 400), color="lightblue")
270
+ draw = ImageDraw.Draw(img)
271
+
272
+ # Add text to the image
273
+ try:
274
+ # Try to use a default font
275
+ font = ImageFont.load_default()
276
+ except Exception as e:
277
+ logger.error(f"Error loading font: {e}")
278
+ font = None
279
+
280
+ text = f"Generated: {prompt[:50]}..."
281
+ draw.text((50, 200), text, fill="black", font=font)
282
+
283
+ # Convert to base64
284
+ buffer = io.BytesIO()
285
+ img.save(buffer, format="PNG")
286
+ img_base64 = base64.b64encode(buffer.getvalue()).decode("utf-8")
287
+
288
+ return {"type": "image", "data": img_base64, "mime_type": "image/png", "file_name": "sample.png"}
289
+
233
290
  def list_tools(self):
234
291
  """List all available tool methods in this application.
235
292
 
@@ -240,6 +297,8 @@ class SampleToolApp(BaseApplication):
240
297
  self.get_current_time,
241
298
  self.get_current_date,
242
299
  self.calculate,
243
- self.file_operations,
300
+ self.read_file,
301
+ self.write_file,
244
302
  self.get_simple_weather,
303
+ self.generate_image,
245
304
  ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp
3
- Version: 0.1.24rc17
3
+ Version: 0.1.24rc19
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
@@ -32,7 +32,6 @@ Requires-Dist: pyyaml>=6.0.2
32
32
  Requires-Dist: rich>=14.0.0
33
33
  Requires-Dist: streamlit>=1.46.1
34
34
  Requires-Dist: typer>=0.15.2
35
- Requires-Dist: universal-mcp-applications>=0.1.2
36
35
  Provides-Extra: dev
37
36
  Requires-Dist: litellm>=1.30.7; extra == 'dev'
38
37
  Requires-Dist: pre-commit>=4.2.0; extra == 'dev'
@@ -6,13 +6,13 @@ universal_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
6
6
  universal_mcp/types.py,sha256=DAzYyS7Zqdi38SpnhdOm08_JeN2CJiC0hDIpLLIhlSw,316
7
7
  universal_mcp/agentr/README.md,sha256=t15pVgkCwZM5wzgLgrf0Zv6hVL7dPmKXvAeTf8CiXPQ,6641
8
8
  universal_mcp/agentr/__init__.py,sha256=fv1ZnOCduIUiJ9oN4e6Ya_hA2oWQvcEuDU3Ek1vEufI,180
9
- universal_mcp/agentr/client.py,sha256=TQgwrNc7dEMXuprELf0Q-fdYdrH92Ppd7PUDZoD-KcY,7429
9
+ universal_mcp/agentr/client.py,sha256=CwfmOAgWsApm0buxZPni3vW6MFgrwdddXsXmQA5UYrA,7882
10
10
  universal_mcp/agentr/integration.py,sha256=V5GjqocqS02tRoI8MeV9PL6m-BzejwBzgJhOHo4MxAE,4212
11
- universal_mcp/agentr/registry.py,sha256=6KFTv45p_crqRqpdcwj1ksaF41PKXzgYIACRxsLaj3o,6909
11
+ universal_mcp/agentr/registry.py,sha256=cWkg9cUEmksWqeuh4xazFf1WtRQ3lEZf1s53hqTlmG4,8657
12
12
  universal_mcp/agentr/server.py,sha256=bIPmHMiKKwnUYnxmfZVRh1thcn7Rytm_-bNiXTfANzc,2098
13
13
  universal_mcp/applications/application.py,sha256=do45GC0jnNepZHL8US2yDNq9s0ZnE6bII4Xbw90GRTc,23727
14
14
  universal_mcp/applications/utils.py,sha256=8Pp9lZU6IPt9z9BnuJ-vpv-NGuzryt1c4e4-ShDd2XI,1450
15
- universal_mcp/applications/sample/app.py,sha256=E0JwaWD7qytwawb_iWc1pBnJ-Te7MMtab4MxOOebLdc,8972
15
+ universal_mcp/applications/sample/app.py,sha256=D9zPezC13xXVMlfO2A0fHgJQD_I-bnpf9UOOHveHMek,10537
16
16
  universal_mcp/client/oauth.py,sha256=O00zOUfQxINaruFU2zt-64DIR1_mAqrY8ykLQo-teJU,8679
17
17
  universal_mcp/client/token_store.py,sha256=6VAzjzJG49wYvmEDqksFvb-fVqdjHIKWv7yYyh_AuF8,3912
18
18
  universal_mcp/client/transport.py,sha256=qqtb0ky6yvLBxsaA9-oFU0v9MwfcQb4eomq-O2fmwtQ,11850
@@ -47,8 +47,8 @@ universal_mcp/utils/openapi/readme.py,sha256=R2Jp7DUXYNsXPDV6eFTkLiy7MXbSULUj1vH
47
47
  universal_mcp/utils/openapi/test_generator.py,sha256=vucBh9klWmQOUA740TFwfM9ry2nkwKWQiNRcsiZ9HbY,12229
48
48
  universal_mcp/utils/templates/README.md.j2,sha256=Mrm181YX-o_-WEfKs01Bi2RJy43rBiq2j6fTtbWgbTA,401
49
49
  universal_mcp/utils/templates/api_client.py.j2,sha256=972Im7LNUAq3yZTfwDcgivnb-b8u6_JLKWXwoIwXXXQ,908
50
- universal_mcp-0.1.24rc17.dist-info/METADATA,sha256=Z_twD5L03occBIYlR1zfsz-hvETI6HRvDqaNv4vuD5Y,3304
51
- universal_mcp-0.1.24rc17.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
52
- universal_mcp-0.1.24rc17.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
53
- universal_mcp-0.1.24rc17.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
54
- universal_mcp-0.1.24rc17.dist-info/RECORD,,
50
+ universal_mcp-0.1.24rc19.dist-info/METADATA,sha256=zKP0JF9Lp3hnTW-8ZvLutdDpGEcqAishzXtrICY3a4Y,3255
51
+ universal_mcp-0.1.24rc19.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
52
+ universal_mcp-0.1.24rc19.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
53
+ universal_mcp-0.1.24rc19.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
54
+ universal_mcp-0.1.24rc19.dist-info/RECORD,,