kailash 0.6.4__py3-none-any.whl → 0.6.6__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 -3
- kailash/api/studio.py +2 -1
- kailash/api/workflow_api.py +2 -1
- kailash/core/resilience/bulkhead.py +21 -6
- kailash/core/resilience/health_monitor.py +578 -0
- kailash/mcp_server/ai_registry_server.py +3 -3
- kailash/mcp_server/server.py +33 -16
- kailash/middleware/communication/api_gateway.py +8 -1
- kailash/middleware/core/agent_ui.py +5 -0
- kailash/nodes/admin/security_event.py +5 -1
- kailash/nodes/admin/user_management.py +1 -0
- kailash/nodes/api/http.py +7 -2
- kailash/nodes/auth/mfa.py +1 -0
- kailash/nodes/base.py +3 -1
- kailash/nodes/cache/cache_invalidation.py +18 -14
- kailash/nodes/code/python.py +5 -2
- kailash/nodes/data/async_sql.py +1956 -129
- kailash/nodes/data/redis.py +52 -23
- kailash/nodes/rag/registry.py +5 -1
- kailash/nodes/security/behavior_analysis.py +1 -0
- kailash/runtime/local.py +9 -3
- kailash/runtime/parameter_injector.py +5 -5
- kailash/workflow/builder.py +19 -15
- {kailash-0.6.4.dist-info → kailash-0.6.6.dist-info}/METADATA +1 -1
- {kailash-0.6.4.dist-info → kailash-0.6.6.dist-info}/RECORD +29 -28
- {kailash-0.6.4.dist-info → kailash-0.6.6.dist-info}/WHEEL +0 -0
- {kailash-0.6.4.dist-info → kailash-0.6.6.dist-info}/entry_points.txt +0 -0
- {kailash-0.6.4.dist-info → kailash-0.6.6.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.6.4.dist-info → kailash-0.6.6.dist-info}/top_level.txt +0 -0
kailash/mcp_server/server.py
CHANGED
@@ -203,42 +203,47 @@ class MCPServerBase(ABC):
|
|
203
203
|
try:
|
204
204
|
# Try independent FastMCP package first (when available)
|
205
205
|
from fastmcp import FastMCP
|
206
|
+
|
206
207
|
self._mcp = FastMCP(self.name)
|
207
208
|
except ImportError:
|
208
209
|
logger.warning("FastMCP not available, using fallback mode")
|
209
210
|
# Use same fallback as MCPServer
|
210
211
|
self._mcp = self._create_fallback_server()
|
211
|
-
|
212
|
+
|
212
213
|
def _create_fallback_server(self):
|
213
214
|
"""Create a fallback server when FastMCP is not available."""
|
215
|
+
|
214
216
|
class FallbackMCPServer:
|
215
217
|
def __init__(self, name: str):
|
216
218
|
self.name = name
|
217
219
|
self._tools = {}
|
218
220
|
self._resources = {}
|
219
221
|
self._prompts = {}
|
220
|
-
|
222
|
+
|
221
223
|
def tool(self, *args, **kwargs):
|
222
224
|
def decorator(func):
|
223
225
|
self._tools[func.__name__] = func
|
224
226
|
return func
|
227
|
+
|
225
228
|
return decorator
|
226
|
-
|
229
|
+
|
227
230
|
def resource(self, uri):
|
228
231
|
def decorator(func):
|
229
232
|
self._resources[uri] = func
|
230
233
|
return func
|
234
|
+
|
231
235
|
return decorator
|
232
|
-
|
236
|
+
|
233
237
|
def prompt(self, name):
|
234
238
|
def decorator(func):
|
235
239
|
self._prompts[name] = func
|
236
240
|
return func
|
241
|
+
|
237
242
|
return decorator
|
238
|
-
|
243
|
+
|
239
244
|
def run(self, **kwargs):
|
240
245
|
raise NotImplementedError("FastMCP not available")
|
241
|
-
|
246
|
+
|
242
247
|
return FallbackMCPServer(self.name)
|
243
248
|
|
244
249
|
def start(self):
|
@@ -503,6 +508,7 @@ class MCPServer:
|
|
503
508
|
try:
|
504
509
|
# Try independent FastMCP package first (when available)
|
505
510
|
from fastmcp import FastMCP
|
511
|
+
|
506
512
|
self._mcp = FastMCP(self.name)
|
507
513
|
logger.info(f"Initialized FastMCP server: {self.name}")
|
508
514
|
except ImportError as e1:
|
@@ -510,6 +516,7 @@ class MCPServer:
|
|
510
516
|
try:
|
511
517
|
# Fallback to official MCP FastMCP (when fixed)
|
512
518
|
from mcp.server import FastMCP
|
519
|
+
|
513
520
|
self._mcp = FastMCP(self.name)
|
514
521
|
logger.info(f"Initialized official FastMCP server: {self.name}")
|
515
522
|
except ImportError as e2:
|
@@ -517,56 +524,66 @@ class MCPServer:
|
|
517
524
|
# Final fallback: Create a minimal FastMCP-compatible wrapper
|
518
525
|
logger.info(f"Using low-level MCP Server fallback for: {self.name}")
|
519
526
|
self._mcp = self._create_fallback_server()
|
520
|
-
|
527
|
+
|
521
528
|
def _create_fallback_server(self):
|
522
529
|
"""Create a fallback server when FastMCP is not available."""
|
523
530
|
logger.info("Creating fallback server implementation")
|
524
|
-
|
531
|
+
|
525
532
|
class FallbackMCPServer:
|
526
533
|
"""Minimal FastMCP-compatible server for when FastMCP is unavailable."""
|
527
|
-
|
534
|
+
|
528
535
|
def __init__(self, name: str):
|
529
536
|
self.name = name
|
530
537
|
self._tools = {}
|
531
538
|
self._resources = {}
|
532
539
|
self._prompts = {}
|
533
540
|
logger.info(f"Fallback MCP server '{name}' initialized")
|
534
|
-
|
541
|
+
|
535
542
|
def tool(self, *args, **kwargs):
|
536
543
|
"""Tool decorator that stores tool registration."""
|
544
|
+
|
537
545
|
def decorator(func):
|
538
546
|
tool_name = func.__name__
|
539
547
|
self._tools[tool_name] = func
|
540
548
|
logger.debug(f"Registered fallback tool: {tool_name}")
|
541
549
|
return func
|
550
|
+
|
542
551
|
return decorator
|
543
|
-
|
552
|
+
|
544
553
|
def resource(self, uri):
|
545
554
|
"""Resource decorator that stores resource registration."""
|
555
|
+
|
546
556
|
def decorator(func):
|
547
557
|
self._resources[uri] = func
|
548
558
|
logger.debug(f"Registered fallback resource: {uri}")
|
549
559
|
return func
|
560
|
+
|
550
561
|
return decorator
|
551
|
-
|
562
|
+
|
552
563
|
def prompt(self, name):
|
553
564
|
"""Prompt decorator that stores prompt registration."""
|
565
|
+
|
554
566
|
def decorator(func):
|
555
567
|
self._prompts[name] = func
|
556
568
|
logger.debug(f"Registered fallback prompt: {name}")
|
557
569
|
return func
|
570
|
+
|
558
571
|
return decorator
|
559
|
-
|
572
|
+
|
560
573
|
def run(self, **kwargs):
|
561
574
|
"""Placeholder run method."""
|
562
|
-
logger.warning(
|
563
|
-
|
575
|
+
logger.warning(
|
576
|
+
f"Fallback server '{self.name}' run() called - FastMCP features limited"
|
577
|
+
)
|
578
|
+
logger.info(
|
579
|
+
f"Registered: {len(self._tools)} tools, {len(self._resources)} resources, {len(self._prompts)} prompts"
|
580
|
+
)
|
564
581
|
# In a real implementation, we would set up low-level MCP protocol here
|
565
582
|
raise NotImplementedError(
|
566
583
|
"Full MCP protocol not implemented in fallback mode. "
|
567
584
|
"Install 'fastmcp>=2.10.0' or wait for official MCP package fix."
|
568
585
|
)
|
569
|
-
|
586
|
+
|
570
587
|
return FallbackMCPServer(self.name)
|
571
588
|
|
572
589
|
def tool(
|
@@ -15,7 +15,14 @@ from datetime import datetime, timezone
|
|
15
15
|
from typing import Any, Dict, List, Optional, Union
|
16
16
|
from urllib.parse import parse_qs
|
17
17
|
|
18
|
-
from fastapi import
|
18
|
+
from fastapi import (
|
19
|
+
Depends,
|
20
|
+
FastAPI,
|
21
|
+
HTTPException,
|
22
|
+
Request,
|
23
|
+
WebSocket,
|
24
|
+
WebSocketDisconnect,
|
25
|
+
)
|
19
26
|
from fastapi.middleware.cors import CORSMiddleware
|
20
27
|
from fastapi.responses import JSONResponse, StreamingResponse
|
21
28
|
from fastapi.security import HTTPAuthorizationCredentials, HTTPBearer
|
@@ -135,6 +135,7 @@ Author: Kailash SDK Team
|
|
135
135
|
"""
|
136
136
|
|
137
137
|
import asyncio
|
138
|
+
import copy
|
138
139
|
import logging
|
139
140
|
import time
|
140
141
|
import uuid
|
@@ -563,6 +564,8 @@ class AgentUIMiddleware:
|
|
563
564
|
workflow = session.workflows[workflow_id]
|
564
565
|
elif workflow_id in self.shared_workflows:
|
565
566
|
workflow = self.shared_workflows[workflow_id]
|
567
|
+
# FIX: Copy shared workflow to session before execution
|
568
|
+
session.add_workflow(workflow_id, workflow)
|
566
569
|
else:
|
567
570
|
raise ValueError(f"Workflow {workflow_id} not found")
|
568
571
|
|
@@ -658,6 +661,8 @@ class AgentUIMiddleware:
|
|
658
661
|
workflow = session.workflows[workflow_id]
|
659
662
|
elif workflow_id in self.shared_workflows:
|
660
663
|
workflow = self.shared_workflows[workflow_id]
|
664
|
+
# FIX: Copy shared workflow to session before execution
|
665
|
+
session.add_workflow(workflow_id, workflow)
|
661
666
|
else:
|
662
667
|
raise ValueError(f"Workflow {workflow_id} not found")
|
663
668
|
|
@@ -24,7 +24,11 @@ from enum import Enum
|
|
24
24
|
from typing import Any, Dict, List, Optional, Tuple
|
25
25
|
|
26
26
|
from kailash.access_control import UserContext
|
27
|
-
from kailash.nodes.admin.audit_log import
|
27
|
+
from kailash.nodes.admin.audit_log import (
|
28
|
+
AuditEventType,
|
29
|
+
AuditSeverity,
|
30
|
+
EnterpriseAuditLogNode,
|
31
|
+
)
|
28
32
|
from kailash.nodes.base import Node, NodeParameter, register_node
|
29
33
|
from kailash.nodes.data import AsyncSQLDatabaseNode
|
30
34
|
from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
|
@@ -26,6 +26,7 @@ from typing import Any, Dict, List, Optional, Set, Union
|
|
26
26
|
from uuid import uuid4
|
27
27
|
|
28
28
|
import bcrypt
|
29
|
+
|
29
30
|
from kailash.nodes.base import Node, NodeParameter, register_node
|
30
31
|
from kailash.nodes.data import SQLDatabaseNode
|
31
32
|
from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
|
kailash/nodes/api/http.py
CHANGED
@@ -12,11 +12,16 @@ from typing import Any
|
|
12
12
|
|
13
13
|
import aiohttp
|
14
14
|
import requests
|
15
|
+
from pydantic import BaseModel
|
16
|
+
|
15
17
|
from kailash.nodes.base import Node, NodeParameter, register_node
|
16
18
|
from kailash.nodes.base_async import AsyncNode
|
17
19
|
from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
|
18
|
-
from kailash.utils.resource_manager import
|
19
|
-
|
20
|
+
from kailash.utils.resource_manager import (
|
21
|
+
AsyncResourcePool,
|
22
|
+
ResourcePool,
|
23
|
+
managed_resource,
|
24
|
+
)
|
20
25
|
|
21
26
|
|
22
27
|
class HTTPMethod(str, Enum):
|
kailash/nodes/auth/mfa.py
CHANGED
@@ -17,6 +17,7 @@ from datetime import UTC, datetime, timedelta
|
|
17
17
|
from typing import Any, Dict, List, Optional, Tuple
|
18
18
|
|
19
19
|
import qrcode
|
20
|
+
|
20
21
|
from kailash.nodes.base import Node, NodeParameter, register_node
|
21
22
|
from kailash.nodes.mixins import LoggingMixin, PerformanceMixin, SecurityMixin
|
22
23
|
from kailash.nodes.security.audit_log import AuditLogNode
|
kailash/nodes/base.py
CHANGED
@@ -1070,7 +1070,9 @@ class Node(ABC):
|
|
1070
1070
|
# ENTERPRISE PARAMETER INJECTION FIX: Runtime inputs should take precedence over config dict
|
1071
1071
|
# First apply config dict values, then re-apply runtime inputs to ensure they override
|
1072
1072
|
for key, value in nested_config.items():
|
1073
|
-
if
|
1073
|
+
if (
|
1074
|
+
key not in runtime_inputs
|
1075
|
+
): # Only use config values if not overridden by runtime
|
1074
1076
|
merged_inputs[key] = value
|
1075
1077
|
# Don't remove the config key as some nodes might need it
|
1076
1078
|
|
@@ -368,7 +368,7 @@ class CacheInvalidationNode(AsyncNode):
|
|
368
368
|
return
|
369
369
|
|
370
370
|
redis_url = kwargs.get("redis_url", "redis://localhost:6379")
|
371
|
-
|
371
|
+
|
372
372
|
# Only recreate Redis client if the current one is problematic
|
373
373
|
if self._redis_client:
|
374
374
|
try:
|
@@ -382,18 +382,22 @@ class CacheInvalidationNode(AsyncNode):
|
|
382
382
|
await self._redis_client.aclose()
|
383
383
|
except:
|
384
384
|
pass # Ignore errors when closing old client
|
385
|
-
|
385
|
+
|
386
386
|
try:
|
387
387
|
self._redis_client = redis.from_url(redis_url)
|
388
388
|
# Test connection with proper error handling
|
389
389
|
try:
|
390
390
|
await asyncio.wait_for(self._redis_client.ping(), timeout=2.0)
|
391
|
-
self.logger.debug(
|
391
|
+
self.logger.debug(
|
392
|
+
f"Fresh Redis connection established to {redis_url}"
|
393
|
+
)
|
392
394
|
except (asyncio.TimeoutError, RuntimeError) as e:
|
393
395
|
if "Event loop is closed" in str(e):
|
394
396
|
# Event loop issue - create new client without ping test
|
395
397
|
self._redis_client = redis.from_url(redis_url)
|
396
|
-
self.logger.debug(
|
398
|
+
self.logger.debug(
|
399
|
+
"Created Redis client without ping test due to event loop issue"
|
400
|
+
)
|
397
401
|
else:
|
398
402
|
raise
|
399
403
|
except Exception as e:
|
@@ -843,10 +847,10 @@ class CacheInvalidationNode(AsyncNode):
|
|
843
847
|
# Event loop is running, schedule the coroutine
|
844
848
|
import concurrent.futures
|
845
849
|
import threading
|
846
|
-
|
850
|
+
|
847
851
|
result_holder = {}
|
848
852
|
exception_holder = {}
|
849
|
-
|
853
|
+
|
850
854
|
def run_in_new_loop():
|
851
855
|
try:
|
852
856
|
# Create a new event loop for this thread
|
@@ -854,17 +858,17 @@ class CacheInvalidationNode(AsyncNode):
|
|
854
858
|
asyncio.set_event_loop(new_loop)
|
855
859
|
try:
|
856
860
|
result = new_loop.run_until_complete(self.async_run(**kwargs))
|
857
|
-
result_holder[
|
861
|
+
result_holder["result"] = result
|
858
862
|
finally:
|
859
863
|
new_loop.close()
|
860
864
|
except Exception as e:
|
861
|
-
exception_holder[
|
862
|
-
|
865
|
+
exception_holder["error"] = e
|
866
|
+
|
863
867
|
thread = threading.Thread(target=run_in_new_loop)
|
864
868
|
thread.start()
|
865
869
|
thread.join()
|
866
|
-
|
867
|
-
if
|
868
|
-
raise exception_holder[
|
869
|
-
|
870
|
-
return result_holder[
|
870
|
+
|
871
|
+
if "error" in exception_holder:
|
872
|
+
raise exception_holder["error"]
|
873
|
+
|
874
|
+
return result_holder["result"]
|
kailash/nodes/code/python.py
CHANGED
@@ -57,7 +57,11 @@ from pathlib import Path
|
|
57
57
|
from typing import Any, get_type_hints
|
58
58
|
|
59
59
|
from kailash.nodes.base import Node, NodeMetadata, NodeParameter, register_node
|
60
|
-
from kailash.sdk_exceptions import
|
60
|
+
from kailash.sdk_exceptions import (
|
61
|
+
NodeConfigurationError,
|
62
|
+
NodeExecutionError,
|
63
|
+
SafetyViolationError,
|
64
|
+
)
|
61
65
|
from kailash.security import (
|
62
66
|
ExecutionTimeoutError,
|
63
67
|
MemoryLimitError,
|
@@ -402,7 +406,6 @@ class CodeExecutor:
|
|
402
406
|
|
403
407
|
# Sanitize inputs
|
404
408
|
sanitized_inputs = validate_node_parameters(inputs, self.security_config)
|
405
|
-
|
406
409
|
|
407
410
|
# Create isolated namespace
|
408
411
|
import builtins
|