kailash 0.1.5__py3-none-any.whl → 0.2.0__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 +1 -1
- kailash/access_control.py +740 -0
- kailash/api/__main__.py +6 -0
- kailash/api/auth.py +668 -0
- kailash/api/custom_nodes.py +285 -0
- kailash/api/custom_nodes_secure.py +377 -0
- kailash/api/database.py +620 -0
- kailash/api/studio.py +915 -0
- kailash/api/studio_secure.py +893 -0
- kailash/mcp/__init__.py +53 -0
- kailash/mcp/__main__.py +13 -0
- kailash/mcp/ai_registry_server.py +712 -0
- kailash/mcp/client.py +447 -0
- kailash/mcp/client_new.py +334 -0
- kailash/mcp/server.py +293 -0
- kailash/mcp/server_new.py +336 -0
- kailash/mcp/servers/__init__.py +12 -0
- kailash/mcp/servers/ai_registry.py +289 -0
- kailash/nodes/__init__.py +4 -2
- kailash/nodes/ai/__init__.py +2 -0
- kailash/nodes/ai/a2a.py +714 -67
- kailash/nodes/ai/intelligent_agent_orchestrator.py +31 -37
- kailash/nodes/ai/iterative_llm_agent.py +1280 -0
- kailash/nodes/ai/llm_agent.py +324 -1
- kailash/nodes/ai/self_organizing.py +5 -6
- kailash/nodes/base.py +15 -2
- kailash/nodes/base_async.py +45 -0
- kailash/nodes/base_cycle_aware.py +374 -0
- kailash/nodes/base_with_acl.py +338 -0
- kailash/nodes/code/python.py +135 -27
- kailash/nodes/data/readers.py +16 -6
- kailash/nodes/data/writers.py +16 -6
- kailash/nodes/logic/__init__.py +8 -0
- kailash/nodes/logic/convergence.py +642 -0
- kailash/nodes/logic/loop.py +153 -0
- kailash/nodes/logic/operations.py +187 -27
- kailash/nodes/mixins/__init__.py +11 -0
- kailash/nodes/mixins/mcp.py +228 -0
- kailash/nodes/mixins.py +387 -0
- kailash/runtime/__init__.py +2 -1
- kailash/runtime/access_controlled.py +458 -0
- kailash/runtime/local.py +106 -33
- kailash/runtime/parallel_cyclic.py +529 -0
- kailash/sdk_exceptions.py +90 -5
- kailash/security.py +845 -0
- kailash/tracking/manager.py +38 -15
- kailash/tracking/models.py +1 -1
- kailash/tracking/storage/filesystem.py +30 -2
- kailash/utils/__init__.py +8 -0
- kailash/workflow/__init__.py +18 -0
- kailash/workflow/convergence.py +270 -0
- kailash/workflow/cycle_analyzer.py +768 -0
- kailash/workflow/cycle_builder.py +573 -0
- kailash/workflow/cycle_config.py +709 -0
- kailash/workflow/cycle_debugger.py +760 -0
- kailash/workflow/cycle_exceptions.py +601 -0
- kailash/workflow/cycle_profiler.py +671 -0
- kailash/workflow/cycle_state.py +338 -0
- kailash/workflow/cyclic_runner.py +985 -0
- kailash/workflow/graph.py +500 -39
- kailash/workflow/migration.py +768 -0
- kailash/workflow/safety.py +365 -0
- kailash/workflow/templates.py +744 -0
- kailash/workflow/validation.py +693 -0
- {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/METADATA +256 -12
- kailash-0.2.0.dist-info/RECORD +125 -0
- kailash/nodes/mcp/__init__.py +0 -11
- kailash/nodes/mcp/client.py +0 -554
- kailash/nodes/mcp/resource.py +0 -682
- kailash/nodes/mcp/server.py +0 -577
- kailash-0.1.5.dist-info/RECORD +0 -88
- {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/WHEEL +0 -0
- {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/entry_points.txt +0 -0
- {kailash-0.1.5.dist-info → kailash-0.2.0.dist-info}/licenses/LICENSE +0 -0
- {kailash-0.1.5.dist-info → kailash-0.2.0.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 EmbeddedResource, ImageContent, 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 == int:
|
206
|
+
param_type = "integer"
|
207
|
+
elif param.annotation == float:
|
208
|
+
param_type = "number"
|
209
|
+
elif param.annotation == bool:
|
210
|
+
param_type = "boolean"
|
211
|
+
elif param.annotation == dict:
|
212
|
+
param_type = "object"
|
213
|
+
elif param.annotation == 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,
|
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
|
-
"
|
24
|
+
"mixins",
|
23
25
|
"transform",
|
24
26
|
]
|
kailash/nodes/ai/__init__.py
CHANGED
@@ -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",
|