sunholo 0.144.1__py3-none-any.whl → 0.144.2__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.
@@ -58,7 +58,7 @@ except ImportError:
58
58
  MCPClientManager = None
59
59
 
60
60
  try:
61
- from ...mcp.vac_mcp_server import VACMCPServer
61
+ from ...mcp.vac_mcp_server_fastmcp import VACMCPServer
62
62
  except ImportError:
63
63
  VACMCPServer = None
64
64
 
@@ -81,31 +81,205 @@ class VACRequest(BaseModel):
81
81
 
82
82
  class VACRoutesFastAPI:
83
83
  """
84
- FastAPI implementation of VAC routes with streaming support.
84
+ FastAPI implementation of VAC routes with streaming support and extensible MCP integration.
85
85
 
86
- This class provides a FastAPI-compatible version of the Flask VACRoutes,
87
- with proper async streaming support using callbacks.
86
+ This class provides a comprehensive FastAPI application with:
87
+ - VAC (Virtual Agent Computer) endpoints for AI chat and streaming
88
+ - OpenAI-compatible API endpoints
89
+ - Extensible MCP (Model Context Protocol) server integration for Claude Desktop/Code
90
+ - MCP client support for connecting to external MCP servers
91
+ - A2A (Agent-to-Agent) protocol support
92
+ - Server-Sent Events (SSE) streaming capabilities
93
+
94
+ ## Key Features
95
+
96
+ ### 1. VAC Endpoints
97
+ - `/vac/{vector_name}` - Non-streaming VAC responses
98
+ - `/vac/streaming/{vector_name}` - Plain text streaming responses
99
+ - `/vac/streaming/{vector_name}/sse` - Server-Sent Events streaming
100
+
101
+ ### 2. OpenAI Compatible API
102
+ - `/openai/v1/chat/completions` - OpenAI-compatible chat completions
103
+ - Supports both streaming and non-streaming modes
104
+
105
+ ### 3. MCP Integration
106
+ - **MCP Server**: Expose your VAC as MCP tools for Claude Desktop/Code
107
+ - **MCP Client**: Connect to external MCP servers and use their tools
108
+ - **Custom Tools**: Easily add your own MCP tools using decorators
109
+
110
+ ### 4. A2A Agent Protocol
111
+ - Agent discovery and task execution
112
+ - Compatible with multi-agent workflows
113
+
114
+ ## Basic Usage
88
115
 
89
- Usage Example:
90
116
  ```python
91
117
  from fastapi import FastAPI
92
118
  from sunholo.agents.fastapi import VACRoutesFastAPI
93
119
 
94
120
  app = FastAPI()
95
121
 
96
- async def stream_interpreter(question, vector_name, chat_history, callback, **kwargs):
97
- # Implement your streaming logic with callbacks
98
- ...
122
+ async def my_stream_interpreter(question, vector_name, chat_history, callback, **kwargs):
123
+ # Your streaming VAC logic here
124
+ # Use callback.async_on_llm_new_token(token) for streaming
125
+ # Return final result with sources
126
+ return {"answer": "Response", "sources": []}
127
+
128
+ # Create VAC routes with MCP server enabled
129
+ vac_routes = VACRoutesFastAPI(
130
+ app=app,
131
+ stream_interpreter=my_stream_interpreter,
132
+ enable_mcp_server=True # Enable MCP server for Claude Desktop/Code
133
+ )
134
+
135
+ # Your FastAPI app now includes:
136
+ # - All VAC endpoints
137
+ # - MCP server at /mcp (for Claude Desktop/Code to connect)
138
+ # - Built-in VAC tools: vac_stream, vac_query, list_available_vacs, get_vac_info
139
+ ```
140
+
141
+ ## Adding Custom MCP Tools
142
+
143
+ ### Method 1: Using Decorators
144
+ ```python
145
+ vac_routes = VACRoutesFastAPI(app, stream_interpreter, enable_mcp_server=True)
146
+
147
+ @vac_routes.add_mcp_tool
148
+ async def get_weather(city: str) -> str:
149
+ '''Get weather information for a city.'''
150
+ # Your weather API logic
151
+ return f"Weather in {city}: Sunny, 22°C"
152
+
153
+ @vac_routes.add_mcp_tool("custom_search", "Search our database")
154
+ async def search_database(query: str, limit: int = 10) -> list:
155
+ '''Search internal database with custom name and description.'''
156
+ # Your database search logic
157
+ return [{"result": f"Found: {query}"}]
158
+ ```
159
+
160
+ ### Method 2: Programmatic Registration
161
+ ```python
162
+ async def my_business_tool(param: str) -> dict:
163
+ return {"processed": param}
164
+
165
+ # Add tool with custom name and description
166
+ vac_routes.add_mcp_tool(
167
+ my_business_tool,
168
+ "process_business_data",
169
+ "Process business data with our custom logic"
170
+ )
171
+ ```
172
+
173
+ ### Method 3: Advanced MCP Server Access
174
+ ```python
175
+ # Get direct access to MCP server for advanced customization
176
+ mcp_server = vac_routes.get_mcp_server()
177
+
178
+ @mcp_server.add_tool
179
+ async def advanced_tool(complex_param: dict) -> str:
180
+ return f"Advanced processing: {complex_param}"
181
+
182
+ # List all registered tools
183
+ print("Available MCP tools:", vac_routes.list_mcp_tools())
184
+ ```
99
185
 
100
- async def vac_interpreter(question, vector_name, chat_history, **kwargs):
101
- # Implement your static VAC logic
102
- ...
186
+ ## MCP Client Integration
187
+
188
+ Connect to external MCP servers and use their tools:
189
+
190
+ ```python
191
+ mcp_servers = [
192
+ {
193
+ "name": "filesystem-server",
194
+ "command": "npx",
195
+ "args": ["@modelcontextprotocol/server-filesystem", "/path/to/files"]
196
+ }
197
+ ]
103
198
 
104
199
  vac_routes = VACRoutesFastAPI(
105
- app,
106
- stream_interpreter,
107
- vac_interpreter,
108
- enable_mcp_server=True
200
+ app, stream_interpreter,
201
+ mcp_servers=mcp_servers, # Connect to external MCP servers
202
+ enable_mcp_server=True # Also expose our own MCP server
203
+ )
204
+
205
+ # External MCP tools available at:
206
+ # GET /mcp/tools - List all external tools
207
+ # POST /mcp/call - Call external MCP tools
208
+ ```
209
+
210
+ ## Claude Desktop Integration
211
+
212
+ ### Option 1: Remote Integration (Recommended for Development)
213
+ ```python
214
+ # Run your FastAPI app
215
+ uvicorn.run(vac_routes.app, host="0.0.0.0", port=8000)
216
+
217
+ # Configure Claude Desktop (Settings > Connectors > Add custom connector):
218
+ # URL: http://localhost:8000/mcp
219
+ ```
220
+
221
+ ### Option 2: Local Integration
222
+ Create a standalone script for Claude Desktop:
223
+ ```python
224
+ # claude_mcp_server.py
225
+ from sunholo.mcp.extensible_mcp_server import create_mcp_server
226
+
227
+ server = create_mcp_server("my-app", include_vac_tools=True)
228
+
229
+ @server.add_tool
230
+ async def my_app_tool(param: str) -> str:
231
+ return f"My app processed: {param}"
232
+
233
+ if __name__ == "__main__":
234
+ server.run()
235
+
236
+ # Install: fastmcp install claude-desktop claude_mcp_server.py --with sunholo[anthropic]
237
+ ```
238
+
239
+ ## Available Built-in MCP Tools
240
+
241
+ When `enable_mcp_server=True`, these tools are automatically available:
242
+
243
+ - **`vac_stream`**: Stream responses from any configured VAC
244
+ - **`vac_query`**: Query VACs with non-streaming responses
245
+ - **`list_available_vacs`**: List all available VAC configurations
246
+ - **`get_vac_info`**: Get detailed information about a specific VAC
247
+
248
+ ## Error Handling and Best Practices
249
+
250
+ ```python
251
+ @vac_routes.add_mcp_tool
252
+ async def robust_tool(user_input: str) -> str:
253
+ '''Example of robust tool implementation.'''
254
+ try:
255
+ # Validate input
256
+ if not user_input or len(user_input) > 1000:
257
+ return "Error: Invalid input length"
258
+
259
+ # Your business logic
260
+ result = await process_user_input(user_input)
261
+
262
+ return f"Processed: {result}"
263
+
264
+ except Exception as e:
265
+ # Log error and return user-friendly message
266
+ log.error(f"Tool error: {e}")
267
+ return f"Error processing request: {str(e)}"
268
+ ```
269
+
270
+ ## Configuration Options
271
+
272
+ ```python
273
+ vac_routes = VACRoutesFastAPI(
274
+ app=app,
275
+ stream_interpreter=my_stream_func,
276
+ vac_interpreter=my_vac_func, # Optional non-streaming function
277
+ additional_routes=[], # Custom FastAPI routes
278
+ mcp_servers=[], # External MCP servers to connect to
279
+ add_langfuse_eval=True, # Enable Langfuse evaluation
280
+ enable_mcp_server=True, # Enable MCP server for Claude
281
+ enable_a2a_agent=False, # Enable A2A agent protocol
282
+ a2a_vac_names=None # VACs available for A2A
109
283
  )
110
284
  ```
111
285
  """
