universal-mcp 0.1.24rc2__py3-none-any.whl → 0.1.24rc3__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 (57) hide show
  1. universal_mcp/agentr/__init__.py +6 -0
  2. universal_mcp/agentr/agentr.py +30 -0
  3. universal_mcp/{utils/agentr.py → agentr/client.py} +19 -3
  4. universal_mcp/agentr/integration.py +104 -0
  5. universal_mcp/agentr/registry.py +91 -0
  6. universal_mcp/agentr/server.py +51 -0
  7. universal_mcp/agents/__init__.py +6 -0
  8. universal_mcp/agents/auto.py +576 -0
  9. universal_mcp/agents/base.py +88 -0
  10. universal_mcp/agents/cli.py +27 -0
  11. universal_mcp/agents/codeact/__init__.py +243 -0
  12. universal_mcp/agents/codeact/sandbox.py +27 -0
  13. universal_mcp/agents/codeact/test.py +15 -0
  14. universal_mcp/agents/codeact/utils.py +61 -0
  15. universal_mcp/agents/hil.py +104 -0
  16. universal_mcp/agents/llm.py +10 -0
  17. universal_mcp/agents/react.py +58 -0
  18. universal_mcp/agents/simple.py +40 -0
  19. universal_mcp/agents/utils.py +111 -0
  20. universal_mcp/analytics.py +5 -7
  21. universal_mcp/applications/__init__.py +42 -75
  22. universal_mcp/applications/application.py +1 -1
  23. universal_mcp/applications/sample/app.py +245 -0
  24. universal_mcp/cli.py +10 -3
  25. universal_mcp/config.py +33 -7
  26. universal_mcp/exceptions.py +4 -0
  27. universal_mcp/integrations/__init__.py +0 -15
  28. universal_mcp/integrations/integration.py +9 -91
  29. universal_mcp/servers/__init__.py +2 -14
  30. universal_mcp/servers/server.py +10 -51
  31. universal_mcp/tools/__init__.py +3 -0
  32. universal_mcp/tools/adapters.py +20 -11
  33. universal_mcp/tools/manager.py +29 -56
  34. universal_mcp/tools/registry.py +41 -0
  35. universal_mcp/tools/tools.py +22 -1
  36. universal_mcp/types.py +10 -0
  37. universal_mcp/utils/common.py +245 -0
  38. universal_mcp/utils/openapi/api_generator.py +46 -18
  39. universal_mcp/utils/openapi/cli.py +445 -19
  40. universal_mcp/utils/openapi/openapi.py +284 -21
  41. universal_mcp/utils/openapi/postprocessor.py +275 -0
  42. universal_mcp/utils/openapi/preprocessor.py +1 -1
  43. universal_mcp/utils/openapi/test_generator.py +287 -0
  44. universal_mcp/utils/prompts.py +188 -341
  45. universal_mcp/utils/testing.py +190 -2
  46. {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/METADATA +16 -2
  47. universal_mcp-0.1.24rc3.dist-info/RECORD +70 -0
  48. universal_mcp/applications/sample_tool_app.py +0 -80
  49. universal_mcp/client/agents/__init__.py +0 -4
  50. universal_mcp/client/agents/base.py +0 -38
  51. universal_mcp/client/agents/llm.py +0 -115
  52. universal_mcp/client/agents/react.py +0 -67
  53. universal_mcp/client/cli.py +0 -181
  54. universal_mcp-0.1.24rc2.dist-info/RECORD +0 -53
  55. {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/WHEEL +0 -0
  56. {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc3.dist-info}/entry_points.txt +0 -0
  57. {universal_mcp-0.1.24rc2.dist-info → universal_mcp-0.1.24rc3.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.24rc3
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
@@ -12,13 +12,22 @@ Requires-Dist: gql[all]>=3.5.2
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,70 @@
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/__init__.py,sha256=ogOhH_OJwkoUZu_2nQJc7-vEGmYQxEjOE511-6ubrX0,217
10
+ universal_mcp/agentr/agentr.py,sha256=JfawuREfXAyeNUE7o58DzTPhmQXuwsB_Da7c1Gf3Qxw,1059
11
+ universal_mcp/agentr/client.py,sha256=oyF6VKq56UMVf5L1WnFTMSZ85W8Qcy-5HZ5XOGiIELM,4139
12
+ universal_mcp/agentr/integration.py,sha256=V5GjqocqS02tRoI8MeV9PL6m-BzejwBzgJhOHo4MxAE,4212
13
+ universal_mcp/agentr/registry.py,sha256=b9sr5JyT3HLj3e7GFpdXpT7ofGwLQc--y8k2DqF5dE0,3542
14
+ universal_mcp/agentr/server.py,sha256=bIPmHMiKKwnUYnxmfZVRh1thcn7Rytm_-bNiXTfANzc,2098
15
+ universal_mcp/agents/__init__.py,sha256=vgixOLTCcCmSweENV7GSAuOPyHXlE4XAbvOXyr4MrRA,185
16
+ universal_mcp/agents/auto.py,sha256=o__71BCOHSfaj7Xt0PhsamVXdeP4o7irhtmu1q6-3Fo,25336
17
+ universal_mcp/agents/base.py,sha256=U5JtpOopmUi73qcxtY9T2qJpYD7e6c62iVlIr3m5Chc,3430
18
+ universal_mcp/agents/cli.py,sha256=7GdRBpu9rhZPiC2vaNQXWI7K-0yCnvdlmE0IFpvy2Gk,539
19
+ universal_mcp/agents/hil.py,sha256=CTgX7CoFEyTFIaNaL-id2WALOPd0VBb79pHkQK8quM8,3671
20
+ universal_mcp/agents/llm.py,sha256=YNxN43bVhGfdYs09yPkdkGCKJkj-2UNqkB1EFmtnUS4,309
21
+ universal_mcp/agents/react.py,sha256=6L--LcuU5Ityi2UiZSYJWgp-lXGkxvpsx8mjvpoNRBQ,2021
22
+ universal_mcp/agents/simple.py,sha256=UfmQIIff--_Y0DQ6oivRciHqSZvRqy_qwQn_UYVzYy8,1146
23
+ universal_mcp/agents/utils.py,sha256=7kwFpD0Rv6JqHG-LlNCVwSu_xRX-N119mUmiBroHJL4,4109
24
+ universal_mcp/agents/codeact/__init__.py,sha256=5D_I3lI_3tWjZERRoFav_bPe9UDaJ53pDzZYtyixg3E,10097
25
+ universal_mcp/agents/codeact/sandbox.py,sha256=lGRzhuXTHCB1qauuOI3bH1-fPTsyL6Lf9EmMIz4C2xQ,1039
26
+ universal_mcp/agents/codeact/test.py,sha256=bva-KkBNbGZn2f9nmmo9SNPQnY24Ni5gLHhJ5I0cm0k,481
27
+ universal_mcp/agents/codeact/utils.py,sha256=VuMvLTxBBh3pgaJk8RWj5AK8XZFF-1gnZJ6jFLeM_CI,1690
28
+ universal_mcp/applications/__init__.py,sha256=HrCnGdAT7w4puw2_VulBfjOLku9D5DuMaOwAuQzu6nI,2067
29
+ universal_mcp/applications/application.py,sha256=pGF9Rb2D6qzlaSwlcfZ-dNqPtsLkQTqL3jpsRuJ6-qE,23835
30
+ universal_mcp/applications/sample/app.py,sha256=E0JwaWD7qytwawb_iWc1pBnJ-Te7MMtab4MxOOebLdc,8972
31
+ universal_mcp/client/oauth.py,sha256=O00zOUfQxINaruFU2zt-64DIR1_mAqrY8ykLQo-teJU,8679
32
+ universal_mcp/client/token_store.py,sha256=6VAzjzJG49wYvmEDqksFvb-fVqdjHIKWv7yYyh_AuF8,3912
33
+ universal_mcp/client/transport.py,sha256=xgAKBJ1-yCcTtl9cxzJgRn6to5Y9EvCwLc_WpDck3Dg,11838
34
+ universal_mcp/integrations/__init__.py,sha256=tfzLyPEPly5tfIcT8K6-oKCr_MEFGxOROHy_NeVy0KM,200
35
+ universal_mcp/integrations/integration.py,sha256=H-hOoDHqk78A4Fi_TGN7OOFS7PDfqXK_nedH8iSz-6A,16459
36
+ universal_mcp/servers/__init__.py,sha256=speBb_E94UJa4A6Fv8RHFeoJ7cR-q2bCMtKV7R21P5w,142
37
+ universal_mcp/servers/server.py,sha256=qHeHm4UFVUr8TAailbEkWGq7EdlOASkOevY_0lyrWWs,5882
38
+ universal_mcp/stores/__init__.py,sha256=quvuwhZnpiSLuojf0NfmBx2xpaCulv3fbKtKaSCEmuM,603
39
+ universal_mcp/stores/store.py,sha256=yWbEGZb53z3fpVyqGWbes63z1CtIzC_IuM49OXy__UY,10137
40
+ universal_mcp/tools/__init__.py,sha256=jC8hsqfTdtn32yU57AVFUXiU3ZmUOCfCERSCaNEIH7E,395
41
+ universal_mcp/tools/adapters.py,sha256=YJ2oqgc8JgmtsdRRtvO-PO0Q0bKqTJ4Y3J6yxlESoTo,3947
42
+ universal_mcp/tools/docstring_parser.py,sha256=efEOE-ME7G5Jbbzpn7pN2xNuyu2M5zfZ1Tqu1lRB0Gk,8392
43
+ universal_mcp/tools/func_metadata.py,sha256=F4jd--hoZWKPBbZihVtluYKUsIdXdq4a0VWRgMl5k-Q,10838
44
+ universal_mcp/tools/manager.py,sha256=MajVskIptgXv1uZzwnSRycj1TSi7nhn4ebNSRkSSEDs,10455
45
+ universal_mcp/tools/registry.py,sha256=XsmVZL1rY5XgIBPTmvKKBWFLAvB3d9LfYMb11b4wSPI,1169
46
+ universal_mcp/tools/tools.py,sha256=1Q8bKiqj1E_-swvjmNHv16Orpd4p_HQtMKGxfqPmoPI,4570
47
+ universal_mcp/utils/__init__.py,sha256=8wi4PGWu-SrFjNJ8U7fr2iFJ1ktqlDmSKj1xYd7KSDc,41
48
+ universal_mcp/utils/common.py,sha256=3aJK3AnBkmYf-dbsFLaEu_dGuXQ0Qi2HuqYTueLVhXQ,10968
49
+ universal_mcp/utils/installation.py,sha256=PU_GfHPqzkumKk-xG4L9CkBzSmABxmchwblZkx-zY-I,7204
50
+ universal_mcp/utils/prompts.py,sha256=FJhqE0gPXDzYHS8gOjAVrdqVxc9X12ESnpd4C3jDSMI,27547
51
+ universal_mcp/utils/singleton.py,sha256=RoOiKxBOAhp0TK1QaMDYi-8GjRcy2Vh-bAOuIAcYan0,775
52
+ universal_mcp/utils/testing.py,sha256=J857Xt5K-hMxTc8UNJWlkzLbca1zObjwNhNXzYGxBHI,8009
53
+ universal_mcp/utils/openapi/__inti__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
54
+ universal_mcp/utils/openapi/api_generator.py,sha256=892AWoOCzzvFHVfSpEBg1m4LFRnWMmOSeY0DgLDW0fU,6960
55
+ universal_mcp/utils/openapi/api_splitter.py,sha256=io7fV-E8hUIR4NxFlakqydbgrQF6aBAnZHPMlpxw-wc,20967
56
+ universal_mcp/utils/openapi/cli.py,sha256=az5ObS74R-MmDCOZ1PHTJVKZJrHnsBOAweOUa7A-GqA,25918
57
+ universal_mcp/utils/openapi/docgen.py,sha256=DNmwlhg_-TRrHa74epyErMTRjV2nutfCQ7seb_Rq5hE,21366
58
+ universal_mcp/utils/openapi/filters.py,sha256=96FajO5nLbvjNPy2A1HvSS9jqpzMDHd4q_QTP-DIsPI,3842
59
+ universal_mcp/utils/openapi/openapi.py,sha256=0Pn_ugkEwL0eMtctjc0XDMPZdB3SBOqza_J6BK8i_SY,62165
60
+ universal_mcp/utils/openapi/postprocessor.py,sha256=NKvpXi73INRXxj1KOu8Ph3loWGICx2Uyy2Q8uOOqBoc,12177
61
+ universal_mcp/utils/openapi/preprocessor.py,sha256=r4n0WQI__OzPL8FTza7jxiM4EYeZwa-3tvEJaJYZC44,63081
62
+ universal_mcp/utils/openapi/readme.py,sha256=R2Jp7DUXYNsXPDV6eFTkLiy7MXbSULUj1vHh4O_nB4c,2974
63
+ universal_mcp/utils/openapi/test_generator.py,sha256=h44gQXEXmrw4pD3-XNHKB7T9c2lDomqrJxVO6oszCqM,12186
64
+ universal_mcp/utils/templates/README.md.j2,sha256=Mrm181YX-o_-WEfKs01Bi2RJy43rBiq2j6fTtbWgbTA,401
65
+ universal_mcp/utils/templates/api_client.py.j2,sha256=972Im7LNUAq3yZTfwDcgivnb-b8u6_JLKWXwoIwXXXQ,908
66
+ universal_mcp-0.1.24rc3.dist-info/METADATA,sha256=rJ2CEZw0gwZkc3O39e1u_6VMcCr1dAhNBviG0YZXwto,3116
67
+ universal_mcp-0.1.24rc3.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
68
+ universal_mcp-0.1.24rc3.dist-info/entry_points.txt,sha256=QlBrVKmA2jIM0q-C-3TQMNJTTWOsOFQvgedBq2rZTS8,56
69
+ universal_mcp-0.1.24rc3.dist-info/licenses/LICENSE,sha256=NweDZVPslBAZFzlgByF158b85GR0f5_tLQgq1NS48To,1063
70
+ universal_mcp-0.1.24rc3.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