sunholo 0.144.3__py3-none-any.whl → 0.144.4__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.
@@ -534,44 +534,64 @@ class VACRoutesFastAPI:
534
534
  async def app_lifespan(app: FastAPI):
535
535
  yield
536
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:
537
+ # Import here to avoid circular imports
538
+ if VACMCPServer:
539
+ from fastmcp import FastMCP
540
+
541
+ # Create MCP server directly to get its lifespan
542
+ mcp_server = FastMCP("sunholo-vac-fastapi-server")
543
+
544
+ # Register built-in VAC tools directly
545
+ from sunholo.mcp.vac_tools import register_vac_tools
546
+ register_vac_tools(mcp_server, None)
547
+
548
+ # Get the MCP app with path="" so when mounted at /mcp it's accessible at /mcp
549
+ mcp_app = mcp_server.http_app(path="", stateless_http=True)
550
+
551
+ # Create combined lifespan
552
+ @asynccontextmanager
553
+ async def combined_lifespan(app: FastAPI):
554
+ async with app_lifespan(app):
556
555
  async with mcp_app.lifespan(app):
557
556
  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
- )
557
+
558
+ # Create the actual app with combined lifespan
559
+ app = FastAPI(
560
+ title=title,
561
+ lifespan=combined_lifespan
562
+ )
563
+
564
+ # Mount the MCP app at /mcp
565
+ app.mount("/mcp", mcp_app)
566
+
567
+ # Now create VAC routes WITHOUT MCP (since we already mounted it)
568
+ vac_routes = VACRoutesFastAPI(
569
+ app,
570
+ stream_interpreter=stream_interpreter,
571
+ vac_interpreter=vac_interpreter,
572
+ enable_mcp_server=False, # Don't enable again since we manually mounted
573
+ **kwargs
574
+ )
575
+
576
+ # Store reference to MCP server for tool registration
577
+ vac_routes.vac_mcp_server = type('MockMCPServer', (), {
578
+ 'add_tool': lambda self, func, name=None, desc=None: mcp_server.tool(func) if name is None else mcp_server.tool(name=name)(func),
579
+ 'server': mcp_server
580
+ })()
581
+ else:
582
+ # No MCP support available
583
+ app = FastAPI(
584
+ title=title,
585
+ lifespan=app_lifespan
586
+ )
587
+
588
+ vac_routes = VACRoutesFastAPI(
589
+ app,
590
+ stream_interpreter=stream_interpreter,
591
+ vac_interpreter=vac_interpreter,
592
+ enable_mcp_server=False,
593
+ **kwargs
594
+ )
575
595
 
576
596
  return app, vac_routes
577
597
 
@@ -682,7 +702,8 @@ class VACRoutesFastAPI:
682
702
  # so we can't easily check if it's configured. The error will be
683
703
  # caught below if lifespan is missing.
684
704
 
685
- self.app.mount("/mcp", mcp_app)
705
+ # Mount at root - the MCP app already has /mcp path configured
706
+ self.app.mount("", mcp_app)
686
707
  log.info("✅ MCP server mounted at /mcp endpoint")
687
708
 
688
709
  except RuntimeError as e:
sunholo/mcp/__init__.py CHANGED
@@ -26,4 +26,13 @@ except ImportError as e:
26
26
  print(f"Warning: VACMCPServer not available - {e}")
27
27
  VACMCPServer = None
28
28
 