@@ -123,18 +297,86 @@ class VACRoutesFastAPI:
123
297
  a2a_vac_names: Optional[List[str]] = None
124
298
  ):
125
299
  """
126
- Initialize FastAPI VAC routes.
300
+ Initialize FastAPI VAC routes with comprehensive AI and MCP integration.
127
301
 
128
302
  Args:
129
- app: FastAPI application instance
130
- stream_interpreter: Async or sync function for streaming responses
131
- vac_interpreter: Optional function for non-streaming responses
132
- additional_routes: List of additional routes to register
133
- mcp_servers: List of MCP server configurations
134
- add_langfuse_eval: Whether to add Langfuse evaluation
135
- enable_mcp_server: Whether to enable MCP server endpoint
136
- enable_a2a_agent: Whether to enable A2A agent endpoints
137
- a2a_vac_names: List of VAC names for A2A agent
303
+ app: FastAPI application instance to register routes on
304
+ stream_interpreter: Function for streaming VAC responses. Can be async or sync.
305
+ Called with (question, vector_name, chat_history, callback, **kwargs)
306
+ vac_interpreter: Optional function for non-streaming VAC responses. If not provided,
307
+ will use stream_interpreter without streaming callbacks.
308
+ additional_routes: List of custom route dictionaries to register:
309
+ [{"path": "/custom", "handler": func, "methods": ["GET"]}]
310
+ mcp_servers: List of external MCP server configurations to connect to:
311
+ [{"name": "server-name", "command": "python", "args": ["server.py"]}]
312
+ add_langfuse_eval: Whether to enable Langfuse evaluation and tracing
313
+ enable_mcp_server: Whether to enable the MCP server at /mcp endpoint for
314
+ Claude Desktop/Code integration. When True, automatically
315
+ includes built-in VAC tools and supports custom tool registration.
316
+ enable_a2a_agent: Whether to enable A2A (Agent-to-Agent) protocol endpoints
317
+ a2a_vac_names: List of VAC names available for A2A agent interactions
318
+
319
+ ## Stream Interpreter Function
320
+
321
+ Your stream_interpreter should handle streaming responses:
322
+
323
+ ```python
324
+ async def my_stream_interpreter(question: str, vector_name: str,
325
+ chat_history: list, callback, **kwargs):
326
+ # Process the question using your AI/RAG pipeline
327
+
328
+ # For streaming tokens:
329
+ await callback.async_on_llm_new_token("partial response...")
330
+
331
+ # Return final result with sources:
332
+ return {
333
+ "answer": "Final complete answer",
334
+ "sources": [{"title": "Source 1", "url": "..."}]
335
+ }
336
+ ```
337
+
338
+ ## MCP Server Integration
339
+
340
+ When enable_mcp_server=True, the following happens:
341
+ 1. MCP server is mounted at /mcp endpoint
342
+ 2. Built-in VAC tools are automatically registered:
343
+ - vac_stream, vac_query, list_available_vacs, get_vac_info
344
+ 3. You can add custom MCP tools using add_mcp_tool()
345
+ 4. Claude Desktop/Code can connect to http://your-server/mcp
346
+
347
+ ## Complete Example
348
+
349
+ ```python
350
+ app = FastAPI(title="My VAC Application")
351
+
352
+ async def my_vac_logic(question, vector_name, chat_history, callback, **kwargs):
353
+ # Your AI/RAG implementation
354
+ result = await process_with_ai(question)
355
+ return {"answer": result, "sources": []}
356
+
357
+ # External MCP servers to connect to
358
+ external_mcp = [
359
+ {"name": "filesystem", "command": "mcp-server-fs", "args": ["/data"]}
360
+ ]
361
+
362
+ vac_routes = VACRoutesFastAPI(
363
+ app=app,
364
+ stream_interpreter=my_vac_logic,
365
+ mcp_servers=external_mcp,
366
+ enable_mcp_server=True # Enable for Claude integration
367
+ )
368
+
369
+ # Add custom MCP tools for your business logic
370
+ @vac_routes.add_mcp_tool
371
+ async def get_customer_info(customer_id: str) -> dict:
372
+ return await fetch_customer(customer_id)
373
+
374
+ # Your app now has:
375
+ # - VAC endpoints: /vac/{vector_name}, /vac/streaming/{vector_name}
376
+ # - OpenAI API: /openai/v1/chat/completions
377
+ # - MCP server: /mcp (with built-in + custom tools)
378
+ # - MCP client: /mcp/tools, /mcp/call (for external servers)
379
+ ```
138
380
  """
