kailash 0.6.1__py3-none-any.whl → 0.6.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.
Files changed (41) hide show
  1. kailash/__init__.py +1 -1
  2. kailash/core/actors/connection_actor.py +3 -3
  3. kailash/gateway/api.py +7 -5
  4. kailash/gateway/enhanced_gateway.py +1 -1
  5. kailash/{mcp → mcp_server}/__init__.py +12 -7
  6. kailash/{mcp → mcp_server}/ai_registry_server.py +2 -2
  7. kailash/{mcp/server_enhanced.py → mcp_server/server.py} +231 -48
  8. kailash/{mcp → mcp_server}/servers/ai_registry.py +2 -2
  9. kailash/{mcp → mcp_server}/utils/__init__.py +1 -6
  10. kailash/middleware/auth/access_control.py +5 -5
  11. kailash/middleware/gateway/checkpoint_manager.py +45 -8
  12. kailash/middleware/mcp/client_integration.py +1 -1
  13. kailash/middleware/mcp/enhanced_server.py +2 -2
  14. kailash/nodes/admin/permission_check.py +110 -30
  15. kailash/nodes/admin/schema.sql +387 -0
  16. kailash/nodes/admin/tenant_isolation.py +249 -0
  17. kailash/nodes/admin/transaction_utils.py +244 -0
  18. kailash/nodes/admin/user_management.py +37 -9
  19. kailash/nodes/ai/ai_providers.py +55 -3
  20. kailash/nodes/ai/iterative_llm_agent.py +1 -1
  21. kailash/nodes/ai/llm_agent.py +118 -16
  22. kailash/nodes/data/sql.py +24 -0
  23. kailash/resources/registry.py +6 -0
  24. kailash/runtime/async_local.py +7 -0
  25. kailash/utils/export.py +152 -0
  26. kailash/workflow/builder.py +42 -0
  27. kailash/workflow/graph.py +86 -17
  28. kailash/workflow/templates.py +4 -9
  29. {kailash-0.6.1.dist-info → kailash-0.6.3.dist-info}/METADATA +3 -2
  30. {kailash-0.6.1.dist-info → kailash-0.6.3.dist-info}/RECORD +40 -38
  31. kailash/mcp/server.py +0 -292
  32. /kailash/{mcp → mcp_server}/client.py +0 -0
  33. /kailash/{mcp → mcp_server}/client_new.py +0 -0
  34. /kailash/{mcp → mcp_server}/utils/cache.py +0 -0
  35. /kailash/{mcp → mcp_server}/utils/config.py +0 -0
  36. /kailash/{mcp → mcp_server}/utils/formatters.py +0 -0
  37. /kailash/{mcp → mcp_server}/utils/metrics.py +0 -0
  38. {kailash-0.6.1.dist-info → kailash-0.6.3.dist-info}/WHEEL +0 -0
  39. {kailash-0.6.1.dist-info → kailash-0.6.3.dist-info}/entry_points.txt +0 -0
  40. {kailash-0.6.1.dist-info → kailash-0.6.3.dist-info}/licenses/LICENSE +0 -0
  41. {kailash-0.6.1.dist-info → kailash-0.6.3.dist-info}/top_level.txt +0 -0
kailash/__init__.py CHANGED
@@ -33,7 +33,7 @@ except ImportError:
33
33
  # For backward compatibility
34
34
  WorkflowGraph = Workflow
35
35
 
36
- __version__ = "0.6.1"
36
+ __version__ = "0.6.3"
37
37
 
