sunholo 0.142.0__py3-none-any.whl → 0.143.3__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.
@@ -0,0 +1,98 @@
1
+ """
2
+ Proper MCP integration for VACRoutes using the official MCP Python SDK.
3
+ This shows how to integrate MCP servers with your Flask/VACRoutes application.
4
+ """
5
+
6
+ from typing import Dict, Any, List, Optional
7
+
8
+ # Official MCP imports
9
+ from mcp import StdioServerParameters, ClientSession
10
+ from mcp.client.stdio import stdio_client
11
+ from mcp.types import Tool, Resource, TextContent, CallToolResult
12
+
13
+
14
+ class MCPClientManager:
15
+ """Manages MCP client connections to various MCP servers."""
16
+
17
+ def __init__(self):
18
+ self.sessions: Dict[str, ClientSession] = {}
19
+ self.server_configs: Dict[str, Dict[str, Any]] = {}
20
+
21
+ async def connect_to_server(self, server_name: str, command: str, args: List[str] = None) -> ClientSession:
22
+ """Connect to an MCP server via stdio."""
23
+ if server_name in self.sessions:
24
+ return self.sessions[server_name]
25
+
26
+ # Create server parameters
27
+ server_params = StdioServerParameters(
28
+ command=command,
29
+ args=args or []
30
+ )
31
+
32
+ # Connect to the server
33
+ async with stdio_client(server_params) as (read, write):
34
+ # Create and initialize client session directly
35
+ session = ClientSession(read, write)
36
+ await session.initialize()
37
+ self.sessions[server_name] = session
38
+ self.server_configs[server_name] = {
39
+ "command": command,
40
+ "args": args
41
+ }
42
+ return session
43
+
44
+ async def list_tools(self, server_name: Optional[str] = None) -> List[Tool]:
45
+ """List available tools from one or all connected servers."""
46
+ if server_name:
47
+ session = self.sessions.get(server_name)
48
+ if session:
49
+ return await session.list_tools()
50
+ return []
51
+
52
+ # List from all servers
53
+ all_tools = []
54
+ for name, session in self.sessions.items():
55
+ tools = await session.list_tools()
56
+ # Add server name to tool metadata
57
+ for tool in tools:
58
+ tool.metadata = tool.metadata or {}
59
+ tool.metadata["server"] = name
60
+ all_tools.extend(tools)
61
+ return all_tools
62
+
63
+ async def call_tool(self, server_name: str, tool_name: str, arguments: Dict[str, Any]) -> CallToolResult:
64
+ """Call a tool on a specific MCP server."""
65
+ session = self.sessions.get(server_name)
66
+ if not session:
67
+ raise ValueError(f"Not connected to server: {server_name}")
68
+
69
+ # Call the tool
70
+ result = await session.call_tool(tool_name, arguments)
71
+ return result
72
+
73
+ async def list_resources(self, server_name: Optional[str] = None) -> List[Resource]:
74
+ """List available resources from servers."""
75
+ if server_name:
76
+ session = self.sessions.get(server_name)
77
+ if session:
78
+ return await session.list_resources()
79
+ return []
80
+
81
+ # List from all servers
82
+ all_resources = []
83
+ for name, session in self.sessions.items():
84
+ resources = await session.list_resources()
85
+ for resource in resources:
86
+ resource.metadata = resource.metadata or {}
87
+ resource.metadata["server"] = name
88
+ all_resources.extend(resources)
89
+ return all_resources
90
+
91
+ async def read_resource(self, server_name: str, uri: str) -> List[TextContent]:
92
+ """Read a resource from an MCP server."""
93
+ session = self.sessions.get(server_name)
94
+ if not session:
95
+ raise ValueError(f"Not connected to server: {server_name}")
96
+
97
+ result = await session.read_resource(uri)
98
+ return result.contents
@@ -0,0 +1,259 @@
1
+ # Copyright [2024] [Holosun ApS]
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ """
16
+ MCP Server wrapper for VAC functionality.
17
+ This module exposes VAC streaming capabilities as MCP tools.
18
+ """
19
+
20
+ from typing import Any, Sequence, Dict, List, Optional, Callable
21
+ import json
22
+ import asyncio
23
+ from functools import partial
24
+
25
+ try:
26
+ from mcp.server import Server
27
+ from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource
28
+ except ImportError:
29
+ Server = None
30
+ Tool = None
31
+ TextContent = None
32
+
33
+ from ..custom_logging import log
34
+ from ..streaming import start_streaming_chat_async
35
+
36
+
37
+ class VACMCPServer:
38
+ """MCP Server that exposes VAC functionality as tools."""
39
+
40
+ def __init__(self, stream_interpreter: Callable, vac_interpreter: Callable = None):
41
+ """
42
+ Initialize the VAC MCP Server.
43
+
44
+ Args:
45
+ stream_interpreter: The streaming interpreter function
46
+ vac_interpreter: The static VAC interpreter function (optional)
47
+ """
48
+ if Server is None:
49
+ raise ImportError("MCP server requires `pip install sunholo[anthropic]`")
50
+
51
+ self.stream_interpreter = stream_interpreter
52
+ self.vac_interpreter = vac_interpreter
53
+ self.server = Server("sunholo-vac-server")
54
+
55
+ # Set up handlers
56
+ self._setup_handlers()
57
+
58
+ def _setup_handlers(self):
59
+ """Set up MCP protocol handlers."""
60
+
61
+ @self.server.list_tools()
62
+ async def list_tools() -> List[Tool]:
63
+ """List available VAC tools."""
64
+ tools = [
65
+ Tool(
66
+ name="vac_stream",
67
+ description="Stream responses from a Sunholo VAC (Virtual Agent Computer)",
68
+ inputSchema={
69
+ "type": "object",
70
+ "properties": {
71
+ "vector_name": {
72
+ "type": "string",
73
+ "description": "Name of the VAC to interact with"
74
+ },
75
+ "user_input": {
76
+ "type": "string",
77
+ "description": "The user's question or input"
78
+ },
79
+ "chat_history": {
80
+ "type": "array",
81
+ "description": "Previous conversation history",
82
+ "items": {
83
+ "type": "object",
84
+ "properties": {
85
+ "human": {"type": "string"},
86
+ "ai": {"type": "string"}
87
+ }
88
+ },
89
+ "default": []
90
+ },
91
+ "stream_wait_time": {
92
+ "type": "number",
93
+ "description": "Time to wait between stream chunks",
94
+ "default": 7
95
+ },
96
+ "stream_timeout": {
97
+ "type": "number",
98
+ "description": "Maximum time to wait for response",
99
+ "default": 120
100
+ }
101
+ },
102
+ "required": ["vector_name", "user_input"]
103
+ }
104
+ )
105
+ ]
106
+
107
+ # Add static VAC tool if interpreter is provided
108
+ if self.vac_interpreter:
109
+ tools.append(
110
+ Tool(
111
+ name="vac_query",
112
+ description="Query a Sunholo VAC (non-streaming)",
113
+ inputSchema={
114
+ "type": "object",
115
+ "properties": {
116
+ "vector_name": {
117
+ "type": "string",
118
+ "description": "Name of the VAC to interact with"
119
+ },
120
+ "user_input": {
121
+ "type": "string",
122
+ "description": "The user's question or input"
123
+ },
124
+ "chat_history": {
125
+ "type": "array",
126
+ "description": "Previous conversation history",
127
+ "items": {
128
+ "type": "object",
129
+ "properties": {
130
+ "human": {"type": "string"},
131
+ "ai": {"type": "string"}
132
+ }
133
+ },
134
+ "default": []
135
+ }
136
+ },
137
+ "required": ["vector_name", "user_input"]
138
+ }
139
+ )
140
+ )
141
+
142
+ return tools
143
+
144
+ @self.server.call_tool()
145
+ async def call_tool(
146
+ name: str,
147
+ arguments: Any
148
+ ) -> Sequence[TextContent | ImageContent | EmbeddedResource]:
149
+ """Handle tool calls for VAC interactions."""
150
+
151
+ if name == "vac_stream":
152
+ return await self._handle_vac_stream(arguments)
153
+ elif name == "vac_query" and self.vac_interpreter:
154
+ return await self._handle_vac_query(arguments)
155
+ else:
156
+ raise ValueError(f"Unknown tool: {name}")
157
+
158
+ async def _handle_vac_stream(self, arguments: Dict[str, Any]) -> Sequence[TextContent]:
159
+ """Handle streaming VAC requests."""
160
+ vector_name = arguments.get("vector_name")
161
+ user_input = arguments.get("user_input")
162
+ chat_history = arguments.get("chat_history", [])
163
+ stream_wait_time = arguments.get("stream_wait_time", 7)
164
+ stream_timeout = arguments.get("stream_timeout", 120)
165
+
166
+ if not vector_name or not user_input:
167
+ raise ValueError("Missing required arguments: vector_name and user_input")
168
+
169
+ log.info(f"MCP streaming request for VAC '{vector_name}': {user_input}")
170
+
171
+ try:
172
+ # Collect streaming responses
173
+ full_response = ""
174
+
175
+ async for chunk in start_streaming_chat_async(
176
+ question=user_input,
177
+ vector_name=vector_name,
178
+ qna_func_async=self.stream_interpreter,
179
+ chat_history=chat_history,
180
+ wait_time=stream_wait_time,
181
+ timeout=stream_timeout
182
+ ):
183
+ if isinstance(chunk, dict) and 'answer' in chunk:
184
+ full_response = chunk['answer']
185
+ elif isinstance(chunk, str):
186
+ full_response += chunk
187
+
188
+ return [
189
+ TextContent(
190
+ type="text",
191
+ text=full_response or "No response generated"
192
+ )
193
+ ]
194
+
195
+ except Exception as e:
196
+ log.error(f"Error in MCP VAC stream: {str(e)}")
197
+ return [
198
+ TextContent(
199
+ type="text",
200
+ text=f"Error: {str(e)}"
201
+ )
202
+ ]
203
+
204
+ async def _handle_vac_query(self, arguments: Dict[str, Any]) -> Sequence[TextContent]:
205
+ """Handle non-streaming VAC requests."""
206
+ vector_name = arguments.get("vector_name")
207
+ user_input = arguments.get("user_input")
208
+ chat_history = arguments.get("chat_history", [])
209
+
210
+ if not vector_name or not user_input:
211
+ raise ValueError("Missing required arguments: vector_name and user_input")
212
+
213
+ log.info(f"MCP query request for VAC '{vector_name}': {user_input}")
214
+
215
+ try:
216
+ # Run in executor if not async
217
+ if asyncio.iscoroutinefunction(self.vac_interpreter):
218
+ result = await self.vac_interpreter(
219
+ question=user_input,
220
+ vector_name=vector_name,
221
+ chat_history=chat_history
222
+ )
223
+ else:
224
+ loop = asyncio.get_event_loop()
225
+ result = await loop.run_in_executor(
226
+ None,
227
+ partial(
228
+ self.vac_interpreter,
229
+ question=user_input,
230
+ vector_name=vector_name,
231
+ chat_history=chat_history
232
+ )
233
+ )
234
+
235
+ # Extract answer from result
236
+ if isinstance(result, dict):
237
+ answer = result.get("answer", str(result))
238
+ else:
239
+ answer = str(result)
240
+
241
+ return [
242
+ TextContent(
243
+ type="text",
244
+ text=answer
245
+ )
246
+ ]
247
+
248
+ except Exception as e:
249
+ log.error(f"Error in MCP VAC query: {str(e)}")
250
+ return [
251
+ TextContent(
252
+ type="text",
253
+ text=f"Error: {str(e)}"
254
+ )
255
+ ]
256
+
257
+ def get_server(self) -> Server:
258
+ """Get the underlying MCP server instance."""
259
+ return self.server
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sunholo
3
- Version: 0.142.0
3
+ Version: 0.143.3
4
4
  Summary: AI DevOps - a package to help deploy GenAI to the Cloud.
