kailash 0.1.5__py3-none-any.whl → 0.2.1__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 (77) hide show
  1. kailash/__init__.py +1 -1
  2. kailash/access_control.py +740 -0
  3. kailash/api/__main__.py +6 -0
  4. kailash/api/auth.py +668 -0
  5. kailash/api/custom_nodes.py +285 -0
  6. kailash/api/custom_nodes_secure.py +377 -0
  7. kailash/api/database.py +620 -0
  8. kailash/api/studio.py +915 -0
  9. kailash/api/studio_secure.py +893 -0
  10. kailash/mcp/__init__.py +53 -0
  11. kailash/mcp/__main__.py +13 -0
  12. kailash/mcp/ai_registry_server.py +712 -0
  13. kailash/mcp/client.py +447 -0
  14. kailash/mcp/client_new.py +334 -0
  15. kailash/mcp/server.py +293 -0
  16. kailash/mcp/server_new.py +336 -0
  17. kailash/mcp/servers/__init__.py +12 -0
  18. kailash/mcp/servers/ai_registry.py +289 -0
  19. kailash/nodes/__init__.py +4 -2
  20. kailash/nodes/ai/__init__.py +2 -0
  21. kailash/nodes/ai/a2a.py +714 -67
  22. kailash/nodes/ai/intelligent_agent_orchestrator.py +31 -37
  23. kailash/nodes/ai/iterative_llm_agent.py +1280 -0
  24. kailash/nodes/ai/llm_agent.py +324 -1
  25. kailash/nodes/ai/self_organizing.py +5 -6
  26. kailash/nodes/base.py +15 -2
  27. kailash/nodes/base_async.py +45 -0
  28. kailash/nodes/base_cycle_aware.py +374 -0
  29. kailash/nodes/base_with_acl.py +338 -0
  30. kailash/nodes/code/python.py +135 -27
  31. kailash/nodes/data/__init__.py +1 -2
  32. kailash/nodes/data/readers.py +16 -6
  33. kailash/nodes/data/sql.py +699 -256
  34. kailash/nodes/data/writers.py +16 -6
  35. kailash/nodes/logic/__init__.py +8 -0
  36. kailash/nodes/logic/convergence.py +642 -0
  37. kailash/nodes/logic/loop.py +153 -0
  38. kailash/nodes/logic/operations.py +187 -27
  39. kailash/nodes/mixins/__init__.py +11 -0
  40. kailash/nodes/mixins/mcp.py +228 -0
  41. kailash/nodes/mixins.py +387 -0
  42. kailash/runtime/__init__.py +2 -1
  43. kailash/runtime/access_controlled.py +458 -0
  44. kailash/runtime/local.py +106 -33
  45. kailash/runtime/parallel_cyclic.py +529 -0
  46. kailash/sdk_exceptions.py +90 -5
  47. kailash/security.py +845 -0
  48. kailash/tracking/manager.py +38 -15
  49. kailash/tracking/models.py +1 -1
  50. kailash/tracking/storage/filesystem.py +30 -2
  51. kailash/utils/__init__.py +8 -0
  52. kailash/workflow/__init__.py +18 -0
  53. kailash/workflow/convergence.py +270 -0
  54. kailash/workflow/cycle_analyzer.py +889 -0
  55. kailash/workflow/cycle_builder.py +579 -0
  56. kailash/workflow/cycle_config.py +725 -0
  57. kailash/workflow/cycle_debugger.py +860 -0
  58. kailash/workflow/cycle_exceptions.py +615 -0
  59. kailash/workflow/cycle_profiler.py +741 -0
  60. kailash/workflow/cycle_state.py +338 -0
  61. kailash/workflow/cyclic_runner.py +985 -0
  62. kailash/workflow/graph.py +500 -39
  63. kailash/workflow/migration.py +809 -0
  64. kailash/workflow/safety.py +365 -0
  65. kailash/workflow/templates.py +763 -0
  66. kailash/workflow/validation.py +751 -0
  67. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/METADATA +259 -12
  68. kailash-0.2.1.dist-info/RECORD +125 -0
  69. kailash/nodes/mcp/__init__.py +0 -11
  70. kailash/nodes/mcp/client.py +0 -554
  71. kailash/nodes/mcp/resource.py +0 -682
  72. kailash/nodes/mcp/server.py +0 -577
  73. kailash-0.1.5.dist-info/RECORD +0 -88
  74. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/WHEEL +0 -0
  75. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/entry_points.txt +0 -0
  76. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/licenses/LICENSE +0 -0
  77. {kailash-0.1.5.dist-info → kailash-0.2.1.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,336 @@
1
+ """
2
+ MCP Server - Base class for creating MCP servers using FastMCP.
3
+
4
+ This provides a clean way to create MCP servers that run as standalone services,
5
+ not as workflow nodes.
6
+ """
7
+
8
+ import asyncio
9
+ import logging
10
+ import signal
11
+ import sys
12
+ from typing import Callable, List, Optional
13
+
14
+ try:
15
+ from mcp.server import Server
16
+ from mcp.server.models import InitializationOptions
17
+ from mcp.types import Resource, TextContent, Tool
18
+
19
+ MCP_AVAILABLE = True
20
+ except ImportError:
21
+ MCP_AVAILABLE = False
22
+
23
+
24
+ class MCPServer:
25
+ """
26
+ Base class for creating MCP servers.
27
+
28
+ This class provides a foundation for building MCP servers that expose
29
+ tools, resources, and prompts. Servers run as standalone processes,
30
+ not as workflow nodes.
31
+
32
+ Examples:
33
+ >>> class MyServer(MCPServer):
34
+ ... def setup(self):
35
+ ... @self.tool()
36
+ ... def search(query: str) -> str:
37
+ ... return f"Searching for: {query}"
38
+ ...
39
+ ... @self.resource("data://example")
40
+ ... def get_example():
41
+ ... return "Example data"
42
+ >>>
43
+ >>> server = MyServer("my-server", port=8080)
44
+ >>> server.run() # Runs until stopped
45
+ """
46
+
47
+ def __init__(self, name: str, port: Optional[int] = None):
48
+ """
49
+ Initialize MCP server.
50
+
51
+ Args:
52
+ name: Server name
53
+ port: Port for HTTP transport (None for stdio)
54
+ """
55
+ if not MCP_AVAILABLE:
56
+ raise ImportError("MCP SDK not available. Install with: pip install mcp")
57
+
58
+ self.name = name
59
+ self.port = port
60
+ self.logger = logging.getLogger(f"mcp.{name}")
61
+
62
+ # Create MCP server
63
+ self.server = Server(name)
64
+ self._setup_done = False
65
+ self._tools = {}
66
+ self._resources = {}
67
+ self._prompts = {}
68
+
69
+ # Setup signal handlers
70
+ signal.signal(signal.SIGINT, self._signal_handler)
71
+ signal.signal(signal.SIGTERM, self._signal_handler)
72
+
73
+ def _signal_handler(self, signum, frame):
74
+ """Handle shutdown signals."""
75
+ self.logger.info(f"Received signal {signum}, shutting down...")
76
+ sys.exit(0)
77
+
78
+ def setup(self):
79
+ """
80
+ Override this method to setup server tools, resources, and prompts.
81
+
82
+ Example:
83
+ def setup(self):
84
+ @self.tool()
85
+ def my_tool(arg: str) -> str:
86
+ return f"Processed: {arg}"
87
+ """
88
+ pass
89
+
90
+ def tool(self, name: Optional[str] = None, description: Optional[str] = None):
91
+ """
92
+ Decorator to register a tool with the MCP server.
93
+
94
+ Args:
95
+ name: Tool name (defaults to function name)
96
+ description: Tool description (defaults to docstring)
97
+
98
+ Returns:
99
+ Decorator function
100
+ """
101
+
102
+ def decorator(func: Callable):
103
+ tool_name = name or func.__name__
104
+ tool_desc = description or (func.__doc__ or "").strip()
105
+
106
+ # Register with MCP server
107
+ @self.server.call_tool()
108
+ async def handle_tool_call(name: str, arguments: dict) -> List[TextContent]:
109
+ if name == tool_name:
110
+ try:
111
+ # Call the tool function
112
+ if asyncio.iscoroutinefunction(func):
113
+ result = await func(**arguments)
114
+ else:
115
+ result = func(**arguments)
116
+
117
+ # Convert result to TextContent
118
+ if isinstance(result, str):
119
+ return [TextContent(type="text", text=result)]
120
+ else:
121
+ import json
122
+
123
+ return [
124
+ TextContent(
125
+ type="text", text=json.dumps(result, indent=2)
126
+ )
127
+ ]
128
+ except Exception as e:
129
+ error_msg = f"Tool '{tool_name}' failed: {str(e)}"
130
+ self.logger.error(error_msg, exc_info=True)
131
+ return [TextContent(type="text", text=f"Error: {error_msg}")]
132
+
133
+ raise ValueError(f"Unknown tool: {name}")
134
+
135
+ # Store tool metadata
136
+ self._tools[tool_name] = {
137
+ "name": tool_name,
138
+ "description": tool_desc,
139
+ "function": func,
140
+ }
141
+
142
+ return func
143
+
144
+ return decorator
145
+
146
+ def resource(
147
+ self, uri: str, name: Optional[str] = None, description: Optional[str] = None
148
+ ):
149
+ """
150
+ Decorator to register a resource with the MCP server.
151
+
152
+ Args:
153
+ uri: Resource URI
154
+ name: Resource name
155
+ description: Resource description
156
+
157
+ Returns:
158
+ Decorator function
159
+ """
160
+
161
+ def decorator(func: Callable):
162
+ resource_name = name or uri.split("/")[-1]
163
+ resource_desc = description or (func.__doc__ or "").strip()
164
+
165
+ # Store resource metadata
166
+ self._resources[uri] = {
167
+ "uri": uri,
168
+ "name": resource_name,
169
+ "description": resource_desc,
170
+ "function": func,
171
+ }
172
+
173
+ return func
174
+
175
+ return decorator
176
+
177
+ def _setup_handlers(self):
178
+ """Setup MCP protocol handlers."""
179
+ if self._setup_done:
180
+ return
181
+
182
+ # Call user setup
183
+ self.setup()
184
+
185
+ # Register list_tools handler
186
+ @self.server.list_tools()
187
+ async def handle_list_tools() -> List[Tool]:
188
+ tools = []
189
+ for tool_name, tool_info in self._tools.items():
190
+ # Generate input schema from function signature
191
+ import inspect
192
+
193
+ sig = inspect.signature(tool_info["function"])
194
+
195
+ properties = {}
196
+ required = []
197
+
198
+ for param_name, param in sig.parameters.items():
199
+ if param_name == "self":
200
+ continue
201
+
202
+ # Determine type
203
+ param_type = "string" # Default
204
+ if param.annotation != inspect.Parameter.empty:
205
+ if param.annotation is int:
206
+ param_type = "integer"
207
+ elif param.annotation is float:
208
+ param_type = "number"
209
+ elif param.annotation is bool:
210
+ param_type = "boolean"
211
+ elif param.annotation is dict:
212
+ param_type = "object"
213
+ elif param.annotation is list:
214
+ param_type = "array"
215
+
216
+ properties[param_name] = {
217
+ "type": param_type,
218
+ "description": f"Parameter {param_name}",
219
+ }
220
+
221
+ if param.default == inspect.Parameter.empty:
222
+ required.append(param_name)
223
+
224
+ input_schema = {
225
+ "type": "object",
226
+ "properties": properties,
227
+ "required": required,
228
+ }
229
+
230
+ tools.append(
231
+ Tool(
232
+ name=tool_name,
233
+ description=tool_info["description"],
234
+ inputSchema=input_schema,
235
+ )
236
+ )
237
+
238
+ return tools
239
+
240
+ # Register list_resources handler
241
+ @self.server.list_resources()
242
+ async def handle_list_resources() -> List[Resource]:
243
+ resources = []
244
+ for uri, resource_info in self._resources.items():
245
+ resources.append(
246
+ Resource(
247
+ uri=uri,
248
+ name=resource_info["name"],
249
+ description=resource_info["description"],
250
+ mimeType="text/plain", # Default, could be customized
251
+ )
252
+ )
253
+ return resources
254
+
255
+ # Register read_resource handler
256
+ @self.server.read_resource()
257
+ async def handle_read_resource(uri: str) -> List[TextContent]:
258
+ if uri in self._resources:
259
+ resource_info = self._resources[uri]
260
+ func = resource_info["function"]
261
+
262
+ try:
263
+ # Call the resource function
264
+ if asyncio.iscoroutinefunction(func):
265
+ result = await func()
266
+ else:
267
+ result = func()
268
+
269
+ # Convert result to TextContent
270
+ if isinstance(result, str):
271
+ return [TextContent(type="text", text=result)]
272
+ else:
273
+ import json
274
+
275
+ return [
276
+ TextContent(type="text", text=json.dumps(result, indent=2))
277
+ ]
278
+ except Exception as e:
279
+ error_msg = f"Resource '{uri}' failed: {str(e)}"
280
+ self.logger.error(error_msg, exc_info=True)
281
+ return [TextContent(type="text", text=f"Error: {error_msg}")]
282
+
283
+ raise ValueError(f"Resource not found: {uri}")
284
+
285
+ self._setup_done = True
286
+
287
+ def run(self):
288
+ """
289
+ Run the MCP server.
290
+
291
+ This method blocks until the server is stopped.
292
+ """
293
+ self._setup_handlers()
294
+
295
+ self.logger.info(f"Starting MCP server '{self.name}'")
296
+
297
+ if self.port is not None:
298
+ # HTTP/SSE transport
299
+ self.logger.info(f"Server will listen on port {self.port}")
300
+ # Note: Actual HTTP server implementation would go here
301
+ # For now, we'll use stdio as the primary transport
302
+
303
+ # Run with stdio transport
304
+ import mcp.server.stdio
305
+
306
+ # Configure server options
307
+ init_options = InitializationOptions(
308
+ server_name=self.name,
309
+ server_version="1.0.0",
310
+ capabilities=self.server.get_capabilities(),
311
+ )
312
+
313
+ # Run the server
314
+ asyncio.run(
315
+ mcp.server.stdio.stdio_server(self.server.request_handlers, init_options)
316
+ )
317
+
318
+ async def arun(self):
319
+ """
320
+ Async version of run().
321
+
322
+ Use this if you need to run the server in an existing async context.
323
+ """
324
+ self._setup_handlers()
325
+
326
+ self.logger.info(f"Starting MCP server '{self.name}' (async)")
327
+
328
+ import mcp.server.stdio
329
+
330
+ init_options = InitializationOptions(
331
+ server_name=self.name,
332
+ server_version="1.0.0",
333
+ capabilities=self.server.get_capabilities(),
334
+ )
335
+
336
+ await mcp.server.stdio.stdio_server(self.server.request_handlers, init_options)
@@ -0,0 +1,12 @@
1
+ """
2
+ Pre-built MCP servers for common use cases.
3
+
4
+ Available servers:
5
+ - AIRegistryServer: Provides access to AI use case registry data
6
+ - FileSystemServer: Provides access to local file system
7
+ - DatabaseServer: Provides access to database resources
8
+ """
9
+
10
+ from .ai_registry import AIRegistryServer
11
+
12
+ __all__ = ["AIRegistryServer"]
@@ -0,0 +1,289 @@
1
+ """AI Registry MCP Server using FastMCP.
2
+
3
+ This server provides access to AI use case registry data via MCP,
4
+ exposing tools for searching, analyzing, and exploring AI implementations.
5
+ """
6
+
7
+ import json
8
+ import os
9
+ from pathlib import Path
10
+ from typing import Any, Dict
11
+
12
+ from kailash.mcp.server import MCPServer
13
+
14
+
15
+ class AIRegistryServer(MCPServer):
16
+ """MCP server for AI use case registry.
17
+
18
+ Provides tools and resources for exploring AI use cases from
19
+ ISO/IEC standards and industry implementations.
20
+
21
+ Examples:
22
+ >>> server = AIRegistryServer(
23
+ ... registry_file="data/ai_registry.json",
24
+ ... port=8080
25
+ ... )
26
+ >>> server.start() # Runs until stopped
27
+ """
28
+
29
+ def __init__(
30
+ self,
31
+ registry_file: str = "research/combined_ai_registry.json",
32
+ name: str = "ai-registry",
33
+ port: int = 8080,
34
+ host: str = "localhost",
35
+ ):
36
+ """Initialize the AI Registry server.
37
+
38
+ Args:
39
+ registry_file: Path to JSON file containing AI use cases
40
+ name: Server name
41
+ port: Port to listen on
42
+ host: Host to bind to
43
+ """
44
+ super().__init__(name, port, host)
45
+ self.registry_file = registry_file
46
+ self._registry_data = None
47
+ self._load_registry()
48
+
49
+ def _load_registry(self):
50
+ """Load the AI registry data from file."""
51
+ try:
52
+ registry_path = Path(self.registry_file)
53
+ if registry_path.exists():
54
+ with open(registry_path, "r", encoding="utf-8") as f:
55
+ self._registry_data = json.load(f)
56
+ else:
57
+ # Provide sample data if file not found
58
+ self._registry_data = {
59
+ "use_cases": [
60
+ {
61
+ "use_case_id": 1,
62
+ "name": "Medical Diagnosis Assistant",
63
+ "application_domain": "Healthcare",
64
+ "description": "AI system to assist doctors in diagnosing diseases",
65
+ "ai_methods": ["Machine Learning", "Deep Learning"],
66
+ "status": "Production",
67
+ },
68
+ {
69
+ "use_case_id": 2,
70
+ "name": "Fraud Detection System",
71
+ "application_domain": "Finance",
72
+ "description": "Real-time fraud detection in financial transactions",
73
+ "ai_methods": ["Anomaly Detection", "Pattern Recognition"],
74
+ "status": "Production",
75
+ },
76
+ ]
77
+ }
78
+ except Exception as e:
79
+ self.logger.error(f"Failed to load registry: {e}")
80
+ self._registry_data = {"use_cases": []}
81
+
82
+ def setup(self):
83
+ """Setup server tools and resources."""
84
+
85
+ @self.add_tool()
86
+ def search_use_cases(query: str, limit: int = 10) -> Dict[str, Any]:
87
+ """Search for AI use cases matching the query.
88
+
89
+ Args:
90
+ query: Search query string
91
+ limit: Maximum number of results to return
92
+
93
+ Returns:
94
+ Search results with matching use cases
95
+ """
96
+ if not self._registry_data:
97
+ return {"results": [], "count": 0, "query": query}
98
+
99
+ use_cases = self._registry_data.get("use_cases", [])
100
+ results = []
101
+
102
+ query_lower = query.lower()
103
+ for use_case in use_cases:
104
+ # Search in multiple fields
105
+ searchable_text = " ".join(
106
+ [
107
+ str(use_case.get("name", "")),
108
+ str(use_case.get("description", "")),
109
+ str(use_case.get("application_domain", "")),
110
+ " ".join(use_case.get("ai_methods", [])),
111
+ ]
112
+ )
113
+
114
+ if query_lower in searchable_text.lower():
115
+ results.append(use_case)
116
+ if len(results) >= limit:
117
+ break
118
+
119
+ return {"results": results, "count": len(results), "query": query}
120
+
121
+ @self.add_tool()
122
+ def filter_by_domain(domain: str) -> Dict[str, Any]:
123
+ """Filter use cases by application domain.
124
+
125
+ Args:
126
+ domain: Application domain to filter by
127
+
128
+ Returns:
129
+ Use cases in the specified domain
130
+ """
131
+ if not self._registry_data:
132
+ return {"domain": domain, "use_cases": [], "count": 0}
133
+
134
+ use_cases = self._registry_data.get("use_cases", [])
135
+ filtered = [
136
+ uc
137
+ for uc in use_cases
138
+ if uc.get("application_domain", "").lower() == domain.lower()
139
+ ]
140
+
141
+ return {"domain": domain, "use_cases": filtered, "count": len(filtered)}
142
+
143
+ @self.add_tool()
144
+ def get_use_case_details(use_case_id: int) -> Dict[str, Any]:
145
+ """Get detailed information about a specific use case.
146
+
147
+ Args:
148
+ use_case_id: ID of the use case
149
+
150
+ Returns:
151
+ Detailed use case information
152
+ """
153
+ if not self._registry_data:
154
+ return {"error": "No registry data available"}
155
+
156
+ use_cases = self._registry_data.get("use_cases", [])
157
+ for use_case in use_cases:
158
+ if use_case.get("use_case_id") == use_case_id:
159
+ return {"use_case": use_case}
160
+
161
+ return {"error": f"Use case {use_case_id} not found"}
162
+
163
+ @self.add_tool()
164
+ def list_domains() -> Dict[str, Any]:
165
+ """List all available application domains.
166
+
167
+ Returns:
168
+ List of domains with use case counts
169
+ """
170
+ if not self._registry_data:
171
+ return {"domains": []}
172
+
173
+ use_cases = self._registry_data.get("use_cases", [])
174
+ domain_counts = {}
175
+
176
+ for use_case in use_cases:
177
+ domain = use_case.get("application_domain", "Unknown")
178
+ domain_counts[domain] = domain_counts.get(domain, 0) + 1
179
+
180
+ domains = [
181
+ {"name": domain, "count": count}
182
+ for domain, count in domain_counts.items()
183
+ ]
184
+
185
+ return {
186
+ "domains": sorted(domains, key=lambda x: x["count"], reverse=True),
187
+ "total_domains": len(domains),
188
+ }
189
+
190
+ @self.add_resource("registry://stats")
191
+ def get_registry_stats():
192
+ """Get statistics about the AI registry."""
193
+ if not self._registry_data:
194
+ return {"error": "No registry data available"}
195
+
196
+ use_cases = self._registry_data.get("use_cases", [])
197
+
198
+ # Calculate statistics
199
+ total_count = len(use_cases)
200
+ domain_counts = {}
201
+ method_counts = {}
202
+ status_counts = {}
203
+
204
+ for use_case in use_cases:
205
+ # Count by domain
206
+ domain = use_case.get("application_domain", "Unknown")
207
+ domain_counts[domain] = domain_counts.get(domain, 0) + 1
208
+
209
+ # Count by AI method
210
+ for method in use_case.get("ai_methods", []):
211
+ method_counts[method] = method_counts.get(method, 0) + 1
212
+
213
+ # Count by status
214
+ status = use_case.get("status", "Unknown")
215
+ status_counts[status] = status_counts.get(status, 0) + 1
216
+
217
+ return {
218
+ "total_use_cases": total_count,
219
+ "domains": domain_counts,
220
+ "ai_methods": method_counts,
221
+ "status_distribution": status_counts,
222
+ }
223
+
224
+ @self.add_resource("registry://domains/{domain}")
225
+ def get_domain_resource(domain: str):
226
+ """Get all use cases for a specific domain."""
227
+ result = filter_by_domain(domain)
228
+ return result
229
+
230
+ @self.add_prompt("analyze_use_case")
231
+ def analyze_use_case_prompt(
232
+ use_case_name: str, focus_area: str = "implementation"
233
+ ) -> str:
234
+ """Generate a prompt for analyzing a use case.
235
+
236
+ Args:
237
+ use_case_name: Name of the use case
238
+ focus_area: Area to focus on (implementation, challenges, benefits)
239
+
240
+ Returns:
241
+ Analysis prompt
242
+ """
243
+ return f"""Please analyze the AI use case '{use_case_name}' with a focus on {focus_area}.
244
+
245
+ Consider the following aspects:
246
+ 1. Technical implementation requirements
247
+ 2. Key challenges and how to address them
248
+ 3. Expected benefits and ROI
249
+ 4. Best practices and recommendations
250
+ 5. Similar use cases and lessons learned
251
+
252
+ Provide a comprehensive analysis with actionable insights."""
253
+
254
+
255
+ # Convenience function to start the server
256
+ def start_server(
257
+ registry_file: str = "research/combined_ai_registry.json",
258
+ port: int = 8080,
259
+ host: str = "localhost",
260
+ ):
261
+ """Start the AI Registry MCP server.
262
+
263
+ Args:
264
+ registry_file: Path to registry JSON file
265
+ port: Port to listen on
266
+ host: Host to bind to
267
+ """
268
+ server = AIRegistryServer(registry_file, port=port, host=host)
269
+ server.start()
270
+
271
+
272
+ if __name__ == "__main__":
273
+ # Allow running as a module
274
+ import sys
275
+
276
+ registry_file = os.environ.get(
277
+ "REGISTRY_FILE", "research/combined_ai_registry.json"
278
+ )
279
+ port = int(os.environ.get("PORT", "8080"))
280
+ host = os.environ.get("HOST", "localhost")
281
+
282
+ if "--help" in sys.argv:
283
+ print("AI Registry MCP Server")
284
+ print("Environment variables:")
285
+ print(" REGISTRY_FILE - Path to AI registry JSON file")
286
+ print(" PORT - Port to listen on (default: 8080)")
287
+ print(" HOST - Host to bind to (default: localhost)")
288
+ else:
289
+ start_server(registry_file, port, host)
kailash/nodes/__init__.py CHANGED
@@ -1,14 +1,16 @@
1
1
  """Node system for the Kailash SDK."""
2
2
 
3
3
  # Import all node modules to ensure registration
4
- from kailash.nodes import ai, api, code, data, logic, mcp, transform
4
+ from kailash.nodes import ai, api, code, data, logic, mixins, transform
5
5
  from kailash.nodes.base import Node, NodeParameter, NodeRegistry, register_node
6
6
  from kailash.nodes.base_async import AsyncNode
7
+ from kailash.nodes.base_cycle_aware import CycleAwareNode
7
8
  from kailash.nodes.code import PythonCodeNode
8
9
 
9
10
  __all__ = [
10
11
  "Node",
11
12
  "AsyncNode",
13
+ "CycleAwareNode",
12
14
  "NodeParameter",
13
15
  "NodeRegistry",
14
16
  "register_node",
@@ -19,6 +21,6 @@ __all__ = [
19
21
  "code",
20
22
  "data",
21
23
  "logic",
22
- "mcp",
24
+ "mixins",
23
25
  "transform",
24
26
  ]
@@ -25,6 +25,7 @@ from .intelligent_agent_orchestrator import (
25
25
  OrchestrationManagerNode,
26
26
  QueryAnalysisNode,
27
27
  )
28
+ from .iterative_llm_agent import IterativeLLMAgentNode
28
29
  from .llm_agent import LLMAgentNode
29
30
  from .models import (
30
31
  ModelPredictor,
@@ -51,6 +52,7 @@ __all__ = [
51
52
  "FunctionCallingAgent",
52
53
  "PlanningAgent",
53
54
  "LLMAgentNode",
55
+ "IterativeLLMAgentNode",
54
56
  # A2A Communication
55
57
  "A2AAgentNode",
56
58
  "SharedMemoryPoolNode",