universal-mcp 0.1.24rc4__tar.gz → 0.1.24rc6__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.24rc4 → universal_mcp-0.1.24rc6}/PKG-INFO +3 -6
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/pyproject.toml +3 -6
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/tests/test_tool_manager.py +7 -6
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/tests/test_zenquotes.py +2 -1
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/base.py +23 -10
- universal_mcp-0.1.24rc6/src/universal_mcp/agents/react.py +64 -0
- universal_mcp-0.1.24rc6/src/universal_mcp/agents/tools.py +35 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/config.py +0 -93
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/tools/manager.py +15 -22
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/tools/tools.py +11 -5
- universal_mcp-0.1.24rc6/src/universal_mcp/types.py +35 -0
- universal_mcp-0.1.24rc4/src/universal_mcp/agents/react.py +0 -58
- universal_mcp-0.1.24rc4/src/universal_mcp/types.py +0 -10
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/.gitignore +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/LICENSE +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/README.md +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/tests/__init__.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/tests/conftest.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/tests/test_api_generator.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/tests/test_api_integration.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/tests/test_applications.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/tests/test_localserver.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/tests/test_stores.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/tests/test_tool.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/__init__.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agentr/README.md +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agentr/__init__.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agentr/agentr.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agentr/client.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agentr/integration.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agentr/registry.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agentr/server.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/__init__.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/auto.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/cli.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/codeact/__init__.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/codeact/sandbox.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/codeact/test.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/codeact/utils.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/hil.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/llm.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/simple.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/utils.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/analytics.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/applications/__init__.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/applications/application.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/applications/sample/app.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/cli.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/client/oauth.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/client/token_store.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/client/transport.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/exceptions.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/integrations/__init__.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/integrations/integration.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/logger.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/py.typed +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/servers/__init__.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/servers/server.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/stores/__init__.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/stores/store.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/tools/__init__.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/tools/adapters.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/tools/docstring_parser.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/tools/func_metadata.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/tools/registry.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/__init__.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/common.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/installation.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/__inti__.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/api_generator.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/api_splitter.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/cli.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/docgen.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/filters.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/openapi.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/postprocessor.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/preprocessor.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/readme.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/test_generator.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/prompts.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/singleton.py +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/templates/README.md.j2 +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/templates/api_client.py.j2 +0 -0
- {universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/testing.py +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.24rc6
|
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
|
@@ -8,7 +8,7 @@ License-File: LICENSE
|
|
8
8
|
Requires-Python: >=3.11
|
9
9
|
Requires-Dist: black>=25.1.0
|
10
10
|
Requires-Dist: cookiecutter>=2.6.0
|
11
|
-
Requires-Dist: gql
|
11
|
+
Requires-Dist: gql>=4.0.0
|
12
12
|
Requires-Dist: jinja2>=3.1.3
|
13
13
|
Requires-Dist: jsonref>=1.1.0
|
14
14
|
Requires-Dist: keyring>=25.6.0
|
@@ -19,24 +19,21 @@ Requires-Dist: langgraph>=0.5.2
|
|
19
19
|
Requires-Dist: langsmith>=0.4.5
|
20
20
|
Requires-Dist: loguru>=0.7.3
|
21
21
|
Requires-Dist: mcp>=1.10.0
|
22
|
-
Requires-Dist: mkdocs-material>=9.6.15
|
23
|
-
Requires-Dist: mkdocs>=1.6.1
|
24
22
|
Requires-Dist: posthog>=3.24.0
|
25
23
|
Requires-Dist: pydantic-settings>=2.8.1
|
26
24
|
Requires-Dist: pydantic>=2.11.1
|
27
25
|
Requires-Dist: pyyaml>=6.0.2
|
28
26
|
Requires-Dist: rich>=14.0.0
|
29
27
|
Requires-Dist: streamlit>=1.46.1
|
30
|
-
Requires-Dist: ty>=0.0.1a17
|
31
28
|
Requires-Dist: typer>=0.15.2
|
32
29
|
Provides-Extra: dev
|
33
30
|
Requires-Dist: litellm>=1.30.7; extra == 'dev'
|
34
|
-
Requires-Dist: mypy>=1.16.0; extra == 'dev'
|
35
31
|
Requires-Dist: pre-commit>=4.2.0; extra == 'dev'
|
36
32
|
Requires-Dist: pyright>=1.1.398; extra == 'dev'
|
37
33
|
Requires-Dist: pytest-asyncio>=0.26.0; extra == 'dev'
|
38
34
|
Requires-Dist: pytest>=8.3.5; extra == 'dev'
|
39
35
|
Requires-Dist: ruff>=0.11.4; extra == 'dev'
|
36
|
+
Requires-Dist: ty>=0.0.1a17; extra == 'dev'
|
40
37
|
Provides-Extra: docs
|
41
38
|
Requires-Dist: mkdocs-glightbox>=0.4.0; extra == 'docs'
|
42
39
|
Requires-Dist: mkdocs-material[imaging]>=9.5.45; extra == 'docs'
|
@@ -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-rc6"
|
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 = [
|
@@ -16,7 +16,7 @@ dependencies = [
|
|
16
16
|
"Jinja2>=3.1.3",
|
17
17
|
"black>=25.1.0",
|
18
18
|
"cookiecutter>=2.6.0",
|
19
|
-
"gql
|
19
|
+
"gql>=4.0.0",
|
20
20
|
"jsonref>=1.1.0",
|
21
21
|
"keyring>=25.6.0",
|
22
22
|
"langchain-mcp-adapters>=0.1.9",
|
@@ -26,21 +26,18 @@ dependencies = [
|
|
26
26
|
"langsmith>=0.4.5",
|
27
27
|
"loguru>=0.7.3",
|
28
28
|
"mcp>=1.10.0",
|
29
|
-
"mkdocs>=1.6.1",
|
30
|
-
"mkdocs-material>=9.6.15",
|
31
29
|
"posthog>=3.24.0",
|
32
30
|
"pydantic>=2.11.1",
|
33
31
|
"pydantic-settings>=2.8.1",
|
34
32
|
"pyyaml>=6.0.2",
|
35
33
|
"rich>=14.0.0",
|
36
34
|
"streamlit>=1.46.1",
|
37
|
-
"ty>=0.0.1a17",
|
38
35
|
"typer>=0.15.2",
|
39
36
|
]
|
40
37
|
|
41
38
|
[project.optional-dependencies]
|
42
39
|
dev = [
|
43
|
-
"
|
40
|
+
"ty>=0.0.1a17",
|
44
41
|
"litellm>=1.30.7",
|
45
42
|
"pre-commit>=4.2.0",
|
46
43
|
"pyright>=1.1.398",
|
@@ -4,6 +4,7 @@ from universal_mcp.applications.application import BaseApplication
|
|
4
4
|
from universal_mcp.exceptions import ToolError, ToolNotFoundError
|
5
5
|
from universal_mcp.tools.adapters import ToolFormat
|
6
6
|
from universal_mcp.tools.manager import Tool, ToolManager
|
7
|
+
from universal_mcp.types import TOOL_NAME_SEPARATOR
|
7
8
|
|
8
9
|
|
9
10
|
# Dummy tools for testing
|
@@ -158,8 +159,8 @@ async def test_call_tool_from_app(tool_manager: ToolManager):
|
|
158
159
|
tool_manager.register_tools_from_app(app)
|
159
160
|
tools = tool_manager.list_tools()
|
160
161
|
assert len(tools) == 1
|
161
|
-
assert "
|
162
|
-
result = await tool_manager.call_tool("
|
162
|
+
assert f"example_app{TOOL_NAME_SEPARATOR}dummy_add" in [t.name for t in tools]
|
163
|
+
result = await tool_manager.call_tool(f"example_app{TOOL_NAME_SEPARATOR}dummy_add", {"a": 2, "b": 3})
|
163
164
|
assert result == 5
|
164
165
|
|
165
166
|
|
@@ -170,8 +171,8 @@ async def test_call_tool_from_app_with_tags(tool_manager: ToolManager):
|
|
170
171
|
tool_manager.register_tools_from_app(app, tags=["math"])
|
171
172
|
tools = tool_manager.list_tools()
|
172
173
|
assert len(tools) == 2
|
173
|
-
assert "
|
174
|
-
assert "
|
174
|
+
assert "example_app__dummy_add" in [t.name for t in tools]
|
175
|
+
assert "example_app__dummy_multiply" in [t.name for t in tools]
|
175
176
|
|
176
177
|
|
177
178
|
@pytest.mark.asyncio
|
@@ -181,5 +182,5 @@ async def test_load_tool_from_name(tool_manager: ToolManager):
|
|
181
182
|
tool_manager.register_tools_from_app(app, tool_names=["dummy_multiply", "dummy_add"])
|
182
183
|
tools = tool_manager.list_tools()
|
183
184
|
assert len(tools) == 2
|
184
|
-
assert "
|
185
|
-
assert "
|
185
|
+
assert f"example_app{TOOL_NAME_SEPARATOR}dummy_multiply" in [t.name for t in tools]
|
186
|
+
assert f"example_app{TOOL_NAME_SEPARATOR}dummy_add" in [t.name for t in tools]
|
@@ -2,6 +2,7 @@ import pytest
|
|
2
2
|
|
3
3
|
from universal_mcp.config import AppConfig, ServerConfig
|
4
4
|
from universal_mcp.servers import LocalServer
|
5
|
+
from universal_mcp.types import TOOL_NAME_SEPARATOR
|
5
6
|
|
6
7
|
|
7
8
|
@pytest.mark.asyncio
|
@@ -20,7 +21,7 @@ async def test_zenquotes():
|
|
20
21
|
tools = await server.list_tools()
|
21
22
|
assert len(tools) > 0
|
22
23
|
# Get a random quote
|
23
|
-
result = await server.call_tool("
|
24
|
+
result = await server.call_tool(f"zenquotes{TOOL_NAME_SEPARATOR}get_quote", {})
|
24
25
|
assert len(result) > 0
|
25
26
|
quote = result[0].text
|
26
27
|
assert quote is not None
|
@@ -3,26 +3,31 @@ from typing import cast
|
|
3
3
|
from uuid import uuid4
|
4
4
|
|
5
5
|
from langchain_core.messages import AIMessageChunk
|
6
|
+
from langgraph.checkpoint.base import BaseCheckpointSaver
|
6
7
|
from langgraph.checkpoint.memory import MemorySaver
|
7
8
|
from langgraph.types import Command
|
8
9
|
|
10
|
+
from .llm import get_llm
|
9
11
|
from .utils import RichCLI
|
10
12
|
|
11
13
|
|
12
14
|
class BaseAgent:
|
13
|
-
def __init__(self, name: str, instructions: str, model: str):
|
15
|
+
def __init__(self, name: str, instructions: str, model: str, memory: BaseCheckpointSaver | None = None, **kwargs):
|
14
16
|
self.name = name
|
15
17
|
self.instructions = instructions
|
16
18
|
self.model = model
|
17
|
-
self.memory = MemorySaver()
|
19
|
+
self.memory = memory or MemorySaver()
|
20
|
+
self._graph = None
|
21
|
+
self.llm = get_llm(model)
|
18
22
|
self.cli = RichCLI()
|
19
23
|
|
20
|
-
|
21
|
-
def graph(self):
|
24
|
+
async def _build_graph(self):
|
22
25
|
raise NotImplementedError("Subclasses must implement this method")
|
23
26
|
|
24
27
|
async def stream(self, thread_id: str, user_input: str):
|
25
|
-
|
28
|
+
if self._graph is None:
|
29
|
+
self._graph = await self._build_graph()
|
30
|
+
async for event, _ in self._graph.astream(
|
26
31
|
{"messages": [{"role": "user", "content": user_input}]},
|
27
32
|
config={"configurable": {"thread_id": thread_id}},
|
28
33
|
stream_mode="messages",
|
@@ -32,25 +37,33 @@ class BaseAgent:
|
|
32
37
|
|
33
38
|
async def stream_interactive(self, thread_id: str, user_input: str):
|
34
39
|
with self.cli.display_agent_response_streaming(self.name) as stream_updater:
|
35
|
-
async for event in self.
|
40
|
+
async for event in self.astream(thread_id, user_input):
|
36
41
|
stream_updater.update(event.content)
|
37
42
|
|
38
|
-
async def
|
39
|
-
"""
|
43
|
+
async def run(self, user_input: str, thread_id: str = str(uuid4())):
|
44
|
+
"""Run the agent"""
|
45
|
+
if not self._graph:
|
46
|
+
self._graph = await self._build_graph()
|
47
|
+
return await self._graph.ainvoke(
|
48
|
+
{"messages": [{"role": "user", "content": user_input}]},
|
49
|
+
config={"configurable": {"thread_id": thread_id}},
|
50
|
+
)
|
40
51
|
|
41
52
|
async def run_interactive(self, thread_id: str = str(uuid4())):
|
42
53
|
"""Main application loop"""
|
43
54
|
|
55
|
+
if not self._graph:
|
56
|
+
self._graph = await self._build_graph()
|
44
57
|
# Display welcome
|
45
58
|
self.cli.display_welcome(self.name)
|
46
59
|
|
47
60
|
# Main loop
|
48
61
|
while True:
|
49
62
|
try:
|
50
|
-
state = self.
|
63
|
+
state = self._graph.get_state(config={"configurable": {"thread_id": thread_id}})
|
51
64
|
if state.interrupts:
|
52
65
|
value = self.cli.handle_interrupt(state.interrupts[0])
|
53
|
-
self.
|
66
|
+
self._graph.invoke(Command(resume=value), config={"configurable": {"thread_id": thread_id}})
|
54
67
|
continue
|
55
68
|
|
56
69
|
user_input = self.cli.get_user_input()
|
@@ -0,0 +1,64 @@
|
|
1
|
+
from langgraph.checkpoint.base import BaseCheckpointSaver
|
2
|
+
from langgraph.prebuilt import create_react_agent
|
3
|
+
from loguru import logger
|
4
|
+
|
5
|
+
from universal_mcp.agents.base import BaseAgent
|
6
|
+
from universal_mcp.agents.tools import load_agentr_tools, load_mcp_tools
|
7
|
+
from universal_mcp.types import ToolConfig
|
8
|
+
|
9
|
+
|
10
|
+
class ReactAgent(BaseAgent):
|
11
|
+
def __init__(
|
12
|
+
self,
|
13
|
+
name: str,
|
14
|
+
instructions: str,
|
15
|
+
model: str,
|
16
|
+
memory: BaseCheckpointSaver | None = None,
|
17
|
+
tools: ToolConfig | None = None,
|
18
|
+
max_iterations: int = 10,
|
19
|
+
**kwargs,
|
20
|
+
):
|
21
|
+
super().__init__(name, instructions, model, memory, **kwargs)
|
22
|
+
self.tools = tools
|
23
|
+
self.max_iterations = max_iterations
|
24
|
+
|
25
|
+
async def _build_graph(self):
|
26
|
+
if self.tools:
|
27
|
+
config = self.tools.model_dump(exclude_none=True)
|
28
|
+
agentr_tools = await load_agentr_tools(config["agentrServers"]) if config.get("agentrServers") else []
|
29
|
+
mcp_tools = await load_mcp_tools(config["mcpServers"]) if config.get("mcpServers") else []
|
30
|
+
tools = agentr_tools + mcp_tools
|
31
|
+
else:
|
32
|
+
tools = []
|
33
|
+
logger.debug(f"Initialized ReactAgent: name={self.name}, model={self.model}")
|
34
|
+
return create_react_agent(
|
35
|
+
self.llm,
|
36
|
+
tools,
|
37
|
+
prompt=self._build_system_message(),
|
38
|
+
checkpointer=self.memory,
|
39
|
+
)
|
40
|
+
|
41
|
+
def _build_system_message(self) -> str:
|
42
|
+
system_message = f"""You are {self.name}. {self.instructions}
|
43
|
+
|
44
|
+
You have access to various tools that can help you answer questions and complete tasks. When you need to use a tool:
|
45
|
+
|
46
|
+
1. Think about what information you need
|
47
|
+
2. Call the appropriate tool with the right parameters
|
48
|
+
3. Use the tool results to provide a comprehensive answer
|
49
|
+
|
50
|
+
Always explain your reasoning and be thorough in your responses. If you need to use multiple tools to answer a question completely, do so."""
|
51
|
+
return system_message
|
52
|
+
|
53
|
+
|
54
|
+
if __name__ == "__main__":
|
55
|
+
import asyncio
|
56
|
+
|
57
|
+
agent = ReactAgent(
|
58
|
+
"Universal React Agent",
|
59
|
+
instructions="",
|
60
|
+
model="gpt-4o",
|
61
|
+
tools=ToolConfig(agentrServers={"google-mail": {"tools": ["send_email"]}}),
|
62
|
+
)
|
63
|
+
result = asyncio.run(agent.run(user_input="Send an email with the subject 'Hello' to john.doe@example.com"))
|
64
|
+
print(result["messages"][-1].content)
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import json
|
2
|
+
|
3
|
+
from langchain_mcp_adapters.client import MultiServerMCPClient
|
4
|
+
|
5
|
+
from universal_mcp.agentr.integration import AgentrIntegration
|
6
|
+
from universal_mcp.applications import app_from_slug
|
7
|
+
from universal_mcp.tools.adapters import ToolFormat
|
8
|
+
from universal_mcp.tools.manager import ToolManager
|
9
|
+
from universal_mcp.types import ToolConfig
|
10
|
+
|
11
|
+
|
12
|
+
async def load_agentr_tools(agentr_servers: dict):
|
13
|
+
tool_manager = ToolManager()
|
14
|
+
for app_name, tool_names in agentr_servers.items():
|
15
|
+
app = app_from_slug(app_name)
|
16
|
+
integration = AgentrIntegration(name=app_name)
|
17
|
+
app_instance = app(integration=integration)
|
18
|
+
tool_manager.register_tools_from_app(app_instance, tool_names=tool_names["tools"])
|
19
|
+
tools = tool_manager.list_tools(format=ToolFormat.LANGCHAIN)
|
20
|
+
return tools
|
21
|
+
|
22
|
+
|
23
|
+
async def load_mcp_tools(mcp_servers: dict):
|
24
|
+
client = MultiServerMCPClient(mcp_servers)
|
25
|
+
tools = await client.get_tools()
|
26
|
+
return tools
|
27
|
+
|
28
|
+
|
29
|
+
async def load_tools(path: str) -> ToolConfig:
|
30
|
+
with open(path) as f:
|
31
|
+
data = json.load(f)
|
32
|
+
config = ToolConfig.model_validate(data)
|
33
|
+
agentr_tools = await load_agentr_tools(config.model_dump(exclude_none=True)["agentrServers"])
|
34
|
+
mcp_tools = await load_mcp_tools(config.model_dump(exclude_none=True)["mcpServers"])
|
35
|
+
return agentr_tools + mcp_tools
|
@@ -176,96 +176,3 @@ class ServerConfig(BaseSettings):
|
|
176
176
|
with open(path) as f:
|
177
177
|
data = json.load(f)
|
178
178
|
return cls.model_validate(data)
|
179
|
-
|
180
|
-
|
181
|
-
class ClientTransportConfig(BaseModel):
|
182
|
-
"""Configuration for how an MCP client connects to an MCP server.
|
183
|
-
|
184
|
-
Specifies the transport protocol and its associated parameters, such as
|
185
|
-
the command for stdio, URL for HTTP-based transports (SSE, streamable_http),
|
186
|
-
and any necessary headers or environment variables.
|
187
|
-
"""
|
188
|
-
|
189
|
-
transport: str | None = Field(
|
190
|
-
default=None,
|
191
|
-
description="The transport protocol (e.g., 'stdio', 'sse', 'streamable_http'). Auto-detected in model_validate if not set.",
|
192
|
-
)
|
193
|
-
command: str | None = Field(
|
194
|
-
default=None, description="The command to execute for 'stdio' transport (e.g., 'python -m mcp_server.run')."
|
195
|
-
)
|
196
|
-
args: list[str] = Field(default=[], description="List of arguments for the 'stdio' command.")
|
197
|
-
env: dict[str, str] = Field(default={}, description="Environment variables to set for the 'stdio' command.")
|
198
|
-
url: str | None = Field(default=None, description="The URL for 'sse' or 'streamable_http' transport.")
|
199
|
-
headers: dict[str, str] = Field(
|
200
|
-
default={}, description="HTTP headers to include for 'sse' or 'streamable_http' transport."
|
201
|
-
)
|
202
|
-
|
203
|
-
@model_validator(mode="after")
|
204
|
-
def determine_transport_if_not_set(self) -> Self:
|
205
|
-
"""Determines and sets the transport type if not explicitly provided.
|
206
|
-
|
207
|
-
- If `command` is present, transport is set to 'stdio'.
|
208
|
-
- If `url` is present, transport is 'streamable_http' if URL ends with '/mcp',
|
209
|
-
otherwise 'sse' if URL ends with '/sse'.
|
210
|
-
- Raises ValueError if transport cannot be determined or if neither
|
211
|
-
`command` nor `url` is provided.
|
212
|
-
"""
|
213
|
-
if self.command:
|
214
|
-
self.transport = "stdio"
|
215
|
-
elif self.url:
|
216
|
-
# Remove search params from url
|
217
|
-
url = self.url.split("?")[0]
|
218
|
-
if url.rstrip("/").endswith("mcp"):
|
219
|
-
self.transport = "streamable_http"
|
220
|
-
elif url.rstrip("/").endswith("sse"):
|
221
|
-
self.transport = "sse"
|
222
|
-
else:
|
223
|
-
raise ValueError(f"Unknown transport: {self.url}")
|
224
|
-
else:
|
225
|
-
raise ValueError("Either command or url must be provided")
|
226
|
-
return self
|
227
|
-
|
228
|
-
|
229
|
-
class ClientConfig(BaseSettings):
|
230
|
-
"""Configuration for a client application that interacts with MCP servers and an LLM.
|
231
|
-
|
232
|
-
Defines connections to one or more MCP servers (via `mcpServers`) and
|
233
|
-
optionally, settings for an LLM to be used by the client (e.g., by an agent).
|
234
|
-
"""
|
235
|
-
|
236
|
-
mcpServers: dict[str, ClientTransportConfig] = Field(
|
237
|
-
...,
|
238
|
-
description="Dictionary of MCP server connections. Keys are descriptive names for the server, values are `ClientTransportConfig` objects defining how to connect to each server.",
|
239
|
-
)
|
240
|
-
apps: list[AppConfig] = Field(
|
241
|
-
default=[],
|
242
|
-
description="List of application configurations to load",
|
243
|
-
)
|
244
|
-
store: StoreConfig | None = Field(
|
245
|
-
default=None,
|
246
|
-
description="Default credential store configuration for applications that do not define their own specific store.",
|
247
|
-
)
|
248
|
-
model: str = Field(
|
249
|
-
default="openrouter/auto",
|
250
|
-
description="The model to use for the LLM.",
|
251
|
-
)
|
252
|
-
|
253
|
-
@classmethod
|
254
|
-
def load_json_config(cls, path: Path) -> Self:
|
255
|
-
"""Loads client configuration from a JSON file.
|
256
|
-
|
257
|
-
Args:
|
258
|
-
path (str, optional): The path to the JSON configuration file.
|
259
|
-
Defaults to "client_config.json".
|
260
|
-
|
261
|
-
Returns:
|
262
|
-
ClientConfig: An instance of ClientConfig populated with data
|
263
|
-
from the JSON file.
|
264
|
-
"""
|
265
|
-
with open(path) as f:
|
266
|
-
data = json.load(f)
|
267
|
-
return cls.model_validate(data)
|
268
|
-
|
269
|
-
def save_json_config(self, path: str) -> None:
|
270
|
-
with open(path, "w") as f:
|
271
|
-
json.dump(self.model_dump(), f, indent=4)
|
@@ -12,12 +12,7 @@ from universal_mcp.tools.adapters import (
|
|
12
12
|
convert_tool_to_openai_tool,
|
13
13
|
)
|
14
14
|
from universal_mcp.tools.tools import Tool
|
15
|
-
from universal_mcp.types import ToolFormat
|
16
|
-
|
17
|
-
# Constants
|
18
|
-
DEFAULT_IMPORTANT_TAG = "important"
|
19
|
-
TOOL_NAME_SEPARATOR = "_"
|
20
|
-
DEFAULT_APP_NAME = "common"
|
15
|
+
from universal_mcp.types import DEFAULT_APP_NAME, DEFAULT_IMPORTANT_TAG, TOOL_NAME_SEPARATOR, ToolFormat
|
21
16
|
|
22
17
|
|
23
18
|
def _get_app_and_tool_name(tool_name: str) -> tuple[str, str]:
|
@@ -31,8 +26,13 @@ def _get_app_and_tool_name(tool_name: str) -> tuple[str, str]:
|
|
31
26
|
return app_name, tool_name_without_app_name
|
32
27
|
|
33
28
|
|
29
|
+
def _sanitize_tool_names(tool_names: list[str]) -> list[str]:
|
30
|
+
"""Sanitize tool names by removing empty strings and converting to lowercase."""
|
31
|
+
return [_get_app_and_tool_name(name)[1].lower() for name in tool_names if name]
|
32
|
+
|
33
|
+
|
34
34
|
def _filter_by_name(tools: list[Tool], tool_names: list[str] | None) -> list[Tool]:
|
35
|
-
"""Filter tools by name using
|
35
|
+
"""Filter tools by name using set comparison for efficient matching.
|
36
36
|
|
37
37
|
Args:
|
38
38
|
tools: List of tools to filter.
|
@@ -45,16 +45,14 @@ def _filter_by_name(tools: list[Tool], tool_names: list[str] | None) -> list[Too
|
|
45
45
|
return tools
|
46
46
|
|
47
47
|
logger.debug(f"Filtering tools by names: {tool_names}")
|
48
|
-
|
49
|
-
|
48
|
+
tool_names_set = set(_sanitize_tool_names(tool_names))
|
49
|
+
logger.debug(f"Tool names set: {tool_names_set}")
|
50
50
|
filtered_tools = []
|
51
51
|
for tool in tools:
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
break
|
57
|
-
|
52
|
+
if tool.tool_name.lower() in tool_names_set:
|
53
|
+
filtered_tools.append(tool)
|
54
|
+
logger.debug(f"Tool '{tool.name}' matched name filter")
|
55
|
+
logger.debug(f"Filtered tools: {[tool.name for tool in filtered_tools]}")
|
58
56
|
return filtered_tools
|
59
57
|
|
60
58
|
|
@@ -200,11 +198,6 @@ class ToolManager:
|
|
200
198
|
app_name: Application name to group the tools under.
|
201
199
|
"""
|
202
200
|
for tool in tools:
|
203
|
-
app_name, tool_name = _get_app_and_tool_name(tool.name)
|
204
|
-
|
205
|
-
# Add prefix to tool name, if not already present
|
206
|
-
tool.name = f"{app_name}{TOOL_NAME_SEPARATOR}{tool_name}"
|
207
|
-
tool.tags.append(app_name)
|
208
201
|
self.add_tool(tool)
|
209
202
|
|
210
203
|
def remove_tool(self, name: str) -> bool:
|
@@ -259,14 +252,14 @@ class ToolManager:
|
|
259
252
|
|
260
253
|
try:
|
261
254
|
tool_instance = Tool.from_function(function)
|
262
|
-
tool_instance.
|
255
|
+
tool_instance.app_name = app.name
|
263
256
|
if app.name not in tool_instance.tags:
|
264
257
|
tool_instance.tags.append(app.name)
|
265
258
|
tools.append(tool_instance)
|
266
259
|
except Exception as e:
|
267
260
|
tool_name = getattr(function, "__name__", "unknown")
|
268
261
|
logger.error(f"Failed to create Tool from '{tool_name}' in {app.name}: {e}")
|
269
|
-
|
262
|
+
print([tool.name for tool in tools])
|
270
263
|
if tags:
|
271
264
|
tools = _filter_by_tags(tools, tags)
|
272
265
|
|
@@ -7,6 +7,7 @@ from pydantic import BaseModel, Field, create_model
|
|
7
7
|
|
8
8
|
from universal_mcp.exceptions import NotAuthorizedError, ToolError
|
9
9
|
from universal_mcp.tools.docstring_parser import parse_docstring
|
10
|
+
from universal_mcp.types import TOOL_NAME_SEPARATOR
|
10
11
|
|
11
12
|
from .func_metadata import FuncMetadata
|
12
13
|
|
@@ -31,8 +32,9 @@ class Tool(BaseModel):
|
|
31
32
|
"""Internal tool registration info."""
|
32
33
|
|
33
34
|
fn: Callable[..., Any] = Field(exclude=True)
|
34
|
-
|
35
|
-
|
35
|
+
app_name: str | None = Field(default=None, description="Name of the app that the tool belongs to")
|
36
|
+
tool_name: str = Field(description="Name of the tool")
|
37
|
+
description: str | None = Field(default=None, description="Summary line from the tool's docstring")
|
36
38
|
args_description: dict[str, str] = Field(
|
37
39
|
default_factory=dict, description="Descriptions of arguments from the docstring"
|
38
40
|
)
|
@@ -44,11 +46,15 @@ class Tool(BaseModel):
|
|
44
46
|
tags: list[str] = Field(default_factory=list, description="Tags for categorizing the tool")
|
45
47
|
parameters: dict[str, Any] = Field(description="JSON schema for tool parameters")
|
46
48
|
output_schema: dict[str, Any] | None = Field(default=None, description="JSON schema for tool output")
|
47
|
-
fn_metadata: FuncMetadata = Field(
|
48
|
-
description="Metadata about the function including a pydantic model for tool arguments"
|
49
|
+
fn_metadata: FuncMetadata | None = Field(
|
50
|
+
default=None, description="Metadata about the function including a pydantic model for tool arguments"
|
49
51
|
)
|
50
52
|
is_async: bool = Field(description="Whether the tool is async")
|
51
53
|
|
54
|
+
@property
|
55
|
+
def name(self) -> str:
|
56
|
+
return f"{self.app_name}{TOOL_NAME_SEPARATOR}{self.tool_name}" if self.app_name else self.tool_name
|
57
|
+
|
52
58
|
@classmethod
|
53
59
|
def from_function(
|
54
60
|
cls,
|
@@ -81,7 +87,7 @@ class Tool(BaseModel):
|
|
81
87
|
|
82
88
|
return cls(
|
83
89
|
fn=fn,
|
84
|
-
|
90
|
+
tool_name=func_name,
|
85
91
|
description=parsed_doc["summary"],
|
86
92
|
args_description=simple_args_descriptions,
|
87
93
|
returns_description=parsed_doc["returns"],
|
@@ -0,0 +1,35 @@
|
|
1
|
+
from enum import Enum
|
2
|
+
from typing import Literal
|
3
|
+
|
4
|
+
from pydantic import BaseModel
|
5
|
+
|
6
|
+
# Constants
|
7
|
+
DEFAULT_IMPORTANT_TAG = "important"
|
8
|
+
TOOL_NAME_SEPARATOR = "__"
|
9
|
+
DEFAULT_APP_NAME = "common"
|
10
|
+
|
11
|
+
|
12
|
+
class ToolFormat(str, Enum):
|
13
|
+
"""Supported tool formats."""
|
14
|
+
|
15
|
+
NATIVE = "native"
|
16
|
+
MCP = "mcp"
|
17
|
+
LANGCHAIN = "langchain"
|
18
|
+
OPENAI = "openai"
|
19
|
+
|
20
|
+
|
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 ToolConfig(BaseModel):
|
34
|
+
mcpServers: dict[str, MCPConnection] | None = None
|
35
|
+
agentrServers: dict[str, AgentrConnection] | None = None
|
@@ -1,58 +0,0 @@
|
|
1
|
-
from langgraph.prebuilt import create_react_agent
|
2
|
-
from loguru import logger
|
3
|
-
|
4
|
-
from universal_mcp.agentr.registry import AgentrRegistry
|
5
|
-
from universal_mcp.tools.adapters import ToolFormat
|
6
|
-
from universal_mcp.tools.manager import ToolManager
|
7
|
-
|
8
|
-
from .base import BaseAgent
|
9
|
-
from .llm import get_llm
|
10
|
-
|
11
|
-
|
12
|
-
class ReactAgent(BaseAgent):
|
13
|
-
def __init__(
|
14
|
-
self, name: str, instructions: str, model: str, tools: list[str] | None = None, max_iterations: int = 10
|
15
|
-
):
|
16
|
-
super().__init__(name, instructions, model)
|
17
|
-
self.llm = get_llm(model)
|
18
|
-
self.max_iterations = max_iterations
|
19
|
-
self.tool_manager = ToolManager()
|
20
|
-
registry = AgentrRegistry()
|
21
|
-
if tools:
|
22
|
-
registry.load_tools(tools, self.tool_manager)
|
23
|
-
logger.debug(f"Initialized ReactAgent: name={name}, model={model}")
|
24
|
-
self._graph = self._build_graph()
|
25
|
-
|
26
|
-
@property
|
27
|
-
def graph(self):
|
28
|
-
return self._graph
|
29
|
-
|
30
|
-
def _build_graph(self):
|
31
|
-
tools = self.tool_manager.list_tools(format=ToolFormat.LANGCHAIN) if self.tool_manager else []
|
32
|
-
return create_react_agent(
|
33
|
-
self.llm,
|
34
|
-
tools,
|
35
|
-
prompt=self._build_system_message(),
|
36
|
-
checkpointer=self.memory,
|
37
|
-
)
|
38
|
-
|
39
|
-
def _build_system_message(self) -> str:
|
40
|
-
system_message = f"""You are {self.name}. {self.instructions}
|
41
|
-
|
42
|
-
You have access to various tools that can help you answer questions and complete tasks. When you need to use a tool:
|
43
|
-
|
44
|
-
1. Think about what information you need
|
45
|
-
2. Call the appropriate tool with the right parameters
|
46
|
-
3. Use the tool results to provide a comprehensive answer
|
47
|
-
|
48
|
-
Always explain your reasoning and be thorough in your responses. If you need to use multiple tools to answer a question completely, do so."""
|
49
|
-
return system_message
|
50
|
-
|
51
|
-
|
52
|
-
if __name__ == "__main__":
|
53
|
-
import asyncio
|
54
|
-
|
55
|
-
agent = ReactAgent(
|
56
|
-
"Universal React Agent", "You are a helpful assistant", "gpt-4.1", tools=["google-mail_send_email"]
|
57
|
-
)
|
58
|
-
asyncio.run(agent.run_interactive())
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/codeact/__init__.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/codeact/sandbox.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/codeact/test.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/agents/codeact/utils.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/applications/__init__.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/applications/application.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/applications/sample/app.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/integrations/__init__.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/integrations/integration.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/tools/docstring_parser.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/tools/func_metadata.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/__inti__.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/api_generator.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/api_splitter.py
RENAMED
File without changes
|
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/docgen.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/filters.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/openapi.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/postprocessor.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/preprocessor.py
RENAMED
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/openapi/readme.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{universal_mcp-0.1.24rc4 → universal_mcp-0.1.24rc6}/src/universal_mcp/utils/templates/README.md.j2
RENAMED
File without changes
|
File without changes
|
File without changes
|