5
5
  Author-email: Holosun ApS <multivac@sunholo.com>
6
6
  License: Apache License, Version 2.0
@@ -15,14 +15,16 @@ Classifier: Programming Language :: Python :: 3
15
15
  Classifier: Programming Language :: Python :: 3.10
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
- Requires-Python: >=3.10
18
+ Requires-Python: >=3.11
19
19
  Description-Content-Type: text/markdown
20
20
  License-File: LICENSE.txt
21
+ Requires-Dist: a2a-python>=0.0.1
21
22
  Requires-Dist: aiohttp
23
+ Requires-Dist: flask>=3.1.0
22
24
  Requires-Dist: google-auth
23
- Requires-Dist: ollama>=0.4.7
24
- Requires-Dist: pillow>=11.0.0
25
+ Requires-Dist: mcp>=1.1.1
25
26
  Requires-Dist: pydantic
27
+ Requires-Dist: pytest-asyncio>=1.0.0
26
28
  Requires-Dist: requests
27
29
  Requires-Dist: ruamel.yaml
28
30
  Requires-Dist: tenacity
@@ -69,7 +71,7 @@ Requires-Dist: langchain-anthropic>=0.1.23; extra == "all"
69
71
  Requires-Dist: langchain-google-vertexai; extra == "all"