29
- __all__ = ['MCPClientManager', 'VACMCPServer']
29
+ # SSE utilities are always available
30
+ from .sse_utils import parse_sse_response, is_sse_response, extract_sse_data
31
+
32
+ __all__ = [
33
+ 'MCPClientManager',
34
+ 'VACMCPServer',
35
+ 'parse_sse_response',
36
+ 'is_sse_response',
37
+ 'extract_sse_data'
38
+ ]
@@ -0,0 +1,105 @@
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
+ Utilities for parsing Server-Sent Events (SSE) responses from MCP servers.
17
+ """
18
+
19
+ import json
20
+ from typing import Any, Dict, Optional
21
+
22
+
23
+ def parse_sse_response(text: str) -> Dict[str, Any]:
24
+ """
25
+ Parse SSE-formatted response from MCP server.
26
+
27
+ FastMCP returns responses in SSE format when using HTTP transport,
28
+ even with stateless_http=True. This function extracts the JSON data
29
+ from the SSE format.
30
+
31
+ Args:
32
+ text: Raw response text from MCP server
33
+
34
+ Returns:
35
+ Parsed JSON data from the SSE response
36
+
37
+ Raises:
38
+ ValueError: If the response cannot be parsed
39
+
40
+ Example:
41
+ >>> response_text = 'event: message\\ndata: {"jsonrpc": "2.0", "id": 1, "result": {...}}'
42
+ >>> data = parse_sse_response(response_text)
43
+ >>> print(data['result'])
44
+ """
45
+ # Check if it's SSE format
46
+ if text.startswith('event:') or text.startswith('data:'):
47
+ # Parse SSE format - extract JSON from data: line
48
+ lines = text.split('\n')
49
+
50
+ # Find the data line
51
+ data_line = None
52
+ for line in lines:
53
+ if line.startswith('data:'):
54
+ data_line = line
55
+ break
56
+
57
+ if data_line:
58
+ # Remove 'data:' prefix and parse JSON
59
+ json_str = data_line[5:].strip()
60
+ try:
61
+ return json.loads(json_str)
62
+ except json.JSONDecodeError as e:
63
+ raise ValueError(f"Failed to parse JSON from SSE data: {e}")
64
+ else:
65
+ raise ValueError("No data line found in SSE response")
66
+ else:
67
+ # Try parsing as regular JSON
68
+ try:
69
+ return json.loads(text)
70
+ except json.JSONDecodeError as e:
71
+ raise ValueError(f"Response is not valid JSON or SSE format: {e}")
72
+
73
+
74
+ def is_sse_response(text: str) -> bool:
75
+ """
76
+ Check if a response is in SSE format.
77
+
78
+ Args:
79
+ text: Response text to check
80
+
81
+ Returns:
82
+ True if the response appears to be SSE format
83
+ """
84
+ return text.startswith('event:') or text.startswith('data:')
85
+
86
+
87
+ def extract_sse_data(text: str) -> Optional[str]:
88
+ """
89
+ Extract the data portion from an SSE response.
90
+
91
+ Args:
92
+ text: SSE-formatted response text
93
+
94
+ Returns:
95
+ The extracted data string, or None if not found
96
+ """
97
+ if not is_sse_response(text):
98
+ return None
99
+
100
+ lines = text.split('\n')
101
+ for line in lines:
102
+ if line.startswith('data:'):
103
+ return line[5:].strip()
104
+
105
+ return None
@@ -73,7 +73,8 @@ 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.http_app()
76
+ # Following FastMCP docs: when mounted at root "", path="/mcp" gives us /mcp endpoint
77
+ return self.server.http_app(path="/mcp")
77
78
 
78
79
  def add_tool(self, func: Callable, name: str = None, description: str = None):
79
80
  """
sunholo/mcp/vac_tools.py CHANGED
@@ -54,12 +54,9 @@ def get_vac_config(vector_name: str = None) -> 'ConfigManager':
54
54
 
55
55
  default_vac = os.getenv("DEFAULT_VAC_NAME", "demo")
56
56
  vac_name = vector_name or default_vac
57
- vac_config_folder = os.getenv("VAC_CONFIG_FOLDER")
58
57
 
59
- if vac_config_folder:
60
- return ConfigManager(vac_name, config_folder=vac_config_folder)
61
- else:
62
- return ConfigManager(vac_name)
58
+ # ConfigManager uses VAC_CONFIG_FOLDER env var automatically
59
+ return ConfigManager(vac_name)
63
60
 
64
61
 
65
62
  async def call_vac_async(question: str, vector_name: str, chat_history: List[Dict[str, str]] = None) -> str:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sunholo
3
- Version: 0.144.3
3
+ Version: 0.144.4
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=s0wzGupaIsfQqJlDA1f8MXoVY8LGsHr0VP2xeRQOzM0,59860
19
+ sunholo/agents/fastapi/vac_routes.py,sha256=tDZ-2U6UAHlloFpH-5HDd2Ob_80GiOr5CMO6XZPDGXM,60877
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
@@ -115,15 +115,16 @@ sunholo/llamaindex/llamaindex_class.py,sha256=PnpPoc7LpP7xvKIXYu-UvI4ehj67pGhE1E
115
115
  sunholo/llamaindex/user_history.py,sha256=ZtkecWuF9ORduyGB8kF8gP66bm9DdvCI-ZiK6Kt-cSE,2265
116
116
  sunholo/lookup/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
