sunholo 0.144.2__py3-none-any.whl → 0.144.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.
@@ -113,31 +113,92 @@ class VACRoutesFastAPI:
113
113
 
114
114
  ## Basic Usage
115
115
 
116
+ ### Simplified Setup (Recommended)
117
+
118
+ Use the helper method for automatic lifespan management:
119
+
116
120
  ```python
117
- from fastapi import FastAPI
118
121
  from sunholo.agents.fastapi import VACRoutesFastAPI
119
122
 
120
- app = FastAPI()
121
-
122
123
  async def my_stream_interpreter(question, vector_name, chat_history, callback, **kwargs):
123
124
  # Your streaming VAC logic here
124
125
  # Use callback.async_on_llm_new_token(token) for streaming
125
- # Return final result with sources
126
126
  return {"answer": "Response", "sources": []}
127
127
 
128
- # Create VAC routes with MCP server enabled
128
+ # Single call sets up everything with MCP server and proper lifespan management
129
+ app, vac_routes = VACRoutesFastAPI.create_app_with_mcp(
130
+ title="My VAC Application",
131
+ stream_interpreter=my_stream_interpreter
132
+ # MCP server is automatically enabled when using this method
133
+ )
134
+
135
+ # Add custom endpoints if needed
136
+ @app.get("/custom")
137
+ async def custom_endpoint():
138
+ return {"message": "Hello"}
139
+
140
+ # Run the app
141
+ if __name__ == "__main__":
142
+ import uvicorn
143
+ uvicorn.run(app, host="0.0.0.0", port=8000)
144
+ ```
145
+
146
+ ### Manual Setup (Advanced)
147
+
148
+ For more control over lifespan management:
149
+
150
+ ```python
151
+ from contextlib import asynccontextmanager
152
+ from fastapi import FastAPI
153
+ from sunholo.agents.fastapi import VACRoutesFastAPI
154
+
155
+ async def my_stream_interpreter(question, vector_name, chat_history, callback, **kwargs):
156
+ return {"answer": "Response", "sources": []}
157
+
158
+ # Define your app's lifespan
159
+ @asynccontextmanager
160
+ async def app_lifespan(app: FastAPI):
161
+ print("Starting up...")
162
+ yield
163
+ print("Shutting down...")
164
+
165
+ # Create temp app to get MCP lifespan
166
+ temp_app = FastAPI()
167
+ vac_routes_temp = VACRoutesFastAPI(
168
+ temp_app,
169
+ stream_interpreter=my_stream_interpreter,
170
+ enable_mcp_server=True
171
+ )
172
+
173
+ # Get MCP lifespan
174
+ mcp_lifespan = vac_routes_temp.get_mcp_lifespan()
175
+
176
+ # Combine lifespans
177
+ @asynccontextmanager
178
+ async def combined_lifespan(app: FastAPI):
179
+ async with app_lifespan(app):
180
+ if mcp_lifespan:
181
+ async with mcp_lifespan(app):
182
+ yield
183
+ else:
184
+ yield
185
+
186
+ # Create app with combined lifespan
187
+ app = FastAPI(title="My VAC Application", lifespan=combined_lifespan)
188
+
189
+ # Initialize VAC routes
129
190
  vac_routes = VACRoutesFastAPI(
130
191
  app=app,
131
192
  stream_interpreter=my_stream_interpreter,
132
- enable_mcp_server=True # Enable MCP server for Claude Desktop/Code
193
+ enable_mcp_server=True
133
194
  )
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
195
  ```
140
196
 
197
+ Your FastAPI app now includes:
198
+ - All VAC endpoints
199
+ - MCP server at /mcp (for Claude Desktop/Code to connect)
200
+ - Built-in VAC tools: vac_stream, vac_query, list_available_vacs, get_vac_info
201
+
141
202
  ## Adding Custom MCP Tools
142
203
 
143
204
  ### Method 1: Using Decorators
@@ -416,6 +477,138 @@ class VACRoutesFastAPI:
416
477
 
417
478
  self.register_routes()
418
479
 