139
381
  self.app = app
140
382
  self.stream_interpreter = stream_interpreter
@@ -152,11 +394,17 @@ class VACRoutesFastAPI:
152
394
  # MCP server initialization
153
395
  self.enable_mcp_server = enable_mcp_server
154
396
  self.vac_mcp_server = None
397
+ self._custom_mcp_tools = []
398
+ self._custom_mcp_resources = []
399
+
155
400
  if self.enable_mcp_server and VACMCPServer:
156
401
  self.vac_mcp_server = VACMCPServer(
157
- stream_interpreter=self.stream_interpreter,
158
- vac_interpreter=self.vac_interpreter
402
+ server_name="sunholo-vac-fastapi-server",
403
+ include_vac_tools=True
159
404
  )
405
+
406
+ # Add any pre-registered custom tools
407
+ self._register_custom_tools()
160
408
 
161
409
  # A2A agent initialization
162
410
  self.enable_a2a_agent = enable_a2a_agent
@@ -232,10 +480,15 @@ class VACRoutesFastAPI:
232
480
  self.app.get("/mcp/resources")(self.handle_mcp_list_resources)
233
481
  self.app.post("/mcp/resources/read")(self.handle_mcp_read_resource)
234
482
 
235
- # MCP server endpoint
483
+ # MCP server endpoint - mount the FastMCP app
236
484
  if self.enable_mcp_server and self.vac_mcp_server:
