universal-mcp 0.1.24rc13__py3-none-any.whl → 0.1.24rc17__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- universal_mcp/agentr/registry.py +4 -4
- universal_mcp/applications/application.py +0 -2
- universal_mcp/applications/utils.py +52 -0
- universal_mcp/servers/server.py +4 -3
- universal_mcp/tools/manager.py +0 -3
- universal_mcp/types.py +1 -21
- universal_mcp/utils/prompts.py +0 -2
- universal_mcp/utils/testing.py +1 -1
- {universal_mcp-0.1.24rc13.dist-info → universal_mcp-0.1.24rc17.dist-info}/METADATA +2 -1
- universal_mcp-0.1.24rc17.dist-info/RECORD +54 -0
- universal_mcp/__init__.py +0 -0
- universal_mcp/agents/__init__.py +0 -9
- universal_mcp/agents/autoagent/__init__.py +0 -30
- universal_mcp/agents/autoagent/__main__.py +0 -25
- universal_mcp/agents/autoagent/context.py +0 -26
- universal_mcp/agents/autoagent/graph.py +0 -151
- universal_mcp/agents/autoagent/prompts.py +0 -9
- universal_mcp/agents/autoagent/state.py +0 -27
- universal_mcp/agents/autoagent/studio.py +0 -25
- universal_mcp/agents/autoagent/utils.py +0 -13
- universal_mcp/agents/base.py +0 -129
- universal_mcp/agents/bigtool/__init__.py +0 -54
- universal_mcp/agents/bigtool/__main__.py +0 -24
- universal_mcp/agents/bigtool/context.py +0 -24
- universal_mcp/agents/bigtool/graph.py +0 -166
- universal_mcp/agents/bigtool/prompts.py +0 -31
- universal_mcp/agents/bigtool/state.py +0 -27
- universal_mcp/agents/builder.py +0 -80
- universal_mcp/agents/cli.py +0 -27
- universal_mcp/agents/codeact/__init__.py +0 -243
- universal_mcp/agents/codeact/sandbox.py +0 -27
- universal_mcp/agents/codeact/test.py +0 -15
- universal_mcp/agents/codeact/utils.py +0 -61
- universal_mcp/agents/hil.py +0 -104
- universal_mcp/agents/llm.py +0 -45
- universal_mcp/agents/planner/__init__.py +0 -37
- universal_mcp/agents/planner/__main__.py +0 -24
- universal_mcp/agents/planner/graph.py +0 -82
- universal_mcp/agents/planner/prompts.py +0 -1
- universal_mcp/agents/planner/state.py +0 -12
- universal_mcp/agents/react.py +0 -84
- universal_mcp/agents/shared/agent_node.py +0 -34
- universal_mcp/agents/shared/tool_node.py +0 -235
- universal_mcp/agents/simple.py +0 -40
- universal_mcp/agents/tools.py +0 -35
- universal_mcp/agents/utils.py +0 -111
- universal_mcp/analytics.py +0 -111
- universal_mcp/applications/__init__.py +0 -70
- universal_mcp/utils/common.py +0 -278
- universal_mcp-0.1.24rc13.dist-info/RECORD +0 -92
- {universal_mcp-0.1.24rc13.dist-info → universal_mcp-0.1.24rc17.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.24rc13.dist-info → universal_mcp-0.1.24rc17.dist-info}/entry_points.txt +0 -0
- {universal_mcp-0.1.24rc13.dist-info → universal_mcp-0.1.24rc17.dist-info}/licenses/LICENSE +0 -0
universal_mcp/agentr/registry.py
CHANGED
@@ -3,7 +3,7 @@ from typing import Any
|
|
3
3
|
from loguru import logger
|
4
4
|
|
5
5
|
from universal_mcp.agentr.client import AgentrClient
|
6
|
-
from universal_mcp.applications import app_from_slug
|
6
|
+
from universal_mcp.applications.utils import app_from_slug
|
7
7
|
from universal_mcp.tools.manager import ToolManager, _get_app_and_tool_name
|
8
8
|
from universal_mcp.tools.registry import ToolRegistry
|
9
9
|
from universal_mcp.types import ToolConfig, ToolFormat
|
@@ -133,7 +133,7 @@ class AgentrRegistry(ToolRegistry):
|
|
133
133
|
try:
|
134
134
|
# Clear tools from tool manager before loading new tools
|
135
135
|
self.tool_manager.clear_tools()
|
136
|
-
if isinstance(tools,
|
136
|
+
if isinstance(tools, dict):
|
137
137
|
logger.info("Loading tools from tool config")
|
138
138
|
self._load_tools_from_tool_config(tools, self.tool_manager)
|
139
139
|
else:
|
@@ -176,8 +176,8 @@ class AgentrRegistry(ToolRegistry):
|
|
176
176
|
tool_config: The tool configuration containing app names and tools
|
177
177
|
tool_manager: The tool manager to register tools with
|
178
178
|
"""
|
179
|
-
for app_name,
|
180
|
-
self._load_tools(app_name,
|
179
|
+
for app_name, tool_names in tool_config.items():
|
180
|
+
self._load_tools(app_name, tool_names, tool_manager)
|
181
181
|
|
182
182
|
async def call_tool(self, tool_name: str, tool_args: dict[str, Any]) -> dict[str, Any]:
|
183
183
|
"""Call a tool with the given name and arguments."""
|
@@ -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]:
|
@@ -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)
|
universal_mcp/servers/server.py
CHANGED
@@ -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)
|
universal_mcp/tools/manager.py
CHANGED
@@ -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
|
universal_mcp/types.py
CHANGED
@@ -1,7 +1,4 @@
|
|
1
1
|
from enum import Enum
|
2
|
-
from typing import Literal
|
3
|
-
|
4
|
-
from pydantic import BaseModel
|
5
2
|
|
6
3
|
# Constants
|
7
4
|
DEFAULT_IMPORTANT_TAG = "important"
|
@@ -18,21 +15,4 @@ class ToolFormat(str, Enum):
|
|
18
15
|
OPENAI = "openai"
|
19
16
|
|
20
17
|
|
21
|
-
|
22
|
-
tools: list[str]
|
23
|
-
|
24
|
-
|
25
|
-
class MCPConnection(BaseModel):
|
26
|
-
transport: Literal["stdio", "sse", "streamable-http"]
|
27
|
-
command: str | None = None
|
28
|
-
args: list[str] | None = None
|
29
|
-
url: str | None = None
|
30
|
-
headers: dict[str, str] | None = None
|
31
|
-
|
32
|
-
|
33
|
-
class AgentrToolConfig(BaseModel):
|
34
|
-
agentrServers: dict[str, AgentrConnection] | None = None
|
35
|
-
|
36
|
-
|
37
|
-
class ToolConfig(AgentrToolConfig):
|
38
|
-
mcpServers: dict[str, MCPConnection] | None = None
|
18
|
+
ToolConfig = dict[str, list[str]]
|
universal_mcp/utils/prompts.py
CHANGED
@@ -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]:
|
universal_mcp/utils/testing.py
CHANGED
@@ -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,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: universal-mcp
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.24rc17
|
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,6 +32,7 @@ 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
|
35
36
|
Provides-Extra: dev
|
36
37
|
Requires-Dist: litellm>=1.30.7; extra == 'dev'
|
37
38
|
Requires-Dist: pre-commit>=4.2.0; extra == 'dev'
|
@@ -0,0 +1,54 @@
|
|
1
|
+
universal_mcp/cli.py,sha256=pPnIWLhSrLV9ukI8YAg2znehCR3VovhEkmh8XkRT3MU,2505
|
2
|
+
universal_mcp/config.py,sha256=lOlDAgQMT7f6VymmsuCP9sYLlxGKj0hDF3hFcJ2nzS4,8135
|
3
|
+
universal_mcp/exceptions.py,sha256=Uen8UFgLHGlSwXgRUyF-nhqTwdiBuL3okgBVRV2AgtA,2150
|
4
|
+
universal_mcp/logger.py,sha256=VmH_83efpErLEDTJqz55Dp0dioTXfGvMBLZUx5smOLc,2116
|
5
|
+
universal_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
6
|
+
universal_mcp/types.py,sha256=DAzYyS7Zqdi38SpnhdOm08_JeN2CJiC0hDIpLLIhlSw,316
|
7
|
+
universal_mcp/agentr/README.md,sha256=t15pVgkCwZM5wzgLgrf0Zv6hVL7dPmKXvAeTf8CiXPQ,6641
|
8
|
+
universal_mcp/agentr/__init__.py,sha256=fv1ZnOCduIUiJ9oN4e6Ya_hA2oWQvcEuDU3Ek1vEufI,180
|
9
|
+
universal_mcp/agentr/client.py,sha256=TQgwrNc7dEMXuprELf0Q-fdYdrH92Ppd7PUDZoD-KcY,7429
|
10
|
+
universal_mcp/agentr/integration.py,sha256=V5GjqocqS02tRoI8MeV9PL6m-BzejwBzgJhOHo4MxAE,4212
|
11
|
+
universal_mcp/agentr/registry.py,sha256=6KFTv45p_crqRqpdcwj1ksaF41PKXzgYIACRxsLaj3o,6909
|
12
|
+
universal_mcp/agentr/server.py,sha256=bIPmHMiKKwnUYnxmfZVRh1thcn7Rytm_-bNiXTfANzc,2098
|
13
|
+
universal_mcp/applications/application.py,sha256=do45GC0jnNepZHL8US2yDNq9s0ZnE6bII4Xbw90GRTc,23727
|
14
|
+
universal_mcp/applications/utils.py,sha256=8Pp9lZU6IPt9z9BnuJ-vpv-NGuzryt1c4e4-ShDd2XI,1450
|
15
|
+
universal_mcp/applications/sample/app.py,sha256=E0JwaWD7qytwawb_iWc1pBnJ-Te7MMtab4MxOOebLdc,8972
|
16
|
+
universal_mcp/client/oauth.py,sha256=O00zOUfQxINaruFU2zt-64DIR1_mAqrY8ykLQo-teJU,8679
|
17
|
+
universal_mcp/client/token_store.py,sha256=6VAzjzJG49wYvmEDqksFvb-fVqdjHIKWv7yYyh_AuF8,3912
|
18
|
+
universal_mcp/client/transport.py,sha256=qqtb0ky6yvLBxsaA9-oFU0v9MwfcQb4eomq-O2fmwtQ,11850
|
19
|
+
universal_mcp/integrations/__init__.py,sha256=tfzLyPEPly5tfIcT8K6-oKCr_MEFGxOROHy_NeVy0KM,200
|
20
|
+
universal_mcp/integrations/integration.py,sha256=H-hOoDHqk78A4Fi_TGN7OOFS7PDfqXK_nedH8iSz-6A,16459
|
21
|
+
universal_mcp/servers/__init__.py,sha256=speBb_E94UJa4A6Fv8RHFeoJ7cR-q2bCMtKV7R21P5w,142
|
22
|
+
universal_mcp/servers/server.py,sha256=bJTG86X_kn0R8lWIYvCZw-cp-rVAOKd12-E5WOEsz8Y,5950
|
23
|
+
universal_mcp/stores/__init__.py,sha256=quvuwhZnpiSLuojf0NfmBx2xpaCulv3fbKtKaSCEmuM,603
|
24
|
+
universal_mcp/stores/store.py,sha256=yWbEGZb53z3fpVyqGWbes63z1CtIzC_IuM49OXy__UY,10137
|
25
|
+
universal_mcp/tools/__init__.py,sha256=jC8hsqfTdtn32yU57AVFUXiU3ZmUOCfCERSCaNEIH7E,395
|
26
|
+
universal_mcp/tools/adapters.py,sha256=YJ2oqgc8JgmtsdRRtvO-PO0Q0bKqTJ4Y3J6yxlESoTo,3947
|
27
|
+
universal_mcp/tools/docstring_parser.py,sha256=efEOE-ME7G5Jbbzpn7pN2xNuyu2M5zfZ1Tqu1lRB0Gk,8392
|
28
|
+
universal_mcp/tools/func_metadata.py,sha256=F4jd--hoZWKPBbZihVtluYKUsIdXdq4a0VWRgMl5k-Q,10838
|
29
|
+
universal_mcp/tools/manager.py,sha256=T8iSk7Q6s3MFaQcwUapPvcQ6_l7_g4Xt2xXk55lPJ1w,10321
|
30
|
+
universal_mcp/tools/registry.py,sha256=LD0J_bPsd8PRTObyvXglqTW1jfZX98m7KBdyP8Yn7wA,2585
|
31
|
+
universal_mcp/tools/tools.py,sha256=Lk-wUO3rfhwdxaRANtC7lQr5fXi7nclf0oHzxNAb79Q,4927
|
32
|
+
universal_mcp/utils/__init__.py,sha256=8wi4PGWu-SrFjNJ8U7fr2iFJ1ktqlDmSKj1xYd7KSDc,41
|
33
|
+
universal_mcp/utils/installation.py,sha256=PU_GfHPqzkumKk-xG4L9CkBzSmABxmchwblZkx-zY-I,7204
|
34
|
+
universal_mcp/utils/prompts.py,sha256=yaZ8lj5GooA8bbT42lcWR2r35HAnOalCvyWS1I2ltGM,27439
|
35
|
+
universal_mcp/utils/singleton.py,sha256=RoOiKxBOAhp0TK1QaMDYi-8GjRcy2Vh-bAOuIAcYan0,775
|
36
|
+
universal_mcp/utils/testing.py,sha256=PJYB-QVlBuDkRW-luHkJapf4T1oaNjxt8gvQoTS7cCA,8021
|
37
|
+
universal_mcp/utils/openapi/__inti__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
38
|
+
universal_mcp/utils/openapi/api_generator.py,sha256=sygUE8Sropq3Pz09N1kSwGelgnTCAz_lOGspTaYCWEs,6985
|
39
|
+
universal_mcp/utils/openapi/api_splitter.py,sha256=io7fV-E8hUIR4NxFlakqydbgrQF6aBAnZHPMlpxw-wc,20967
|
40
|
+
universal_mcp/utils/openapi/cli.py,sha256=az5ObS74R-MmDCOZ1PHTJVKZJrHnsBOAweOUa7A-GqA,25918
|
41
|
+
universal_mcp/utils/openapi/docgen.py,sha256=DNmwlhg_-TRrHa74epyErMTRjV2nutfCQ7seb_Rq5hE,21366
|
42
|
+
universal_mcp/utils/openapi/filters.py,sha256=96FajO5nLbvjNPy2A1HvSS9jqpzMDHd4q_QTP-DIsPI,3842
|
43
|
+
universal_mcp/utils/openapi/openapi.py,sha256=mcmahLJrR-CS-r2RDaYOgMV1Eq7ZuCHMk7_Qrijadh4,62613
|
44
|
+
universal_mcp/utils/openapi/postprocessor.py,sha256=j_OZ5u2VCb44rfvnJZdKcxQRyGgrEVMr8ue9oN0Ed7A,12235
|
45
|
+
universal_mcp/utils/openapi/preprocessor.py,sha256=r4n0WQI__OzPL8FTza7jxiM4EYeZwa-3tvEJaJYZC44,63081
|
46
|
+
universal_mcp/utils/openapi/readme.py,sha256=R2Jp7DUXYNsXPDV6eFTkLiy7MXbSULUj1vHh4O_nB4c,2974
|
47
|
+
universal_mcp/utils/openapi/test_generator.py,sha256=vucBh9klWmQOUA740TFwfM9ry2nkwKWQiNRcsiZ9HbY,12229
|
48
|
+
universal_mcp/utils/templates/README.md.j2,sha256=Mrm181YX-o_-WEfKs01Bi2RJy43rBiq2j6fTtbWgbTA,401
|
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,,
|
universal_mcp/__init__.py
DELETED
File without changes
|
universal_mcp/agents/__init__.py
DELETED
@@ -1,9 +0,0 @@
|
|
1
|
-
from universal_mcp.agents.autoagent import AutoAgent
|
2
|
-
from universal_mcp.agents.base import BaseAgent
|
3
|
-
from universal_mcp.agents.bigtool import BigToolAgent
|
4
|
-
from universal_mcp.agents.builder import BuilderAgent
|
5
|
-
from universal_mcp.agents.planner import PlannerAgent
|
6
|
-
from universal_mcp.agents.react import ReactAgent
|
7
|
-
from universal_mcp.agents.simple import SimpleAgent
|
8
|
-
|
9
|
-
__all__ = ["BaseAgent", "ReactAgent", "SimpleAgent", "AutoAgent", "BigToolAgent", "PlannerAgent", "BuilderAgent"]
|
@@ -1,30 +0,0 @@
|
|
1
|
-
from langgraph.checkpoint.base import BaseCheckpointSaver
|
2
|
-
|
3
|
-
from universal_mcp.agents.autoagent.graph import build_graph
|
4
|
-
from universal_mcp.agents.base import BaseAgent
|
5
|
-
from universal_mcp.tools.registry import ToolRegistry
|
6
|
-
|
7
|
-
|
8
|
-
class AutoAgent(BaseAgent):
|
9
|
-
def __init__(
|
10
|
-
self,
|
11
|
-
name: str,
|
12
|
-
instructions: str,
|
13
|
-
model: str,
|
14
|
-
memory: BaseCheckpointSaver | None = None,
|
15
|
-
registry: ToolRegistry | None = None,
|
16
|
-
**kwargs,
|
17
|
-
):
|
18
|
-
super().__init__(name, instructions, model, memory, **kwargs)
|
19
|
-
self.tool_registry = registry
|
20
|
-
|
21
|
-
async def _build_graph(self):
|
22
|
-
builder = await build_graph(self.tool_registry, self.instructions)
|
23
|
-
return builder.compile(checkpointer=self.memory)
|
24
|
-
|
25
|
-
@property
|
26
|
-
def graph(self):
|
27
|
-
return self._graph
|
28
|
-
|
29
|
-
|
30
|
-
__all__ = ["AutoAgent"]
|
@@ -1,25 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
|
3
|
-
from loguru import logger
|
4
|
-
|
5
|
-
from universal_mcp.agentr.registry import AgentrRegistry
|
6
|
-
from universal_mcp.agents.autoagent import AutoAgent
|
7
|
-
|
8
|
-
|
9
|
-
async def main():
|
10
|
-
agent = AutoAgent(
|
11
|
-
name="autoagent",
|
12
|
-
instructions="You are a helpful assistant that can use tools to help the user.",
|
13
|
-
model="azure/gpt-4.1",
|
14
|
-
registry=AgentrRegistry(),
|
15
|
-
)
|
16
|
-
async for event in agent.stream(
|
17
|
-
user_input="Send an email to manoj@agentr.dev",
|
18
|
-
thread_id="test123",
|
19
|
-
):
|
20
|
-
logger.info(event.content)
|
21
|
-
# from loguru import logger; logger.debug(result)
|
22
|
-
|
23
|
-
|
24
|
-
if __name__ == "__main__":
|
25
|
-
asyncio.run(main())
|
@@ -1,26 +0,0 @@
|
|
1
|
-
from dataclasses import dataclass, field
|
2
|
-
from typing import Annotated
|
3
|
-
|
4
|
-
from universal_mcp.agents.autoagent.prompts import SYSTEM_PROMPT
|
5
|
-
|
6
|
-
|
7
|
-
@dataclass(kw_only=True)
|
8
|
-
class Context:
|
9
|
-
"""The context for the agent."""
|
10
|
-
|
11
|
-
system_prompt: str = field(
|
12
|
-
default=SYSTEM_PROMPT,
|
13
|
-
metadata={
|
14
|
-
"description": "The system prompt to use for the agent's interactions. "
|
15
|
-
"This prompt sets the context and behavior for the agent."
|
16
|
-
},
|
17
|
-
)
|
18
|
-
|
19
|
-
model: Annotated[str, {"__template_metadata__": {"kind": "llm"}}] = field(
|
20
|
-
default="anthropic/claude-4-sonnet-20250514",
|
21
|
-
# default="vertex/gemini-2.5-flash",
|
22
|
-
metadata={
|
23
|
-
"description": "The name of the language model to use for the agent's main interactions. "
|
24
|
-
"Should be in the form: provider/model-name."
|
25
|
-
},
|
26
|
-
)
|
@@ -1,151 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
from datetime import UTC, datetime
|
3
|
-
from typing import cast
|
4
|
-
|
5
|
-
from langchain_core.messages import AIMessage, ToolMessage
|
6
|
-
from langchain_core.tools import tool
|
7
|
-
from langgraph.graph import END, START, StateGraph
|
8
|
-
from langgraph.runtime import Runtime
|
9
|
-
|
10
|
-
from universal_mcp.agents.autoagent.context import Context
|
11
|
-
from universal_mcp.agents.autoagent.prompts import SYSTEM_PROMPT
|
12
|
-
from universal_mcp.agents.autoagent.state import State
|
13
|
-
from universal_mcp.agents.llm import load_chat_model
|
14
|
-
from universal_mcp.tools.registry import ToolRegistry
|
15
|
-
from universal_mcp.types import ToolFormat
|
16
|
-
|
17
|
-
|
18
|
-
async def build_graph(tool_registry: ToolRegistry, instructions: str = ""):
|
19
|
-
@tool()
|
20
|
-
async def search_tools(query: str, app_ids: list[str] | None = None) -> list[str]:
|
21
|
-
"""Retrieve tools using a search query and a list of app ids. Use multiple times if you require tools for different queries."""
|
22
|
-
tools_list = []
|
23
|
-
if app_ids is not None:
|
24
|
-
for app_id in app_ids:
|
25
|
-
tools_list.extend(await tool_registry.search_tools(query, limit=10, app_id=app_id))
|
26
|
-
else:
|
27
|
-
tools_list = await tool_registry.search_tools(query, limit=10)
|
28
|
-
tools_list = [f"{tool['id']}: {tool['description']}" for tool in tools_list]
|
29
|
-
return tools_list
|
30
|
-
|
31
|
-
@tool()
|
32
|
-
async def ask_user(question: str) -> str:
|
33
|
-
"""Ask the user a question. Use this tool to ask the user for any missing information for performing a task, or when you have multiple apps to choose from for performing a task."""
|
34
|
-
full_question = question
|
35
|
-
return f"ASKING_USER: {full_question}"
|
36
|
-
|
37
|
-
@tool()
|
38
|
-
async def load_tools(tools: list[str]) -> list[str]:
|
39
|
-
"""Choose the tools you want to use by passing their tool ids. Loads the tools for the chosen tools and returns the tool ids."""
|
40
|
-
return tools
|
41
|
-
|
42
|
-
async def call_model(
|
43
|
-
state: State,
|
44
|
-
runtime: Runtime[Context],
|
45
|
-
):
|
46
|
-
system_prompt = SYSTEM_PROMPT
|
47
|
-
app_ids = await tool_registry.list_all_apps()
|
48
|
-
connections = await tool_registry.list_connected_apps()
|
49
|
-
connection_ids = set([connection["app_id"] for connection in connections])
|
50
|
-
connected_apps = [app["id"] for app in app_ids if app["id"] in connection_ids]
|
51
|
-
unconnected_apps = [app["id"] for app in app_ids if app["id"] not in connection_ids]
|
52
|
-
app_id_descriptions = "These are the apps connected to the user's account:\n" + "\n".join(
|
53
|
-
[f"{app}" for app in connected_apps]
|
54
|
-
)
|
55
|
-
if unconnected_apps:
|
56
|
-
app_id_descriptions += "\n\nOther (not connected) apps: " + "\n".join(
|
57
|
-
[f"{app}" for app in unconnected_apps]
|
58
|
-
)
|
59
|
-
|
60
|
-
system_prompt = system_prompt.format(system_time=datetime.now(tz=UTC).isoformat(), app_ids=app_id_descriptions)
|
61
|
-
|
62
|
-
messages = [{"role": "system", "content": system_prompt + "\n" + instructions}, *state["messages"]]
|
63
|
-
model = load_chat_model(runtime.context.model)
|
64
|
-
loaded_tools = await tool_registry.export_tools(tools=state["selected_tool_ids"], format=ToolFormat.LANGCHAIN)
|
65
|
-
model_with_tools = model.bind_tools([search_tools, ask_user, load_tools, *loaded_tools], tool_choice="auto")
|
66
|
-
response_raw = model_with_tools.invoke(messages)
|
67
|
-
response = cast(AIMessage, response_raw)
|
68
|
-
return {"messages": [response]}
|
69
|
-
|
70
|
-
# Define the conditional edge that determines whether to continue or not
|
71
|
-
def should_continue(state: State):
|
72
|
-
messages = state["messages"]
|
73
|
-
last_message = messages[-1]
|
74
|
-
# If there is no function call, then we finish
|
75
|
-
if not last_message.tool_calls:
|
76
|
-
return END
|
77
|
-
else:
|
78
|
-
return "tools"
|
79
|
-
|
80
|
-
def tool_router(state: State):
|
81
|
-
last_message = state["messages"][-1]
|
82
|
-
if isinstance(last_message, ToolMessage) and last_message.name == ask_user.name:
|
83
|
-
return END
|
84
|
-
else:
|
85
|
-
return "agent"
|
86
|
-
|
87
|
-
async def tool_node(state: State):
|
88
|
-
outputs = []
|
89
|
-
tool_ids = state["selected_tool_ids"]
|
90
|
-
for tool_call in state["messages"][-1].tool_calls:
|
91
|
-
if tool_call["name"] == ask_user.name:
|
92
|
-
outputs.append(
|
93
|
-
ToolMessage(
|
94
|
-
content=json.dumps(
|
95
|
-
"The user has been asked the question, and the run will wait for the user's response."
|
96
|
-
),
|
97
|
-
name=tool_call["name"],
|
98
|
-
tool_call_id=tool_call["id"],
|
99
|
-
)
|
100
|
-
)
|
101
|
-
elif tool_call["name"] == search_tools.name:
|
102
|
-
tools = await search_tools.ainvoke(tool_call["args"])
|
103
|
-
outputs.append(
|
104
|
-
ToolMessage(
|
105
|
-
content=json.dumps(tools) + "\n\nUse the load_tools tool to load the tools you want to use.",
|
106
|
-
name=tool_call["name"],
|
107
|
-
tool_call_id=tool_call["id"],
|
108
|
-
)
|
109
|
-
)
|
110
|
-
|
111
|
-
elif tool_call["name"] == load_tools.name:
|
112
|
-
tool_ids = await load_tools.ainvoke(tool_call["args"])
|
113
|
-
|
114
|
-
outputs.append(
|
115
|
-
ToolMessage(
|
116
|
-
content=json.dumps(tool_ids),
|
117
|
-
name=tool_call["name"],
|
118
|
-
tool_call_id=tool_call["id"],
|
119
|
-
)
|
120
|
-
)
|
121
|
-
else:
|
122
|
-
await tool_registry.export_tools([tool_call["name"]], ToolFormat.LANGCHAIN)
|
123
|
-
try:
|
124
|
-
tool_result = await tool_registry.call_tool(tool_call["name"], tool_call["args"])
|
125
|
-
outputs.append(
|
126
|
-
ToolMessage(
|
127
|
-
content=json.dumps(tool_result),
|
128
|
-
name=tool_call["name"],
|
129
|
-
tool_call_id=tool_call["id"],
|
130
|
-
)
|
131
|
-
)
|
132
|
-
except Exception as e:
|
133
|
-
outputs.append(
|
134
|
-
ToolMessage(
|
135
|
-
content=json.dumps("Error: " + str(e)),
|
136
|
-
name=tool_call["name"],
|
137
|
-
tool_call_id=tool_call["id"],
|
138
|
-
)
|
139
|
-
)
|
140
|
-
return {"messages": outputs, "selected_tool_ids": tool_ids}
|
141
|
-
|
142
|
-
builder = StateGraph(State, context_schema=Context)
|
143
|
-
|
144
|
-
builder.add_node("agent", call_model)
|
145
|
-
builder.add_node("tools", tool_node)
|
146
|
-
|
147
|
-
builder.add_edge(START, "agent")
|
148
|
-
builder.add_conditional_edges("agent", should_continue)
|
149
|
-
builder.add_conditional_edges("tools", tool_router)
|
150
|
-
|
151
|
-
return builder
|
@@ -1,9 +0,0 @@
|
|
1
|
-
"""Default prompts used by the agent."""
|
2
|
-
|
3
|
-
SYSTEM_PROMPT = """You are a helpful AI assistant. When you lack tools for any task you should use the `search_tools` function to unlock relevant tools. Whenever you need to ask the user for any information, or choose between multiple different applications, you can ask the user using the `ask_user` function.
|
4
|
-
|
5
|
-
System time: {system_time}
|
6
|
-
These are the list of apps available to you:
|
7
|
-
{app_ids}
|
8
|
-
Note that when multiple apps seem relevant for a task, you MUST ask the user to choose the app. Prefer connected apps over unconnected apps while breaking a tie. If more than one relevant app (or none of the relevant apps) are connected, you must ask the user to choose the app. In case the user asks you to use an app that is not connected, call the apps tools normally. You will be provided a link for connection that you should pass on to the user.
|
9
|
-
"""
|
@@ -1,27 +0,0 @@
|
|
1
|
-
from typing import Annotated
|
2
|
-
|
3
|
-
from langgraph.prebuilt.chat_agent_executor import AgentState
|
4
|
-
|
5
|
-
|
6
|
-
def _enqueue(left: list, right: list) -> list:
|
7
|
-
"""Treat left as a FIFO queue, append new items from right (preserve order),
|
8
|
-
keep items unique, and cap total size to 20 (drop oldest items)."""
|
9
|
-
max_size = 30
|
10
|
-
preferred_size = 20
|
11
|
-
if len(right) > preferred_size:
|
12
|
-
preferred_size = min(max_size, len(right))
|
13
|
-
queue = list(left or [])
|
14
|
-
|
15
|
-
for item in right[:preferred_size] or []:
|
16
|
-
if item in queue:
|
17
|
-
queue.remove(item)
|
18
|
-
queue.append(item)
|
19
|
-
|
20
|
-
if len(queue) > preferred_size:
|
21
|
-
queue = queue[-preferred_size:]
|
22
|
-
|
23
|
-
return queue
|
24
|
-
|
25
|
-
|
26
|
-
class State(AgentState):
|
27
|
-
selected_tool_ids: Annotated[list[str], _enqueue]
|
@@ -1,25 +0,0 @@
|
|
1
|
-
import asyncio
|
2
|
-
|
3
|
-
from universal_mcp.agentr.registry import AgentrRegistry
|
4
|
-
from universal_mcp.agents.autoagent import build_graph
|
5
|
-
from universal_mcp.tools import ToolManager
|
6
|
-
|
7
|
-
tool_registry = AgentrRegistry()
|
8
|
-
tool_manager = ToolManager()
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
async def main():
|
13
|
-
instructions = """
|
14
|
-
You are a helpful assistant that can use tools to help the user. If a task requires multiple steps, you should perform separate different searches for different actions. Prefer completing one action before searching for another.
|
15
|
-
"""
|
16
|
-
graph = await build_graph(tool_registry, instructions=instructions)
|
17
|
-
return graph
|
18
|
-
|
19
|
-
graph = asyncio.run(main())
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
@@ -1,13 +0,0 @@
|
|
1
|
-
from langchain_core.messages import BaseMessage
|
2
|
-
|
3
|
-
|
4
|
-
def get_message_text(msg: BaseMessage) -> str:
|
5
|
-
"""Get the text content of a message."""
|
6
|
-
content = msg.content
|
7
|
-
if isinstance(content, str):
|
8
|
-
return content
|
9
|
-
elif isinstance(content, dict):
|
10
|
-
return content.get("text", "")
|
11
|
-
else:
|
12
|
-
txts = [c if isinstance(c, str) else (c.get("text") or "") for c in content]
|
13
|
-
return "".join(txts).strip()
|