480
+ @staticmethod
481
+ def create_app_with_mcp(
482
+ title: str = "VAC Application",
483
+ stream_interpreter: Optional[callable] = None,
484
+ vac_interpreter: Optional[callable] = None,
485
+ app_lifespan: Optional[callable] = None,
486
+ **kwargs
487
+ ) -> tuple[FastAPI, 'VACRoutesFastAPI']:
488
+ """
489
+ Helper method to create a FastAPI app with proper MCP lifespan management.
490
+
491
+ This method simplifies the setup process by handling the lifespan combination
492
+ automatically, avoiding the need for the double initialization pattern.
493
+ MCP server is automatically enabled when using this method.
494
+
495
+ Args:
496
+ title: Title for the FastAPI app
497
+ stream_interpreter: Streaming interpreter function
498
+ vac_interpreter: Non-streaming interpreter function
499
+ app_lifespan: Optional app lifespan context manager
500
+ **kwargs: Additional arguments passed to VACRoutesFastAPI (except enable_mcp_server)
501
+
502
+ Returns:
503
+ Tuple of (FastAPI app, VACRoutesFastAPI instance)
504
+
505
+ Example:
506
+ ```python
507
+ from sunholo.agents.fastapi import VACRoutesFastAPI
508
+
509
+ async def my_interpreter(question, vector_name, chat_history, callback, **kwargs):
510
+ # Your logic here
511
+ return {"answer": "response", "sources": []}
512
+
513
+ # Single call to set up everything (MCP is automatically enabled)
514
+ app, vac_routes = VACRoutesFastAPI.create_app_with_mcp(
515
+ title="My VAC App",
516
+ stream_interpreter=my_interpreter
517
+ )
518
+
519
+ # Add custom endpoints
520
+ @app.get("/custom")
521
+ async def custom_endpoint():
522
+ return {"message": "Custom endpoint"}
523
+
524
+ if __name__ == "__main__":
525
+ import uvicorn
526
+ uvicorn.run(app, host="0.0.0.0", port=8000)
527
+ ```
528
+ """
529
+ from contextlib import asynccontextmanager
530
+
531
+ # Default app lifespan if not provided
532
+ if app_lifespan is None:
533
+ @asynccontextmanager
534
+ async def app_lifespan(app: FastAPI):
535
+ yield
536
+
537
+ # Create temporary app to get MCP app (always enabled for this method)
538
+ temp_app = FastAPI()
539
+ temp_routes = VACRoutesFastAPI(
540
+ temp_app,
541
+ stream_interpreter=stream_interpreter,
542
+ vac_interpreter=vac_interpreter,
543
+ enable_mcp_server=True, # Always enabled for create_app_with_mcp
544
+ **kwargs
545
+ )
546
+
547
+ mcp_app = None
548
+ if temp_routes.vac_mcp_server:
549
+ mcp_app = temp_routes.vac_mcp_server.get_http_app()
550
+
551
+ # Create combined lifespan
552
+ @asynccontextmanager
553
+ async def combined_lifespan(app: FastAPI):
554
+ async with app_lifespan(app):
555
+ if mcp_app:
556
+ async with mcp_app.lifespan(app):
557
+ yield
558
+ else:
559
+ yield
560
+
561
+ # Create the actual app with combined lifespan
562
+ app = FastAPI(
563
+ title=title,
564
+ lifespan=combined_lifespan if mcp_app else app_lifespan
565
+ )
566
+
567
+ # Initialize VAC routes (MCP always enabled for this method)
568
+ vac_routes = VACRoutesFastAPI(
569
+ app,
570
+ stream_interpreter=stream_interpreter,
571
+ vac_interpreter=vac_interpreter,
572
+ enable_mcp_server=True, # Always enabled for create_app_with_mcp
573
+ **kwargs
574
+ )
575
+
576
+ return app, vac_routes
577
+
578
+ def get_mcp_lifespan(self):
579
+ """
580
+ Get the MCP app's lifespan for manual lifespan management.
581
+
582
+ Returns:
583
+ The MCP app's lifespan if MCP server is enabled, None otherwise.
584
+
585
+ Example:
586
+ ```python
587
+ from contextlib import asynccontextmanager
588
+
589
+ # Create temp app to get MCP lifespan
590
+ temp_app = FastAPI()
591
+ vac_routes = VACRoutesFastAPI(temp_app, ..., enable_mcp_server=True)
592
+ mcp_lifespan = vac_routes.get_mcp_lifespan()
593
+
594
+ # Combine with your app's lifespan
595
+ @asynccontextmanager
596
+ async def combined_lifespan(app: FastAPI):
597
+ async with my_app_lifespan(app):
598
+ if mcp_lifespan:
599
+ async with mcp_lifespan(app):
600
+ yield
601
+ else:
602
+ yield
603
+
604
+ app = FastAPI(lifespan=combined_lifespan)
605
+ ```
606
+ """
607
+ if self.vac_mcp_server:
608
+ mcp_app = self.vac_mcp_server.get_http_app()
609
+ return mcp_app.lifespan
610
+ return None
611
+
419
612
  async def vac_interpreter_default(self, question: str, vector_name: str, chat_history=None, **kwargs):