237
- self.app.post("/mcp")(self.handle_mcp_server)
238
- self.app.get("/mcp")(self.handle_mcp_server_info)
485
+ try:
486
+ mcp_app = self.vac_mcp_server.get_http_app()
487
+ self.app.mount("/mcp", mcp_app)
488
+ log.info("MCP server mounted at /mcp endpoint")
489
+ except Exception as e:
490
+ log.error(f"Failed to mount MCP server: {e}")
491
+ raise RuntimeError(f"MCP server initialization failed: {e}")
239
492
 
240
493
  # A2A agent endpoints
241
494
  if self.enable_a2a_agent:
@@ -787,109 +1040,6 @@ class VACRoutesFastAPI:
787
1040
  except Exception as e:
788
1041
  raise HTTPException(status_code=500, detail=str(e))
789
1042
 
790
- async def handle_mcp_server(self, request: Request):
791
- """Handle MCP server requests."""
792
- if not self.vac_mcp_server:
793
- raise HTTPException(status_code=501, detail="MCP server not enabled")
794
-
795
- data = await request.json()
796
- log.info(f"MCP server received: {data}")
797
-
798
- # Process MCP request - simplified version
799
- # Full implementation would handle all MCP protocol methods
800
- method = data.get("method")
801
- params = data.get("params", {})
802
- request_id = data.get("id")
803
-
804
- try:
805
- if method == "initialize":
806
- response = {
807
- "jsonrpc": "2.0",
808
- "result": {
809
- "protocolVersion": "2025-06-18",
810
- "capabilities": {"tools": {}},
811
- "serverInfo": {
812
- "name": "sunholo-vac-server",
813
- "version": sunholo_version()
814
- }
815
- },
816
- "id": request_id
817
- }
818
- elif method == "tools/list":
819
- tools = [
820
- {
821
- "name": "vac_stream",
822
- "description": "Stream responses from a Sunholo VAC",
823
- "inputSchema": {
824
- "type": "object",
825
- "properties": {
826
- "vector_name": {"type": "string"},
827
- "user_input": {"type": "string"},
828
- "chat_history": {"type": "array", "default": []}
829
- },
830
- "required": ["vector_name", "user_input"]
831
- }
832
- }
833
- ]
834
- if self.vac_interpreter:
835
- tools.append({
836
- "name": "vac_query",
837
- "description": "Query a Sunholo VAC (non-streaming)",
838
- "inputSchema": {
839
- "type": "object",
840
- "properties": {
841
- "vector_name": {"type": "string"},
842
- "user_input": {"type": "string"},
843
- "chat_history": {"type": "array", "default": []}
844
- },
845
- "required": ["vector_name", "user_input"]
846
- }
847
- })
848
- response = {
849
- "jsonrpc": "2.0",
850
- "result": {"tools": tools},
851
- "id": request_id
852
- }
853
- elif method == "tools/call":
854
- tool_name = params.get("name")
855
- arguments = params.get("arguments", {})
856
-
857
- if tool_name == "vac_stream":
858
- result = await self.vac_mcp_server._handle_vac_stream(arguments)
859
- elif tool_name == "vac_query":
860
- result = await self.vac_mcp_server._handle_vac_query(arguments)
861
- else:
862
- raise ValueError(f"Unknown tool: {tool_name}")
863
-
864
- response = {
865
- "jsonrpc": "2.0",
866
- "result": {"content": [item.model_dump() for item in result]},
867
- "id": request_id
868
- }
869
- else:
870
- raise ValueError(f"Unknown method: {method}")
871
-
872
- except Exception as e:
873
- response = {
874
- "jsonrpc": "2.0",
875
- "error": {
876
- "code": -32603,
877
- "message": str(e)
878
- },
879
- "id": request_id
880
- }
881
-
882
- return JSONResponse(content=response)
883
-
884
- async def handle_mcp_server_info(self):
885
- """Return MCP server information."""
886
- return JSONResponse(content={
887
- "name": "sunholo-vac-server",
888
- "version": "1.0.0",
889
- "transport": "http",
890
- "endpoint": "/mcp",
891
- "tools": ["vac_stream", "vac_query"] if self.vac_interpreter else ["vac_stream"]
892
- })
893
1043
 
