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.
Files changed (53) hide show
  1. universal_mcp/agentr/registry.py +4 -4
  2. universal_mcp/applications/application.py +0 -2
  3. universal_mcp/applications/utils.py +52 -0
  4. universal_mcp/servers/server.py +4 -3
  5. universal_mcp/tools/manager.py +0 -3
  6. universal_mcp/types.py +1 -21
  7. universal_mcp/utils/prompts.py +0 -2
  8. universal_mcp/utils/testing.py +1 -1
  9. {universal_mcp-0.1.24rc13.dist-info → universal_mcp-0.1.24rc17.dist-info}/METADATA +2 -1
  10. universal_mcp-0.1.24rc17.dist-info/RECORD +54 -0
  11. universal_mcp/__init__.py +0 -0
  12. universal_mcp/agents/__init__.py +0 -9
  13. universal_mcp/agents/autoagent/__init__.py +0 -30
  14. universal_mcp/agents/autoagent/__main__.py +0 -25
  15. universal_mcp/agents/autoagent/context.py +0 -26
  16. universal_mcp/agents/autoagent/graph.py +0 -151
  17. universal_mcp/agents/autoagent/prompts.py +0 -9
  18. universal_mcp/agents/autoagent/state.py +0 -27
  19. universal_mcp/agents/autoagent/studio.py +0 -25
  20. universal_mcp/agents/autoagent/utils.py +0 -13
  21. universal_mcp/agents/base.py +0 -129
  22. universal_mcp/agents/bigtool/__init__.py +0 -54
  23. universal_mcp/agents/bigtool/__main__.py +0 -24
  24. universal_mcp/agents/bigtool/context.py +0 -24
  25. universal_mcp/agents/bigtool/graph.py +0 -166
  26. universal_mcp/agents/bigtool/prompts.py +0 -31
  27. universal_mcp/agents/bigtool/state.py +0 -27
  28. universal_mcp/agents/builder.py +0 -80
  29. universal_mcp/agents/cli.py +0 -27
  30. universal_mcp/agents/codeact/__init__.py +0 -243
  31. universal_mcp/agents/codeact/sandbox.py +0 -27
  32. universal_mcp/agents/codeact/test.py +0 -15
  33. universal_mcp/agents/codeact/utils.py +0 -61
  34. universal_mcp/agents/hil.py +0 -104
  35. universal_mcp/agents/llm.py +0 -45
  36. universal_mcp/agents/planner/__init__.py +0 -37
  37. universal_mcp/agents/planner/__main__.py +0 -24
  38. universal_mcp/agents/planner/graph.py +0 -82
  39. universal_mcp/agents/planner/prompts.py +0 -1
  40. universal_mcp/agents/planner/state.py +0 -12
  41. universal_mcp/agents/react.py +0 -84
  42. universal_mcp/agents/shared/agent_node.py +0 -34
  43. universal_mcp/agents/shared/tool_node.py +0 -235
  44. universal_mcp/agents/simple.py +0 -40
  45. universal_mcp/agents/tools.py +0 -35
  46. universal_mcp/agents/utils.py +0 -111
  47. universal_mcp/analytics.py +0 -111
  48. universal_mcp/applications/__init__.py +0 -70
  49. universal_mcp/utils/common.py +0 -278
  50. universal_mcp-0.1.24rc13.dist-info/RECORD +0 -92
  51. {universal_mcp-0.1.24rc13.dist-info → universal_mcp-0.1.24rc17.dist-info}/WHEEL +0 -0
  52. {universal_mcp-0.1.24rc13.dist-info → universal_mcp-0.1.24rc17.dist-info}/entry_points.txt +0 -0
  53. {universal_mcp-0.1.24rc13.dist-info → universal_mcp-0.1.24rc17.dist-info}/licenses/LICENSE +0 -0
@@ -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, ToolConfig):
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, tool_data in tool_config.agentrServers.items():
180
- self._load_tools(app_name, tool_data.tools, tool_manager)
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)
@@ -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, app_from_config
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 = app_from_config(app_config)(integration=integration)
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
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
- class AgentrConnection(BaseModel):
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]]
@@ -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,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: universal-mcp
3
- Version: 0.1.24rc13
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
@@ -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()