420
613
  """Default VAC interpreter that uses the stream interpreter without streaming."""
421
614
  class NoOpCallback:
@@ -484,11 +677,34 @@ class VACRoutesFastAPI:
484
677
  if self.enable_mcp_server and self.vac_mcp_server:
485
678
  try:
486
679
  mcp_app = self.vac_mcp_server.get_http_app()
680
+
681
+ # Note: FastAPI doesn't expose lifespan as a public attribute,
682
+ # so we can't easily check if it's configured. The error will be
683
+ # caught below if lifespan is missing.
684
+
487
685
  self.app.mount("/mcp", mcp_app)
488
- log.info("MCP server mounted at /mcp endpoint")
686
+ log.info("MCP server mounted at /mcp endpoint")
687
+
688
+ except RuntimeError as e:
689
+ if "Task group is not initialized" in str(e):
690
+ error_msg = (
691
+ "MCP server initialization failed: Lifespan not configured properly.\n"
692
+ "The FastAPI app must be created with the MCP lifespan.\n\n"
693
+ "Quick fix: Use the helper method:\n"
694
+ " app, vac_routes = VACRoutesFastAPI.create_app_with_mcp(\n"
695
+ " stream_interpreter=your_interpreter,\n"
696
+ " enable_mcp_server=True\n"
697
+ " )\n\n"
698
+ "Or manually configure the lifespan - see documentation for details."
699
+ )
700
+ log.error(error_msg)
701
+ raise RuntimeError(error_msg) from e
702
+ else:
703
+ log.error(f"Failed to mount MCP server: {e}")
704
+ raise RuntimeError(f"MCP server initialization failed: {e}") from e
489
705
  except Exception as e:
490
706
  log.error(f"Failed to mount MCP server: {e}")
491
- raise RuntimeError(f"MCP server initialization failed: {e}")
707
+ raise RuntimeError(f"MCP server initialization failed: {e}") from e
492
708
 
493
709
  # A2A agent endpoints
494
710
  if self.enable_a2a_agent:
@@ -73,7 +73,7 @@ class VACMCPServer:
73
73
 
74
74
  def get_http_app(self):
75
75
  """Get the HTTP app for mounting in FastAPI."""
76
- return self.server.get_app()
76
+ return self.server.http_app()
77
77
 
78
78
  def add_tool(self, func: Callable, name: str = None, description: str = None):