70
72
  Requires-Dist: langchain-unstructured; extra == "all"
71
73
  Requires-Dist: langfuse; extra == "all"
72
- Requires-Dist: mcp; extra == "all"
74
+ Requires-Dist: mcp>=1.1.1; extra == "all"
73
75
  Requires-Dist: numpy; extra == "all"
74
76
  Requires-Dist: opencv-python; extra == "all"
75
77
  Requires-Dist: pg8000; extra == "all"
@@ -125,6 +127,7 @@ Requires-Dist: pytesseract; extra == "pipeline"
125
127
  Requires-Dist: tabulate; extra == "pipeline"
126
128
  Requires-Dist: unstructured[all-docs,local-inference]; extra == "pipeline"
127
129
  Provides-Extra: gcp
130
+ Requires-Dist: a2a-python; extra == "gcp"
128
131
  Requires-Dist: aiofiles; extra == "gcp"
129
132
  Requires-Dist: anthropic[vertex]; extra == "gcp"
130
133
  Requires-Dist: google-api-python-client; extra == "gcp"
@@ -155,7 +158,7 @@ Requires-Dist: langchain-openai>=0.3.2; extra == "openai"
155
158
  Requires-Dist: tiktoken; extra == "openai"
156
159
  Provides-Extra: anthropic
