sunholo 0.143.1__py3-none-any.whl → 0.143.7__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.
- sunholo/__init__.py +4 -1
- sunholo/a2a/__init__.py +27 -0
- sunholo/a2a/agent_card.py +345 -0
- sunholo/a2a/task_manager.py +480 -0
- sunholo/a2a/vac_a2a_agent.py +383 -0
- sunholo/agents/flask/vac_routes.py +256 -19
- sunholo/mcp/__init__.py +11 -2
- sunholo/mcp/mcp_manager.py +66 -28
- sunholo/mcp/vac_mcp_server.py +3 -9
- {sunholo-0.143.1.dist-info → sunholo-0.143.7.dist-info}/METADATA +4 -2
- {sunholo-0.143.1.dist-info → sunholo-0.143.7.dist-info}/RECORD +15 -11
- {sunholo-0.143.1.dist-info → sunholo-0.143.7.dist-info}/WHEEL +0 -0
- {sunholo-0.143.1.dist-info → sunholo-0.143.7.dist-info}/entry_points.txt +0 -0
- {sunholo-0.143.1.dist-info → sunholo-0.143.7.dist-info}/licenses/LICENSE.txt +0 -0
- {sunholo-0.143.1.dist-info → sunholo-0.143.7.dist-info}/top_level.txt +0 -0
@@ -38,12 +38,19 @@ except ImportError:
|
|
38
38
|
|
39
39
|
try:
|
40
40
|
from ...mcp.vac_mcp_server import VACMCPServer
|
41
|
-
from mcp.server
|
42
|
-
from mcp import JSONRPCMessage, ErrorData, INTERNAL_ERROR
|
41
|
+
from mcp.server import Server
|
42
|
+
from mcp.types import JSONRPCMessage, ErrorData, INTERNAL_ERROR
|
43
43
|
except ImportError:
|
44
44
|
VACMCPServer = None
|
45
|
-
|
45
|
+
Server = None
|
46
46
|
JSONRPCMessage = None
|
47
|
+
ErrorData = None
|
48
|
+
INTERNAL_ERROR = None
|
49
|
+
|
50
|
+
try:
|
51
|
+
from ...a2a.vac_a2a_agent import VACA2AAgent
|
52
|
+
except (ImportError, SyntaxError):
|
53
|
+
VACA2AAgent = None
|
47
54
|
|
48
55
|
|
49
56
|
# Cache dictionary to store validated API keys
|
@@ -81,7 +88,9 @@ if __name__ == "__main__":
|
|
81
88
|
mcp_servers: List[Dict[str, Any]] = None,
|
82
89
|
async_stream:bool=False,
|
83
90
|
add_langfuse_eval:bool=True,
|
84
|
-
enable_mcp_server:bool=False
|
91
|
+
enable_mcp_server:bool=False,
|
92
|
+
enable_a2a_agent:bool=False,
|
93
|
+
a2a_vac_names: List[str] = None):
|
85
94
|
self.app = app
|
86
95
|
self.stream_interpreter = stream_interpreter
|
87
96
|
self.vac_interpreter = vac_interpreter or partial(self.vac_interpreter_default)
|
@@ -102,6 +111,15 @@ if __name__ == "__main__":
|
|
102
111
|
vac_interpreter=self.vac_interpreter
|
103
112
|
)
|
104
113
|
|
114
|
+
# A2A agent initialization
|
115
|
+
self.enable_a2a_agent = enable_a2a_agent
|
116
|
+
self.vac_a2a_agent = None
|
117
|
+
self.a2a_vac_names = a2a_vac_names
|
118
|
+
if self.enable_a2a_agent and VACA2AAgent:
|
119
|
+
# Extract base URL from request context during route handling
|
120
|
+
# For now, initialize with placeholder - will be updated in route handlers
|
121
|
+
self.vac_a2a_agent = None # Initialized lazily in route handlers
|
122
|
+
|
105
123
|
self.additional_routes = additional_routes if additional_routes is not None else []
|
106
124
|
self.async_stream = async_stream
|
107
125
|
self.add_langfuse_eval = add_langfuse_eval
|
@@ -177,6 +195,15 @@ if __name__ == "__main__":
|
|
177
195
|
# MCP server endpoint
|
178
196
|
if self.enable_mcp_server and self.vac_mcp_server:
|
179
197
|
self.app.route('/mcp', methods=['POST', 'GET'])(self.handle_mcp_server)
|
198
|
+
|
199
|
+
# A2A agent endpoints
|
200
|
+
if self.enable_a2a_agent:
|
201
|
+
self.app.route('/.well-known/agent.json', methods=['GET'])(self.handle_a2a_agent_card)
|
202
|
+
self.app.route('/a2a/tasks/send', methods=['POST'])(self.handle_a2a_task_send)
|
203
|
+
self.app.route('/a2a/tasks/sendSubscribe', methods=['POST'])(self.handle_a2a_task_send_subscribe)
|
204
|
+
self.app.route('/a2a/tasks/get', methods=['POST'])(self.handle_a2a_task_get)
|
205
|
+
self.app.route('/a2a/tasks/cancel', methods=['POST'])(self.handle_a2a_task_cancel)
|
206
|
+
self.app.route('/a2a/tasks/pushNotification/set', methods=['POST'])(self.handle_a2a_push_notification)
|
180
207
|
|
181
208
|
self.register_additional_routes()
|
182
209
|
|
@@ -1037,11 +1064,8 @@ if __name__ == "__main__":
|
|
1037
1064
|
|
1038
1065
|
try:
|
1039
1066
|
# Process the request through the server
|
1040
|
-
|
1041
|
-
|
1042
|
-
write_queue,
|
1043
|
-
InitializationOptions() if InitializationOptions else None
|
1044
|
-
)
|
1067
|
+
# Use the server's run method with HTTP transport
|
1068
|
+
await server.run()
|
1045
1069
|
except Exception as e:
|
1046
1070
|
log.error(f"Error processing MCP request: {e}")
|
1047
1071
|
await write_queue.put(None)
|
@@ -1059,15 +1083,8 @@ if __name__ == "__main__":
|
|
1059
1083
|
loop = asyncio.new_event_loop()
|
1060
1084
|
asyncio.set_event_loop(loop)
|
1061
1085
|
try:
|
1062
|
-
|
1063
|
-
|
1064
|
-
# Parse and return the response
|
1065
|
-
if responses:
|
1066
|
-
# The response should be a single JSON-RPC response
|
1067
|
-
response_data = json_module.loads(responses[0])
|
1068
|
-
return jsonify(response_data)
|
1069
|
-
else:
|
1070
|
-
return jsonify({"error": "No response from MCP server"}), 500
|
1086
|
+
response_data = loop.run_until_complete(process_request())
|
1087
|
+
return jsonify(response_data)
|
1071
1088
|
|
1072
1089
|
except Exception as e:
|
1073
1090
|
log.error(f"MCP server error: {str(e)}")
|
@@ -1101,4 +1118,224 @@ if __name__ == "__main__":
|
|
1101
1118
|
"transport": "http",
|
1102
1119
|
"endpoint": "/mcp",
|
1103
1120
|
"tools": ["vac_stream", "vac_query"] if self.vac_interpreter else ["vac_stream"]
|
1104
|
-
})
|
1121
|
+
})
|
1122
|
+
|
1123
|
+
def _get_or_create_a2a_agent(self):
|
1124
|
+
"""Get or create the A2A agent instance with current request context."""
|
1125
|
+
if not self.enable_a2a_agent or not VACA2AAgent:
|
1126
|
+
return None
|
1127
|
+
|
1128
|
+
if self.vac_a2a_agent is None:
|
1129
|
+
# Extract base URL from current request
|
1130
|
+
base_url = request.url_root.rstrip('/')
|
1131
|
+
|
1132
|
+
self.vac_a2a_agent = VACA2AAgent(
|
1133
|
+
base_url=base_url,
|
1134
|
+
stream_interpreter=self.stream_interpreter,
|
1135
|
+
vac_interpreter=self.vac_interpreter,
|
1136
|
+
vac_names=self.a2a_vac_names
|
1137
|
+
)
|
1138
|
+
|
1139
|
+
return self.vac_a2a_agent
|
1140
|
+
|
1141
|
+
def handle_a2a_agent_card(self):
|
1142
|
+
"""Handle A2A agent card discovery request."""
|
1143
|
+
agent = self._get_or_create_a2a_agent()
|
1144
|
+
if not agent:
|
1145
|
+
return jsonify({"error": "A2A agent not enabled"}), 501
|
1146
|
+
|
1147
|
+
return jsonify(agent.get_agent_card())
|
1148
|
+
|
1149
|
+
def handle_a2a_task_send(self):
|
1150
|
+
"""Handle A2A task send request."""
|
1151
|
+
agent = self._get_or_create_a2a_agent()
|
1152
|
+
if not agent:
|
1153
|
+
return jsonify({"error": "A2A agent not enabled"}), 501
|
1154
|
+
|
1155
|
+
try:
|
1156
|
+
data = request.get_json()
|
1157
|
+
if not data:
|
1158
|
+
return jsonify({
|
1159
|
+
"jsonrpc": "2.0",
|
1160
|
+
"error": {
|
1161
|
+
"code": -32700,
|
1162
|
+
"message": "Parse error: Invalid JSON"
|
1163
|
+
},
|
1164
|
+
"id": None
|
1165
|
+
}), 400
|
1166
|
+
|
1167
|
+
# Run async handler
|
1168
|
+
loop = asyncio.new_event_loop()
|
1169
|
+
asyncio.set_event_loop(loop)
|
1170
|
+
try:
|
1171
|
+
response = loop.run_until_complete(agent.handle_task_send(data))
|
1172
|
+
return jsonify(response)
|
1173
|
+
finally:
|
1174
|
+
loop.close()
|
1175
|
+
|
1176
|
+
except Exception as e:
|
1177
|
+
log.error(f"A2A task send error: {e}")
|
1178
|
+
return jsonify({
|
1179
|
+
"jsonrpc": "2.0",
|
1180
|
+
"error": {
|
1181
|
+
"code": -32603,
|
1182
|
+
"message": f"Internal error: {str(e)}"
|
1183
|
+
},
|
1184
|
+
"id": data.get("id") if 'data' in locals() else None
|
1185
|
+
}), 500
|
1186
|
+
|
1187
|
+
def handle_a2a_task_send_subscribe(self):
|
1188
|
+
"""Handle A2A task send with subscription (SSE)."""
|
1189
|
+
agent = self._get_or_create_a2a_agent()
|
1190
|
+
if not agent:
|
1191
|
+
return jsonify({"error": "A2A agent not enabled"}), 501
|
1192
|
+
|
1193
|
+
try:
|
1194
|
+
data = request.get_json()
|
1195
|
+
if not data:
|
1196
|
+
def error_generator():
|
1197
|
+
yield "data: {\"error\": \"Parse error: Invalid JSON\"}\n\n"
|
1198
|
+
|
1199
|
+
return Response(error_generator(), content_type='text/event-stream')
|
1200
|
+
|
1201
|
+
# Create async generator for SSE
|
1202
|
+
async def sse_generator():
|
1203
|
+
async for chunk in agent.handle_task_send_subscribe(data):
|
1204
|
+
yield chunk
|
1205
|
+
|
1206
|
+
def sync_generator():
|
1207
|
+
loop = asyncio.new_event_loop()
|
1208
|
+
asyncio.set_event_loop(loop)
|
1209
|
+
try:
|
1210
|
+
async_gen = sse_generator()
|
1211
|
+
while True:
|
1212
|
+
try:
|
1213
|
+
chunk = loop.run_until_complete(async_gen.__anext__())
|
1214
|
+
yield chunk
|
1215
|
+
except StopAsyncIteration:
|
1216
|
+
break
|
1217
|
+
finally:
|
1218
|
+
loop.close()
|
1219
|
+
|
1220
|
+
return Response(sync_generator(), content_type='text/event-stream')
|
1221
|
+
|
1222
|
+
except Exception as e:
|
1223
|
+
log.error(f"A2A task send subscribe error: {e}")
|
1224
|
+
def error_generator(err):
|
1225
|
+
yield f"data: {{\"error\": \"Internal error: {str(err)}\"}}\n\n"
|
1226
|
+
|
1227
|
+
return Response(error_generator(e), content_type='text/event-stream')
|
1228
|
+
|
1229
|
+
def handle_a2a_task_get(self):
|
1230
|
+
"""Handle A2A task get request."""
|
1231
|
+
agent = self._get_or_create_a2a_agent()
|
1232
|
+
if not agent:
|
1233
|
+
return jsonify({"error": "A2A agent not enabled"}), 501
|
1234
|
+
|
1235
|
+
try:
|
1236
|
+
data = request.get_json()
|
1237
|
+
if not data:
|
1238
|
+
return jsonify({
|
1239
|
+
"jsonrpc": "2.0",
|
1240
|
+
"error": {
|
1241
|
+
"code": -32700,
|
1242
|
+
"message": "Parse error: Invalid JSON"
|
1243
|
+
},
|
1244
|
+
"id": None
|
1245
|
+
}), 400
|
1246
|
+
|
1247
|
+
# Run async handler
|
1248
|
+
loop = asyncio.new_event_loop()
|
1249
|
+
asyncio.set_event_loop(loop)
|
1250
|
+
try:
|
1251
|
+
response = loop.run_until_complete(agent.handle_task_get(data))
|
1252
|
+
return jsonify(response)
|
1253
|
+
finally:
|
1254
|
+
loop.close()
|
1255
|
+
|
1256
|
+
except Exception as e:
|
1257
|
+
log.error(f"A2A task get error: {e}")
|
1258
|
+
return jsonify({
|
1259
|
+
"jsonrpc": "2.0",
|
1260
|
+
"error": {
|
1261
|
+
"code": -32603,
|
1262
|
+
"message": f"Internal error: {str(e)}"
|
1263
|
+
},
|
1264
|
+
"id": data.get("id") if 'data' in locals() else None
|
1265
|
+
}), 500
|
1266
|
+
|
1267
|
+
def handle_a2a_task_cancel(self):
|
1268
|
+
"""Handle A2A task cancel request."""
|
1269
|
+
agent = self._get_or_create_a2a_agent()
|
1270
|
+
if not agent:
|
1271
|
+
return jsonify({"error": "A2A agent not enabled"}), 501
|
1272
|
+
|
1273
|
+
try:
|
1274
|
+
data = request.get_json()
|
1275
|
+
if not data:
|
1276
|
+
return jsonify({
|
1277
|
+
"jsonrpc": "2.0",
|
1278
|
+
"error": {
|
1279
|
+
"code": -32700,
|
1280
|
+
"message": "Parse error: Invalid JSON"
|
1281
|
+
},
|
1282
|
+
"id": None
|
1283
|
+
}), 400
|
1284
|
+
|
1285
|
+
# Run async handler
|
1286
|
+
loop = asyncio.new_event_loop()
|
1287
|
+
asyncio.set_event_loop(loop)
|
1288
|
+
try:
|
1289
|
+
response = loop.run_until_complete(agent.handle_task_cancel(data))
|
1290
|
+
return jsonify(response)
|
1291
|
+
finally:
|
1292
|
+
loop.close()
|
1293
|
+
|
1294
|
+
except Exception as e:
|
1295
|
+
log.error(f"A2A task cancel error: {e}")
|
1296
|
+
return jsonify({
|
1297
|
+
"jsonrpc": "2.0",
|
1298
|
+
"error": {
|
1299
|
+
"code": -32603,
|
1300
|
+
"message": f"Internal error: {str(e)}"
|
1301
|
+
},
|
1302
|
+
"id": data.get("id") if 'data' in locals() else None
|
1303
|
+
}), 500
|
1304
|
+
|
1305
|
+
def handle_a2a_push_notification(self):
|
1306
|
+
"""Handle A2A push notification settings."""
|
1307
|
+
agent = self._get_or_create_a2a_agent()
|
1308
|
+
if not agent:
|
1309
|
+
return jsonify({"error": "A2A agent not enabled"}), 501
|
1310
|
+
|
1311
|
+
try:
|
1312
|
+
data = request.get_json()
|
1313
|
+
if not data:
|
1314
|
+
return jsonify({
|
1315
|
+
"jsonrpc": "2.0",
|
1316
|
+
"error": {
|
1317
|
+
"code": -32700,
|
1318
|
+
"message": "Parse error: Invalid JSON"
|
1319
|
+
},
|
1320
|
+
"id": None
|
1321
|
+
}), 400
|
1322
|
+
|
1323
|
+
# Run async handler
|
1324
|
+
loop = asyncio.new_event_loop()
|
1325
|
+
asyncio.set_event_loop(loop)
|
1326
|
+
try:
|
1327
|
+
response = loop.run_until_complete(agent.handle_push_notification_set(data))
|
1328
|
+
return jsonify(response)
|
1329
|
+
finally:
|
1330
|
+
loop.close()
|
1331
|
+
|
1332
|
+
except Exception as e:
|
1333
|
+
log.error(f"A2A push notification error: {e}")
|
1334
|
+
return jsonify({
|
1335
|
+
"jsonrpc": "2.0",
|
1336
|
+
"error": {
|
1337
|
+
"code": -32603,
|
1338
|
+
"message": f"Internal error: {str(e)}"
|
1339
|
+
},
|
1340
|
+
"id": data.get("id") if 'data' in locals() else None
|
1341
|
+
}), 500
|
sunholo/mcp/__init__.py
CHANGED
@@ -14,7 +14,16 @@
|
|
14
14
|
|
15
15
|
"""MCP (Model Context Protocol) integration for Sunholo."""
|
16
16
|
|
17
|
-
|
18
|
-
from .
|
17
|
+
try:
|
18
|
+
from .mcp_manager import MCPClientManager
|
19
|
+
except ImportError as e:
|
20
|
+
print(f"Warning: MCPClientManager not available - {e}")
|
21
|
+
MCPClientManager = None
|
22
|
+
|
23
|
+
try:
|
24
|
+
from .vac_mcp_server import VACMCPServer
|
25
|
+
except ImportError as e:
|
26
|
+
print(f"Warning: VACMCPServer not available - {e}")
|
27
|
+
VACMCPServer = None
|
19
28
|
|
20
29
|
__all__ = ['MCPClientManager', 'VACMCPServer']
|
sunholo/mcp/mcp_manager.py
CHANGED
@@ -4,11 +4,34 @@ This shows how to integrate MCP servers with your Flask/VACRoutes application.
|
|
4
4
|
"""
|
5
5
|
|
6
6
|
from typing import Dict, Any, List, Optional
|
7
|
+
import asyncio
|
7
8
|
|
8
|
-
#
|
9
|
-
|
10
|
-
from mcp.client.stdio import
|
11
|
-
from mcp.
|
9
|
+
# MCP SDK imports - try different import paths
|
10
|
+
try:
|
11
|
+
from mcp.client.stdio import StdioClientTransport
|
12
|
+
from mcp.client.session import ClientSession
|
13
|
+
except ImportError:
|
14
|
+
try:
|
15
|
+
# Alternative import paths
|
16
|
+
from mcp.client import StdioClientTransport, ClientSession
|
17
|
+
except ImportError:
|
18
|
+
try:
|
19
|
+
# Another alternative
|
20
|
+
from mcp import StdioClientTransport, ClientSession
|
21
|
+
except ImportError:
|
22
|
+
StdioClientTransport = None
|
23
|
+
ClientSession = None
|
24
|
+
|
25
|
+
try:
|
26
|
+
from mcp.types import Tool, Resource, TextContent, CallToolResult
|
27
|
+
except ImportError:
|
28
|
+
try:
|
29
|
+
from mcp import Tool, Resource, TextContent, CallToolResult
|
30
|
+
except ImportError:
|
31
|
+
Tool = None
|
32
|
+
Resource = None
|
33
|
+
TextContent = None
|
34
|
+
CallToolResult = None
|
12
35
|
|
13
36
|
|
14
37
|
class MCPClientManager:
|
@@ -23,41 +46,42 @@ class MCPClientManager:
|
|
23
46
|
if server_name in self.sessions:
|
24
47
|
return self.sessions[server_name]
|
25
48
|
|
26
|
-
|
27
|
-
|
49
|
+
if not StdioClientTransport or not ClientSession:
|
50
|
+
raise ImportError("MCP client dependencies not available")
|
51
|
+
|
52
|
+
# Create transport and session
|
53
|
+
transport = StdioClientTransport(
|
28
54
|
command=command,
|
29
55
|
args=args or []
|
30
56
|
)
|
57
|
+
session = ClientSession(transport)
|
58
|
+
await session.initialize()
|
31
59
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
self.server_configs[server_name] = {
|
39
|
-
"command": command,
|
40
|
-
"args": args
|
41
|
-
}
|
42
|
-
return session
|
60
|
+
self.sessions[server_name] = session
|
61
|
+
self.server_configs[server_name] = {
|
62
|
+
"command": command,
|
63
|
+
"args": args
|
64
|
+
}
|
65
|
+
return session
|
43
66
|
|
44
67
|
async def list_tools(self, server_name: Optional[str] = None) -> List[Tool]:
|
45
68
|
"""List available tools from one or all connected servers."""
|
46
69
|
if server_name:
|
47
70
|
session = self.sessions.get(server_name)
|
48
71
|
if session:
|
49
|
-
|
72
|
+
result = await session.list_tools()
|
73
|
+
return result.tools
|
50
74
|
return []
|
51
75
|
|
52
76
|
# List from all servers
|
53
77
|
all_tools = []
|
54
78
|
for name, session in self.sessions.items():
|
55
|
-
|
79
|
+
result = await session.list_tools()
|
56
80
|
# Add server name to tool metadata
|
57
|
-
for tool in tools:
|
81
|
+
for tool in result.tools:
|
58
82
|
tool.metadata = tool.metadata or {}
|
59
83
|
tool.metadata["server"] = name
|
60
|
-
all_tools.extend(tools)
|
84
|
+
all_tools.extend(result.tools)
|
61
85
|
return all_tools
|
62
86
|
|
63
87
|
async def call_tool(self, server_name: str, tool_name: str, arguments: Dict[str, Any]) -> CallToolResult:
|
@@ -67,7 +91,13 @@ class MCPClientManager:
|
|
67
91
|
raise ValueError(f"Not connected to server: {server_name}")
|
68
92
|
|
69
93
|
# Call the tool
|
70
|
-
|
94
|
+
try:
|
95
|
+
from mcp.types import CallToolRequest
|
96
|
+
request = CallToolRequest(name=tool_name, arguments=arguments)
|
97
|
+
result = await session.call_tool(request)
|
98
|
+
except ImportError:
|
99
|
+
# Try direct call if Request types not available
|
100
|
+
result = await session.call_tool(tool_name, arguments)
|
71
101
|
return result
|
72
102
|
|
73
103
|
async def list_resources(self, server_name: Optional[str] = None) -> List[Resource]:
|
@@ -75,17 +105,18 @@ class MCPClientManager:
|
|
75
105
|
if server_name:
|
76
106
|
session = self.sessions.get(server_name)
|
77
107
|
if session:
|
78
|
-
|
108
|
+
result = await session.list_resources()
|
109
|
+
return result.resources
|
79
110
|
return []
|
80
111
|
|
81
112
|
# List from all servers
|
82
113
|
all_resources = []
|
83
114
|
for name, session in self.sessions.items():
|
84
|
-
|
85
|
-
for resource in resources:
|
115
|
+
result = await session.list_resources()
|
116
|
+
for resource in result.resources:
|
86
117
|
resource.metadata = resource.metadata or {}
|
87
118
|
resource.metadata["server"] = name
|
88
|
-
all_resources.extend(resources)
|
119
|
+
all_resources.extend(result.resources)
|
89
120
|
return all_resources
|
90
121
|
|
91
122
|
async def read_resource(self, server_name: str, uri: str) -> List[TextContent]:
|
@@ -94,5 +125,12 @@ class MCPClientManager:
|
|
94
125
|
if not session:
|
95
126
|
raise ValueError(f"Not connected to server: {server_name}")
|
96
127
|
|
97
|
-
|
98
|
-
|
128
|
+
try:
|
129
|
+
from mcp.types import ReadResourceRequest
|
130
|
+
request = ReadResourceRequest(uri=uri)
|
131
|
+
result = await session.read_resource(request)
|
132
|
+
except ImportError:
|
133
|
+
# Try direct call if Request types not available
|
134
|
+
result = await session.read_resource(uri)
|
135
|
+
|
136
|
+
return result.contents if hasattr(result, 'contents') else result
|
sunholo/mcp/vac_mcp_server.py
CHANGED
@@ -22,13 +22,8 @@ import json
|
|
22
22
|
import asyncio
|
23
23
|
from functools import partial
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource
|
28
|
-
except ImportError:
|
29
|
-
Server = None
|
30
|
-
Tool = None
|
31
|
-
TextContent = None
|
25
|
+
from mcp.server import Server
|
26
|
+
from mcp.types import Tool, TextContent, ImageContent, EmbeddedResource
|
32
27
|
|
33
28
|
from ..custom_logging import log
|
34
29
|
from ..streaming import start_streaming_chat_async
|
@@ -45,8 +40,7 @@ class VACMCPServer:
|
|
45
40
|
stream_interpreter: The streaming interpreter function
|
46
41
|
vac_interpreter: The static VAC interpreter function (optional)
|
47
42
|
"""
|
48
|
-
|
49
|
-
raise ImportError("MCP server requires `pip install sunholo[anthropic]`")
|
43
|
+
# MCP server is always available with current SDK
|
50
44
|
|
51
45
|
self.stream_interpreter = stream_interpreter
|
52
46
|
self.vac_interpreter = vac_interpreter
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: sunholo
|
3
|
-
Version: 0.143.
|
3
|
+
Version: 0.143.7
|
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,9 +15,10 @@ 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.
|
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
|
22
23
|
Requires-Dist: flask>=3.1.0
|
23
24
|
Requires-Dist: google-auth
|
@@ -126,6 +127,7 @@ Requires-Dist: pytesseract; extra == "pipeline"
|
|
126
127
|
Requires-Dist: tabulate; extra == "pipeline"
|
127
128
|
Requires-Dist: unstructured[all-docs,local-inference]; extra == "pipeline"
|
128
129
|
Provides-Extra: gcp
|
130
|
+
Requires-Dist: a2a-python; extra == "gcp"
|
129
131
|
Requires-Dist: aiofiles; extra == "gcp"
|
130
132
|
Requires-Dist: anthropic[vertex]; extra == "gcp"
|
131
133
|
Requires-Dist: google-api-python-client; extra == "gcp"
|
@@ -1,6 +1,10 @@
|
|
1
|
-
sunholo/__init__.py,sha256=
|
1
|
+
sunholo/__init__.py,sha256=_zHD01JyUVc-kGqm_bWtWxytec3b8-25fm5vK5j_2VU,1256
|
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=
|
21
|
+
sunholo/agents/flask/vac_routes.py,sha256=MvEsIWxrdeuRx-t_RfkodnJyJKP40aKaADWV4Yi3ijw,53375
|
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,10 +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=
|
117
|
+
sunholo/mcp/__init__.py,sha256=Bi0ZYMvWuf1AL_QSrMAREVVdTZFiIokGwrytBXKBJyc,1028
|
114
118
|
sunholo/mcp/cli.py,sha256=d24nnVzhZYz4AWgTqmN-qjKG4rPbf8RhdmEOHZkBHy8,10570
|
115
|
-
sunholo/mcp/mcp_manager.py,sha256=
|
116
|
-
sunholo/mcp/vac_mcp_server.py,sha256=
|
119
|
+
sunholo/mcp/mcp_manager.py,sha256=g75vv6XvM24U7uz366slE-p76Qs4AvVcsarHSF9qIvE,5061
|
120
|
+
sunholo/mcp/vac_mcp_server.py,sha256=DSSMsTeLU_1C2hHTvgLjZsg1chyMfb_S9ruCUw2f_dc,9969
|
117
121
|
sunholo/ollama/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
118
122
|
sunholo/ollama/ollama_images.py,sha256=H2cpcNu88R4TwyfL_nnqkQhdvBQ2FPCAy4Ok__0yQmo,2351
|
119
123
|
sunholo/pubsub/__init__.py,sha256=DfTEk4zmCfqn6gFxRrqDO0pOrvXTDqH-medpgYO4PGw,117
|
@@ -171,9 +175,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
|
|
171
175
|
sunholo/vertex/memory_tools.py,sha256=tBZxqVZ4InTmdBvLlOYwoSEWu4-kGquc-gxDwZCC4FA,7667
|
172
176
|
sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
|
173
177
|
sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
|
174
|
-
sunholo-0.143.
|
175
|
-
sunholo-0.143.
|
176
|
-
sunholo-0.143.
|
177
|
-
sunholo-0.143.
|
178
|
-
sunholo-0.143.
|
179
|
-
sunholo-0.143.
|
178
|
+
sunholo-0.143.7.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
|
179
|
+
sunholo-0.143.7.dist-info/METADATA,sha256=c1IhnVsC3r20UUvBImOmvOIirVrkuNY6Tj-xFuF3jr0,18413
|
180
|
+
sunholo-0.143.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
181
|
+
sunholo-0.143.7.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
|
182
|
+
sunholo-0.143.7.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
|
183
|
+
sunholo-0.143.7.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|