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.
Files changed (58) hide show
  1. universal_mcp/agentr/README.md +201 -0
  2. universal_mcp/agentr/__init__.py +6 -0
  3. universal_mcp/agentr/agentr.py +30 -0
  4. universal_mcp/{utils/agentr.py → agentr/client.py} +19 -3
  5. universal_mcp/agentr/integration.py +104 -0
  6. universal_mcp/agentr/registry.py +91 -0
  7. universal_mcp/agentr/server.py +51 -0
  8. universal_mcp/agents/__init__.py +6 -0
  9. universal_mcp/agents/auto.py +576 -0
  10. universal_mcp/agents/base.py +88 -0
  11. universal_mcp/agents/cli.py +27 -0
  12. universal_mcp/agents/codeact/__init__.py +243 -0
  13. universal_mcp/agents/codeact/sandbox.py +27 -0
  14. universal_mcp/agents/codeact/test.py +15 -0
  15. universal_mcp/agents/codeact/utils.py +61 -0
  16. universal_mcp/agents/hil.py +104 -0
  17. universal_mcp/agents/llm.py +10 -0
  18. universal_mcp/agents/react.py +58 -0
  19. universal_mcp/agents/simple.py +40 -0
  20. universal_mcp/agents/utils.py +111 -0
  21. universal_mcp/analytics.py +5 -7
  22. universal_mcp/applications/__init__.py +42 -75
  23. universal_mcp/applications/application.py +1 -1
  24. universal_mcp/applications/sample/app.py +245 -0
  25. universal_mcp/cli.py +10 -3
  26. universal_mcp/config.py +33 -7
  27. universal_mcp/exceptions.py +4 -0
  28. universal_mcp/integrations/__init__.py +0 -15
  29. universal_mcp/integrations/integration.py +9 -91
  30. universal_mcp/servers/__init__.py +2 -14
  31. universal_mcp/servers/server.py +10 -51
  32. universal_mcp/tools/__init__.py +3 -0
  33. universal_mcp/tools/adapters.py +20 -11
  34. universal_mcp/tools/manager.py +29 -56
  35. universal_mcp/tools/registry.py +41 -0
  36. universal_mcp/tools/tools.py +22 -1
  37. universal_mcp/types.py +10 -0
  38. universal_mcp/utils/common.py +245 -0
  39. universal_mcp/utils/openapi/api_generator.py +46 -18
  40. universal_mcp/utils/openapi/cli.py +445 -19
  41. universal_mcp/utils/openapi/openapi.py +284 -21
  42. universal_mcp/utils/openapi/postprocessor.py +275 -0
  43. universal_mcp/utils/openapi/preprocessor.py +1 -1
  44. universal_mcp/utils/openapi/test_generator.py +287 -0
  45. universal_mcp/utils/prompts.py +188 -341
  46. universal_mcp/utils/testing.py +190 -2
  47. {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc4.dist-info}/METADATA +17 -3
  48. universal_mcp-0.1.24rc4.dist-info/RECORD +71 -0
  49. universal_mcp/applications/sample_tool_app.py +0 -80
  50. universal_mcp/client/agents/__init__.py +0 -4
  51. universal_mcp/client/agents/base.py +0 -38
  52. universal_mcp/client/agents/llm.py +0 -115
  53. universal_mcp/client/agents/react.py +0 -67
  54. universal_mcp/client/cli.py +0 -181
  55. universal_mcp-0.1.24rc2.dist-info/RECORD +0 -53
  56. {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc4.dist-info}/WHEEL +0 -0
  57. {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc4.dist-info}/entry_points.txt +0 -0
  58. {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc4.dist-info}/licenses/LICENSE +0 -0
@@ -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
- from universal_mcp.tools.tools import Tool
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.24rc2
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]>=3.5.2
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.9.3
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,4 +0,0 @@
1
- from .base import AgentType, BaseAgent
2
- from .react import ReActAgent
3
-
4
- __all__ = ["AgentType", "BaseAgent", "ReActAgent"]
@@ -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