117
117
  sunholo/lookup/model_lookup.yaml,sha256=O7o-jP53MLA06C8pI-ILwERShO-xf6z_258wtpZBv6A,739
118
- sunholo/mcp/__init__.py,sha256=Bi0ZYMvWuf1AL_QSrMAREVVdTZFiIokGwrytBXKBJyc,1028
118
+ sunholo/mcp/__init__.py,sha256=hvVdBeXrYZq1saa57xlN88e7KIi4oA0VFEpgLJttTcs,1228
119
119
  sunholo/mcp/cli.py,sha256=RyTrTBQMUaNMAZ1Nyh-XKb9qGnCA5hMxpKp5-9lqfrI,821
120
120
  sunholo/mcp/cli_fastmcp.py,sha256=MWx7kJ4RHX0tTygWs247aOYr4bCKOwjnmccOPjcTVnc,6104
121
121
  sunholo/mcp/extensible_mcp_server.py,sha256=docJT800-wJLApU6kEa3lwu9FHyy1yvtJIk8JI05Z3o,8960
122
122
  sunholo/mcp/mcp_manager.py,sha256=g75vv6XvM24U7uz366slE-p76Qs4AvVcsarHSF9qIvE,5061
123
+ sunholo/mcp/sse_utils.py,sha256=LBugTxAIccQmcU2ueKIcvVlR2GjhVajwqHDnVn2s6e8,3173
123
124
  sunholo/mcp/stdio_http_bridge.py,sha256=IunHOtnjKAkRWef3SJnqnAL2r2qBRpCH2k_Q_y0Tdf8,3237
124
125
  sunholo/mcp/vac_mcp_server.py,sha256=MotoCw5lDsxCeVtwh1499yGFku9w-78xXhGkIHTUo3w,838
125
- sunholo/mcp/vac_mcp_server_fastmcp.py,sha256=R95GDWRbVyAVqVhWVkJv9wd5gH1bWKiz69IU5rWPnIc,4451
126
- sunholo/mcp/vac_tools.py,sha256=pTtHxPHD5k80wRnmJw1-RJK8L8IOOCWpGyTL1W2M934,8744
126
+ sunholo/mcp/vac_mcp_server_fastmcp.py,sha256=Xvcwl3-kqqap-yRvOrxYHmNPQi1RBAznoZKXk8b3ydQ,4556
127
+ sunholo/mcp/vac_tools.py,sha256=T7JmN4rAWYGmXbnXdpJlgE91DZGi4byzK9uWENndROA,8642
127
128
  sunholo/ollama/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
128
129
  sunholo/ollama/ollama_images.py,sha256=H2cpcNu88R4TwyfL_nnqkQhdvBQ2FPCAy4Ok__0yQmo,2351
129
130
  sunholo/pubsub/__init__.py,sha256=DfTEk4zmCfqn6gFxRrqDO0pOrvXTDqH-medpgYO4PGw,117
@@ -181,9 +182,9 @@ sunholo/vertex/init.py,sha256=1OQwcPBKZYBTDPdyU7IM4X4OmiXLdsNV30C-fee2scQ,2875
181
182
  sunholo/vertex/memory_tools.py,sha256=tBZxqVZ4InTmdBvLlOYwoSEWu4-kGquc-gxDwZCC4FA,7667
182
183
  sunholo/vertex/safety.py,sha256=S9PgQT1O_BQAkcqauWncRJaydiP8Q_Jzmu9gxYfy1VA,2482
183
184
  sunholo/vertex/type_dict_to_json.py,sha256=uTzL4o9tJRao4u-gJOFcACgWGkBOtqACmb6ihvCErL8,4694
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,,
185
+ sunholo-0.144.4.dist-info/licenses/LICENSE.txt,sha256=SdE3QjnD3GEmqqg9EX3TM9f7WmtOzqS1KJve8rhbYmU,11345
186
+ sunholo-0.144.4.dist-info/METADATA,sha256=anqykTQweoYFxojD32NfblATFycMbT3x8-SYQ4uHwys,18700
187
+ sunholo-0.144.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
188
+ sunholo-0.144.4.dist-info/entry_points.txt,sha256=bZuN5AIHingMPt4Ro1b_T-FnQvZ3teBes-3OyO0asl4,49
189
+ sunholo-0.144.4.dist-info/top_level.txt,sha256=wt5tadn5--5JrZsjJz2LceoUvcrIvxjHJe-RxuudxAk,8
190
+ sunholo-0.144.4.dist-info/RECORD,,