universal-mcp 0.1.24rc14__tar.gz → 0.1.24rc19__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.
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/PKG-INFO +1 -1
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/pyproject.toml +1 -1
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/tests/test_api_integration.py +2 -4
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/agentr/client.py +11 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/agentr/registry.py +40 -5
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/applications/application.py +0 -2
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/applications/sample/app.py +79 -20
- universal_mcp-0.1.24rc19/src/universal_mcp/applications/utils.py +52 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/servers/server.py +4 -3
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/tools/manager.py +0 -3
- universal_mcp-0.1.24rc19/src/universal_mcp/types.py +18 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/prompts.py +0 -2
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/testing.py +1 -1
- universal_mcp-0.1.24rc14/src/evals/dataset.py +0 -24
- universal_mcp-0.1.24rc14/src/evals/datasets/creative_writing.jsonl +0 -3
- universal_mcp-0.1.24rc14/src/evals/datasets/exact.jsonl +0 -6
- universal_mcp-0.1.24rc14/src/evals/datasets/tasks.jsonl +0 -22
- universal_mcp-0.1.24rc14/src/evals/evaluators.py +0 -40
- universal_mcp-0.1.24rc14/src/evals/run.py +0 -149
- universal_mcp-0.1.24rc14/src/evals/test.py +0 -41
- universal_mcp-0.1.24rc14/src/evals/utils.py +0 -114
- universal_mcp-0.1.24rc14/src/tests/__init__.py +0 -0
- universal_mcp-0.1.24rc14/src/tests/test_agents.py +0 -318
- universal_mcp-0.1.24rc14/src/tests/test_applications.py +0 -76
- universal_mcp-0.1.24rc14/src/universal_mcp/__init__.py +0 -0
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/__init__.py +0 -10
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/autoagent/__init__.py +0 -30
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/autoagent/__main__.py +0 -25
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/autoagent/context.py +0 -26
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/autoagent/graph.py +0 -151
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/autoagent/prompts.py +0 -9
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/autoagent/state.py +0 -27
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/autoagent/studio.py +0 -25
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/autoagent/utils.py +0 -13
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/base.py +0 -129
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool/__init__.py +0 -54
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool/__main__.py +0 -24
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool/context.py +0 -24
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool/graph.py +0 -166
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool/prompts.py +0 -31
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool/state.py +0 -27
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool2/__init__.py +0 -53
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool2/__main__.py +0 -24
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool2/agent.py +0 -11
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool2/context.py +0 -33
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool2/graph.py +0 -169
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool2/prompts.py +0 -12
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/bigtool2/state.py +0 -27
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/builder.py +0 -80
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/cli.py +0 -27
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/codeact/__init__.py +0 -243
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/codeact/sandbox.py +0 -27
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/codeact/test.py +0 -15
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/codeact/utils.py +0 -61
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/hil.py +0 -104
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/llm.py +0 -45
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/planner/__init__.py +0 -37
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/planner/__main__.py +0 -24
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/planner/graph.py +0 -82
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/planner/prompts.py +0 -1
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/planner/state.py +0 -12
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/react.py +0 -84
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/shared/agent_node.py +0 -34
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/shared/tool_node.py +0 -235
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/simple.py +0 -40
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/tools.py +0 -35
- universal_mcp-0.1.24rc14/src/universal_mcp/agents/utils.py +0 -111
- universal_mcp-0.1.24rc14/src/universal_mcp/analytics.py +0 -111
- universal_mcp-0.1.24rc14/src/universal_mcp/applications/__init__.py +0 -70
- universal_mcp-0.1.24rc14/src/universal_mcp/types.py +0 -38
- universal_mcp-0.1.24rc14/src/universal_mcp/utils/common.py +0 -278
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/.gitignore +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/LICENSE +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/README.md +0 -0
- {universal_mcp-0.1.24rc14/src/evals → universal_mcp-0.1.24rc19/src/tests}/__init__.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/tests/conftest.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/tests/test_api_generator.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/tests/test_localserver.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/tests/test_stores.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/tests/test_tool.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/tests/test_tool_manager.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/tests/test_zenquotes.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/agentr/README.md +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/agentr/__init__.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/agentr/integration.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/agentr/server.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/cli.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/client/oauth.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/client/token_store.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/client/transport.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/config.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/exceptions.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/integrations/__init__.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/integrations/integration.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/logger.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/py.typed +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/servers/__init__.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/stores/__init__.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/stores/store.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/tools/__init__.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/tools/adapters.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/tools/docstring_parser.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/tools/func_metadata.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/tools/registry.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/tools/tools.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/__init__.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/installation.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/openapi/__inti__.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/openapi/api_generator.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/openapi/api_splitter.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/openapi/cli.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/openapi/docgen.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/openapi/filters.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/openapi/openapi.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/openapi/postprocessor.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/openapi/preprocessor.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/openapi/readme.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/openapi/test_generator.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/singleton.py +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/templates/README.md.j2 +0 -0
- {universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/utils/templates/api_client.py.j2 +0 -0
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: universal-mcp
|
3
|
-
Version: 0.1.
|
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
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
4
4
|
|
5
5
|
[project]
|
6
6
|
name = "universal-mcp"
|
7
|
-
version = "0.1.24-
|
7
|
+
version = "0.1.24-rc19"
|
8
8
|
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."
|
9
9
|
readme = "README.md"
|
10
10
|
authors = [
|
@@ -1,7 +1,6 @@
|
|
1
1
|
import pytest
|
2
2
|
|
3
|
-
from universal_mcp.applications import
|
4
|
-
from universal_mcp.config import AppConfig # <-- Import the AppConfig model
|
3
|
+
from universal_mcp.applications.utils import app_from_slug
|
5
4
|
from universal_mcp.exceptions import NotAuthorizedError
|
6
5
|
from universal_mcp.integrations import ApiKeyIntegration
|
7
6
|
from universal_mcp.stores import MemoryStore
|
@@ -11,8 +10,7 @@ def test_perplexity_api_no_key():
|
|
11
10
|
store = MemoryStore()
|
12
11
|
|
13
12
|
integration = ApiKeyIntegration("PERPLEXITY", store=store)
|
14
|
-
|
15
|
-
PerplexityApp = app_from_config(perplexity_app_config)
|
13
|
+
PerplexityApp = app_from_slug("perplexity")
|
16
14
|
|
17
15
|
app = PerplexityApp(integration=integration)
|
18
16
|
|
@@ -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
|
-
from universal_mcp.applications import app_from_slug
|
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: 
|
19
|
+
- Example: 
|
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."""
|
@@ -133,7 +152,7 @@ class AgentrRegistry(ToolRegistry):
|
|
133
152
|
try:
|
134
153
|
# Clear tools from tool manager before loading new tools
|
135
154
|
self.tool_manager.clear_tools()
|
136
|
-
if isinstance(tools,
|
155
|
+
if isinstance(tools, dict):
|
137
156
|
logger.info("Loading tools from tool config")
|
138
157
|
self._load_tools_from_tool_config(tools, self.tool_manager)
|
139
158
|
else:
|
@@ -176,12 +195,28 @@ class AgentrRegistry(ToolRegistry):
|
|
176
195
|
tool_config: The tool configuration containing app names and tools
|
177
196
|
tool_manager: The tool manager to register tools with
|
178
197
|
"""
|
179
|
-
for app_name,
|
180
|
-
self._load_tools(app_name,
|
198
|
+
for app_name, tool_names in tool_config.items():
|
199
|
+
self._load_tools(app_name, tool_names, tool_manager)
|
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
|
-
|
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."""
|
{universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/applications/application.py
RENAMED
@@ -9,7 +9,6 @@ from gql.transport.requests import RequestsHTTPTransport
|
|
9
9
|
from graphql import DocumentNode
|
10
10
|
from loguru import logger
|
11
11
|
|
12
|
-
from universal_mcp.analytics import analytics
|
13
12
|
from universal_mcp.integrations.integration import Integration
|
14
13
|
|
15
14
|
|
@@ -37,7 +36,6 @@ class BaseApplication(ABC):
|
|
37
36
|
"""
|
38
37
|
self.name = name
|
39
38
|
logger.debug(f"Initializing Application '{name}' with kwargs: {kwargs}")
|
40
|
-
analytics.track_app_loaded(name) # Track app loading
|
41
39
|
|
42
40
|
@abstractmethod
|
43
41
|
def list_tools(self) -> list[Callable]:
|
{universal_mcp-0.1.24rc14 → universal_mcp-0.1.24rc19}/src/universal_mcp/applications/sample/app.py
RENAMED
@@ -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
|
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="
|
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
|
48
|
-
"""
|
54
|
+
def read_file(self, filename: str):
|
55
|
+
"""Read content from a file.
|
49
56
|
|
50
57
|
Args:
|
51
|
-
|
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
|
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
|
-
|
60
|
-
|
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
|
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.
|
300
|
+
self.read_file,
|
301
|
+
self.write_file,
|
244
302
|
self.get_simple_weather,
|
303
|
+
self.generate_image,
|
245
304
|
]
|
@@ -0,0 +1,52 @@
|
|
1
|
+
import importlib
|
2
|
+
|
3
|
+
from loguru import logger
|
4
|
+
|
5
|
+
from universal_mcp.applications.application import BaseApplication
|
6
|
+
|
7
|
+
# --- Default Name Generators ---
|
8
|
+
|
9
|
+
|
10
|
+
def get_default_package_name(slug: str) -> str:
|
11
|
+
"""
|
12
|
+
Convert a repository slug to a package name.
|
13
|
+
"""
|
14
|
+
slug = slug.strip().lower().replace("-", "_")
|
15
|
+
package_name = f"universal_mcp.applications.{slug}"
|
16
|
+
return package_name
|
17
|
+
|
18
|
+
|
19
|
+
def get_default_module_path(slug: str) -> str:
|
20
|
+
"""
|
21
|
+
Convert a repository slug to a module path.
|
22
|
+
"""
|
23
|
+
package_name = get_default_package_name(slug)
|
24
|
+
module_path = f"{package_name}.app"
|
25
|
+
return module_path
|
26
|
+
|
27
|
+
|
28
|
+
def get_default_class_name(slug: str) -> str:
|
29
|
+
"""
|
30
|
+
Convert a repository slug to a class name.
|
31
|
+
"""
|
32
|
+
slug = slug.strip().lower()
|
33
|
+
parts = slug.replace("-", "_").split("_")
|
34
|
+
class_name = "".join(part.capitalize() for part in parts) + "App"
|
35
|
+
return class_name
|
36
|
+
|
37
|
+
|
38
|
+
# --- Application Loaders ---
|
39
|
+
|
40
|
+
|
41
|
+
def app_from_slug(slug: str) -> type[BaseApplication]:
|
42
|
+
"""Loads an application from a slug."""
|
43
|
+
return load_app_from_package(slug)
|
44
|
+
|
45
|
+
|
46
|
+
def load_app_from_package(slug: str) -> type[BaseApplication]:
|
47
|
+
"""Loads an application from a pip-installable package."""
|
48
|
+
logger.debug(f"Loading '{slug}' as a package.")
|
49
|
+
module_path_str = get_default_module_path(slug)
|
50
|
+
class_name_str = get_default_class_name(slug)
|
51
|
+
module = importlib.import_module(module_path_str)
|
52
|
+
return getattr(module, class_name_str)
|
@@ -5,7 +5,8 @@ from loguru import logger
|
|
5
5
|
from mcp.server.fastmcp import FastMCP
|
6
6
|
from mcp.types import TextContent
|
7
7
|
|
8
|
-
from universal_mcp.applications import BaseApplication
|
8
|
+
from universal_mcp.applications.application import BaseApplication
|
9
|
+
from universal_mcp.applications.utils import app_from_slug
|
9
10
|
from universal_mcp.config import ServerConfig
|
10
11
|
from universal_mcp.exceptions import ConfigurationError, ToolError
|
11
12
|
from universal_mcp.integrations.integration import ApiKeyIntegration, OAuthIntegration
|
@@ -44,8 +45,8 @@ def load_from_local_config(config: ServerConfig, tool_manager: ToolManager) -> N
|
|
44
45
|
integration = OAuthIntegration(config.name, store=store, **app_config.integration.credentials)
|
45
46
|
else:
|
46
47
|
raise ValueError(f"Unsupported integration type: {app_config.integration.type}")
|
47
|
-
app =
|
48
|
-
tool_manager.register_tools_from_app(app, app_config.actions)
|
48
|
+
app = app_from_slug(app_config.name)(integration=integration)
|
49
|
+
tool_manager.register_tools_from_app(app, tool_names=app_config.actions)
|
49
50
|
logger.info(f"Loaded app: {app_config.name}")
|
50
51
|
except Exception as e:
|
51
52
|
logger.error(f"Failed to load app {app_config.name}: {e}", exc_info=True)
|
@@ -3,7 +3,6 @@ from typing import Any
|
|
3
3
|
|
4
4
|
from loguru import logger
|
5
5
|
|
6
|
-
from universal_mcp.analytics import analytics
|
7
6
|
from universal_mcp.applications.application import BaseApplication
|
8
7
|
from universal_mcp.exceptions import ToolNotFoundError
|
9
8
|
from universal_mcp.tools.adapters import (
|
@@ -298,8 +297,6 @@ class ToolManager:
|
|
298
297
|
raise ToolNotFoundError(f"Unknown tool: {name}")
|
299
298
|
try:
|
300
299
|
result = await tool.run(arguments, context)
|
301
|
-
analytics.track_tool_called(name, app_name, "success")
|
302
300
|
return result
|
303
301
|
except Exception as e:
|
304
|
-
analytics.track_tool_called(name, app_name, "error", str(e))
|
305
302
|
raise e
|
@@ -0,0 +1,18 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
|
3
|
+
# Constants
|
4
|
+
DEFAULT_IMPORTANT_TAG = "important"
|
5
|
+
TOOL_NAME_SEPARATOR = "__"
|
6
|
+
DEFAULT_APP_NAME = "common"
|
7
|
+
|
8
|
+
|
9
|
+
class ToolFormat(str, Enum):
|
10
|
+
"""Supported tool formats."""
|
11
|
+
|
12
|
+
NATIVE = "native"
|
13
|
+
MCP = "mcp"
|
14
|
+
LANGCHAIN = "langchain"
|
15
|
+
OPENAI = "openai"
|
16
|
+
|
17
|
+
|
18
|
+
ToolConfig = dict[str, list[str]]
|
@@ -229,7 +229,6 @@ from loguru import logger
|
|
229
229
|
|
230
230
|
import httpx
|
231
231
|
|
232
|
-
from universal_mcp.analytics import analytics
|
233
232
|
from universal_mcp.integrations import Integration
|
234
233
|
|
235
234
|
class BaseApplication(ABC):
|
@@ -256,7 +255,6 @@ class BaseApplication(ABC):
|
|
256
255
|
"""
|
257
256
|
self.name = name
|
258
257
|
logger.debug(f"Initializing Application '{name}' with kwargs: {kwargs}")
|
259
|
-
analytics.track_app_loaded(name) # Track app loading
|
260
258
|
|
261
259
|
@abstractmethod
|
262
260
|
def list_tools(self) -> list[Callable]:
|
@@ -8,7 +8,7 @@ from pydantic import BaseModel, SecretStr
|
|
8
8
|
|
9
9
|
from universal_mcp.agentr import AgentrIntegration
|
10
10
|
from universal_mcp.agentr.client import AgentrClient
|
11
|
-
from universal_mcp.applications import APIApplication, BaseApplication
|
11
|
+
from universal_mcp.applications.application import APIApplication, BaseApplication
|
12
12
|
from universal_mcp.tools import Tool, ToolManager
|
13
13
|
from universal_mcp.types import ToolFormat
|
14
14
|
|
@@ -1,24 +0,0 @@
|
|
1
|
-
import csv
|
2
|
-
import json
|
3
|
-
from typing import Any
|
4
|
-
|
5
|
-
|
6
|
-
def load_dataset(file_path: str) -> list[dict[str, Any]]:
|
7
|
-
"""
|
8
|
-
Loads a dataset from a CSV or JSONL file.
|
9
|
-
|
10
|
-
Args:
|
11
|
-
file_path: The path to the dataset file.
|
12
|
-
|
13
|
-
Returns:
|
14
|
-
A list of dictionaries, where each dictionary represents an example.
|
15
|
-
"""
|
16
|
-
if file_path.endswith(".csv"):
|
17
|
-
with open(file_path, encoding="utf-8") as f:
|
18
|
-
reader = csv.DictReader(f)
|
19
|
-
return list(reader)
|
20
|
-
elif file_path.endswith(".jsonl"):
|
21
|
-
with open(file_path, encoding="utf-8") as f:
|
22
|
-
return [json.loads(line) for line in f]
|
23
|
-
else:
|
24
|
-
raise ValueError("Unsupported file format. Please use CSV or JSONL.")
|
@@ -1,6 +0,0 @@
|
|
1
|
-
{"user_input": "What is 2 + 2?", "expected_output": "4"}
|
2
|
-
{"user_input": "What is the opposite of 'hot'?", "expected_output": "cold"}
|
3
|
-
{"user_input": "Who is the first president of the United States?", "expected_output": "George Washington"}
|
4
|
-
{"user_input": "What is the capital of France?", "expected_output": "Paris"}
|
5
|
-
{"user_input": "Who wrote 'To Kill a Mockingbird'?", "expected_output": "Harper Lee"}
|
6
|
-
{"user_input": "What is the boiling point of water at sea level in Celsius?", "expected_output": "100"}
|
@@ -1,22 +0,0 @@
|
|
1
|
-
{"user_input": "Send an email to manoj@agentr.dev from my Gmail account", "difficulty": 1, "expected_tools": {"agentrServers": {"google-mail": {"tools": ["send_email"]}}}
|
2
|
-
{"user_input": "Show me events from today's Google Calendar.", "difficulty": 1, "expected_tools": {"agentrServers": {"google-calendar": {"tools": ["list_events"]}}}}
|
3
|
-
{"user_input": "Create a Google Doc summarizing the last 5 merged pull requests in my GitHub repo- universal-mcp/universal-mcp, including links and commit highlights.", "difficulty": 4, "expected_tools": {"agentrServers": {"github": {"tools": ["get_pull_request"]}, "google-docs": {"tools": ["create_document"]}}}}
|
4
|
-
{"user_input": "Summarize the key insights from all marketing emails received this week from my Gmail and add a section in a Google Doc with action points.", "difficulty": 4, "expected_tools": {"agentrServers": {"google-gemini": {"tools": ["prompt_document"]}, "google-docs": {"tools": ["style_text"]}}}}
|
5
|
-
{"user_input": "Create a Google Sheet of the best cafes and restaurants near IIT Bombay", "difficulty": 3, "expected_tools": {"agentrServers": {"google-sheet": {"tools": ["create_spreadsheet"]}, "serpapi": {"tools": ["google_maps_search"]}}}}
|
6
|
-
{"user_input": "Track the top posts in r/startups over the past 7 days using Reddit and create a trend report on what's being discussed most (e.g., hiring, funding, MVPs) in a Google Doc.", "difficulty": 5, "expected_tools": {"agentrServers": {"reddit": {"tools": ["search"]}, "google-docs": {"tools": ["add_content", "create_footer"]}}}}
|
7
|
-
{"user_input": "Find the best restaurants in Goa using perplexity web search", "difficulty": 2, "expected_tools": {"agentrServers": {}}}
|
8
|
-
{"user_input": "List the unread emails from the last 24 hours from my Gmail, sorted by sender.", "difficulty": 2, "expected_tools": {"agentrServers": {"google-mail": {"tools": ["list_messages"]}}}}
|
9
|
-
{"user_input": "Tell me how many meetings I have tomorrow and when they start from my Google Calendar.", "difficulty": 1, "expected_tools": {"agentrServers": {"google-calendar": {"tools": ["get_today_events"]}}}}
|
10
|
-
{"user_input": "Create a meeting with aditakarsh@example.com on the topic of the latest trends in AI at 8PM today using Google Calendar.", "difficulty": 2, "expected_tools": {"agentrServers": {"google-calendar": {"tools": ["add_an_event"]}}}}
|
11
|
-
{"user_input": "What are the topics of my meetings today from Google Calendar and who are the attendees? Give a 1-line context for each attendee using LinkedIn or web search.", "difficulty": 4, "expected_tools": {"agentrServers": {"google-calendar": {"tools": ["list_events"]}, "serpapi": {"tools": ["search"]}, "perplexity": {"tools": ["chat"]}, "tavily": {"tools": ["search"]}}}}
|
12
|
-
{"user_input": "Fetch my last inbox mail from Microsoft Outlook", "difficulty": 1, "expected_tools": {"agentrServers": {"outlook": {"tools": ["user_get_mail_folder"]}}}}
|
13
|
-
{"user_input": "Fetch unsubscribe links from my Gmail inbox for promo emails I have received in the last 7 days", "difficulty": 3, "expected_tools": {"agentrServers": {"google-mail": {"tools": ["delete_filters", "list_filters"]}}}}
|
14
|
-
{"user_input": "Fetch all unread emails from Gmail and new tickets from ClickUp for me from last night", "difficulty": 4, "expected_tools": {"agentrServers": {"clickup": {"tools": ["tasks_filter_team_tasks"]}}}}
|
15
|
-
{"user_input": "Give me a report on the earnings of Oklo using web search, and projections for the company revenue, stock price", "difficulty": 4, "expected_tools": {"agentrServers": {"serpapi": {"tools": ["search"]}, "perplexity": {"tools": ["chat"]}, "tavily": {"tools": ["search"]}}}}
|
16
|
-
{"user_input": "Create a weekly expense report from my credit card transactions and categorize spending by type (food, transport, entertainment, etc.) in a Google Sheet", "difficulty": 3, "expected_tools": {"agentrServers": {"google-sheet": {"tools": ["create_spreadsheet"]}}}}
|
17
|
-
{"user_input": "Generate a comparison table of SaaS tools for project management using web search, including pricing, features, and user ratings in a Google Sheet", "difficulty": 4, "expected_tools": {"agentrServers": {"serpapi": {"tools": ["search"]}, "google-sheet": {"tools": ["add_table", "list_tables"]}}}}
|
18
|
-
{"user_input": "Research the top 10 Y Combinator startups from the latest batch using web search and create a report on their industries and funding status in Google Docs", "difficulty": 5, "expected_tools": {"agentrServers": {"google-docs": {"tools": ["create_footer", "create_header"]}}}}
|
19
|
-
{"user_input": "Find and summarize the key takeaways from the latest earnings calls of FAANG companies using web search and create a report in Google Docs", "difficulty": 5, "expected_tools": {"agentrServers": {"serpapi": {"tools": ["search"]}, "google-docs": {"tools": ["style_text", "create_footer"]}}}}
|
20
|
-
{"user_input": "Draft personalized LinkedIn outreach messages for 10 potential collaborators in the fintech space based on their recent posts using LinkedIn data in a Google Sheet", "difficulty": 5, "expected_tools": {"agentrServers": {}}}
|
21
|
-
{"user_input": "Monitor my Twitter mentions and DMs from the past 48 hours and create a response priority list in Google Sheets", "difficulty": 4, "expected_tools": {"agentrServers": {"twitter": {"tools": ["search_stream"]}, "google-sheet": {"tools": ["add_sheet", "add_table"]}}}}
|
22
|
-
{"user_input": "Create a content calendar for next month with trending AI/ML topics using web search and optimal posting times based on my audience analytics in Google Sheets", "difficulty": 5, "expected_tools": {"agentrServers": {"google-calendar": {"tools": ["add_an_event"]}, "google-sheet": {"tools": ["append_values"]}, "serpapi": {"tools": ["search"]}, "perplexity": {"tools": ["chat"]}, "tavily": {"tools": ["search"]}}}}
|
@@ -1,40 +0,0 @@
|
|
1
|
-
from langsmith.evaluation import EvaluationResult, run_evaluator
|
2
|
-
from langsmith.schemas import Example, Run
|
3
|
-
from openevals.llm import create_llm_as_judge
|
4
|
-
from openevals.prompts import CORRECTNESS_PROMPT
|
5
|
-
|
6
|
-
|
7
|
-
@run_evaluator
|
8
|
-
def exact_match_evaluator(run: Run, example: Example | None = None) -> EvaluationResult:
|
9
|
-
"""
|
10
|
-
A simple evaluator that checks for exact match between the agent's output
|
11
|
-
and the expected output from the dataset.
|
12
|
-
"""
|
13
|
-
if example is None or "expected_output" not in example.outputs:
|
14
|
-
return EvaluationResult(key="exact_match", score=0, comment="No expected output provided.")
|
15
|
-
|
16
|
-
# The agent's response might be in a list of messages
|
17
|
-
agent_response_raw = run.outputs.get("output", "")
|
18
|
-
if isinstance(agent_response_raw, list):
|
19
|
-
# Extract text from the last dictionary in the list
|
20
|
-
agent_response = agent_response_raw[-1].get("text", "").strip() if agent_response_raw else ""
|
21
|
-
else:
|
22
|
-
agent_response = str(agent_response_raw).strip()
|
23
|
-
|
24
|
-
expected_output = example.outputs["expected_output"].strip()
|
25
|
-
|
26
|
-
if agent_response == expected_output:
|
27
|
-
score = 1
|
28
|
-
comment = "Exact match."
|
29
|
-
else:
|
30
|
-
score = 0
|
31
|
-
comment = f"Mismatch: Expected '{expected_output}', but got '{agent_response}'."
|
32
|
-
|
33
|
-
return EvaluationResult(key="exact_match", score=score, comment=comment)
|
34
|
-
|
35
|
-
|
36
|
-
correctness_evaluator = create_llm_as_judge(
|
37
|
-
prompt=CORRECTNESS_PROMPT,
|
38
|
-
feedback_key="correctness",
|
39
|
-
model="anthropic:claude-4-sonnet-20250514",
|
40
|
-
)
|