894
1044
  def _get_or_create_a2a_agent(self, request: Request):
895
1045
  """Get or create the A2A agent instance with current request context."""
@@ -1039,4 +1189,94 @@ class VACRoutesFastAPI:
1039
1189
  "id": data.get("id") if 'data' in locals() else None
1040
1190
  },
1041
1191
  status_code=500
1042
- )
1192
+ )
1193
+
1194
+ # MCP Tool Registration Methods
1195
+
1196
+ def _register_custom_tools(self):
1197
+ """Register any custom tools that were added before MCP server initialization."""
1198
+ if self.vac_mcp_server:
1199
+ for tool_func, name, description in self._custom_mcp_tools:
1200
+ self.vac_mcp_server.add_tool(tool_func, name, description)
1201
+ for resource_func, name, description in self._custom_mcp_resources:
1202
+ self.vac_mcp_server.add_resource(resource_func, name, description)
1203
+
1204
+ def add_mcp_tool(self, func: Callable, name: str = None, description: str = None):
1205
+ """
1206
+ Add a custom MCP tool to the server.
1207
+
1208
+ Args:
1209
+ func: The tool function
1210
+ name: Optional custom name for the tool
1211
+ description: Optional description (uses docstring if not provided)
1212
+
1213
+ Example:
1214
+ @app.add_mcp_tool
1215
+ async def my_custom_tool(param: str) -> str:
1216
+ '''Custom tool that does something useful.'''
1217
+ return f"Result: {param}"
1218
+
1219
+ # Or with custom name and description
1220
+ app.add_mcp_tool(my_function, "custom_name", "Custom description")
1221
+ """
1222
+ if self.vac_mcp_server:
1223
+ self.vac_mcp_server.add_tool(func, name, description)
1224
+ else:
1225
+ # Store for later registration
1226
+ self._custom_mcp_tools.append((func, name, description))
1227
+
1228
+ return func # Allow use as decorator
1229
+
1230
+ def add_mcp_resource(self, func: Callable, name: str = None, description: str = None):
1231
+ """
1232
+ Add a custom MCP resource to the server.
1233
+
1234
+ Args:
1235
+ func: The resource function
1236
+ name: Optional custom name for the resource
1237
+ description: Optional description (uses docstring if not provided)
1238
+
1239
+ Example:
1240
+ @app.add_mcp_resource
1241
+ async def my_custom_resource(uri: str) -> str:
1242
+ '''Custom resource that provides data.'''
1243
+ return f"Resource data for: {uri}"
1244
+ """
1245
+ if self.vac_mcp_server:
1246
+ self.vac_mcp_server.add_resource(func, name, description)
1247
+ else:
1248
+ # Store for later registration
1249
+ self._custom_mcp_resources.append((func, name, description))
1250
+
1251
+ return func # Allow use as decorator
1252
+
1253
+ def get_mcp_server(self):
1254
+ """
1255
+ Get the MCP server instance for advanced customization.
1256
+
1257
+ Returns:
1258
+ VACMCPServer instance or None if MCP server is not enabled
1259
+ """
1260
+ return self.vac_mcp_server
1261
+
1262
+ def list_mcp_tools(self) -> List[str]:
1263
+ """
1264
+ List all registered MCP tools.
1265
+
1266
+ Returns:
1267
+ List of tool names
1268
+ """
1269
+ if self.vac_mcp_server:
1270
+ return self.vac_mcp_server.list_tools()
1271
+ return []
1272
+
1273
+ def list_mcp_resources(self) -> List[str]:
1274
+ """
1275
+ List all registered MCP resources.
1276
+
1277
+ Returns:
1278
+ List of resource names
1279
+ """
1280
+ if self.vac_mcp_server:
1281
+ return self.vac_mcp_server.list_resources()
1282
+ return []