kailash 0.3.0__py3-none-any.whl → 0.3.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.
Files changed (113) hide show
  1. kailash/access_control.py +40 -39
  2. kailash/api/auth.py +26 -32
  3. kailash/api/custom_nodes.py +29 -29
  4. kailash/api/custom_nodes_secure.py +35 -35
  5. kailash/api/database.py +17 -17
  6. kailash/api/gateway.py +19 -19
  7. kailash/api/mcp_integration.py +24 -23
  8. kailash/api/studio.py +45 -45
  9. kailash/api/workflow_api.py +8 -8
  10. kailash/cli/commands.py +5 -8
  11. kailash/manifest.py +42 -42
  12. kailash/mcp/__init__.py +1 -1
  13. kailash/mcp/ai_registry_server.py +20 -20
  14. kailash/mcp/client.py +9 -11
  15. kailash/mcp/client_new.py +10 -10
  16. kailash/mcp/server.py +1 -2
  17. kailash/mcp/server_enhanced.py +449 -0
  18. kailash/mcp/servers/ai_registry.py +6 -6
  19. kailash/mcp/utils/__init__.py +31 -0
  20. kailash/mcp/utils/cache.py +267 -0
  21. kailash/mcp/utils/config.py +263 -0
  22. kailash/mcp/utils/formatters.py +293 -0
  23. kailash/mcp/utils/metrics.py +418 -0
  24. kailash/nodes/ai/agents.py +9 -9
  25. kailash/nodes/ai/ai_providers.py +33 -34
  26. kailash/nodes/ai/embedding_generator.py +31 -32
  27. kailash/nodes/ai/intelligent_agent_orchestrator.py +62 -66
  28. kailash/nodes/ai/iterative_llm_agent.py +48 -48
  29. kailash/nodes/ai/llm_agent.py +32 -33
  30. kailash/nodes/ai/models.py +13 -13
  31. kailash/nodes/ai/self_organizing.py +44 -44
  32. kailash/nodes/api/auth.py +11 -11
  33. kailash/nodes/api/graphql.py +13 -13
  34. kailash/nodes/api/http.py +19 -19
  35. kailash/nodes/api/monitoring.py +20 -20
  36. kailash/nodes/api/rate_limiting.py +9 -13
  37. kailash/nodes/api/rest.py +29 -29
  38. kailash/nodes/api/security.py +44 -47
  39. kailash/nodes/base.py +21 -23
  40. kailash/nodes/base_async.py +7 -7
  41. kailash/nodes/base_cycle_aware.py +12 -12
  42. kailash/nodes/base_with_acl.py +5 -5
  43. kailash/nodes/code/python.py +66 -57
  44. kailash/nodes/data/directory.py +6 -6
  45. kailash/nodes/data/event_generation.py +10 -10
  46. kailash/nodes/data/file_discovery.py +28 -31
  47. kailash/nodes/data/readers.py +8 -8
  48. kailash/nodes/data/retrieval.py +10 -10
  49. kailash/nodes/data/sharepoint_graph.py +17 -17
  50. kailash/nodes/data/sources.py +5 -5
  51. kailash/nodes/data/sql.py +13 -13
  52. kailash/nodes/data/streaming.py +25 -25
  53. kailash/nodes/data/vector_db.py +22 -22
  54. kailash/nodes/data/writers.py +7 -7
  55. kailash/nodes/logic/async_operations.py +17 -17
  56. kailash/nodes/logic/convergence.py +11 -11
  57. kailash/nodes/logic/loop.py +4 -4
  58. kailash/nodes/logic/operations.py +11 -11
  59. kailash/nodes/logic/workflow.py +8 -9
  60. kailash/nodes/mixins/mcp.py +17 -17
  61. kailash/nodes/mixins.py +8 -10
  62. kailash/nodes/transform/chunkers.py +3 -3
  63. kailash/nodes/transform/formatters.py +7 -7
  64. kailash/nodes/transform/processors.py +10 -10
  65. kailash/runtime/access_controlled.py +18 -18
  66. kailash/runtime/async_local.py +17 -19
  67. kailash/runtime/docker.py +20 -22
  68. kailash/runtime/local.py +16 -16
  69. kailash/runtime/parallel.py +23 -23
  70. kailash/runtime/parallel_cyclic.py +27 -27
  71. kailash/runtime/runner.py +6 -6
  72. kailash/runtime/testing.py +20 -20
  73. kailash/sdk_exceptions.py +0 -58
  74. kailash/security.py +14 -26
  75. kailash/tracking/manager.py +38 -38
  76. kailash/tracking/metrics_collector.py +15 -14
  77. kailash/tracking/models.py +53 -53
  78. kailash/tracking/storage/base.py +7 -17
  79. kailash/tracking/storage/database.py +22 -23
  80. kailash/tracking/storage/filesystem.py +38 -40
  81. kailash/utils/export.py +21 -21
  82. kailash/utils/templates.py +2 -3
  83. kailash/visualization/api.py +30 -34
  84. kailash/visualization/dashboard.py +17 -17
  85. kailash/visualization/performance.py +16 -16
  86. kailash/visualization/reports.py +25 -27
  87. kailash/workflow/builder.py +8 -8
  88. kailash/workflow/convergence.py +13 -12
  89. kailash/workflow/cycle_analyzer.py +30 -32
  90. kailash/workflow/cycle_builder.py +12 -12
  91. kailash/workflow/cycle_config.py +16 -15
  92. kailash/workflow/cycle_debugger.py +40 -40
  93. kailash/workflow/cycle_exceptions.py +29 -29
  94. kailash/workflow/cycle_profiler.py +21 -21
  95. kailash/workflow/cycle_state.py +20 -22
  96. kailash/workflow/cyclic_runner.py +44 -44
  97. kailash/workflow/graph.py +40 -40
  98. kailash/workflow/mermaid_visualizer.py +9 -11
  99. kailash/workflow/migration.py +22 -22
  100. kailash/workflow/mock_registry.py +6 -6
  101. kailash/workflow/runner.py +9 -9
  102. kailash/workflow/safety.py +12 -13
  103. kailash/workflow/state.py +8 -11
  104. kailash/workflow/templates.py +19 -19
  105. kailash/workflow/validation.py +14 -14
  106. kailash/workflow/visualization.py +22 -22
  107. {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/METADATA +53 -5
  108. kailash-0.3.2.dist-info/RECORD +136 -0
  109. kailash-0.3.0.dist-info/RECORD +0 -130
  110. {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/WHEEL +0 -0
  111. {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/entry_points.txt +0 -0
  112. {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/licenses/LICENSE +0 -0
  113. {kailash-0.3.0.dist-info → kailash-0.3.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,267 @@
1
+ """
2
+ Caching utilities for MCP servers.
3
+
4
+ Provides LRU cache, TTL support, and decorators for method-level caching.
5
+ Based on patterns from production MCP server implementations.
6
+ """
7
+
8
+ import asyncio
9
+ import functools
10
+ import logging
11
+ import threading
12
+ import time
13
+ from typing import Any, Callable, Dict, Optional, Tuple, TypeVar
14
+
15
+ logger = logging.getLogger(__name__)
16
+
17
+ F = TypeVar("F", bound=Callable[..., Any])
18
+
19
+
20
+ class LRUCache:
21
+ """
22
+ Thread-safe LRU cache with TTL (time-to-live) support.
23
+
24
+ Features:
25
+ - Configurable maximum size
26
+ - TTL expiration for entries
27
+ - Thread-safe operations
28
+ - Performance statistics
29
+ """
30
+
31
+ def __init__(self, max_size: int = 128, ttl: int = 300):
32
+ """
33
+ Initialize LRU cache.
34
+
35
+ Args:
36
+ max_size: Maximum number of entries to store
37
+ ttl: Time-to-live in seconds (0 = no expiration)
38
+ """
39
+ self.max_size = max_size
40
+ self.ttl = ttl
41
+ self._cache: Dict[str, Tuple[Any, float]] = {}
42
+ self._access_order: Dict[str, float] = {}
43
+ self._lock = threading.RLock()
44
+
45
+ # Statistics
46
+ self._hits = 0
47
+ self._misses = 0
48
+ self._evictions = 0
49
+
50
+ def get(self, key: str) -> Optional[Any]:
51
+ """Get value from cache if it exists and hasn't expired."""
52
+ with self._lock:
53
+ if key not in self._cache:
54
+ self._misses += 1
55
+ return None
56
+
57
+ value, timestamp = self._cache[key]
58
+
59
+ # Check TTL expiration
60
+ if self.ttl > 0 and time.time() - timestamp > self.ttl:
61
+ del self._cache[key]
62
+ del self._access_order[key]
63
+ self._misses += 1
64
+ return None
65
+
66
+ # Update access time for LRU
67
+ self._access_order[key] = time.time()
68
+ self._hits += 1
69
+ return value
70
+
71
+ def set(self, key: str, value: Any) -> None:
72
+ """Set value in cache, evicting LRU items if necessary."""
73
+ with self._lock:
74
+ current_time = time.time()
75
+
76
+ # If key exists, update it
77
+ if key in self._cache:
78
+ self._cache[key] = (value, current_time)
79
+ self._access_order[key] = current_time
80
+ return
81
+
82
+ # Check if we need to evict
83
+ if len(self._cache) >= self.max_size:
84
+ self._evict_lru()
85
+
86
+ # Add new entry
87
+ self._cache[key] = (value, current_time)
88
+ self._access_order[key] = current_time
89
+
90
+ def _evict_lru(self) -> None:
91
+ """Evict least recently used item."""
92
+ if not self._access_order:
93
+ return
94
+
95
+ lru_key = min(self._access_order.keys(), key=self._access_order.get)
96
+ del self._cache[lru_key]
97
+ del self._access_order[lru_key]
98
+ self._evictions += 1
99
+
100
+ def clear(self) -> None:
101
+ """Clear all entries from cache."""
102
+ with self._lock:
103
+ self._cache.clear()
104
+ self._access_order.clear()
105
+
106
+ def stats(self) -> Dict[str, Any]:
107
+ """Get cache performance statistics."""
108
+ with self._lock:
109
+ total_requests = self._hits + self._misses
110
+ hit_rate = self._hits / total_requests if total_requests > 0 else 0
111
+
112
+ return {
113
+ "hits": self._hits,
114
+ "misses": self._misses,
115
+ "evictions": self._evictions,
116
+ "hit_rate": hit_rate,
117
+ "size": len(self._cache),
118
+ "max_size": self.max_size,
119
+ "ttl": self.ttl,
120
+ }
121
+
122
+
123
+ class CacheManager:
124
+ """
125
+ High-level cache management with multiple caching strategies.
126
+
127
+ Provides easy-to-use caching for MCP servers with different cache types
128
+ for different use cases.
129
+ """
130
+
131
+ def __init__(self, enabled: bool = True, default_ttl: int = 300):
132
+ """
133
+ Initialize cache manager.
134
+
135
+ Args:
136
+ enabled: Whether caching is enabled
137
+ default_ttl: Default TTL for cache entries
138
+ """
139
+ self.enabled = enabled
140
+ self.default_ttl = default_ttl
141
+ self._caches: Dict[str, LRUCache] = {}
142
+
143
+ def get_cache(
144
+ self, name: str, max_size: int = 128, ttl: Optional[int] = None
145
+ ) -> LRUCache:
146
+ """Get or create a named cache."""
147
+ if name not in self._caches:
148
+ cache_ttl = ttl if ttl is not None else self.default_ttl
149
+ self._caches[name] = LRUCache(max_size=max_size, ttl=cache_ttl)
150
+ return self._caches[name]
151
+
152
+ def cached(self, cache_name: str = "default", ttl: Optional[int] = None):
153
+ """
154
+ Decorator to cache function results.
155
+
156
+ Args:
157
+ cache_name: Name of cache to use
158
+ ttl: TTL for this specific cache
159
+
160
+ Returns:
161
+ Decorated function with caching
162
+ """
163
+
164
+ def decorator(func: F) -> F:
165
+ if not self.enabled:
166
+ return func
167
+
168
+ cache = self.get_cache(cache_name, ttl=ttl)
169
+
170
+ @functools.wraps(func)
171
+ def sync_wrapper(*args, **kwargs):
172
+ # Create cache key from function name and arguments
173
+ cache_key = self._create_cache_key(func.__name__, args, kwargs)
174
+
175
+ # Try to get from cache
176
+ result = cache.get(cache_key)
177
+ if result is not None:
178
+ logger.debug(f"Cache hit for {func.__name__}: {cache_key}")
179
+ return result
180
+
181
+ # Execute function and cache result
182
+ logger.debug(f"Cache miss for {func.__name__}: {cache_key}")
183
+ result = func(*args, **kwargs)
184
+ cache.set(cache_key, result)
185
+ return result
186
+
187
+ @functools.wraps(func)
188
+ async def async_wrapper(*args, **kwargs):
189
+ # Create cache key from function name and arguments
190
+ cache_key = self._create_cache_key(func.__name__, args, kwargs)
191
+
192
+ # Try to get from cache
193
+ result = cache.get(cache_key)
194
+ if result is not None:
195
+ logger.debug(f"Cache hit for {func.__name__}: {cache_key}")
196
+ return result
197
+
198
+ # Execute function and cache result
199
+ logger.debug(f"Cache miss for {func.__name__}: {cache_key}")
200
+ result = await func(*args, **kwargs)
201
+ cache.set(cache_key, result)
202
+ return result
203
+
204
+ # Return appropriate wrapper based on function type
205
+ if asyncio.iscoroutinefunction(func):
206
+ return async_wrapper
207
+ else:
208
+ return sync_wrapper
209
+
210
+ return decorator
211
+
212
+ def _create_cache_key(self, func_name: str, args: tuple, kwargs: dict) -> str:
213
+ """Create a cache key from function name and arguments."""
214
+ # Convert args and kwargs to string representation
215
+ args_str = str(args) if args else ""
216
+ kwargs_str = str(sorted(kwargs.items())) if kwargs else ""
217
+ return f"{func_name}:{args_str}:{kwargs_str}"
218
+
219
+ def clear_all(self) -> None:
220
+ """Clear all caches."""
221
+ for cache in self._caches.values():
222
+ cache.clear()
223
+
224
+ def stats(self) -> Dict[str, Dict[str, Any]]:
225
+ """Get statistics for all caches."""
226
+ return {name: cache.stats() for name, cache in self._caches.items()}
227
+
228
+
229
+ # Global cache manager instance
230
+ _global_cache_manager = CacheManager()
231
+
232
+
233
+ def cached_query(cache_name: str = "query", ttl: int = 300, enabled: bool = True):
234
+ """
235
+ Simple decorator for caching query results.
236
+
237
+ This is a convenience decorator that uses the global cache manager.
238
+
239
+ Args:
240
+ cache_name: Name of cache to use
241
+ ttl: Time-to-live for cache entries
242
+ enabled: Whether caching is enabled
243
+
244
+ Example:
245
+ @cached_query("search", ttl=600)
246
+ async def search_data(query: str) -> list:
247
+ # Expensive search operation
248
+ return results
249
+ """
250
+
251
+ def decorator(func: F) -> F:
252
+ if not enabled:
253
+ return func
254
+
255
+ return _global_cache_manager.cached(cache_name, ttl=ttl)(func)
256
+
257
+ return decorator
258
+
259
+
260
+ def get_cache_stats() -> Dict[str, Dict[str, Any]]:
261
+ """Get statistics for the global cache manager."""
262
+ return _global_cache_manager.stats()
263
+
264
+
265
+ def clear_all_caches() -> None:
266
+ """Clear all caches in the global cache manager."""
267
+ _global_cache_manager.clear_all()
@@ -0,0 +1,263 @@
1
+ """
2
+ Configuration management for MCP servers.
3
+
4
+ Provides hierarchical configuration with support for:
5
+ - Default values
6
+ - YAML/JSON configuration files
7
+ - Environment variable overrides
8
+ - Runtime parameter overrides
9
+ - Dot notation access
10
+ """
11
+
12
+ import json
13
+ import logging
14
+ import os
15
+ from pathlib import Path
16
+ from typing import Any, Dict, Optional, Union
17
+
18
+ logger = logging.getLogger(__name__)
19
+
20
+ try:
21
+ import yaml
22
+
23
+ HAS_YAML = True
24
+ except ImportError:
25
+ HAS_YAML = False
26
+ logger.warning("PyYAML not available. YAML configuration files not supported.")
27
+
28
+
29
+ class ConfigManager:
30
+ """
31
+ Hierarchical configuration manager for MCP servers.
32
+
33
+ Configuration precedence (highest to lowest):
34
+ 1. Runtime overrides (set via set() method)
35
+ 2. Environment variables
36
+ 3. Configuration file (YAML/JSON)
37
+ 4. Default values
38
+ """
39
+
40
+ def __init__(
41
+ self,
42
+ config_file: Optional[Union[str, Path]] = None,
43
+ defaults: Optional[Dict[str, Any]] = None,
44
+ ):
45
+ """
46
+ Initialize configuration manager.
47
+
48
+ Args:
49
+ config_file: Path to configuration file (YAML or JSON)
50
+ defaults: Default configuration values
51
+ """
52
+ self._defaults = defaults or {}
53
+ self._file_config = {}
54
+ self._env_config = {}
55
+ self._runtime_config = {}
56
+
57
+ # Load configuration file if provided
58
+ if config_file:
59
+ self.load_file(config_file)
60
+
61
+ # Load environment variables
62
+ self._load_env_vars()
63
+
64
+ def load_file(self, config_file: Union[str, Path]) -> None:
65
+ """Load configuration from file."""
66
+ config_path = Path(config_file)
67
+
68
+ if not config_path.exists():
69
+ logger.warning(f"Configuration file not found: {config_path}")
70
+ return
71
+
72
+ try:
73
+ with open(config_path, "r", encoding="utf-8") as f:
74
+ if config_path.suffix.lower() in [".yaml", ".yml"]:
75
+ if not HAS_YAML:
76
+ raise ValueError("PyYAML not available for YAML configuration")
77
+ self._file_config = yaml.safe_load(f) or {}
78
+ elif config_path.suffix.lower() == ".json":
79
+ self._file_config = json.load(f) or {}
80
+ else:
81
+ raise ValueError(
82
+ f"Unsupported configuration file format: {config_path.suffix}"
83
+ )
84
+
85
+ logger.info(f"Loaded configuration from {config_path}")
86
+
87
+ except Exception as e:
88
+ logger.error(f"Failed to load configuration file {config_path}: {e}")
89
+ self._file_config = {}
90
+
91
+ def _load_env_vars(self) -> None:
92
+ """Load configuration from environment variables."""
93
+ # Look for environment variables with MCP_ prefix
94
+ env_config = {}
95
+
96
+ for key, value in os.environ.items():
97
+ if key.startswith("MCP_"):
98
+ # Convert MCP_SERVER_NAME to server.name
99
+ config_key = key[4:].lower().replace("_", ".")
100
+
101
+ # Try to parse as JSON, fall back to string
102
+ try:
103
+ parsed_value = json.loads(value)
104
+ except (json.JSONDecodeError, ValueError):
105
+ parsed_value = value
106
+
107
+ self._set_nested_value(env_config, config_key, parsed_value)
108
+
109
+ self._env_config = env_config
110
+
111
+ def _set_nested_value(self, config: Dict[str, Any], key: str, value: Any) -> None:
112
+ """Set a nested configuration value using dot notation."""
113
+ keys = key.split(".")
114
+ current = config
115
+
116
+ for k in keys[:-1]:
117
+ if k not in current:
118
+ current[k] = {}
119
+ current = current[k]
120
+
121
+ current[keys[-1]] = value
122
+
123
+ def get(self, key: str, default: Any = None) -> Any:
124
+ """
125
+ Get configuration value using dot notation.
126
+
127
+ Args:
128
+ key: Configuration key (e.g., "server.cache.ttl")
129
+ default: Default value if key not found
130
+
131
+ Returns:
132
+ Configuration value
133
+ """
134
+ # Check in order of precedence
135
+ for config in [
136
+ self._runtime_config,
137
+ self._env_config,
138
+ self._file_config,
139
+ self._defaults,
140
+ ]:
141
+ value = self._get_nested_value(config, key)
142
+ if value is not None:
143
+ return value
144
+
145
+ return default
146
+
147
+ def _get_nested_value(self, config: Dict[str, Any], key: str) -> Any:
148
+ """Get nested value using dot notation."""
149
+ keys = key.split(".")
150
+ current = config
151
+
152
+ try:
153
+ for k in keys:
154
+ current = current[k]
155
+ return current
156
+ except (KeyError, TypeError):
157
+ return None
158
+
159
+ def set(self, key: str, value: Any) -> None:
160
+ """Set runtime configuration value using dot notation."""
161
+ self._set_nested_value(self._runtime_config, key, value)
162
+
163
+ def update(self, config: Dict[str, Any]) -> None:
164
+ """Update runtime configuration with dictionary."""
165
+ for key, value in config.items():
166
+ self.set(key, value)
167
+
168
+ def to_dict(self) -> Dict[str, Any]:
169
+ """Get complete configuration as dictionary."""
170
+ result = {}
171
+
172
+ # Merge all configurations in reverse precedence order
173
+ for config in [
174
+ self._defaults,
175
+ self._file_config,
176
+ self._env_config,
177
+ self._runtime_config,
178
+ ]:
179
+ result = self._deep_merge(result, config)
180
+
181
+ return result
182
+
183
+ def _deep_merge(
184
+ self, base: Dict[str, Any], update: Dict[str, Any]
185
+ ) -> Dict[str, Any]:
186
+ """Deep merge two dictionaries."""
187
+ result = base.copy()
188
+
189
+ for key, value in update.items():
190
+ if (
191
+ key in result
192
+ and isinstance(result[key], dict)
193
+ and isinstance(value, dict)
194
+ ):
195
+ result[key] = self._deep_merge(result[key], value)
196
+ else:
197
+ result[key] = value
198
+
199
+ return result
200
+
201
+ def save(self, config_file: Union[str, Path], format: str = "yaml") -> None:
202
+ """
203
+ Save current configuration to file.
204
+
205
+ Args:
206
+ config_file: Path to save configuration
207
+ format: File format ('yaml' or 'json')
208
+ """
209
+ config_path = Path(config_file)
210
+ config_data = self.to_dict()
211
+
212
+ try:
213
+ with open(config_path, "w", encoding="utf-8") as f:
214
+ if format.lower() == "yaml":
215
+ if not HAS_YAML:
216
+ raise ValueError("PyYAML not available for YAML export")
217
+ yaml.dump(config_data, f, default_flow_style=False, indent=2)
218
+ elif format.lower() == "json":
219
+ json.dump(config_data, f, indent=2)
220
+ else:
221
+ raise ValueError(f"Unsupported format: {format}")
222
+
223
+ logger.info(f"Configuration saved to {config_path}")
224
+
225
+ except Exception as e:
226
+ logger.error(f"Failed to save configuration to {config_path}: {e}")
227
+ raise
228
+
229
+
230
+ def create_default_config() -> Dict[str, Any]:
231
+ """Create default MCP server configuration."""
232
+ return {
233
+ "server": {
234
+ "name": "mcp-server",
235
+ "version": "1.0.0",
236
+ "description": "MCP Server",
237
+ "transport": "stdio",
238
+ },
239
+ "cache": {"enabled": True, "default_ttl": 300, "max_size": 128},
240
+ "metrics": {
241
+ "enabled": True,
242
+ "collect_performance": True,
243
+ "collect_usage": True,
244
+ },
245
+ "logging": {
246
+ "level": "INFO",
247
+ "format": "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
248
+ },
249
+ }
250
+
251
+
252
+ def load_config_file(config_file: Union[str, Path]) -> ConfigManager:
253
+ """
254
+ Convenience function to load configuration from file.
255
+
256
+ Args:
257
+ config_file: Path to configuration file
258
+
259
+ Returns:
260
+ ConfigManager instance with loaded configuration
261
+ """
262
+ defaults = create_default_config()
263
+ return ConfigManager(config_file=config_file, defaults=defaults)