157
160
  Requires-Dist: langchain-anthropic>=0.1.23; extra == "anthropic"
158
- Requires-Dist: mcp; extra == "anthropic"
161
+ Requires-Dist: mcp>=1.1.1; extra == "anthropic"
159
162
  Provides-Extra: tools
160
163
  Requires-Dist: openapi-spec-validator; extra == "tools"
161
164
  Requires-Dist: playwright; extra == "tools"
@@ -1,6 +1,10 @@
1
1
  sunholo/__init__.py,sha256=InRbX4V0-qdNHo9zYH3GEye7ASLR6LX8-SMvPV4Jsaw,1212
2
2
  sunholo/custom_logging.py,sha256=JXZTnXp_DixP3jwYfKw4LYRDS9IuTq7ctCgfZbI2rxA,22023
3
3
  sunholo/langchain_types.py,sha256=uZ4zvgej_f7pLqjtu4YP7qMC_eZD5ym_5x4pyvA1Ih4,1834
4
+ sunholo/a2a/__init__.py,sha256=ohwR-3_toRUdyj3ACNCVPrd_V2lzR5tmL5Hwn1ChSSU,1049
5
+ sunholo/a2a/agent_card.py,sha256=TJOgZ5FZJGgapyFXzBIRnnocUWcfSLah7fjvl0fLYJ4,13387
6
+ sunholo/a2a/task_manager.py,sha256=Ox1oAHarqYdcWku_JFcYDt2pQG3gwfuBTO7WCakE-U0,17670
7
+ sunholo/a2a/vac_a2a_agent.py,sha256=vL-sQceVRBE9d3kjx9m8zGWK1S9J1sHUybAH3tSbVFg,13344
4
8
  sunholo/agents/__init__.py,sha256=AauG3l0y4r5Fzx1zJfZ634M4o-0o7B7J5T8k_gPvNqE,370
5
9
  sunholo/agents/chat_history.py,sha256=gRuIUyU-53A72Q17SmSgf6Ok3YO8hKAZhsc64976018,17782
6
10
  sunholo/agents/dispatch_to_qa.py,sha256=NHihwAoCJ5_Lk11e_jZnucVUGQyZHCB-YpkfMHBCpQk,8882