38
38
  __all__ = [
39
39
  # Core workflow components
@@ -47,7 +47,7 @@ class Message:
47
47
  type: MessageType = MessageType.QUERY
48
48
  payload: Any = None
49
49
  reply_to: Optional[asyncio.Queue] = None
50
- timestamp: datetime = field(default_factory=datetime.utcnow)
50
+ timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
51
51
 
52
52
 
53
53
  @dataclass
@@ -70,8 +70,8 @@ class ConnectionStats:
70
70
  total_execution_time: float = 0.0
71
71
  health_checks_passed: int = 0
72
72
  health_checks_failed: int = 0
73
- created_at: datetime = field(default_factory=datetime.utcnow)
74
- last_used_at: datetime = field(default_factory=datetime.utcnow)
73
+ created_at: datetime = field(default_factory=lambda: datetime.now(UTC))
74
+ last_used_at: datetime = field(default_factory=lambda: datetime.now(UTC))
75
75
  health_score: float = 100.0
76
76
 
77
77
 
kailash/gateway/api.py CHANGED
@@ -11,7 +11,7 @@ from typing import Any, Dict, List, Optional, Union
11
11
 
12
12
  from fastapi import APIRouter, BackgroundTasks, Depends, FastAPI, HTTPException
13
13
  from fastapi.responses import JSONResponse
14
- from pydantic import BaseModel, Field
14
+ from pydantic import BaseModel, ConfigDict, Field
15
15
 
16
16
  from ..resources.registry import ResourceRegistry
17
17
  from .enhanced_gateway import (
@@ -37,14 +37,15 @@ class ResourceReferenceModel(BaseModel):
37
37
  None, description="Reference to credentials secret"
38
38
  )
39
39
 
40
- class Config:
41
- schema_extra = {
40
+ model_config = ConfigDict(
41
+ json_schema_extra={
42
42
  "example": {
43
43
  "type": "database",
44
44
  "config": {"host": "localhost", "port": 5432, "database": "myapp"},
45
45
  "credentials_ref": "db_credentials",
46
46
  }
47
47
  }
48
+ )
48
49
 
49
50
 
50
51
  class WorkflowRequestModel(BaseModel):
@@ -59,8 +60,8 @@ class WorkflowRequestModel(BaseModel):
59
60
  None, description="Additional context variables"
60
61
  )
61
62
 
62
- class Config:
63
- schema_extra = {
63
+ model_config = ConfigDict(
64
+ json_schema_extra={
64
65
  "example": {
65
66
  "inputs": {"user_id": 123, "action": "process"},
66
67
  "resources": {
@@ -74,6 +75,7 @@ class WorkflowRequestModel(BaseModel):
74
75
  "context": {"environment": "production", "trace_id": "abc123"},
75
76
  }
76
77
  }
78
+ )
77
79
 
78
80
 
79
81
  class WorkflowResponseModel(BaseModel):
@@ -40,7 +40,7 @@ class WorkflowRequest:
40
40
  inputs: Dict[str, Any] = field(default_factory=dict)
41
41
  resources: Dict[str, Union[str, ResourceReference]] = field(default_factory=dict)
42
42
  context: Dict[str, Any] = field(default_factory=dict)
43
- timestamp: datetime = field(default_factory=datetime.utcnow)
43
+ timestamp: datetime = field(default_factory=lambda: datetime.now(UTC))
44
44
 
45
45
  def to_dict(self) -> Dict[str, Any]:
46
46
  """Convert to JSON-serializable dict."""
@@ -12,8 +12,8 @@ Design Philosophy:
12
12
 
13
13
  Key Components:
14
14
  - MCPClient: Connects to MCP servers for tool and resource access
15
- - MCPServer: Base framework for creating custom MCP servers
16
- - SimpleMCPServer: Quick server creation for basic use cases
15
+ - MCPServer: Main production-ready server with all features
16
+ - MCPServerBase: Abstract base class for custom server implementations
17
17
 
18
18
  Upstream Dependencies:
19
19
  - Official Anthropic MCP Python SDK for protocol implementation
@@ -28,26 +28,31 @@ Downstream Consumers:
28
28
  Examples:
29
29
  Basic MCP client usage:
30
30
 
31
- >>> from kailash.mcp import MCPClient
31
+ >>> from kailash.mcp_server import MCPClient
32
32
  >>> client = MCPClient()
33
33
  >>> tools = await client.discover_tools(server_config)
34
34
  >>> result = await client.call_tool(server_config, "search", {"query": "AI"})
35
35
 
36
36
  Simple MCP server creation:
37
37
 
38
- >>> from kailash.mcp import SimpleMCPServer
39
- >>> server = SimpleMCPServer("my-tools")
38
+ >>> from kailash.mcp_server import MCPServer
39
+ >>> server = MCPServer("my-tools")
40
40
  >>> @server.tool()
41
41
  ... def calculate(a: int, b: int) -> int:
42
42
  ... return a + b
43
- >>> server.start()
43
+ >>> server.run()
44
44
  """
45
45
 
46
46
  from .client import MCPClient
47
- from .server_enhanced import MCPServer, SimpleMCPServer
47
+
48
+ # For backward compatibility
49
+ from .server import EnhancedMCPServer, MCPServer, MCPServerBase, SimpleMCPServer
48
50
 
49
51
  __all__ = [
50
52
  "MCPClient",
51
53
  "MCPServer",
54
+ "MCPServerBase",
55
+ # Backward compatibility
52
56
  "SimpleMCPServer",
57
+ "EnhancedMCPServer",
53
58
  ]
@@ -5,7 +5,7 @@ AI Registry MCP Server using Anthropic's Official MCP Python SDK.
5
5
  This creates a real MCP server that exposes AI Registry tools following
6
6
  the actual Model Context Protocol specification.
7
7
 
8
- Run as: python -m kailash.mcp.ai_registry_server
8
+ Run as: python -m kailash.mcp_server.ai_registry_server
9
9
  """
10
10
 
11
11
  import asyncio
@@ -708,5 +708,5 @@ if __name__ == "__main__":
708
708
 
709
709
  # For module execution
710
710
  def run_server():
711
- """Entry point for python -m kailash.mcp.ai_registry_server"""
711
+ """Entry point for python -m kailash.mcp_server.ai_registry_server"""
712
712
  asyncio.run(main())
@@ -1,16 +1,39 @@
1
1
  """
2
- Enhanced MCP Server with production-ready capabilities.
3
-
4
- This module provides an enhanced MCP server that includes caching, configuration,
5
- metrics, and other production features by default, while maintaining compatibility
6
- with the official Anthropic FastMCP framework.
2
+ MCP Server Framework with production-ready capabilities.
3
+
4
+ This module provides both basic and enhanced MCP server implementations using
5
+ the official FastMCP framework from Anthropic. Servers run as long-lived
6
+ services that expose tools, resources, and prompts to MCP clients.
7
+
8
+ Basic Usage:
9
+ Abstract base class for custom servers:
10
+
11
+ >>> class MyServer(MCPServerBase):
12
+ ... def setup(self):
13
+ ... @self.add_tool()
14
+ ... def calculate(a: int, b: int) -> int:
15
+ ... return a + b
16
+ >>> server = MyServer("calculator")
17
+ >>> server.start()
18
+
19
+ Production Usage:
20
+ Main server with all production features:
21
+
22
+ >>> from kailash.mcp_server import MCPServer
23
+ >>> server = MCPServer("my-server", enable_cache=True)
24
+ >>> @server.tool(cache_key="search", cache_ttl=600)
25
+ ... def search(query: str) -> dict:
26
+ ... return {"results": f"Found data for {query}"}
27
+ >>> server.run()
7
28
  """
8
29
 
9
30
  import asyncio
10
31
  import functools
11
32
  import logging
33
+ from abc import ABC, abstractmethod
34
+ from collections.abc import Callable
12
35
  from pathlib import Path
13
- from typing import Any, Callable, Dict, Optional, TypeVar, Union
36
+ from typing import Any, Dict, Optional, TypeVar, Union
14
37
 
15
38
  from .utils import CacheManager, ConfigManager, MetricsCollector, format_response
16
39
 
@@ -19,18 +42,199 @@ logger = logging.getLogger(__name__)
19
42
  F = TypeVar("F", bound=Callable[..., Any])
20
43
 
21
44
 
45
+ class MCPServerBase(ABC):
46
+ """Base class for MCP servers using FastMCP.
47
+
48
+ This provides a framework for creating MCP servers that expose
49
+ tools, resources, and prompts via the Model Context Protocol.
50
+
51
+ Examples:
52
+ Creating a custom server:
53
+
54
+ >>> class MyServer(MCPServerBase):
55
+ ... def setup(self):
56
+ ... @self.add_tool()
57
+ ... def search(query: str) -> str:
58
+ ... return f"Results for: {query}"
59
+ ... @self.add_resource("data://example")
60
+ ... def get_example():
61
+ ... return "Example data"
62
+ >>> server = MyServer("my-server", port=8080)
63
+ >>> server.start() # Runs until stopped
64
+ """
65
+
66
+ def __init__(self, name: str, port: int = 8080, host: str = "localhost"):
67
+ """Initialize the MCP server.
68
+
69
+ Args:
70
+ name: Name of the server.
71
+ port: Port to listen on (default: 8080).
72
+ host: Host to bind to (default: "localhost").
73
+ """
74
+ self.name = name
75
+ self.port = port
76
+ self.host = host
77
+ self._mcp = None
78
+ self._running = False
79
+
80
+ @abstractmethod
81
+ def setup(self):
82
+ """Setup server tools, resources, and prompts.
83
+
84
+ This method should be implemented by subclasses to define
85
+ the server's capabilities using decorators.
86
+
87
+ Note:
88
+ Use @self.add_tool(), @self.add_resource(uri), and
89
+ @self.add_prompt(name) decorators to register capabilities.
90
+ """
91
+
92
+ def add_tool(self):
93
+ """Decorator to add a tool to the server.
94
+
95
+ Returns:
96
+ Function decorator for registering tools.
97
+
98
+ Examples:
99
+ >>> @server.add_tool()
100
+ ... def calculate(a: int, b: int) -> int:
101
+ ... '''Add two numbers'''
102
+ ... return a + b
103
+ """
104
+
105
+ def decorator(func: Callable):
106
+ if self._mcp is None:
107
+ self._init_mcp()
108
+
109
+ # Use FastMCP's tool decorator
110
+ return self._mcp.tool()(func)
111
+
112
+ return decorator
113
+
114
+ def add_resource(self, uri: str):
115
+ """Decorator to add a resource to the server.
116
+
117
+ Args:
118
+ uri: URI pattern for the resource (supports wildcards).
119
+
120
+ Returns:
121
+ Function decorator for registering resources.
122
+
123
+ Examples:
124
+ >>> @server.add_resource("file:///data/*")
125
+ ... def get_file(path: str) -> str:
126
+ ... return f"Content of {path}"
127
+ """
128
+
129
+ def decorator(func: Callable):
130
+ if self._mcp is None:
131
+ self._init_mcp()
132
+
133
+ # Use FastMCP's resource decorator
134
+ return self._mcp.resource(uri)(func)
135
+
136
+ return decorator
137
+
138
+ def add_prompt(self, name: str):
139
+ """Decorator to add a prompt template to the server.
140
+
141
+ Args:
142
+ name: Name of the prompt.
143
+
144
+ Returns:
145
+ Function decorator for registering prompts.
146
+
147
+ Examples:
148
+ >>> @server.add_prompt("analyze")
149
+ ... def analyze_prompt(data: str) -> str:
150
+ ... return f"Please analyze the following data: {data}"
151
+ """
152
+
153
+ def decorator(func: Callable):
154
+ if self._mcp is None:
155
+ self._init_mcp()
156
+
157
+ # Use FastMCP's prompt decorator
158
+ return self._mcp.prompt(name)(func)
159
+
160
+ return decorator
161
+
162
+ def _init_mcp(self):
163
+ """Initialize the FastMCP instance."""
164
+ try:
165
+ from mcp.server import FastMCP
166
+
167
+ self._mcp = FastMCP(self.name)
168
+ except ImportError:
169
+ logger.error(
170
+ "FastMCP not available. Install with: pip install 'mcp[server]'"
171
+ )
172
+ raise
173
+
174
+ def start(self):
175
+ """Start the MCP server.
176
+
177
+ This runs the server as a long-lived process until stopped.
178
+
179
+ Raises:
180
+ ImportError: If FastMCP is not available.
181
+ Exception: If server fails to start.
182
+ """
183
+ if self._mcp is None:
184
+ self._init_mcp()
185
+
186
+ # Run setup to register tools/resources
187
+ self.setup()
188
+
189
+ logger.info(f"Starting MCP server '{self.name}' on {self.host}:{self.port}")
190
+ self._running = True
191
+
192
+ try:
193
+ # Run the FastMCP server
194
+ logger.info("Running FastMCP server in stdio mode")
195
+ self._mcp.run()
196
+ except Exception as e:
197
+ logger.error(f"Failed to start server: {e}")
198
+ raise
199
+ finally:
200
+ self._running = False
201
+
202
+ def stop(self):
203
+ """Stop the MCP server."""
204
+ logger.info(f"Stopping MCP server '{self.name}'")
205
+ self._running = False
206
+ # In a real implementation, we'd need to handle graceful shutdown
207
+
208
+
22
209
  class EnhancedMCPServer:
23
210
  """
24
- Production-ready MCP server with enhanced capabilities.
211
+ Production-ready MCP server (available as SimpleMCPServer).
25
212
 
26
- Features included by default:
27
- - Caching with TTL support
213
+ This is the main concrete MCP server implementation with all production
214
+ features available. Features can be enabled/disabled as needed.
215
+
216
+ Features available:
217
+ - Caching with TTL support (enable_cache=True)
218
+ - Metrics collection and monitoring (enable_metrics=True)
219
+ - Response formatting utilities (enable_formatting=True)
28
220
  - Hierarchical configuration management
29
- - Metrics collection and monitoring
30
- - Response formatting utilities
31
221
  - Error handling and logging
32
222
 
33
- All features can be disabled if not needed.
223
+ Examples:
224
+ Basic usage (recommended):
225
+ >>> from kailash.mcp_server import MCPServer
226
+ >>> server = MCPServer("my-server")
227
+ >>> @server.tool()
228
+ ... def search(query: str) -> dict:
229
+ ... return {"results": f"Found: {query}"}
230
+ >>> server.run()
231
+
232
+ With production features enabled:
233
+ >>> server = MCPServer("my-server", enable_cache=True, enable_metrics=True)
234
+ >>> @server.tool(cache_key="search", cache_ttl=600)
235
+ ... def search(query: str) -> dict:
236
+ ... return {"results": f"Found: {query}"}
237
+ >>> server.run()
34
238
  """
35
239
 
36
240
  def __init__(
@@ -104,15 +308,21 @@ class EnhancedMCPServer:
104
308
  return
105
309
 
106
310
  try:
107
- from mcp.server.fastmcp import FastMCP
311
+ # Now we can safely import from external mcp.server (no namespace collision)
312
+ from mcp.server import FastMCP
108
313
 
109
314
  self._mcp = FastMCP(self.name)
110
315
  logger.info(f"Initialized FastMCP server: {self.name}")
111
- except ImportError:
316
+ except ImportError as e:
317
+ logger.error(
318
+ f"FastMCP import failed with: {e}. Details: {type(e).__name__}"
319
+ )
112
320
  logger.error(
113
321
  "FastMCP not available. Install with: pip install 'mcp[server]'"
114
322
  )
115
- raise
323
+ raise ImportError(
324
+ "FastMCP not available. Install with: pip install 'mcp[server]'"
325
+ ) from e
116
326
 
117
327
  def tool(
118
328
  self,
@@ -413,37 +623,10 @@ class EnhancedMCPServer:
413
623
  self._running = False
414
624
 
415
625
 
416
- # For backward compatibility, make EnhancedMCPServer the default MCPServer
626
+ # Clean public API design:
627
+ # - MCPServerBase: Abstract base for custom implementations (e.g., AIRegistryServer)
628
+ # - MCPServer: Main concrete server with all production features
629
+ # - SimpleMCPServer: Alias for backward compatibility
630
+ # - EnhancedMCPServer: Alias for backward compatibility
417
631
  MCPServer = EnhancedMCPServer
418
-
419
-
420
- class SimpleMCPServer(EnhancedMCPServer):
421
- """
422
- Simplified MCP server with minimal configuration.
423
-
424
- This inherits all enhanced capabilities but disables some features
425
- by default for simpler use cases.
426
- """
427
-
428
- def __init__(self, name: str, description: str = ""):
429
- """
430
- Initialize simple MCP server.
431
-
432
- Args:
433
- name: Server name
434
- description: Server description
435
- """
436
- # Initialize with some features disabled for simplicity
437
- super().__init__(
438
- name=name,
439
- enable_cache=False, # Disable cache by default
440
- enable_metrics=False, # Disable metrics by default
441
- enable_formatting=True, # Keep formatting for better output
442
- )
443
-
444
- self.description = description
445
-
446
- # Update config for simple use
447
- self.config.update(
448
- {"server": {"name": name, "description": description, "version": "1.0.0"}}
449
- )
632
+ SimpleMCPServer = EnhancedMCPServer
@@ -9,10 +9,10 @@ import os
9
9
  from pathlib import Path
10
10
  from typing import Any
11
11
 
12
- from kailash.mcp.server import MCPServer
12
+ from kailash.mcp_server.server import MCPServerBase
13
13
 
14
14
 
15
- class AIRegistryServer(MCPServer):
15
+ class AIRegistryServer(MCPServerBase):
16
16
  """MCP server for AI use case registry.
17
17
 
18
18
  Provides tools and resources for exploring AI use cases from
@@ -10,12 +10,7 @@ This module provides production-ready utilities for MCP servers including:
10
10
 
11
11
  from .cache import CacheManager, LRUCache, cached_query
12
12
  from .config import ConfigManager
13
- from .formatters import (
14
- format_response,
15
- json_formatter,
16
- markdown_formatter,
17
- search_formatter,
18
- )
13
+ from .formatters import format_response, json_formatter, markdown_formatter, search_formatter
19
14
  from .metrics import MetricsCollector
20
15
 
21
16
  __all__ = [
@@ -60,11 +60,11 @@ class MiddlewareAccessControlManager:
60
60
  self.enable_audit = enable_audit
61
61
 
62
62
  # Kailash nodes for operations
63
- self.user_mgmt_node = UserManagementNode("middleware_user_mgmt")
64
- self.role_mgmt_node = RoleManagementNode("middleware_role_mgmt")
65
- self.permission_check_node = PermissionCheckNode("middleware_perm_check")
66
- self.audit_node = AuditLogNode("middleware_audit") if enable_audit else None
67
- self.security_event_node = SecurityEventNode("middleware_security")
63
+ self.user_mgmt_node = UserManagementNode()
64
+ self.role_mgmt_node = RoleManagementNode()
65
+ self.permission_check_node = PermissionCheckNode()
66
+ self.audit_node = AuditLogNode() if enable_audit else None
67
+ self.security_event_node = SecurityEventNode()
68
68
 
69
69
  async def check_session_access(
70
70
  self, user_context: UserContext, session_id: str, action: str = "access"
@@ -175,8 +175,32 @@ class CheckpointManager:
175
175
  compression_enabled: bool = True,
176
176
  compression_threshold_bytes: int = 1024, # 1KB
177
177
  retention_hours: int = 24,
178
+ # Backward compatibility parameter
179
+ storage: Optional[DiskStorage] = None,
178
180
  ):
179
- """Initialize checkpoint manager."""
181
+ """Initialize checkpoint manager.
182
+
183
+ Args:
184
+ memory_storage: Memory storage backend (optional)
185
+ disk_storage: Disk storage backend (optional)
186
+ cloud_storage: Cloud storage backend (optional)
187
+ compression_enabled: Enable compression for large checkpoints
188
+ compression_threshold_bytes: Minimum size for compression
189
+ retention_hours: Hours to retain checkpoints
190
+ storage: DEPRECATED - Use disk_storage instead
191
+ """
192
+ # Handle backward compatibility
193
+ if storage is not None:
194
+ import warnings
195
+
196
+ warnings.warn(
197
+ "The 'storage' parameter is deprecated. Use 'disk_storage' instead.",
198
+ DeprecationWarning,
199
+ stacklevel=2,
200
+ )
201
+ if disk_storage is None:
202
+ disk_storage = storage
203
+
180
204
  self.memory_storage = memory_storage or MemoryStorage()
181
205
  self.disk_storage = disk_storage or DiskStorage()
182
206
  self.cloud_storage = cloud_storage # Optional cloud backend
@@ -189,11 +213,23 @@ class CheckpointManager:
189
213
  self.load_count = 0
190
214
  self.compression_ratio_sum = 0.0
191
215
 
192
- # Start garbage collection task
193
- self._gc_task = asyncio.create_task(self._garbage_collection_loop())
216
+ # Initialize garbage collection task (will be started when first used)
217
+ self._gc_task = None
218
+ self._gc_started = False
219
+
220
+ def _ensure_gc_started(self):
221
+ """Ensure garbage collection task is started (lazy initialization)."""
222
+ if not self._gc_started:
223
+ try:
224
+ self._gc_task = asyncio.create_task(self._garbage_collection_loop())
225
+ self._gc_started = True
226
+ except RuntimeError:
227
+ # No event loop running, GC will be started later
228
+ pass
194
229
 
195
230
  async def save_checkpoint(self, checkpoint: Checkpoint) -> None:
196
231
  """Save checkpoint to storage."""
232
+ self._ensure_gc_started()
197
233
  start_time = time.time()
198
234
 
199
235
  # Serialize checkpoint
@@ -391,8 +427,9 @@ class CheckpointManager:
391
427
 
392
428
  async def close(self) -> None:
393
429
  """Close checkpoint manager and cleanup."""
394
- self._gc_task.cancel()
395
- try:
396
- await self._gc_task
397
- except asyncio.CancelledError:
398
- pass
430
+ if self._gc_task is not None:
431
+ self._gc_task.cancel()
432
+ try:
433
+ await self._gc_task
434
+ except asyncio.CancelledError:
435
+ pass
@@ -20,7 +20,7 @@ from kailash.workflow.builder import WorkflowBuilder
20
20
 
21
21
  # Import existing Kailash MCP components
22
22
  try:
23
- from kailash.mcp import MCPClient
23
+ from kailash.mcp_server import MCPClient
24
24
 
25
25
  _KAILASH_MCP_AVAILABLE = True
26
26
  except ImportError:
@@ -24,8 +24,8 @@ from kailash.workflow.builder import WorkflowBuilder
24
24
 
25
25
  # Import existing Kailash MCP components
26
26
  try:
27
- from kailash.mcp import MCPServer, SimpleMCPServer
28
- from kailash.mcp.utils import CacheManager, ConfigManager, MetricsCollector
27
+ from kailash.mcp_server import MCPServer, SimpleMCPServer
28
+ from kailash.mcp_server.utils import CacheManager, ConfigManager, MetricsCollector
29
29
 
30
30
  _KAILASH_MCP_AVAILABLE = True
31
31
  except ImportError: