universal-mcp 0.1.24rc2__py3-none-any.whl → 0.1.24rc4__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/README.md +201 -0
- universal_mcp/agentr/__init__.py +6 -0
- universal_mcp/agentr/agentr.py +30 -0
- universal_mcp/{utils/agentr.py → agentr/client.py} +19 -3
- universal_mcp/agentr/integration.py +104 -0
- universal_mcp/agentr/registry.py +91 -0
- universal_mcp/agentr/server.py +51 -0
- universal_mcp/agents/__init__.py +6 -0
- universal_mcp/agents/auto.py +576 -0
- universal_mcp/agents/base.py +88 -0
- universal_mcp/agents/cli.py +27 -0
- universal_mcp/agents/codeact/__init__.py +243 -0
- universal_mcp/agents/codeact/sandbox.py +27 -0
- universal_mcp/agents/codeact/test.py +15 -0
- universal_mcp/agents/codeact/utils.py +61 -0
- universal_mcp/agents/hil.py +104 -0
- universal_mcp/agents/llm.py +10 -0
- universal_mcp/agents/react.py +58 -0
- universal_mcp/agents/simple.py +40 -0
- universal_mcp/agents/utils.py +111 -0
- universal_mcp/analytics.py +5 -7
- universal_mcp/applications/__init__.py +42 -75
- universal_mcp/applications/application.py +1 -1
- universal_mcp/applications/sample/app.py +245 -0
- universal_mcp/cli.py +10 -3
- universal_mcp/config.py +33 -7
- universal_mcp/exceptions.py +4 -0
- universal_mcp/integrations/__init__.py +0 -15
- universal_mcp/integrations/integration.py +9 -91
- universal_mcp/servers/__init__.py +2 -14
- universal_mcp/servers/server.py +10 -51
- universal_mcp/tools/__init__.py +3 -0
- universal_mcp/tools/adapters.py +20 -11
- universal_mcp/tools/manager.py +29 -56
- universal_mcp/tools/registry.py +41 -0
- universal_mcp/tools/tools.py +22 -1
- universal_mcp/types.py +10 -0
- universal_mcp/utils/common.py +245 -0
- universal_mcp/utils/openapi/api_generator.py +46 -18
- universal_mcp/utils/openapi/cli.py +445 -19
- universal_mcp/utils/openapi/openapi.py +284 -21
- universal_mcp/utils/openapi/postprocessor.py +275 -0
- universal_mcp/utils/openapi/preprocessor.py +1 -1
- universal_mcp/utils/openapi/test_generator.py +287 -0
- universal_mcp/utils/prompts.py +188 -341
- universal_mcp/utils/testing.py +190 -2
- {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc4.dist-info}/METADATA +17 -3
- universal_mcp-0.1.24rc4.dist-info/RECORD +71 -0
- universal_mcp/applications/sample_tool_app.py +0 -80
- universal_mcp/client/agents/__init__.py +0 -4
- universal_mcp/client/agents/base.py +0 -38
- universal_mcp/client/agents/llm.py +0 -115
- universal_mcp/client/agents/react.py +0 -67
- universal_mcp/client/cli.py +0 -181
- universal_mcp-0.1.24rc2.dist-info/RECORD +0 -53
- {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc4.dist-info}/WHEEL +0 -0
- {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc4.dist-info}/entry_points.txt +0 -0
- {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc4.dist-info}/licenses/LICENSE +0 -0
universal_mcp/utils/testing.py
CHANGED
@@ -1,9 +1,44 @@
|
|
1
|
+
import os
|
2
|
+
from dataclasses import dataclass
|
3
|
+
|
4
|
+
from langchain_core.messages import AIMessage, HumanMessage
|
5
|
+
from langgraph.prebuilt import create_react_agent
|
1
6
|
from loguru import logger
|
7
|
+
from pydantic import BaseModel, SecretStr
|
8
|
+
|
9
|
+
from universal_mcp.agentr import AgentrIntegration
|
10
|
+
from universal_mcp.agentr.client import AgentrClient
|
11
|
+
from universal_mcp.applications import APIApplication, BaseApplication
|
12
|
+
from universal_mcp.tools import Tool, ToolManager
|
13
|
+
from universal_mcp.types import ToolFormat
|
14
|
+
|
15
|
+
|
16
|
+
class ValidateResult(BaseModel):
|
17
|
+
success: bool
|
18
|
+
reasoning: str
|
19
|
+
|
20
|
+
|
21
|
+
def check_application_instance(app_instance: BaseApplication, app_name: str):
|
22
|
+
"""
|
23
|
+
Performs a series of assertions to validate an application instance and its tools.
|
2
24
|
|
3
|
-
|
25
|
+
This function checks for the following:
|
26
|
+
- The application instance is not None.
|
27
|
+
- The application instance's name matches the expected application name.
|
28
|
+
- The application has at least one tool.
|
29
|
+
- Each tool has a non-None name with a valid length (1-47 characters).
|
30
|
+
- Each tool has a non-None description.
|
31
|
+
- All tool names are unique within the application.
|
32
|
+
- The application has at least one tool tagged as "important".
|
4
33
|
|
34
|
+
Args:
|
35
|
+
app_instance: The application instance to check. Must be an instance of BaseApplication.
|
36
|
+
app_name: The expected name of the application.
|
37
|
+
|
38
|
+
Raises:
|
39
|
+
AssertionError: If any of the validation checks fail.
|
40
|
+
"""
|
5
41
|
|
6
|
-
def check_application_instance(app_instance, app_name):
|
7
42
|
assert app_instance is not None, f"Application object is None for {app_name}"
|
8
43
|
assert app_instance.name == app_name, (
|
9
44
|
f"Application instance name '{app_instance.name}' does not match expected name '{app_name}'"
|
@@ -29,3 +64,156 @@ def check_application_instance(app_instance, app_name):
|
|
29
64
|
if "important" in tool.tags:
|
30
65
|
important_tools.append(tool.name)
|
31
66
|
assert len(important_tools) > 0, f"No important tools found for {app_name}"
|
67
|
+
|
68
|
+
|
69
|
+
@dataclass
|
70
|
+
class AutomationTestCase:
|
71
|
+
"""Generic test case for automation testing."""
|
72
|
+
|
73
|
+
app: str
|
74
|
+
app_instance: APIApplication | None = None
|
75
|
+
tools: list[str] | None = None
|
76
|
+
tasks: list[str] | None = None
|
77
|
+
validate_query: str | None = None
|
78
|
+
|
79
|
+
|
80
|
+
def create_agentr_client(app_name: str) -> AgentrClient:
|
81
|
+
"""
|
82
|
+
Create an AgentrClient with appropriate API key and base URL.
|
83
|
+
|
84
|
+
Args:
|
85
|
+
app_name: Name of the application (used for app-specific environment variables)
|
86
|
+
|
87
|
+
Returns:
|
88
|
+
AgentrClient instance
|
89
|
+
"""
|
90
|
+
api_key = os.environ.get(f"{app_name.upper()}_API_KEY") or os.environ.get("AGENTR_API_KEY")
|
91
|
+
base_url = os.environ.get(f"{app_name.upper()}_BASE_URL") or os.environ.get(
|
92
|
+
"AGENTR_BASE_URL", "https://api.agentr.dev"
|
93
|
+
)
|
94
|
+
return AgentrClient(api_key=api_key, base_url=base_url)
|
95
|
+
|
96
|
+
|
97
|
+
def create_integration(app_name: str) -> AgentrIntegration:
|
98
|
+
"""
|
99
|
+
Create an AgentRIntegration instance with appropriate client.
|
100
|
+
|
101
|
+
Args:
|
102
|
+
app_name: Name of the application
|
103
|
+
|
104
|
+
Returns:
|
105
|
+
AgentRIntegration instance
|
106
|
+
"""
|
107
|
+
client = create_agentr_client(app_name)
|
108
|
+
return AgentrIntegration(name=app_name, client=client)
|
109
|
+
|
110
|
+
|
111
|
+
def create_app_with_integration(app_name: str, app_class: type[APIApplication]) -> APIApplication:
|
112
|
+
"""
|
113
|
+
Create an application instance with integration.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
app_name: Name of the application
|
117
|
+
app_class: Class of the application to instantiate
|
118
|
+
|
119
|
+
Returns:
|
120
|
+
Application instance with integration
|
121
|
+
"""
|
122
|
+
integration = create_integration(app_name)
|
123
|
+
return app_class(integration=integration)
|
124
|
+
|
125
|
+
|
126
|
+
def load_app_with_integration(app_name: str, app_class: type[APIApplication]) -> APIApplication:
|
127
|
+
"""
|
128
|
+
Load application instance with real integration.
|
129
|
+
|
130
|
+
Args:
|
131
|
+
app_name: Name of the application
|
132
|
+
app_class: Class of the application to instantiate
|
133
|
+
|
134
|
+
Returns:
|
135
|
+
Instantiated application with integration
|
136
|
+
"""
|
137
|
+
integration = create_integration(app_name)
|
138
|
+
return app_class(integration=integration)
|
139
|
+
|
140
|
+
|
141
|
+
async def execute_automation_test(test_case: AutomationTestCase, app_instance: APIApplication | None = None) -> None:
|
142
|
+
"""
|
143
|
+
Execute an automation test case using LangGraph ReAct agent.
|
144
|
+
|
145
|
+
Args:
|
146
|
+
test_case: Test case to execute
|
147
|
+
app_instance: The application instance to test (optional if provided in test_case)
|
148
|
+
"""
|
149
|
+
tool_manager = ToolManager()
|
150
|
+
|
151
|
+
if app_instance is None:
|
152
|
+
app_instance = test_case.app_instance
|
153
|
+
if app_instance is None:
|
154
|
+
raise ValueError("No app_instance provided in test_case or as parameter")
|
155
|
+
|
156
|
+
all_tools = app_instance.list_tools()
|
157
|
+
logger.info(f"Available tools from app: {[getattr(t, '__name__', str(t)) for t in all_tools]}")
|
158
|
+
|
159
|
+
tool_manager.register_tools_from_app(app_instance)
|
160
|
+
|
161
|
+
all_registered = tool_manager.get_tools_by_app(app_name=app_instance.name)
|
162
|
+
logger.info(f"All registered tools: {[t.name for t in all_registered]}")
|
163
|
+
|
164
|
+
if test_case.tools:
|
165
|
+
tools = tool_manager.list_tools(
|
166
|
+
format=ToolFormat.LANGCHAIN, app_name=app_instance.name, tool_names=test_case.tools
|
167
|
+
)
|
168
|
+
else:
|
169
|
+
tools = tool_manager.list_tools(format=ToolFormat.LANGCHAIN, app_name=app_instance.name)
|
170
|
+
|
171
|
+
logger.info(f"Tools for test: {[tool.name for tool in tools]}")
|
172
|
+
|
173
|
+
azure_endpoint = os.environ.get("AZURE_OPENAI_ENDPOINT")
|
174
|
+
azure_api_key = os.environ.get("AZURE_OPENAI_API_KEY")
|
175
|
+
azure_deployment = os.environ.get("AZURE_OPENAI_DEPLOYMENT", "o4-mini")
|
176
|
+
api_version = os.environ.get("AZURE_OPENAI_API_VERSION", "2025-03-01-preview")
|
177
|
+
|
178
|
+
if not azure_endpoint or not azure_api_key:
|
179
|
+
raise ValueError(
|
180
|
+
"Azure OpenAI credentials not found. Please set AZURE_OPENAI_ENDPOINT and AZURE_OPENAI_API_KEY environment variables."
|
181
|
+
)
|
182
|
+
|
183
|
+
from langchain_openai import AzureChatOpenAI
|
184
|
+
|
185
|
+
llm = AzureChatOpenAI(
|
186
|
+
azure_endpoint=azure_endpoint,
|
187
|
+
azure_deployment=azure_deployment,
|
188
|
+
api_key=SecretStr(azure_api_key) if azure_api_key else None,
|
189
|
+
api_version=api_version,
|
190
|
+
)
|
191
|
+
logger.info(f"Using Azure OpenAI with deployment: {azure_deployment}")
|
192
|
+
|
193
|
+
agent = create_react_agent(
|
194
|
+
model=llm,
|
195
|
+
tools=tools,
|
196
|
+
)
|
197
|
+
|
198
|
+
messages = []
|
199
|
+
for task in test_case.tasks or []:
|
200
|
+
try:
|
201
|
+
messages.append(HumanMessage(content=task))
|
202
|
+
response = await agent.ainvoke({"messages": messages})
|
203
|
+
messages.append(AIMessage(content=response["messages"][-1].content))
|
204
|
+
logger.info(f"Task: {task}")
|
205
|
+
logger.info(f"Response: {response['messages'][-1].content}")
|
206
|
+
logger.info("---")
|
207
|
+
except Exception as e:
|
208
|
+
logger.error(f"Error: {e}")
|
209
|
+
import traceback
|
210
|
+
|
211
|
+
traceback.print_exc()
|
212
|
+
raise AssertionError(f"Task execution failed: {e}") from e
|
213
|
+
|
214
|
+
if test_case.validate_query:
|
215
|
+
messages.append(HumanMessage(content=test_case.validate_query))
|
216
|
+
structured_llm = llm.with_structured_output(ValidateResult)
|
217
|
+
result = await structured_llm.ainvoke(messages)
|
218
|
+
logger.info(f"Validation result: {result}")
|
219
|
+
assert result.success, f"Validation failed: {result.reasoning}"
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: universal-mcp
|
3
|
-
Version: 0.1.
|
3
|
+
Version: 0.1.24rc4
|
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,17 +8,26 @@ 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[all]
|
11
|
+
Requires-Dist: gql[all]==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
|
15
|
+
Requires-Dist: langchain-mcp-adapters>=0.1.9
|
16
|
+
Requires-Dist: langchain-openai>=0.3.27
|
17
|
+
Requires-Dist: langgraph-cli[inmem]>=0.3.4
|
18
|
+
Requires-Dist: langgraph>=0.5.2
|
19
|
+
Requires-Dist: langsmith>=0.4.5
|
15
20
|
Requires-Dist: loguru>=0.7.3
|
16
|
-
Requires-Dist: mcp>=1.
|
21
|
+
Requires-Dist: mcp>=1.10.0
|
22
|
+
Requires-Dist: mkdocs-material>=9.6.15
|
23
|
+
Requires-Dist: mkdocs>=1.6.1
|
17
24
|
Requires-Dist: posthog>=3.24.0
|
18
25
|
Requires-Dist: pydantic-settings>=2.8.1
|
19
26
|
Requires-Dist: pydantic>=2.11.1
|
20
27
|
Requires-Dist: pyyaml>=6.0.2
|
21
28
|
Requires-Dist: rich>=14.0.0
|
29
|
+
Requires-Dist: streamlit>=1.46.1
|
30
|
+
Requires-Dist: ty>=0.0.1a17
|
22
31
|
Requires-Dist: typer>=0.15.2
|
23
32
|
Provides-Extra: dev
|
24
33
|
Requires-Dist: litellm>=1.30.7; extra == 'dev'
|
@@ -28,6 +37,11 @@ Requires-Dist: pyright>=1.1.398; extra == 'dev'
|
|
28
37
|
Requires-Dist: pytest-asyncio>=0.26.0; extra == 'dev'
|
29
38
|
Requires-Dist: pytest>=8.3.5; extra == 'dev'
|
30
39
|
Requires-Dist: ruff>=0.11.4; extra == 'dev'
|
40
|
+
Provides-Extra: docs
|
41
|
+
Requires-Dist: mkdocs-glightbox>=0.4.0; extra == 'docs'
|
42
|
+
Requires-Dist: mkdocs-material[imaging]>=9.5.45; extra == 'docs'
|
43
|
+
Requires-Dist: mkdocs>=1.6.1; extra == 'docs'
|
44
|
+
Requires-Dist: mkdocstrings-python>=1.12.2; extra == 'docs'
|
31
45
|
Description-Content-Type: text/markdown
|
32
46
|
|
33
47
|
# Universal MCP
|
@@ -0,0 +1,71 @@
|
|
1
|
+
universal_mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
universal_mcp/analytics.py,sha256=RzS88HSvJRGMjdJeLHnOgWzfKSb1jVnvOcD7NHqfERw,3733
|
3
|
+
universal_mcp/cli.py,sha256=pPnIWLhSrLV9ukI8YAg2znehCR3VovhEkmh8XkRT3MU,2505
|
4
|
+
universal_mcp/config.py,sha256=pkKs0gST65umzmNEvjHiOAtmiBaaICi45WG4Z0My0ak,11983
|
5
|
+
universal_mcp/exceptions.py,sha256=Uen8UFgLHGlSwXgRUyF-nhqTwdiBuL3okgBVRV2AgtA,2150
|
6
|
+
universal_mcp/logger.py,sha256=VmH_83efpErLEDTJqz55Dp0dioTXfGvMBLZUx5smOLc,2116
|
7
|
+
universal_mcp/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
8
|
+
universal_mcp/types.py,sha256=dVK7uSMuhvx5Xk6L7GGdjeaAIKiEwQskTmaVFwIS8LQ,176
|
9
|
+
universal_mcp/agentr/README.md,sha256=xXM8JzPyrM2__pGhxHrGEUn9uP2y2bdF00wwcQtBUCI,6441
|
10
|
+
universal_mcp/agentr/__init__.py,sha256=ogOhH_OJwkoUZu_2nQJc7-vEGmYQxEjOE511-6ubrX0,217
|
11
|
+
universal_mcp/agentr/agentr.py,sha256=JfawuREfXAyeNUE7o58DzTPhmQXuwsB_Da7c1Gf3Qxw,1059
|
12
|
+
universal_mcp/agentr/client.py,sha256=oyF6VKq56UMVf5L1WnFTMSZ85W8Qcy-5HZ5XOGiIELM,4139
|
13
|
+
universal_mcp/agentr/integration.py,sha256=V5GjqocqS02tRoI8MeV9PL6m-BzejwBzgJhOHo4MxAE,4212
|
14
|
+
universal_mcp/agentr/registry.py,sha256=b9sr5JyT3HLj3e7GFpdXpT7ofGwLQc--y8k2DqF5dE0,3542
|
15
|
+
universal_mcp/agentr/server.py,sha256=bIPmHMiKKwnUYnxmfZVRh1thcn7Rytm_-bNiXTfANzc,2098
|
16
|
+
universal_mcp/agents/__init__.py,sha256=vgixOLTCcCmSweENV7GSAuOPyHXlE4XAbvOXyr4MrRA,185
|
17
|
+
universal_mcp/agents/auto.py,sha256=o__71BCOHSfaj7Xt0PhsamVXdeP4o7irhtmu1q6-3Fo,25336
|
18
|
+
universal_mcp/agents/base.py,sha256=U5JtpOopmUi73qcxtY9T2qJpYD7e6c62iVlIr3m5Chc,3430
|
19
|
+
universal_mcp/agents/cli.py,sha256=7GdRBpu9rhZPiC2vaNQXWI7K-0yCnvdlmE0IFpvy2Gk,539
|
20
|
+
universal_mcp/agents/hil.py,sha256=CTgX7CoFEyTFIaNaL-id2WALOPd0VBb79pHkQK8quM8,3671
|
21
|
+
universal_mcp/agents/llm.py,sha256=YNxN43bVhGfdYs09yPkdkGCKJkj-2UNqkB1EFmtnUS4,309
|
22
|
+
universal_mcp/agents/react.py,sha256=6L--LcuU5Ityi2UiZSYJWgp-lXGkxvpsx8mjvpoNRBQ,2021
|
23
|
+
universal_mcp/agents/simple.py,sha256=UfmQIIff--_Y0DQ6oivRciHqSZvRqy_qwQn_UYVzYy8,1146
|
24
|
+
universal_mcp/agents/utils.py,sha256=7kwFpD0Rv6JqHG-LlNCVwSu_xRX-N119mUmiBroHJL4,4109
|
25
|
+
universal_mcp/agents/codeact/__init__.py,sha256=5D_I3lI_3tWjZERRoFav_bPe9UDaJ53pDzZYtyixg3E,10097
|
26
|
+
universal_mcp/agents/codeact/sandbox.py,sha256=lGRzhuXTHCB1qauuOI3bH1-fPTsyL6Lf9EmMIz4C2xQ,1039
|
27
|
+
universal_mcp/agents/codeact/test.py,sha256=bva-KkBNbGZn2f9nmmo9SNPQnY24Ni5gLHhJ5I0cm0k,481
|
28
|
+
universal_mcp/agents/codeact/utils.py,sha256=VuMvLTxBBh3pgaJk8RWj5AK8XZFF-1gnZJ6jFLeM_CI,1690
|
29
|
+
universal_mcp/applications/__init__.py,sha256=HrCnGdAT7w4puw2_VulBfjOLku9D5DuMaOwAuQzu6nI,2067
|
30
|
+
universal_mcp/applications/application.py,sha256=pGF9Rb2D6qzlaSwlcfZ-dNqPtsLkQTqL3jpsRuJ6-qE,23835
|
31
|
+
universal_mcp/applications/sample/app.py,sha256=E0JwaWD7qytwawb_iWc1pBnJ-Te7MMtab4MxOOebLdc,8972
|
32
|
+
universal_mcp/client/oauth.py,sha256=O00zOUfQxINaruFU2zt-64DIR1_mAqrY8ykLQo-teJU,8679
|
33
|
+
universal_mcp/client/token_store.py,sha256=6VAzjzJG49wYvmEDqksFvb-fVqdjHIKWv7yYyh_AuF8,3912
|
34
|
+
universal_mcp/client/transport.py,sha256=xgAKBJ1-yCcTtl9cxzJgRn6to5Y9EvCwLc_WpDck3Dg,11838
|
35
|
+
universal_mcp/integrations/__init__.py,sha256=tfzLyPEPly5tfIcT8K6-oKCr_MEFGxOROHy_NeVy0KM,200
|
36
|
+
universal_mcp/integrations/integration.py,sha256=H-hOoDHqk78A4Fi_TGN7OOFS7PDfqXK_nedH8iSz-6A,16459
|
37
|
+
universal_mcp/servers/__init__.py,sha256=speBb_E94UJa4A6Fv8RHFeoJ7cR-q2bCMtKV7R21P5w,142
|
38
|
+
universal_mcp/servers/server.py,sha256=qHeHm4UFVUr8TAailbEkWGq7EdlOASkOevY_0lyrWWs,5882
|
39
|
+
universal_mcp/stores/__init__.py,sha256=quvuwhZnpiSLuojf0NfmBx2xpaCulv3fbKtKaSCEmuM,603
|
40
|
+
universal_mcp/stores/store.py,sha256=yWbEGZb53z3fpVyqGWbes63z1CtIzC_IuM49OXy__UY,10137
|
41
|
+
universal_mcp/tools/__init__.py,sha256=jC8hsqfTdtn32yU57AVFUXiU3ZmUOCfCERSCaNEIH7E,395
|
42
|
+
universal_mcp/tools/adapters.py,sha256=YJ2oqgc8JgmtsdRRtvO-PO0Q0bKqTJ4Y3J6yxlESoTo,3947
|
43
|
+
universal_mcp/tools/docstring_parser.py,sha256=efEOE-ME7G5Jbbzpn7pN2xNuyu2M5zfZ1Tqu1lRB0Gk,8392
|
44
|
+
universal_mcp/tools/func_metadata.py,sha256=F4jd--hoZWKPBbZihVtluYKUsIdXdq4a0VWRgMl5k-Q,10838
|
45
|
+
universal_mcp/tools/manager.py,sha256=MajVskIptgXv1uZzwnSRycj1TSi7nhn4ebNSRkSSEDs,10455
|
46
|
+
universal_mcp/tools/registry.py,sha256=XsmVZL1rY5XgIBPTmvKKBWFLAvB3d9LfYMb11b4wSPI,1169
|
47
|
+
universal_mcp/tools/tools.py,sha256=1Q8bKiqj1E_-swvjmNHv16Orpd4p_HQtMKGxfqPmoPI,4570
|
48
|
+
universal_mcp/utils/__init__.py,sha256=8wi4PGWu-SrFjNJ8U7fr2iFJ1ktqlDmSKj1xYd7KSDc,41
|
49
|
+
universal_mcp/utils/common.py,sha256=3aJK3AnBkmYf-dbsFLaEu_dGuXQ0Qi2HuqYTueLVhXQ,10968
|
50
|
+
universal_mcp/utils/installation.py,sha256=PU_GfHPqzkumKk-xG4L9CkBzSmABxmchwblZkx-zY-I,7204
|
51
|
+
universal_mcp/utils/prompts.py,sha256=FJhqE0gPXDzYHS8gOjAVrdqVxc9X12ESnpd4C3jDSMI,27547
|
52
|
+
universal_mcp/utils/singleton.py,sha256=RoOiKxBOAhp0TK1QaMDYi-8GjRcy2Vh-bAOuIAcYan0,775
|
53
|
+
universal_mcp/utils/testing.py,sha256=J857Xt5K-hMxTc8UNJWlkzLbca1zObjwNhNXzYGxBHI,8009
|
54
|
+
universal_mcp/utils/openapi/__inti__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
55
|
+
universal_mcp/utils/openapi/api_generator.py,sha256=892AWoOCzzvFHVfSpEBg1m4LFRnWMmOSeY0DgLDW0fU,6960
|
56
|
+
universal_mcp/utils/openapi/api_splitter.py,sha256=io7fV-E8hUIR4NxFlakqydbgrQF6aBAnZHPMlpxw-wc,20967
|
57
|
+
universal_mcp/utils/openapi/cli.py,sha256=az5ObS74R-MmDCOZ1PHTJVKZJrHnsBOAweOUa7A-GqA,25918
|
58
|
+
universal_mcp/utils/openapi/docgen.py,sha256=DNmwlhg_-TRrHa74epyErMTRjV2nutfCQ7seb_Rq5hE,21366
|
59
|
+
universal_mcp/utils/openapi/filters.py,sha256=96FajO5nLbvjNPy2A1HvSS9jqpzMDHd4q_QTP-DIsPI,3842
|
60
|
+
universal_mcp/utils/openapi/openapi.py,sha256=0Pn_ugkEwL0eMtctjc0XDMPZdB3SBOqza_J6BK8i_SY,62165
|
61
|
+
universal_mcp/utils/openapi/postprocessor.py,sha256=NKvpXi73INRXxj1KOu8Ph3loWGICx2Uyy2Q8uOOqBoc,12177
|
62
|
+
universal_mcp/utils/openapi/preprocessor.py,sha256=r4n0WQI__OzPL8FTza7jxiM4EYeZwa-3tvEJaJYZC44,63081
|
63
|
+
universal_mcp/utils/openapi/readme.py,sha256=R2Jp7DUXYNsXPDV6eFTkLiy7MXbSULUj1vHh4O_nB4c,2974
|
64
|
+
universal_mcp/utils/openapi/test_generator.py,sha256=h44gQXEXmrw4pD3-XNHKB7T9c2lDomqrJxVO6oszCqM,12186
|
65
|
+
universal_mcp/utils/templates/README.md.j2,sha256=Mrm181YX-o_-WEfKs01Bi2RJy43rBiq2j6fTtbWgbTA,401
|
66
|
+
universal_mcp/utils/templates/api_client.py.j2,sha256=972Im7LNUAq3yZTfwDcgivnb-b8u6_JLKWXwoIwXXXQ,908
|
67
|
+
universal_mcp-0.1.24rc4.dist-info/METADATA,sha256=q3DM_RctCJdHxfeY2CwcRiH5VuESYHr7sb_GB-c4vRw,3116
|
68
|
+
universal_mcp-0.1.24rc4.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
69
|
+
universal_mcp-0.1.24rc4.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
|
70
|
+
universal_mcp-0.1.24rc4.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
|
71
|
+
universal_mcp-0.1.24rc4.dist-info/RECORD,,
|
@@ -1,80 +0,0 @@
|
|
1
|
-
import datetime
|
2
|
-
|
3
|
-
from universal_mcp.applications.application import BaseApplication
|
4
|
-
|
5
|
-
|
6
|
-
class SampleToolApp(BaseApplication):
|
7
|
-
"""A sample application providing basic utility tools."""
|
8
|
-
|
9
|
-
def __init__(self):
|
10
|
-
"""Initializes the SampleToolApp with the name 'sample_tool_app'."""
|
11
|
-
super().__init__("sample_tool_app")
|
12
|
-
|
13
|
-
def get_current_time(self):
|
14
|
-
"""Get the current system time as a formatted string.
|
15
|
-
|
16
|
-
Returns:
|
17
|
-
str: The current time in the format 'YYYY-MM-DD HH:MM:SS'.
|
18
|
-
"""
|
19
|
-
return datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
20
|
-
|
21
|
-
def get_current_date(self):
|
22
|
-
"""Get the current system date as a formatted string.
|
23
|
-
|
24
|
-
Returns:
|
25
|
-
str: The current date in the format 'YYYY-MM-DD'.
|
26
|
-
"""
|
27
|
-
return datetime.datetime.now().strftime("%Y-%m-%d")
|
28
|
-
|
29
|
-
def calculate(self, expression: str):
|
30
|
-
"""Safely evaluate a mathematical expression.
|
31
|
-
|
32
|
-
Args:
|
33
|
-
expression (str): The mathematical expression to evaluate.
|
34
|
-
|
35
|
-
Returns:
|
36
|
-
str: The result of the calculation, or an error message if evaluation fails.
|
37
|
-
"""
|
38
|
-
try:
|
39
|
-
# Safe evaluation of mathematical expressions
|
40
|
-
result = eval(expression, {"__builtins__": {}}, {})
|
41
|
-
return f"Result: {result}"
|
42
|
-
except Exception as e:
|
43
|
-
return f"Error in calculation: {str(e)}"
|
44
|
-
|
45
|
-
def file_operations(self, operation: str, filename: str, content: str = ""):
|
46
|
-
"""Perform file read or write operations.
|
47
|
-
|
48
|
-
Args:
|
49
|
-
operation (str): The operation to perform, either 'read' or 'write'.
|
50
|
-
filename (str): The name of the file to operate on.
|
51
|
-
content (str, optional): The content to write to the file (used only for 'write'). Defaults to "".
|
52
|
-
|
53
|
-
Returns:
|
54
|
-
str: The result of the file operation, or an error message if the operation fails.
|
55
|
-
"""
|
56
|
-
try:
|
57
|
-
if operation == "read":
|
58
|
-
with open(filename) as f:
|
59
|
-
return f"File content:\n{f.read()}"
|
60
|
-
elif operation == "write":
|
61
|
-
with open(filename, "w") as f:
|
62
|
-
f.write(content)
|
63
|
-
return f"Successfully wrote to {filename}"
|
64
|
-
else:
|
65
|
-
return "Invalid operation. Use 'read' or 'write'"
|
66
|
-
except Exception as e:
|
67
|
-
return f"File operation error: {str(e)}"
|
68
|
-
|
69
|
-
def list_tools(self):
|
70
|
-
"""List all available tool methods in this application.
|
71
|
-
|
72
|
-
Returns:
|
73
|
-
list: A list of callable tool methods.
|
74
|
-
"""
|
75
|
-
return [
|
76
|
-
self.get_current_time,
|
77
|
-
self.get_current_date,
|
78
|
-
self.calculate,
|
79
|
-
self.file_operations,
|
80
|
-
]
|
@@ -1,38 +0,0 @@
|
|
1
|
-
# agents/base.py
|
2
|
-
from abc import ABC, abstractmethod
|
3
|
-
from enum import Enum
|
4
|
-
from typing import Any
|
5
|
-
|
6
|
-
from pydantic import BaseModel
|
7
|
-
|
8
|
-
from universal_mcp.tools.manager import ToolManager
|
9
|
-
|
10
|
-
|
11
|
-
class AgentType(Enum):
|
12
|
-
REACT = "react"
|
13
|
-
CODEACT = "codeact"
|
14
|
-
SIMPLE = "simple"
|
15
|
-
|
16
|
-
|
17
|
-
class AgentResponse(BaseModel):
|
18
|
-
thought: str | None = None
|
19
|
-
action: str | None = None
|
20
|
-
action_input: dict[str, Any] | None = None
|
21
|
-
observation: str | None = None
|
22
|
-
answer: str | None = None
|
23
|
-
finished: bool = False
|
24
|
-
|
25
|
-
|
26
|
-
class BaseAgent(ABC):
|
27
|
-
def __init__(self, name: str, instructions: str, model: str, debug: bool = False):
|
28
|
-
self.name = name
|
29
|
-
self.instructions = instructions
|
30
|
-
self.model = model
|
31
|
-
self.conversation_history: list[dict[str, Any]] = []
|
32
|
-
|
33
|
-
@abstractmethod
|
34
|
-
def process_step(self, user_input: str, tool_manager: ToolManager):
|
35
|
-
pass
|
36
|
-
|
37
|
-
def reset_conversation(self):
|
38
|
-
self.conversation_history = []
|
@@ -1,115 +0,0 @@
|
|
1
|
-
import json
|
2
|
-
from typing import Any
|
3
|
-
|
4
|
-
from loguru import logger
|
5
|
-
from openai import OpenAI
|
6
|
-
from openai.types.chat import (
|
7
|
-
ChatCompletion,
|
8
|
-
ChatCompletionMessage,
|
9
|
-
ChatCompletionToolParam,
|
10
|
-
)
|
11
|
-
|
12
|
-
from universal_mcp.tools.adapters import ToolFormat
|
13
|
-
from universal_mcp.tools.manager import ToolManager
|
14
|
-
|
15
|
-
|
16
|
-
class LLMClient:
|
17
|
-
def __init__(self, model: str):
|
18
|
-
self.model = model
|
19
|
-
self.client = OpenAI()
|
20
|
-
logger.info(f"LLMClient initialized with model: {self.model}")
|
21
|
-
|
22
|
-
async def generate_response(
|
23
|
-
self,
|
24
|
-
messages: list[dict[str, str]],
|
25
|
-
tools: list[ChatCompletionToolParam] | None = None,
|
26
|
-
temperature: float = 0.7,
|
27
|
-
max_tokens: int = 1000,
|
28
|
-
) -> ChatCompletionMessage:
|
29
|
-
"""Generate response using OpenAI with native tool calling"""
|
30
|
-
try:
|
31
|
-
logger.debug(f"Generating response with messages: {messages}")
|
32
|
-
kwargs = {"model": self.model, "messages": messages, "temperature": temperature, "max_tokens": max_tokens}
|
33
|
-
if tools:
|
34
|
-
kwargs["tools"] = tools
|
35
|
-
kwargs["tool_choice"] = "auto"
|
36
|
-
logger.debug(f"Using tools: {tools}")
|
37
|
-
response: ChatCompletion = self.client.chat.completions.create(**kwargs)
|
38
|
-
logger.debug(f"OpenAI response: {response}")
|
39
|
-
choice = response.choices[0]
|
40
|
-
message: ChatCompletionMessage = choice.message
|
41
|
-
logger.info(f"Generated message: {message}")
|
42
|
-
return message
|
43
|
-
except Exception as e:
|
44
|
-
logger.error(f"Error in generate_response: {e}")
|
45
|
-
raise e
|
46
|
-
|
47
|
-
async def handle_tool_calls(
|
48
|
-
self, tool_calls: list[ChatCompletionToolParam], tool_manager: ToolManager
|
49
|
-
) -> list[dict[str, Any]]:
|
50
|
-
"""Handle tool calls"""
|
51
|
-
messages: list[dict[str, Any]] = []
|
52
|
-
for tool_call in tool_calls:
|
53
|
-
tool_name = tool_call.function.name
|
54
|
-
tool_args = tool_call.function.arguments
|
55
|
-
logger.info(f"Handling tool call: {tool_name} with args: {tool_args}")
|
56
|
-
tool_result = await tool_manager.call_tool(tool_name, tool_args)
|
57
|
-
logger.debug(f"Tool result for {tool_name}: {tool_result}")
|
58
|
-
messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": str(tool_result)})
|
59
|
-
return messages
|
60
|
-
|
61
|
-
async def generate_response_with_tool_results(
|
62
|
-
self, messages: list[dict[str, str]], tool_manager: ToolManager, max_iterations: int = 5
|
63
|
-
) -> ChatCompletionMessage:
|
64
|
-
"""Handle complete tool calling conversation loop using OpenAI"""
|
65
|
-
conversation = messages.copy()
|
66
|
-
iteration = 0
|
67
|
-
tools = tool_manager.list_tools(format=ToolFormat.OPENAI)
|
68
|
-
logger.info(f"Starting tool calling loop with max_iterations={max_iterations}")
|
69
|
-
|
70
|
-
while iteration < max_iterations:
|
71
|
-
iteration += 1
|
72
|
-
logger.info(f"Iteration {iteration}: Generating response with conversation: {conversation}")
|
73
|
-
|
74
|
-
# Generate response with tools
|
75
|
-
response = await self.generate_response(conversation, tools)
|
76
|
-
|
77
|
-
# If no tool calls, return the response
|
78
|
-
tool_calls = response.tool_calls
|
79
|
-
if not tool_calls:
|
80
|
-
logger.info("No tool calls detected, returning response.")
|
81
|
-
return response
|
82
|
-
|
83
|
-
logger.info(f"Tool calls detected: {tool_calls}")
|
84
|
-
|
85
|
-
# Add assistant message with tool calls
|
86
|
-
assistant_msg = {"role": "assistant", "content": response.content}
|
87
|
-
if tool_calls:
|
88
|
-
assistant_msg["tool_calls"] = tool_calls
|
89
|
-
conversation.append(assistant_msg)
|
90
|
-
|
91
|
-
# Execute tool calls and add results
|
92
|
-
for tool_call in tool_calls:
|
93
|
-
function = tool_call.function
|
94
|
-
tool_name = function.name
|
95
|
-
arguments = function.arguments
|
96
|
-
logger.info(f"Executing tool: {tool_name} with arguments: {arguments}")
|
97
|
-
try:
|
98
|
-
tool_args = json.loads(arguments)
|
99
|
-
except Exception as e:
|
100
|
-
logger.warning(f"Failed to parse tool arguments as JSON: {arguments}. Error: {e}")
|
101
|
-
tool_args = {"query": arguments}
|
102
|
-
# Execute the tool
|
103
|
-
try:
|
104
|
-
tool_result = await tool_manager.call_tool(tool_name, tool_args)
|
105
|
-
logger.debug(f"Tool result for {tool_name}: {tool_result}")
|
106
|
-
except Exception as e:
|
107
|
-
logger.error(f"Error executing tool {tool_name}: {e}")
|
108
|
-
tool_result = f"Error executing tool {tool_name}: {e}"
|
109
|
-
# Add tool result to conversation
|
110
|
-
conversation.append({"role": "tool", "tool_call_id": tool_call.id, "content": str(tool_result)})
|
111
|
-
# Continue the conversation loop
|
112
|
-
|
113
|
-
logger.info("Max iterations reached or tool loop complete. Generating final response.")
|
114
|
-
# Final response after tool execution
|
115
|
-
return await self.generate_response(conversation)
|
@@ -1,67 +0,0 @@
|
|
1
|
-
from loguru import logger
|
2
|
-
|
3
|
-
from universal_mcp.tools.manager import ToolManager
|
4
|
-
|
5
|
-
from .base import BaseAgent
|
6
|
-
from .llm import LLMClient
|
7
|
-
|
8
|
-
|
9
|
-
class ReActAgent(BaseAgent):
|
10
|
-
def __init__(self, name: str, instructions: str, model: str):
|
11
|
-
super().__init__(name, instructions, model)
|
12
|
-
self.llm_client = LLMClient(model)
|
13
|
-
self.max_iterations = 10
|
14
|
-
logger.debug(f"Initialized ReActAgent: name={name}, model={model}")
|
15
|
-
|
16
|
-
def _build_system_message(self) -> str:
|
17
|
-
system_message = f"""You are {self.name}. {self.instructions}
|
18
|
-
|
19
|
-
You have access to various tools that can help you answer questions and complete tasks. When you need to use a tool:
|
20
|
-
|
21
|
-
1. Think about what information you need
|
22
|
-
2. Call the appropriate tool with the right parameters
|
23
|
-
3. Use the tool results to provide a comprehensive answer
|
24
|
-
|
25
|
-
Always explain your reasoning and be thorough in your responses. If you need to use multiple tools to answer a question completely, do so."""
|
26
|
-
logger.debug(f"System message built: {system_message}")
|
27
|
-
return system_message
|
28
|
-
|
29
|
-
def _build_messages(self, user_input: str) -> list[dict[str, str]]:
|
30
|
-
"""Build message history for the conversation"""
|
31
|
-
messages = [{"role": "system", "content": self._build_system_message()}]
|
32
|
-
|
33
|
-
# Add conversation history
|
34
|
-
for entry in self.conversation_history:
|
35
|
-
messages.append({"role": "user", "content": entry["human"]})
|
36
|
-
messages.append({"role": "assistant", "content": entry["assistant"]})
|
37
|
-
|
38
|
-
# Add current user input
|
39
|
-
messages.append({"role": "user", "content": user_input})
|
40
|
-
|
41
|
-
logger.debug(f"Built messages for user_input='{user_input}': {messages}")
|
42
|
-
return messages
|
43
|
-
|
44
|
-
async def process_step(self, user_input: str, tool_manager: ToolManager) -> str:
|
45
|
-
"""Process user input using native tool calling"""
|
46
|
-
|
47
|
-
logger.info(f"Processing user input: {user_input}")
|
48
|
-
|
49
|
-
# Build conversation messages
|
50
|
-
messages = self._build_messages(user_input)
|
51
|
-
|
52
|
-
# Use native tool calling with conversation loop
|
53
|
-
try:
|
54
|
-
response = await self.llm_client.generate_response_with_tool_results(
|
55
|
-
messages, tool_manager, self.max_iterations
|
56
|
-
)
|
57
|
-
final_answer = response.content
|
58
|
-
logger.info(f"LLM response received: {final_answer}")
|
59
|
-
except Exception as e:
|
60
|
-
logger.error(f"Error during LLM response generation: {e}")
|
61
|
-
raise
|
62
|
-
|
63
|
-
# Store in conversation history
|
64
|
-
self.conversation_history.append({"human": user_input, "assistant": final_answer})
|
65
|
-
logger.debug(f"Updated conversation history: {self.conversation_history[-1]}")
|
66
|
-
|
67
|
-
return final_answer
|