@@ -14,7 +18,7 @@ sunholo/agents/fastapi/base.py,sha256=W-cyF8ZDUH40rc-c-Apw3-_8IIi2e4Y9qRtnoVnsc1
14
18
  sunholo/agents/fastapi/qna_routes.py,sha256=lKHkXPmwltu9EH3RMwmD153-J6pE7kWQ4BhBlV3to-s,3864
15
19
  sunholo/agents/flask/__init__.py,sha256=dEoByI3gDNUOjpX1uVKP7uPjhfFHJubbiaAv3xLopnk,63
16
20
  sunholo/agents/flask/base.py,sha256=vnpxFEOnCmt9humqj-jYPLfJcdwzsop9NorgkJ-tSaU,1756
17
- sunholo/agents/flask/vac_routes.py,sha256=TEM0u2vkZC0BSKJABxQVPm4QiUsEFoPOwJZIOxzi1Sk,32621
21
+ sunholo/agents/flask/vac_routes.py,sha256=4Wenjq1Mg9gnmVt_5nHH3Uzhvio269lo2N9v38VxT48,53850
18
22
  sunholo/archive/__init__.py,sha256=qNHWm5rGPVOlxZBZCpA1wTYPbalizRT7f8X4rs2t290,31
19
23
  sunholo/archive/archive.py,sha256=PxVfDtO2_2ZEEbnhXSCbXLdeoHoQVImo4y3Jr2XkCFY,1204
20
24
  sunholo/auth/__init__.py,sha256=TeP-OY0XGxYV_8AQcVGoh35bvyWhNUcMRfhuD5l44Sk,91
@@ -110,8 +114,10 @@ sunholo/llamaindex/llamaindex_class.py,sha256=PnpPoc7LpP7xvKIXYu-UvI4ehj67pGhE1E
110
114
  sunholo/llamaindex/user_history.py,sha256=ZtkecWuF9ORduyGB8kF8gP66bm9DdvCI-ZiK6Kt-cSE,2265
111
115
  sunholo/lookup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
112
116
  sunholo/lookup/model_lookup.yaml,sha256=O7o-jP53MLA06C8pI-ILwERShO-xf6z_258wtpZBv6A,739
113
- sunholo/mcp/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
117
+ sunholo/mcp/__init__.py,sha256=GmKB8Z0ecXUzpuzYEOXSI0aaZP7LZJc7yTCMEFdAmi4,791
114
118
  sunholo/mcp/cli.py,sha256=d24nnVzhZYz4AWgTqmN-qjKG4rPbf8RhdmEOHZkBHy8,10570
119
+ sunholo/mcp/mcp_manager.py,sha256=ooa_2JSNaKIuK9azEV0OaPVf5Mp_tL_DHaYEyFCmgKY,3752
120
+ sunholo/mcp/vac_mcp_server.py,sha256=fRcerTqp_pTK8AVITpGPhrez_IaDuUDr4WnVUHPv8GM,10114
115
121
  sunholo/ollama/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
116
122
  sunholo/ollama/ollama_images.py,sha256=H2cpcNu88R4TwyfL_nnqkQhdvBQ2FPCAy4Ok__0yQmo,2351
117
123
  sunholo/pubsub/__init__.py,sha256=DfTEk4zmCfqn6gFxRrqDO0pOrvXTDqH-medpgYO4PGw,117
@@ -169,9 +175,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
169
175
  sunholo/vertex/memory_tools.py,sha256=tBZxqVZ4InTmdBvLlOYwoSEWu4-kGquc-gxDwZCC4FA,7667
170
176
  sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
171
177
  sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
172
- sunholo-0.142.0.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
173
- sunholo-0.142.0.dist-info/METADATA,sha256=9v1AM0UCFJv_qx8vYOFO8gprTDulHCAxnMck7Jcmszs,18292
174
- sunholo-0.142.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
175
- sunholo-0.142.0.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
176
- sunholo-0.142.0.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
177
- sunholo-0.142.0.dist-info/RECORD,,
178
+ sunholo-0.143.3.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
179
+ sunholo-0.143.3.dist-info/METADATA,sha256=d36VCf5W1j1lyZ7HIe_vBHxOPyQ_f_UNhTD1X6_FjiQ,18413
180
+ sunholo-0.143.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
181
+ sunholo-0.143.3.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
182
+ sunholo-0.143.3.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
183
+ sunholo-0.143.3.dist-info/RECORD,,