kailash 0.6.5__py3-none-any.whl → 0.7.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.
Files changed (64) hide show
  1. kailash/__init__.py +35 -4
  2. kailash/adapters/__init__.py +5 -0
  3. kailash/adapters/mcp_platform_adapter.py +273 -0
  4. kailash/channels/__init__.py +21 -0
  5. kailash/channels/api_channel.py +409 -0
  6. kailash/channels/base.py +271 -0
  7. kailash/channels/cli_channel.py +661 -0
  8. kailash/channels/event_router.py +496 -0
  9. kailash/channels/mcp_channel.py +648 -0
  10. kailash/channels/session.py +423 -0
  11. kailash/mcp_server/discovery.py +1 -1
  12. kailash/middleware/core/agent_ui.py +5 -0
  13. kailash/middleware/mcp/enhanced_server.py +22 -16
  14. kailash/nexus/__init__.py +21 -0
  15. kailash/nexus/factory.py +413 -0
  16. kailash/nexus/gateway.py +545 -0
  17. kailash/nodes/__init__.py +2 -0
  18. kailash/nodes/ai/iterative_llm_agent.py +988 -17
  19. kailash/nodes/ai/llm_agent.py +29 -9
  20. kailash/nodes/api/__init__.py +2 -2
  21. kailash/nodes/api/monitoring.py +1 -1
  22. kailash/nodes/base_async.py +54 -14
  23. kailash/nodes/code/async_python.py +1 -1
  24. kailash/nodes/data/bulk_operations.py +939 -0
  25. kailash/nodes/data/query_builder.py +373 -0
  26. kailash/nodes/data/query_cache.py +512 -0
  27. kailash/nodes/monitoring/__init__.py +10 -0
  28. kailash/nodes/monitoring/deadlock_detector.py +964 -0
  29. kailash/nodes/monitoring/performance_anomaly.py +1078 -0
  30. kailash/nodes/monitoring/race_condition_detector.py +1151 -0
  31. kailash/nodes/monitoring/transaction_metrics.py +790 -0
  32. kailash/nodes/monitoring/transaction_monitor.py +931 -0
  33. kailash/nodes/system/__init__.py +17 -0
  34. kailash/nodes/system/command_parser.py +820 -0
  35. kailash/nodes/transaction/__init__.py +48 -0
  36. kailash/nodes/transaction/distributed_transaction_manager.py +983 -0
  37. kailash/nodes/transaction/saga_coordinator.py +652 -0
  38. kailash/nodes/transaction/saga_state_storage.py +411 -0
  39. kailash/nodes/transaction/saga_step.py +467 -0
  40. kailash/nodes/transaction/transaction_context.py +756 -0
  41. kailash/nodes/transaction/two_phase_commit.py +978 -0
  42. kailash/nodes/transform/processors.py +17 -1
  43. kailash/nodes/validation/__init__.py +21 -0
  44. kailash/nodes/validation/test_executor.py +532 -0
  45. kailash/nodes/validation/validation_nodes.py +447 -0
  46. kailash/resources/factory.py +1 -1
  47. kailash/runtime/async_local.py +84 -21
  48. kailash/runtime/local.py +21 -2
  49. kailash/runtime/parameter_injector.py +187 -31
  50. kailash/security.py +16 -1
  51. kailash/servers/__init__.py +32 -0
  52. kailash/servers/durable_workflow_server.py +430 -0
  53. kailash/servers/enterprise_workflow_server.py +466 -0
  54. kailash/servers/gateway.py +183 -0
  55. kailash/servers/workflow_server.py +290 -0
  56. kailash/utils/data_validation.py +192 -0
  57. kailash/workflow/builder.py +291 -12
  58. kailash/workflow/validation.py +144 -8
  59. {kailash-0.6.5.dist-info → kailash-0.7.0.dist-info}/METADATA +1 -1
  60. {kailash-0.6.5.dist-info → kailash-0.7.0.dist-info}/RECORD +64 -26
  61. {kailash-0.6.5.dist-info → kailash-0.7.0.dist-info}/WHEEL +0 -0
  62. {kailash-0.6.5.dist-info → kailash-0.7.0.dist-info}/entry_points.txt +0 -0
  63. {kailash-0.6.5.dist-info → kailash-0.7.0.dist-info}/licenses/LICENSE +0 -0
  64. {kailash-0.6.5.dist-info → kailash-0.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,648 @@
1
+ """MCP Channel implementation for Model Context Protocol integration."""
2
+
3
+ import asyncio
4
+ import json
5
+ import logging
6
+ from dataclasses import dataclass, field
7
+ from typing import Any, Callable, Dict, List, Optional
8
+
9
+ from .base import (
10
+ Channel,
11
+ ChannelConfig,
12
+ ChannelEvent,
13
+ ChannelResponse,
14
+ ChannelStatus,
15
+ ChannelType,
16
+ )
17
+
18
+ try:
19
+ from ..middleware.mcp.enhanced_server import MCPServerConfig, MiddlewareMCPServer
20
+
21
+ _MCP_AVAILABLE = True
22
+ except ImportError:
23
+ _MCP_AVAILABLE = False
24
+
25
+ # Create mock classes for when MCP is not available
26
+ class MiddlewareMCPServer:
27
+ def __init__(self, *args, **kwargs):
28
+ raise ImportError("MCP server not available")
29
+
30
+ class MCPServerConfig:
31
+ def __init__(self):
32
+ pass
33
+
34
+
35
+ from ..runtime.local import LocalRuntime
36
+ from ..workflow import Workflow
37
+
38
+ logger = logging.getLogger(__name__)
39
+
40
+
41
+ @dataclass
42
+ class MCPToolRegistration:
43
+ """Represents an MCP tool registration."""
44
+
45
+ name: str
46
+ description: str
47
+ parameters: Dict[str, Any] = field(default_factory=dict)
48
+ handler: Optional[Callable] = None
49
+ workflow_name: Optional[str] = None
50
+ metadata: Dict[str, Any] = field(default_factory=dict)
51
+
52
+
53
+ class MCPChannel(Channel):
54
+ """Model Context Protocol channel implementation.
55
+
56
+ This channel provides MCP server capabilities, allowing external MCP clients
57
+ to connect and execute workflows through the MCP protocol.
58
+ """
59
+
60
+ def __init__(
61
+ self, config: ChannelConfig, mcp_server: Optional[MiddlewareMCPServer] = None
62
+ ):
63
+ """Initialize MCP channel.
64
+
65
+ Args:
66
+ config: Channel configuration
67
+ mcp_server: Optional existing MCP server, will create one if not provided
68
+ """
69
+ super().__init__(config)
70
+
71
+ # Tool and workflow registry (initialize before creating MCP server)
72
+ self._tool_registry: Dict[str, MCPToolRegistration] = {}
73
+ self._workflow_registry: Dict[str, Workflow] = {}
74
+
75
+ # Create or use provided MCP server
76
+ if mcp_server:
77
+ self.mcp_server = mcp_server
78
+ else:
79
+ self.mcp_server = self._create_mcp_server()
80
+
81
+ # Runtime for executing workflows
82
+ self.runtime = LocalRuntime()
83
+
84
+ # MCP-specific state
85
+ self._clients: Dict[str, Dict[str, Any]] = {}
86
+ self._server_task: Optional[asyncio.Task] = None
87
+
88
+ logger.info(f"Initialized MCP channel {self.name}")
89
+
90
+ def _create_mcp_server(self) -> MiddlewareMCPServer:
91
+ """Create a new MCP server with channel configuration."""
92
+ if not _MCP_AVAILABLE:
93
+ raise ImportError("MCP server components not available")
94
+
95
+ # Extract MCP config from channel config with platform adapter support
96
+ from kailash.adapters import MCPPlatformAdapter
97
+
98
+ mcp_config = MCPServerConfig()
99
+
100
+ # Check if we have platform-format configuration
101
+ platform_config = self.config.extra_config.get("platform_config")
102
+ if platform_config and isinstance(platform_config, dict):
103
+ # Translate platform configuration to SDK format
104
+ try:
105
+ translated_config = MCPPlatformAdapter.translate_server_config(
106
+ platform_config
107
+ )
108
+ mcp_config.name = translated_config.get(
109
+ "name", f"{self.name}-mcp-server"
110
+ )
111
+ mcp_config.description = translated_config.get(
112
+ "description", f"MCP server for {self.name} channel"
113
+ )
114
+ except Exception as e:
115
+ self.logger.warning(f"Failed to translate platform config: {e}")
116
+ # Fall back to default configuration
117
+ mcp_config.name = f"{self.name}-mcp-server"
118
+ mcp_config.description = f"MCP server for {self.name} channel"
119
+ else:
120
+ # Use direct configuration
121
+ mcp_config.name = self.config.extra_config.get(
122
+ "server_name", f"{self.name}-mcp-server"
123
+ )
124
+ mcp_config.description = self.config.extra_config.get(
125
+ "description", f"MCP server for {self.name} channel"
126
+ )
127
+
128
+ # MiddlewareMCPServer only accepts config, event_stream, and agent_ui
129
+ # Host and port are handled by the channel itself, not the MCP server
130
+ server = MiddlewareMCPServer(config=mcp_config)
131
+
132
+ # Set up default tools
133
+ self._setup_default_tools(server)
134
+
135
+ return server
136
+
137
+ def _setup_default_tools(self, server: MiddlewareMCPServer) -> None:
138
+ """Set up default MCP tools for workflow execution."""
139
+
140
+ # Tool: List available workflows
141
+ self.register_tool(
142
+ name="list_workflows",
143
+ description="List all available workflows in this channel",
144
+ parameters={},
145
+ handler=self._handle_list_workflows,
146
+ )
147
+
148
+ # Tool: Execute workflow
149
+ self.register_tool(
150
+ name="execute_workflow",
151
+ description="Execute a workflow with given parameters",
152
+ parameters={
153
+ "workflow_name": {
154
+ "type": "string",
155
+ "description": "Name of the workflow to execute",
156
+ "required": True,
157
+ },
158
+ "inputs": {
159
+ "type": "object",
160
+ "description": "Input parameters for the workflow",
161
+ "required": False,
162
+ },
163
+ },
164
+ handler=self._handle_execute_workflow,
165
+ )
166
+
167
+ # Tool: Get workflow schema
168
+ self.register_tool(
169
+ name="get_workflow_schema",
170
+ description="Get the input/output schema for a workflow",
171
+ parameters={
172
+ "workflow_name": {
173
+ "type": "string",
174
+ "description": "Name of the workflow",
175
+ "required": True,
176
+ }
177
+ },
178
+ handler=self._handle_get_workflow_schema,
179
+ )
180
+
181
+ # Tool: Channel status
182
+ self.register_tool(
183
+ name="channel_status",
184
+ description="Get status information about this MCP channel",
185
+ parameters={
186
+ "verbose": {
187
+ "type": "boolean",
188
+ "description": "Include detailed status information",
189
+ "required": False,
190
+ }
191
+ },
192
+ handler=self._handle_channel_status,
193
+ )
194
+
195
+ async def start(self) -> None:
196
+ """Start the MCP channel server."""
197
+ if self.status == ChannelStatus.RUNNING:
198
+ logger.warning(f"MCP channel {self.name} is already running")
199
+ return
200
+
201
+ try:
202
+ self.status = ChannelStatus.STARTING
203
+ self._setup_event_queue()
204
+
205
+ # Start MCP server
206
+ await self.mcp_server.start()
207
+
208
+ # Start server task for handling connections
209
+ self._server_task = asyncio.create_task(self._server_loop())
210
+
211
+ self.status = ChannelStatus.RUNNING
212
+
213
+ # Emit startup event
214
+ await self.emit_event(
215
+ ChannelEvent(
216
+ event_id=f"mcp_startup_{asyncio.get_event_loop().time()}",
217
+ channel_name=self.name,
218
+ channel_type=self.channel_type,
219
+ event_type="channel_started",
220
+ payload={
221
+ "host": self.config.host,
222
+ "port": self.config.port,
223
+ "tools_count": len(self._tool_registry),
224
+ },
225
+ )
226
+ )
227
+
228
+ logger.info(
229
+ f"MCP channel {self.name} started on {self.config.host}:{self.config.port}"
230
+ )
231
+
232
+ except Exception as e:
233
+ self.status = ChannelStatus.ERROR
234
+ logger.error(f"Failed to start MCP channel {self.name}: {e}")
235
+ raise
236
+
237
+ async def stop(self) -> None:
238
+ """Stop the MCP channel server."""
239
+ if self.status == ChannelStatus.STOPPED:
240
+ return
241
+
242
+ try:
243
+ self.status = ChannelStatus.STOPPING
244
+
245
+ # Emit shutdown event
246
+ await self.emit_event(
247
+ ChannelEvent(
248
+ event_id=f"mcp_shutdown_{asyncio.get_event_loop().time()}",
249
+ channel_name=self.name,
250
+ channel_type=self.channel_type,
251
+ event_type="channel_stopping",
252
+ payload={"active_clients": len(self._clients)},
253
+ )
254
+ )
255
+
256
+ # Stop server task
257
+ if self._server_task and not self._server_task.done():
258
+ self._server_task.cancel()
259
+ try:
260
+ await self._server_task
261
+ except asyncio.CancelledError:
262
+ pass
263
+
264
+ # Stop MCP server
265
+ if self.mcp_server:
266
+ await self.mcp_server.stop()
267
+
268
+ await self._cleanup()
269
+ self.status = ChannelStatus.STOPPED
270
+
271
+ logger.info(f"MCP channel {self.name} stopped")
272
+
273
+ except Exception as e:
274
+ self.status = ChannelStatus.ERROR
275
+ logger.error(f"Error stopping MCP channel {self.name}: {e}")
276
+ raise
277
+
278
+ async def handle_request(self, request: Dict[str, Any]) -> ChannelResponse:
279
+ """Handle an MCP request.
280
+
281
+ Args:
282
+ request: MCP request data
283
+
284
+ Returns:
285
+ ChannelResponse with MCP execution results
286
+ """
287
+ try:
288
+ method = request.get("method", "")
289
+ params = request.get("params", {})
290
+ request_id = request.get("id", "")
291
+
292
+ # Emit request event
293
+ await self.emit_event(
294
+ ChannelEvent(
295
+ event_id=f"mcp_request_{asyncio.get_event_loop().time()}",
296
+ channel_name=self.name,
297
+ channel_type=self.channel_type,
298
+ event_type="mcp_request",
299
+ payload={
300
+ "method": method,
301
+ "params": params,
302
+ "request_id": request_id,
303
+ },
304
+ )
305
+ )
306
+
307
+ # Handle different MCP methods
308
+ if method == "tools/list":
309
+ result = await self._handle_tools_list()
310
+ elif method == "tools/call":
311
+ result = await self._handle_tools_call(params)
312
+ elif method == "resources/list":
313
+ result = await self._handle_resources_list()
314
+ elif method == "resources/read":
315
+ result = await self._handle_resources_read(params)
316
+ else:
317
+ result = {
318
+ "error": {"code": -32601, "message": f"Method not found: {method}"}
319
+ }
320
+
321
+ # Emit completion event
322
+ await self.emit_event(
323
+ ChannelEvent(
324
+ event_id=f"mcp_completion_{asyncio.get_event_loop().time()}",
325
+ channel_name=self.name,
326
+ channel_type=self.channel_type,
327
+ event_type="mcp_completed",
328
+ payload={
329
+ "method": method,
330
+ "request_id": request_id,
331
+ "success": "error" not in result,
332
+ },
333
+ )
334
+ )
335
+
336
+ return ChannelResponse(
337
+ success="error" not in result,
338
+ data=result,
339
+ metadata={
340
+ "channel": self.name,
341
+ "method": method,
342
+ "request_id": request_id,
343
+ },
344
+ )
345
+
346
+ except Exception as e:
347
+ logger.error(f"Error handling MCP request: {e}")
348
+
349
+ # Emit error event
350
+ await self.emit_event(
351
+ ChannelEvent(
352
+ event_id=f"mcp_error_{asyncio.get_event_loop().time()}",
353
+ channel_name=self.name,
354
+ channel_type=self.channel_type,
355
+ event_type="mcp_error",
356
+ payload={"error": str(e), "request": request},
357
+ )
358
+ )
359
+
360
+ return ChannelResponse(
361
+ success=False, error=str(e), metadata={"channel": self.name}
362
+ )
363
+
364
+ async def _server_loop(self) -> None:
365
+ """Main server loop for handling MCP connections."""
366
+ while self.status == ChannelStatus.RUNNING:
367
+ try:
368
+ # This would handle MCP protocol connections
369
+ # For now, we'll just wait and check for shutdown
370
+ await asyncio.sleep(1)
371
+
372
+ except asyncio.CancelledError:
373
+ break
374
+ except Exception as e:
375
+ logger.error(f"Error in MCP server loop: {e}")
376
+
377
+ async def _handle_tools_list(self) -> Dict[str, Any]:
378
+ """Handle MCP tools/list request."""
379
+ tools = []
380
+
381
+ for tool_name, registration in self._tool_registry.items():
382
+ tool_def = {
383
+ "name": tool_name,
384
+ "description": registration.description,
385
+ "inputSchema": {
386
+ "type": "object",
387
+ "properties": registration.parameters,
388
+ },
389
+ }
390
+ tools.append(tool_def)
391
+
392
+ return {"tools": tools}
393
+
394
+ async def _handle_tools_call(self, params: Dict[str, Any]) -> Dict[str, Any]:
395
+ """Handle MCP tools/call request."""
396
+ tool_name = params.get("name", "")
397
+ arguments = params.get("arguments", {})
398
+
399
+ if tool_name not in self._tool_registry:
400
+ return {
401
+ "error": {"code": -32602, "message": f"Tool not found: {tool_name}"}
402
+ }
403
+
404
+ registration = self._tool_registry[tool_name]
405
+
406
+ try:
407
+ # Execute tool handler
408
+ if registration.handler:
409
+ if asyncio.iscoroutinefunction(registration.handler):
410
+ result = await registration.handler(arguments)
411
+ else:
412
+ result = registration.handler(arguments)
413
+ elif registration.workflow_name:
414
+ # Execute workflow
415
+ workflow = self._workflow_registry.get(registration.workflow_name)
416
+ if workflow:
417
+ results, run_id = await self.runtime.execute_async(
418
+ workflow, parameters=arguments
419
+ )
420
+ result = {
421
+ "results": results,
422
+ "run_id": run_id,
423
+ "workflow": registration.workflow_name,
424
+ }
425
+ else:
426
+ result = {
427
+ "error": f"Workflow not found: {registration.workflow_name}"
428
+ }
429
+ else:
430
+ result = {"error": "No handler or workflow configured for tool"}
431
+
432
+ return {"content": [{"type": "text", "text": json.dumps(result, indent=2)}]}
433
+
434
+ except Exception as e:
435
+ logger.error(f"Error executing tool {tool_name}: {e}")
436
+ return {"error": {"code": -32603, "message": f"Tool execution failed: {e}"}}
437
+
438
+ async def _handle_resources_list(self) -> Dict[str, Any]:
439
+ """Handle MCP resources/list request."""
440
+ resources = []
441
+
442
+ # Add workflow resources
443
+ for workflow_name in self._workflow_registry.keys():
444
+ resource = {
445
+ "uri": f"workflow://{workflow_name}",
446
+ "name": f"Workflow: {workflow_name}",
447
+ "description": f"Execute the {workflow_name} workflow",
448
+ "mimeType": "application/json",
449
+ }
450
+ resources.append(resource)
451
+
452
+ return {"resources": resources}
453
+
454
+ async def _handle_resources_read(self, params: Dict[str, Any]) -> Dict[str, Any]:
455
+ """Handle MCP resources/read request."""
456
+ uri = params.get("uri", "")
457
+
458
+ if uri.startswith("workflow://"):
459
+ workflow_name = uri[11:] # Remove "workflow://" prefix
460
+ if workflow_name in self._workflow_registry:
461
+ workflow = self._workflow_registry[workflow_name]
462
+ # Return workflow information
463
+ return {
464
+ "contents": [
465
+ {
466
+ "uri": uri,
467
+ "mimeType": "application/json",
468
+ "text": json.dumps(
469
+ {
470
+ "name": workflow_name,
471
+ "description": f"Workflow {workflow_name} definition",
472
+ "available": True,
473
+ },
474
+ indent=2,
475
+ ),
476
+ }
477
+ ]
478
+ }
479
+
480
+ return {"error": {"code": -32602, "message": f"Resource not found: {uri}"}}
481
+
482
+ def register_tool(
483
+ self,
484
+ name: str,
485
+ description: str,
486
+ parameters: Dict[str, Any],
487
+ handler: Optional[Callable] = None,
488
+ workflow_name: Optional[str] = None,
489
+ metadata: Optional[Dict[str, Any]] = None,
490
+ ) -> None:
491
+ """Register an MCP tool.
492
+
493
+ Args:
494
+ name: Tool name
495
+ description: Tool description
496
+ parameters: Tool parameters schema
497
+ handler: Optional handler function
498
+ workflow_name: Optional workflow to execute
499
+ metadata: Optional metadata
500
+ """
501
+ registration = MCPToolRegistration(
502
+ name=name,
503
+ description=description,
504
+ parameters=parameters,
505
+ handler=handler,
506
+ workflow_name=workflow_name,
507
+ metadata=metadata or {},
508
+ )
509
+
510
+ self._tool_registry[name] = registration
511
+ logger.info(f"Registered MCP tool '{name}' with channel {self.name}")
512
+
513
+ def register_workflow(self, name: str, workflow: Workflow) -> None:
514
+ """Register a workflow with this MCP channel.
515
+
516
+ Args:
517
+ name: Workflow name
518
+ workflow: Workflow instance
519
+ """
520
+ self._workflow_registry[name] = workflow
521
+
522
+ # Auto-register as a tool
523
+ self.register_tool(
524
+ name=f"workflow_{name}",
525
+ description=f"Execute the {name} workflow",
526
+ parameters={
527
+ "inputs": {
528
+ "type": "object",
529
+ "description": "Input parameters for the workflow",
530
+ }
531
+ },
532
+ workflow_name=name,
533
+ )
534
+
535
+ logger.info(f"Registered workflow '{name}' with MCP channel {self.name}")
536
+
537
+ async def _handle_list_workflows(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
538
+ """Handle list_workflows tool."""
539
+ workflows = []
540
+
541
+ for workflow_name in self._workflow_registry.keys():
542
+ workflows.append(
543
+ {
544
+ "name": workflow_name,
545
+ "available": True,
546
+ "tool_name": f"workflow_{workflow_name}",
547
+ }
548
+ )
549
+
550
+ return {"workflows": workflows, "count": len(workflows)}
551
+
552
+ async def _handle_execute_workflow(
553
+ self, arguments: Dict[str, Any]
554
+ ) -> Dict[str, Any]:
555
+ """Handle execute_workflow tool."""
556
+ workflow_name = arguments.get("workflow_name", "")
557
+ inputs = arguments.get("inputs", {})
558
+
559
+ if workflow_name not in self._workflow_registry:
560
+ return {"error": f"Workflow not found: {workflow_name}"}
561
+
562
+ workflow = self._workflow_registry[workflow_name]
563
+
564
+ try:
565
+ results, run_id = await self.runtime.execute_async(
566
+ workflow, parameters=inputs
567
+ )
568
+ return {
569
+ "success": True,
570
+ "results": results,
571
+ "run_id": run_id,
572
+ "workflow_name": workflow_name,
573
+ }
574
+ except Exception as e:
575
+ return {"success": False, "error": str(e), "workflow_name": workflow_name}
576
+
577
+ async def _handle_get_workflow_schema(
578
+ self, arguments: Dict[str, Any]
579
+ ) -> Dict[str, Any]:
580
+ """Handle get_workflow_schema tool."""
581
+ workflow_name = arguments.get("workflow_name", "")
582
+
583
+ if workflow_name not in self._workflow_registry:
584
+ return {"error": f"Workflow not found: {workflow_name}"}
585
+
586
+ # This would extract schema from workflow
587
+ # For now, return basic information
588
+ return {
589
+ "workflow_name": workflow_name,
590
+ "schema": {
591
+ "inputs": "object",
592
+ "outputs": "object",
593
+ "description": f"Schema for {workflow_name} workflow",
594
+ },
595
+ }
596
+
597
+ async def _handle_channel_status(self, arguments: Dict[str, Any]) -> Dict[str, Any]:
598
+ """Handle channel_status tool."""
599
+ verbose = arguments.get("verbose", False)
600
+
601
+ status_info = {
602
+ "channel_name": self.name,
603
+ "channel_type": self.channel_type.value,
604
+ "status": self.status.value,
605
+ "tools_count": len(self._tool_registry),
606
+ "workflows_count": len(self._workflow_registry),
607
+ "active_clients": len(self._clients),
608
+ }
609
+
610
+ if verbose:
611
+ status_info.update(
612
+ {
613
+ "tools": list(self._tool_registry.keys()),
614
+ "workflows": list(self._workflow_registry.keys()),
615
+ "host": self.config.host,
616
+ "port": self.config.port,
617
+ "config": {
618
+ "enable_sessions": self.config.enable_sessions,
619
+ "enable_auth": self.config.enable_auth,
620
+ "enable_event_routing": self.config.enable_event_routing,
621
+ },
622
+ }
623
+ )
624
+
625
+ return status_info
626
+
627
+ async def health_check(self) -> Dict[str, Any]:
628
+ """Perform comprehensive health check."""
629
+ base_health = await super().health_check()
630
+
631
+ # Add MCP-specific health checks
632
+ mcp_checks = {
633
+ "mcp_server_running": self.mcp_server is not None,
634
+ "tools_registered": len(self._tool_registry) > 0,
635
+ "workflows_available": len(self._workflow_registry) >= 0,
636
+ "runtime_ready": self.runtime is not None,
637
+ }
638
+
639
+ all_healthy = base_health["healthy"] and all(mcp_checks.values())
640
+
641
+ return {
642
+ **base_health,
643
+ "healthy": all_healthy,
644
+ "checks": {**base_health["checks"], **mcp_checks},
645
+ "tools": len(self._tool_registry),
646
+ "workflows": len(self._workflow_registry),
647
+ "clients": len(self._clients),
648
+ }