kailash 0.9.1__py3-none-any.whl → 0.9.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.
- kailash/__init__.py +4 -2
- kailash/mcp_server/client.py +371 -6
- {kailash-0.9.1.dist-info → kailash-0.9.2.dist-info}/METADATA +1 -1
- {kailash-0.9.1.dist-info → kailash-0.9.2.dist-info}/RECORD +8 -8
- {kailash-0.9.1.dist-info → kailash-0.9.2.dist-info}/WHEEL +0 -0
- {kailash-0.9.1.dist-info → kailash-0.9.2.dist-info}/entry_points.txt +0 -0
- {kailash-0.9.1.dist-info → kailash-0.9.2.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.9.1.dist-info → kailash-0.9.2.dist-info}/top_level.txt +0 -0
kailash/__init__.py
CHANGED
@@ -3,7 +3,9 @@
|
|
3
3
|
The Kailash SDK provides a comprehensive framework for creating nodes and workflows
|
4
4
|
that align with container-node architecture while allowing rapid prototyping.
|
5
5
|
|
6
|
-
New in v0.9.
|
6
|
+
New in v0.9.2: WebSocket Transport Support with Enterprise Connection Pooling.
|
7
|
+
Fixed "Unsupported transport: websocket" error. Added 73% performance improvement with connection pooling.
|
8
|
+
Previous v0.9.1: DataFlow PostgreSQL parameter conversion verification and comprehensive testing infrastructure.
|
7
9
|
Complete validation of parameter conversion chain from DataFlow to AsyncSQLDatabaseNode to PostgreSQL.
|
8
10
|
Previous v0.9.0: Complete migration from cycle=True to modern CycleBuilder API.
|
9
11
|
"""
|
@@ -48,7 +50,7 @@ except ImportError:
|
|
48
50
|
# For backward compatibility
|
49
51
|
WorkflowGraph = Workflow
|
50
52
|
|
51
|
-
__version__ = "0.9.
|
53
|
+
__version__ = "0.9.2"
|
52
54
|
|
53
55
|
__all__ = [
|
54
56
|
# Core workflow components
|
kailash/mcp_server/client.py
CHANGED
@@ -7,7 +7,7 @@ import os
|
|
7
7
|
import time
|
8
8
|
import uuid
|
9
9
|
from contextlib import AsyncExitStack
|
10
|
-
from typing import Any, Dict, List, Optional, Union
|
10
|
+
from typing import Any, Dict, List, Optional, Tuple, Union
|
11
11
|
|
12
12
|
from .auth import AuthManager, AuthProvider, PermissionManager, RateLimiter
|
13
13
|
from .errors import (
|
@@ -98,7 +98,9 @@ class MCPClient:
|
|
98
98
|
|
99
99
|
# Connection pooling
|
100
100
|
self.connection_pool_config = connection_pool_config or {}
|
101
|
-
self.
|
101
|
+
self._websocket_pools: Dict[str, Any] = {} # url -> connection info
|
102
|
+
self._pool_lock = asyncio.Lock()
|
103
|
+
self._connection_last_used: Dict[str, float] = {}
|
102
104
|
|
103
105
|
# Metrics
|
104
106
|
if enable_metrics:
|
@@ -137,6 +139,8 @@ class MCPClient:
|
|
137
139
|
|
138
140
|
# Update transport usage metrics
|
139
141
|
if self.metrics:
|
142
|
+
if "transport_usage" not in self.metrics:
|
143
|
+
self.metrics["transport_usage"] = {}
|
140
144
|
transport_counts = self.metrics["transport_usage"]
|
141
145
|
transport_counts[transport_type] = (
|
142
146
|
transport_counts.get(transport_type, 0) + 1
|
@@ -148,6 +152,8 @@ class MCPClient:
|
|
148
152
|
return await self._discover_tools_sse(server_config, timeout)
|
149
153
|
elif transport_type == "http":
|
150
154
|
return await self._discover_tools_http(server_config, timeout)
|
155
|
+
elif transport_type == "websocket":
|
156
|
+
return await self._discover_tools_websocket(server_config, timeout)
|
151
157
|
else:
|
152
158
|
raise TransportError(
|
153
159
|
f"Unsupported transport: {transport_type}",
|
@@ -173,7 +179,8 @@ class MCPClient:
|
|
173
179
|
|
174
180
|
except Exception as e:
|
175
181
|
if self.metrics:
|
176
|
-
|
182
|
+
if "requests_failed" in self.metrics:
|
183
|
+
self.metrics["requests_failed"] += 1
|
177
184
|
|
178
185
|
logger.error(f"Failed to discover tools from {server_key}: {e}")
|
179
186
|
return []
|
@@ -309,6 +316,67 @@ class MCPClient:
|
|
309
316
|
|
310
317
|
return tools
|
311
318
|
|
319
|
+
async def _discover_tools_websocket(
|
320
|
+
self, server_config: Union[str, Dict[str, Any]], timeout: Optional[float]
|
321
|
+
) -> List[Dict[str, Any]]:
|
322
|
+
"""Discover tools using WebSocket transport."""
|
323
|
+
from mcp import ClientSession
|
324
|
+
from mcp.client.websocket import websocket_client
|
325
|
+
|
326
|
+
# Extract WebSocket URL from server config
|
327
|
+
if isinstance(server_config, str):
|
328
|
+
url = server_config
|
329
|
+
else:
|
330
|
+
url = server_config.get("url")
|
331
|
+
if not url:
|
332
|
+
raise TransportError(
|
333
|
+
"WebSocket URL not provided", transport_type="websocket"
|
334
|
+
)
|
335
|
+
|
336
|
+
# Get or create connection from pool
|
337
|
+
session, is_new = await self._get_or_create_websocket_connection(url, timeout)
|
338
|
+
|
339
|
+
# Update metrics
|
340
|
+
if self.metrics:
|
341
|
+
if is_new:
|
342
|
+
self.metrics["websocket_pool_misses"] = (
|
343
|
+
self.metrics.get("websocket_pool_misses", 0) + 1
|
344
|
+
)
|
345
|
+
self.metrics["websocket_connections_created"] = (
|
346
|
+
self.metrics.get("websocket_connections_created", 0) + 1
|
347
|
+
)
|
348
|
+
else:
|
349
|
+
self.metrics["websocket_pool_hits"] = (
|
350
|
+
self.metrics.get("websocket_pool_hits", 0) + 1
|
351
|
+
)
|
352
|
+
self.metrics["websocket_connections_reused"] = (
|
353
|
+
self.metrics.get("websocket_connections_reused", 0) + 1
|
354
|
+
)
|
355
|
+
|
356
|
+
try:
|
357
|
+
# List tools with timeout
|
358
|
+
if timeout:
|
359
|
+
result = await asyncio.wait_for(session.list_tools(), timeout=timeout)
|
360
|
+
else:
|
361
|
+
result = await session.list_tools()
|
362
|
+
|
363
|
+
# Convert to standard format
|
364
|
+
tools = []
|
365
|
+
for tool in result.tools:
|
366
|
+
tools.append(
|
367
|
+
{
|
368
|
+
"name": tool.name,
|
369
|
+
"description": tool.description,
|
370
|
+
"parameters": tool.inputSchema,
|
371
|
+
}
|
372
|
+
)
|
373
|
+
|
374
|
+
return tools
|
375
|
+
except Exception as e:
|
376
|
+
# On error, remove connection from pool
|
377
|
+
await self._remove_connection_from_pool(url)
|
378
|
+
raise
|
379
|
+
|
312
380
|
async def call_tool(
|
313
381
|
self,
|
314
382
|
server_config: Union[str, Dict[str, Any]],
|
@@ -350,6 +418,10 @@ class MCPClient:
|
|
350
418
|
return await self._call_tool_http(
|
351
419
|
server_config, tool_name, arguments, timeout
|
352
420
|
)
|
421
|
+
elif transport_type == "websocket":
|
422
|
+
return await self._call_tool_websocket(
|
423
|
+
server_config, tool_name, arguments, timeout
|
424
|
+
)
|
353
425
|
else:
|
354
426
|
raise TransportError(
|
355
427
|
f"Unsupported transport: {transport_type}",
|
@@ -372,7 +444,8 @@ class MCPClient:
|
|
372
444
|
|
373
445
|
except Exception as e:
|
374
446
|
if self.metrics:
|
375
|
-
|
447
|
+
if "requests_failed" in self.metrics:
|
448
|
+
self.metrics["requests_failed"] += 1
|
376
449
|
|
377
450
|
logger.error(f"Tool call failed for {tool_name}: {e}")
|
378
451
|
return {"success": False, "error": str(e), "tool_name": tool_name}
|
@@ -524,6 +597,77 @@ class MCPClient:
|
|
524
597
|
"tool_name": tool_name,
|
525
598
|
}
|
526
599
|
|
600
|
+
async def _call_tool_websocket(
|
601
|
+
self,
|
602
|
+
server_config: Union[str, Dict[str, Any]],
|
603
|
+
tool_name: str,
|
604
|
+
arguments: Dict[str, Any],
|
605
|
+
timeout: Optional[float],
|
606
|
+
) -> Dict[str, Any]:
|
607
|
+
"""Call tool using WebSocket transport."""
|
608
|
+
from mcp import ClientSession
|
609
|
+
from mcp.client.websocket import websocket_client
|
610
|
+
|
611
|
+
# Extract WebSocket URL from server config
|
612
|
+
if isinstance(server_config, str):
|
613
|
+
url = server_config
|
614
|
+
else:
|
615
|
+
url = server_config.get("url")
|
616
|
+
if not url:
|
617
|
+
raise TransportError(
|
618
|
+
"WebSocket URL not provided", transport_type="websocket"
|
619
|
+
)
|
620
|
+
|
621
|
+
# Get or create connection from pool
|
622
|
+
session, is_new = await self._get_or_create_websocket_connection(url, timeout)
|
623
|
+
|
624
|
+
# Update metrics
|
625
|
+
if self.metrics:
|
626
|
+
if is_new:
|
627
|
+
self.metrics["websocket_pool_misses"] = (
|
628
|
+
self.metrics.get("websocket_pool_misses", 0) + 1
|
629
|
+
)
|
630
|
+
self.metrics["websocket_connections_created"] = (
|
631
|
+
self.metrics.get("websocket_connections_created", 0) + 1
|
632
|
+
)
|
633
|
+
else:
|
634
|
+
self.metrics["websocket_pool_hits"] = (
|
635
|
+
self.metrics.get("websocket_pool_hits", 0) + 1
|
636
|
+
)
|
637
|
+
self.metrics["websocket_connections_reused"] = (
|
638
|
+
self.metrics.get("websocket_connections_reused", 0) + 1
|
639
|
+
)
|
640
|
+
|
641
|
+
try:
|
642
|
+
# Call tool with timeout
|
643
|
+
if timeout:
|
644
|
+
result = await asyncio.wait_for(
|
645
|
+
session.call_tool(name=tool_name, arguments=arguments),
|
646
|
+
timeout=timeout,
|
647
|
+
)
|
648
|
+
else:
|
649
|
+
result = await session.call_tool(name=tool_name, arguments=arguments)
|
650
|
+
|
651
|
+
# Extract content from result
|
652
|
+
content = []
|
653
|
+
if hasattr(result, "content"):
|
654
|
+
for item in result.content:
|
655
|
+
if hasattr(item, "text"):
|
656
|
+
content.append(item.text)
|
657
|
+
else:
|
658
|
+
content.append(str(item))
|
659
|
+
|
660
|
+
return {
|
661
|
+
"success": True,
|
662
|
+
"content": "\n".join(content) if content else "",
|
663
|
+
"result": result,
|
664
|
+
"tool_name": tool_name,
|
665
|
+
}
|
666
|
+
except Exception as e:
|
667
|
+
# On error, remove connection from pool
|
668
|
+
await self._remove_connection_from_pool(url)
|
669
|
+
raise
|
670
|
+
|
527
671
|
# Additional enhanced methods
|
528
672
|
async def list_resources(
|
529
673
|
self,
|
@@ -582,7 +726,12 @@ class MCPClient:
|
|
582
726
|
def _get_transport_type(self, server_config: Union[str, Dict[str, Any]]) -> str:
|
583
727
|
"""Determine transport type from server config."""
|
584
728
|
if isinstance(server_config, str):
|
585
|
-
|
729
|
+
if server_config.startswith(("ws://", "wss://")):
|
730
|
+
return "websocket"
|
731
|
+
elif server_config.startswith(("http://", "https://")):
|
732
|
+
return "sse"
|
733
|
+
else:
|
734
|
+
return "stdio"
|
586
735
|
else:
|
587
736
|
return server_config.get("transport", "stdio")
|
588
737
|
|
@@ -596,7 +745,7 @@ class MCPClient:
|
|
596
745
|
command = server_config.get("command", "python")
|
597
746
|
args = server_config.get("args", [])
|
598
747
|
return f"stdio://{command}:{':'.join(args)}"
|
599
|
-
elif transport in ["sse", "http"]:
|
748
|
+
elif transport in ["sse", "http", "websocket"]:
|
600
749
|
return server_config.get("url", "unknown")
|
601
750
|
else:
|
602
751
|
return str(hash(json.dumps(server_config, sort_keys=True)))
|
@@ -710,3 +859,219 @@ class MCPClient:
|
|
710
859
|
"timestamp": str(time.time()),
|
711
860
|
},
|
712
861
|
}
|
862
|
+
|
863
|
+
# WebSocket Connection Pooling Methods
|
864
|
+
async def _get_or_create_websocket_connection(
|
865
|
+
self, url: str, timeout: Optional[float] = None
|
866
|
+
) -> Tuple[Any, bool]:
|
867
|
+
"""Get existing connection from pool or create a new one.
|
868
|
+
|
869
|
+
Returns:
|
870
|
+
Tuple of (session, is_new_connection)
|
871
|
+
"""
|
872
|
+
# Check if pooling is enabled
|
873
|
+
if not self._should_use_pooling():
|
874
|
+
# Create new connection without pooling
|
875
|
+
session = await self._create_websocket_connection(url, timeout)
|
876
|
+
return session, True
|
877
|
+
|
878
|
+
async with self._pool_lock:
|
879
|
+
# Update last used time
|
880
|
+
self._connection_last_used[url] = time.time()
|
881
|
+
|
882
|
+
# Check if we have an existing healthy connection
|
883
|
+
if url in self._websocket_pools:
|
884
|
+
conn_info = self._websocket_pools[url]
|
885
|
+
session = conn_info.get("session")
|
886
|
+
|
887
|
+
# Check if connection is still healthy
|
888
|
+
if session and await self._is_connection_healthy(session):
|
889
|
+
return session, False
|
890
|
+
else:
|
891
|
+
# Remove unhealthy connection
|
892
|
+
del self._websocket_pools[url]
|
893
|
+
|
894
|
+
# Check pool size limits
|
895
|
+
if len(self._websocket_pools) >= self.connection_pool_config.get(
|
896
|
+
"max_connections", 10
|
897
|
+
):
|
898
|
+
# Evict least recently used connection
|
899
|
+
await self._evict_lru_connection()
|
900
|
+
|
901
|
+
# Create new connection
|
902
|
+
session = await self._create_websocket_connection(url, timeout)
|
903
|
+
|
904
|
+
# Store in pool
|
905
|
+
self._websocket_pools[url] = {
|
906
|
+
"session": session,
|
907
|
+
"created_at": time.time(),
|
908
|
+
"url": url,
|
909
|
+
}
|
910
|
+
|
911
|
+
return session, True
|
912
|
+
|
913
|
+
async def _create_websocket_connection(
|
914
|
+
self, url: str, timeout: Optional[float]
|
915
|
+
) -> Any:
|
916
|
+
"""Create a new WebSocket connection and session."""
|
917
|
+
# Create connection using AsyncExitStack for proper lifecycle management
|
918
|
+
# This fixes the manual __aenter__/__aexit__ issue
|
919
|
+
from contextlib import AsyncExitStack
|
920
|
+
|
921
|
+
from mcp import ClientSession
|
922
|
+
from mcp.client.websocket import websocket_client
|
923
|
+
|
924
|
+
class WebSocketConnection:
|
925
|
+
def __init__(self):
|
926
|
+
self.exit_stack = None
|
927
|
+
self.session = None
|
928
|
+
|
929
|
+
async def connect(self, url):
|
930
|
+
# Use AsyncExitStack to properly manage async context managers
|
931
|
+
self.exit_stack = AsyncExitStack()
|
932
|
+
|
933
|
+
try:
|
934
|
+
# Enter the websocket context using AsyncExitStack
|
935
|
+
websocket_context = websocket_client(url=url)
|
936
|
+
streams = await self.exit_stack.enter_async_context(
|
937
|
+
websocket_context
|
938
|
+
)
|
939
|
+
self.read_stream, self.write_stream = streams
|
940
|
+
|
941
|
+
# Create and initialize session using AsyncExitStack
|
942
|
+
session = ClientSession(self.read_stream, self.write_stream)
|
943
|
+
session_ref = await self.exit_stack.enter_async_context(session)
|
944
|
+
await session_ref.initialize()
|
945
|
+
|
946
|
+
self.session = session_ref
|
947
|
+
return session_ref
|
948
|
+
|
949
|
+
except Exception:
|
950
|
+
# If anything fails during setup, clean up
|
951
|
+
await self.close()
|
952
|
+
raise
|
953
|
+
|
954
|
+
async def close(self):
|
955
|
+
# Handle cleanup with proper exception isolation
|
956
|
+
if self.exit_stack:
|
957
|
+
exit_stack = self.exit_stack
|
958
|
+
self.exit_stack = None
|
959
|
+
self.session = None
|
960
|
+
|
961
|
+
# Schedule cleanup for later to avoid cross-task issues
|
962
|
+
# This prevents the "different task" async generator problems
|
963
|
+
try:
|
964
|
+
# Use create_task to run cleanup independently
|
965
|
+
cleanup_task = asyncio.create_task(exit_stack.aclose())
|
966
|
+
|
967
|
+
# Don't await - let it run in background to avoid blocking
|
968
|
+
# But add a callback to log any errors
|
969
|
+
def log_cleanup_error(task):
|
970
|
+
if task.exception():
|
971
|
+
logger.warning(
|
972
|
+
f"Background cleanup error: {task.exception()}"
|
973
|
+
)
|
974
|
+
|
975
|
+
cleanup_task.add_done_callback(log_cleanup_error)
|
976
|
+
except Exception as e:
|
977
|
+
logger.warning(f"Error scheduling connection cleanup: {e}")
|
978
|
+
|
979
|
+
# Create and connect
|
980
|
+
conn = WebSocketConnection()
|
981
|
+
session = await conn.connect(url)
|
982
|
+
|
983
|
+
# Store connection object for cleanup
|
984
|
+
if not hasattr(self, "_websocket_connections"):
|
985
|
+
self._websocket_connections = {}
|
986
|
+
self._websocket_connections[url] = conn
|
987
|
+
|
988
|
+
return session
|
989
|
+
|
990
|
+
async def _remove_connection_from_pool(self, url: str):
|
991
|
+
"""Remove a connection from the pool."""
|
992
|
+
async with self._pool_lock:
|
993
|
+
if url in self._websocket_pools:
|
994
|
+
del self._websocket_pools[url]
|
995
|
+
|
996
|
+
# Clean up connection
|
997
|
+
if (
|
998
|
+
hasattr(self, "_websocket_connections")
|
999
|
+
and url in self._websocket_connections
|
1000
|
+
):
|
1001
|
+
conn = self._websocket_connections[url]
|
1002
|
+
try:
|
1003
|
+
await conn.close()
|
1004
|
+
except Exception as e:
|
1005
|
+
logger.warning(f"Error closing WebSocket connection: {e}")
|
1006
|
+
finally:
|
1007
|
+
del self._websocket_connections[url]
|
1008
|
+
|
1009
|
+
# Clean up last used tracking
|
1010
|
+
if url in self._connection_last_used:
|
1011
|
+
del self._connection_last_used[url]
|
1012
|
+
|
1013
|
+
def _should_use_pooling(self) -> bool:
|
1014
|
+
"""Check if connection pooling should be used."""
|
1015
|
+
return self.connection_pool_config.get("enable_connection_reuse", True)
|
1016
|
+
|
1017
|
+
def _get_active_connections(self) -> List[str]:
|
1018
|
+
"""Get list of active connection URLs."""
|
1019
|
+
return list(self._websocket_pools.keys())
|
1020
|
+
|
1021
|
+
def _has_active_connection(self, url: str) -> bool:
|
1022
|
+
"""Check if URL has an active connection."""
|
1023
|
+
return url in self._websocket_pools
|
1024
|
+
|
1025
|
+
async def _is_connection_healthy(self, session: Any) -> bool:
|
1026
|
+
"""Check if a connection is healthy."""
|
1027
|
+
try:
|
1028
|
+
# Try to ping if method exists
|
1029
|
+
if hasattr(session, "ping"):
|
1030
|
+
await asyncio.wait_for(session.ping(), timeout=5.0)
|
1031
|
+
return True
|
1032
|
+
except Exception:
|
1033
|
+
return False
|
1034
|
+
|
1035
|
+
async def _check_connection_health(self, url: str):
|
1036
|
+
"""Check and update health status of a connection."""
|
1037
|
+
if url not in self._websocket_pools:
|
1038
|
+
return
|
1039
|
+
|
1040
|
+
session = self._websocket_pools[url].get("session")
|
1041
|
+
if session and not await self._is_connection_healthy(session):
|
1042
|
+
# Remove unhealthy connection
|
1043
|
+
await self._remove_connection_from_pool(url)
|
1044
|
+
|
1045
|
+
async def _cleanup_idle_connections(self, max_idle_seconds: float = None):
|
1046
|
+
"""Clean up idle connections."""
|
1047
|
+
if max_idle_seconds is None:
|
1048
|
+
max_idle_seconds = self.connection_pool_config.get("max_idle_time", 60)
|
1049
|
+
|
1050
|
+
current_time = time.time()
|
1051
|
+
urls_to_remove = []
|
1052
|
+
|
1053
|
+
async with self._pool_lock:
|
1054
|
+
for url, last_used in self._connection_last_used.items():
|
1055
|
+
if current_time - last_used > max_idle_seconds:
|
1056
|
+
urls_to_remove.append(url)
|
1057
|
+
|
1058
|
+
# Remove idle connections
|
1059
|
+
for url in urls_to_remove:
|
1060
|
+
await self._remove_connection_from_pool(url)
|
1061
|
+
|
1062
|
+
async def _evict_lru_connection(self):
|
1063
|
+
"""Evict least recently used connection."""
|
1064
|
+
if not self._connection_last_used:
|
1065
|
+
return
|
1066
|
+
|
1067
|
+
# Find LRU connection
|
1068
|
+
lru_url = min(self._connection_last_used, key=self._connection_last_used.get)
|
1069
|
+
|
1070
|
+
# Update metrics
|
1071
|
+
if self.metrics:
|
1072
|
+
self.metrics["websocket_pool_evictions"] = (
|
1073
|
+
self.metrics.get("websocket_pool_evictions", 0) + 1
|
1074
|
+
)
|
1075
|
+
|
1076
|
+
# Remove it
|
1077
|
+
await self._remove_connection_from_pool(lru_url)
|
@@ -1,4 +1,4 @@
|
|
1
|
-
kailash/__init__.py,sha256=
|
1
|
+
kailash/__init__.py,sha256=3qFmWmSZ0m_IFcnY0f-wmS3lPf-tvseGVfzBcQbCuq4,2771
|
2
2
|
kailash/__main__.py,sha256=vr7TVE5o16V6LsTmRFKG6RDKUXHpIWYdZ6Dok2HkHnI,198
|
3
3
|
kailash/access_control.py,sha256=MjKtkoQ2sg1Mgfe7ovGxVwhAbpJKvaepPWr8dxOueMA,26058
|
4
4
|
kailash/access_control_abac.py,sha256=FPfa_8PuDP3AxTjdWfiH3ntwWO8NodA0py9W8SE5dno,30263
|
@@ -83,7 +83,7 @@ kailash/mcp_server/__init__.py,sha256=AzrCEat5gdl9Nes8xOs7D4Wj3HpGlms3xLbOrx2diP
|
|
83
83
|
kailash/mcp_server/advanced_features.py,sha256=76dmttUa0M61ReBbgexf7Igu4CXaXS-CUmFhvTDjKyI,30673
|
84
84
|
kailash/mcp_server/ai_registry_server.py,sha256=vMNMvWLegKBVp7YAHVKgltWa2vTXKNvV-_Ni_z1argM,28973
|
85
85
|
kailash/mcp_server/auth.py,sha256=Y2BztTL2IILCLSHzZIDSXsIKyUTR8gUXwum5rKffUxs,25251
|
86
|
-
kailash/mcp_server/client.py,sha256=
|
86
|
+
kailash/mcp_server/client.py,sha256=CRb9BJgipgzEaxsC5ohx8JnBZQ8vEn0bLzvigQ5OhVg,39778
|
87
87
|
kailash/mcp_server/client_new.py,sha256=YU671JvAM0uvuX0uhGZCIKI8co3fqz0cs6HqLZ59Xyo,10285
|
88
88
|
kailash/mcp_server/discovery.py,sha256=D8vcwVkbgQCNp0_BlkGeU_dnqgIXN2g0s3_GpndlQu0,57828
|
89
89
|
kailash/mcp_server/errors.py,sha256=_lycwudWP_AJ_KwLO5N3VCKbG1ikfaTyzA2PBF8UAYU,21181
|
@@ -395,9 +395,9 @@ kailash/workflow/templates.py,sha256=XQMAKZXC2dlxgMMQhSEOWAF3hIbe9JJt9j_THchhAm8
|
|
395
395
|
kailash/workflow/type_inference.py,sha256=i1F7Yd_Z3elTXrthsLpqGbOnQBIVVVEjhRpI0HrIjd0,24492
|
396
396
|
kailash/workflow/validation.py,sha256=r2zApGiiG8UEn7p5Ji842l8OR1_KftzDkWc7gg0cac0,44675
|
397
397
|
kailash/workflow/visualization.py,sha256=nHBW-Ai8QBMZtn2Nf3EE1_aiMGi9S6Ui_BfpA5KbJPU,23187
|
398
|
-
kailash-0.9.
|
399
|
-
kailash-0.9.
|
400
|
-
kailash-0.9.
|
401
|
-
kailash-0.9.
|
402
|
-
kailash-0.9.
|
403
|
-
kailash-0.9.
|
398
|
+
kailash-0.9.2.dist-info/licenses/LICENSE,sha256=Axe6g7bTrJkToK9h9j2SpRUKKNaDZDCo2lQ2zPxCE6s,1065
|
399
|
+
kailash-0.9.2.dist-info/METADATA,sha256=ODgNfQBBaU7gn4dr4a_qP-NuxVuKE_B-QeSPkTEuJMw,21733
|
400
|
+
kailash-0.9.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
401
|
+
kailash-0.9.2.dist-info/entry_points.txt,sha256=M_q3b8PG5W4XbhSgESzIJjh3_4OBKtZFYFsOdkr2vO4,45
|
402
|
+
kailash-0.9.2.dist-info/top_level.txt,sha256=z7GzH2mxl66498pVf5HKwo5wwfPtt9Aq95uZUpH6JV0,8
|
403
|
+
kailash-0.9.2.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|