79
79
  """
sunholo/mcp/vac_tools.py CHANGED
@@ -241,9 +241,10 @@ def register_vac_tools(server: 'FastMCP', registry: 'MCPToolRegistry' = None):
241
241
 
242
242
  # Register tools in registry if provided
243
243
  if registry:
244
- registry.register_tool("vac_stream", vac_stream)
245
- registry.register_tool("vac_query", vac_query)
246
- registry.register_tool("list_available_vacs", list_available_vacs)
247
- registry.register_tool("get_vac_info", get_vac_info)
244
+ # Extract the underlying function from FunctionTool objects
245
+ registry.register_tool("vac_stream", vac_stream.fn if hasattr(vac_stream, 'fn') else vac_stream)
246
+ registry.register_tool("vac_query", vac_query.fn if hasattr(vac_query, 'fn') else vac_query)
247
+ registry.register_tool("list_available_vacs", list_available_vacs.fn if hasattr(list_available_vacs, 'fn') else list_available_vacs)
248
+ registry.register_tool("get_vac_info", get_vac_info.fn if hasattr(get_vac_info, 'fn') else get_vac_info)
248
249
 
249
250
  log.info("Registered built-in VAC tools with MCP server")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sunholo
3
- Version: 0.144.2
3
+ Version: 0.144.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
@@ -16,7 +16,7 @@ sunholo/agents/swagger.py,sha256=2tzGmpveUMmTREykZvVnDj3j295wyOMu7mUFDnXdY3c,106
16
16
  sunholo/agents/fastapi/__init__.py,sha256=f7x7kiEjaNyBiOwJHLJ4vdOiePqkXdI52sIAAHtS-ms,141
17
17
  sunholo/agents/fastapi/base.py,sha256=W-cyF8ZDUH40rc-c-Apw3-_8IIi2e4Y9qRtnoVnsc1Q,2521
18
18
  sunholo/agents/fastapi/qna_routes.py,sha256=lKHkXPmwltu9EH3RMwmD153-J6pE7kWQ4BhBlV3to-s,3864
19
- sunholo/agents/fastapi/vac_routes.py,sha256=jV7Kb9CG4qdlntSHZuI_8UALjFuwYOh5ovrZpDkixzg,51909
19
+ sunholo/agents/fastapi/vac_routes.py,sha256=s0wzGupaIsfQqJlDA1f8MXoVY8LGsHr0VP2xeRQOzM0,59860
20
20
  sunholo/agents/flask/__init__.py,sha256=dEoByI3gDNUOjpX1uVKP7uPjhfFHJubbiaAv3xLopnk,63
21
21
  sunholo/agents/flask/base.py,sha256=vnpxFEOnCmt9humqj-jYPLfJcdwzsop9NorgkJ-tSaU,1756
22
22
  sunholo/agents/flask/vac_routes.py,sha256=kaPUDyIH5KhCgeCEtag97qErGVZfqpY1ZEiX3y1_r-s,57505
@@ -122,8 +122,8 @@ sunholo/mcp/extensible_mcp_server.py,sha256=docJT800-wJLApU6kEa3lwu9FHyy1yvtJIk8
122
122
  sunholo/mcp/mcp_manager.py,sha256=g75vv6XvM24U7uz366slE-p76Qs4AvVcsarHSF9qIvE,5061
123
123
  sunholo/mcp/stdio_http_bridge.py,sha256=IunHOtnjKAkRWef3SJnqnAL2r2qBRpCH2k_Q_y0Tdf8,3237
124
124
  sunholo/mcp/vac_mcp_server.py,sha256=MotoCw5lDsxCeVtwh1499yGFku9w-78xXhGkIHTUo3w,838
125
- sunholo/mcp/vac_mcp_server_fastmcp.py,sha256=ZZ0Que7XErlUQlaiMiQ8pesTttq24l7mfG7BiKk4ohs,4450
126
- sunholo/mcp/vac_tools.py,sha256=26IW5iTSEI1orbHYAlcjbBkZ1yjqQhqGqerjYkTVhko,8464
125
+ sunholo/mcp/vac_mcp_server_fastmcp.py,sha256=R95GDWRbVyAVqVhWVkJv9wd5gH1bWKiz69IU5rWPnIc,4451
126
+ sunholo/mcp/vac_tools.py,sha256=pTtHxPHD5k80wRnmJw1-RJK8L8IOOCWpGyTL1W2M934,8744
127
127
  sunholo/ollama/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
128
  sunholo/ollama/ollama_images.py,sha256=H2cpcNu88R4TwyfL_nnqkQhdvBQ2FPCAy4Ok__0yQmo,2351
129
129
  sunholo/pubsub/__init__.py,sha256=DfTEk4zmCfqn6gFxRrqDO0pOrvXTDqH-medpgYO4PGw,117
@@ -181,9 +181,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
181
181
  sunholo/vertex/memory_tools.py,sha256=tBZxqVZ4InTmdBvLlOYwoSEWu4-kGquc-gxDwZCC4FA,7667
182
182
  sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
183
183
  sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
184
- sunholo-0.144.2.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
185
- sunholo-0.144.2.dist-info/METADATA,sha256=QrQ-DLWAPoqQU4Jb0QVWLc7qDOJTe58siJ3AsEdGq60,18700
186
- sunholo-0.144.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
187
- sunholo-0.144.2.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
188
- sunholo-0.144.2.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
189
- sunholo-0.144.2.dist-info/RECORD,,
184
+ sunholo-0.144.3.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
185
+ sunholo-0.144.3.dist-info/METADATA,sha256=RObZwvRVw98mi-1RzJMaNtiLKZ0mui7wnNsaqf-oYmI,18700
186
+ sunholo-0.144.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
187
+ sunholo-0.144.3.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
188
+ sunholo-0.144.3.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
189
+ sunholo-0.144.3.dist-info/RECORD,,