kailash 0.6.2__py3-none-any.whl → 0.6.4__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 (131) hide show
  1. kailash/__init__.py +3 -3
  2. kailash/api/custom_nodes_secure.py +3 -3
  3. kailash/api/gateway.py +1 -1
  4. kailash/api/studio.py +2 -3
  5. kailash/api/workflow_api.py +3 -4
  6. kailash/core/resilience/bulkhead.py +460 -0
  7. kailash/core/resilience/circuit_breaker.py +92 -10
  8. kailash/edge/discovery.py +86 -0
  9. kailash/mcp_server/__init__.py +334 -0
  10. kailash/mcp_server/advanced_features.py +1022 -0
  11. kailash/{mcp → mcp_server}/ai_registry_server.py +29 -4
  12. kailash/mcp_server/auth.py +789 -0
  13. kailash/mcp_server/client.py +712 -0
  14. kailash/mcp_server/discovery.py +1593 -0
  15. kailash/mcp_server/errors.py +673 -0
  16. kailash/mcp_server/oauth.py +1727 -0
  17. kailash/mcp_server/protocol.py +1126 -0
  18. kailash/mcp_server/registry_integration.py +587 -0
  19. kailash/mcp_server/server.py +1747 -0
  20. kailash/{mcp → mcp_server}/servers/ai_registry.py +2 -2
  21. kailash/mcp_server/transports.py +1169 -0
  22. kailash/mcp_server/utils/cache.py +510 -0
  23. kailash/middleware/auth/auth_manager.py +3 -3
  24. kailash/middleware/communication/api_gateway.py +2 -9
  25. kailash/middleware/communication/realtime.py +1 -1
  26. kailash/middleware/mcp/client_integration.py +1 -1
  27. kailash/middleware/mcp/enhanced_server.py +2 -2
  28. kailash/nodes/__init__.py +2 -0
  29. kailash/nodes/admin/audit_log.py +6 -6
  30. kailash/nodes/admin/permission_check.py +8 -8
  31. kailash/nodes/admin/role_management.py +32 -28
  32. kailash/nodes/admin/schema.sql +6 -1
  33. kailash/nodes/admin/schema_manager.py +13 -13
  34. kailash/nodes/admin/security_event.py +16 -20
  35. kailash/nodes/admin/tenant_isolation.py +3 -3
  36. kailash/nodes/admin/transaction_utils.py +3 -3
  37. kailash/nodes/admin/user_management.py +21 -22
  38. kailash/nodes/ai/a2a.py +11 -11
  39. kailash/nodes/ai/ai_providers.py +9 -12
  40. kailash/nodes/ai/embedding_generator.py +13 -14
  41. kailash/nodes/ai/intelligent_agent_orchestrator.py +19 -19
  42. kailash/nodes/ai/iterative_llm_agent.py +3 -3
  43. kailash/nodes/ai/llm_agent.py +213 -36
  44. kailash/nodes/ai/self_organizing.py +2 -2
  45. kailash/nodes/alerts/discord.py +4 -4
  46. kailash/nodes/api/graphql.py +6 -6
  47. kailash/nodes/api/http.py +12 -17
  48. kailash/nodes/api/rate_limiting.py +4 -4
  49. kailash/nodes/api/rest.py +15 -15
  50. kailash/nodes/auth/mfa.py +3 -4
  51. kailash/nodes/auth/risk_assessment.py +2 -2
  52. kailash/nodes/auth/session_management.py +5 -5
  53. kailash/nodes/auth/sso.py +143 -0
  54. kailash/nodes/base.py +6 -2
  55. kailash/nodes/base_async.py +16 -2
  56. kailash/nodes/base_with_acl.py +2 -2
  57. kailash/nodes/cache/__init__.py +9 -0
  58. kailash/nodes/cache/cache.py +1172 -0
  59. kailash/nodes/cache/cache_invalidation.py +870 -0
  60. kailash/nodes/cache/redis_pool_manager.py +595 -0
  61. kailash/nodes/code/async_python.py +2 -1
  62. kailash/nodes/code/python.py +196 -35
  63. kailash/nodes/compliance/data_retention.py +6 -6
  64. kailash/nodes/compliance/gdpr.py +5 -5
  65. kailash/nodes/data/__init__.py +10 -0
  66. kailash/nodes/data/optimistic_locking.py +906 -0
  67. kailash/nodes/data/readers.py +8 -8
  68. kailash/nodes/data/redis.py +349 -0
  69. kailash/nodes/data/sql.py +314 -3
  70. kailash/nodes/data/streaming.py +21 -0
  71. kailash/nodes/enterprise/__init__.py +8 -0
  72. kailash/nodes/enterprise/audit_logger.py +285 -0
  73. kailash/nodes/enterprise/batch_processor.py +22 -3
  74. kailash/nodes/enterprise/data_lineage.py +1 -1
  75. kailash/nodes/enterprise/mcp_executor.py +205 -0
  76. kailash/nodes/enterprise/service_discovery.py +150 -0
  77. kailash/nodes/enterprise/tenant_assignment.py +108 -0
  78. kailash/nodes/logic/async_operations.py +2 -2
  79. kailash/nodes/logic/convergence.py +1 -1
  80. kailash/nodes/logic/operations.py +1 -1
  81. kailash/nodes/monitoring/__init__.py +11 -1
  82. kailash/nodes/monitoring/health_check.py +456 -0
  83. kailash/nodes/monitoring/log_processor.py +817 -0
  84. kailash/nodes/monitoring/metrics_collector.py +627 -0
  85. kailash/nodes/monitoring/performance_benchmark.py +137 -11
  86. kailash/nodes/rag/advanced.py +7 -7
  87. kailash/nodes/rag/agentic.py +49 -2
  88. kailash/nodes/rag/conversational.py +3 -3
  89. kailash/nodes/rag/evaluation.py +3 -3
  90. kailash/nodes/rag/federated.py +3 -3
  91. kailash/nodes/rag/graph.py +3 -3
  92. kailash/nodes/rag/multimodal.py +3 -3
  93. kailash/nodes/rag/optimized.py +5 -5
  94. kailash/nodes/rag/privacy.py +3 -3
  95. kailash/nodes/rag/query_processing.py +6 -6
  96. kailash/nodes/rag/realtime.py +1 -1
  97. kailash/nodes/rag/registry.py +2 -6
  98. kailash/nodes/rag/router.py +1 -1
  99. kailash/nodes/rag/similarity.py +7 -7
  100. kailash/nodes/rag/strategies.py +4 -4
  101. kailash/nodes/security/abac_evaluator.py +6 -6
  102. kailash/nodes/security/behavior_analysis.py +5 -6
  103. kailash/nodes/security/credential_manager.py +1 -1
  104. kailash/nodes/security/rotating_credentials.py +11 -11
  105. kailash/nodes/security/threat_detection.py +8 -8
  106. kailash/nodes/testing/credential_testing.py +2 -2
  107. kailash/nodes/transform/processors.py +5 -5
  108. kailash/runtime/local.py +162 -14
  109. kailash/runtime/parameter_injection.py +425 -0
  110. kailash/runtime/parameter_injector.py +657 -0
  111. kailash/runtime/testing.py +2 -2
  112. kailash/testing/fixtures.py +2 -2
  113. kailash/workflow/builder.py +99 -18
  114. kailash/workflow/builder_improvements.py +207 -0
  115. kailash/workflow/input_handling.py +170 -0
  116. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/METADATA +21 -8
  117. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/RECORD +126 -101
  118. kailash/mcp/__init__.py +0 -53
  119. kailash/mcp/client.py +0 -445
  120. kailash/mcp/server.py +0 -292
  121. kailash/mcp/server_enhanced.py +0 -449
  122. kailash/mcp/utils/cache.py +0 -267
  123. /kailash/{mcp → mcp_server}/client_new.py +0 -0
  124. /kailash/{mcp → mcp_server}/utils/__init__.py +0 -0
  125. /kailash/{mcp → mcp_server}/utils/config.py +0 -0
  126. /kailash/{mcp → mcp_server}/utils/formatters.py +0 -0
  127. /kailash/{mcp → mcp_server}/utils/metrics.py +0 -0
  128. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/WHEEL +0 -0
  129. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/entry_points.txt +0 -0
  130. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/licenses/LICENSE +0 -0
  131. {kailash-0.6.2.dist-info → kailash-0.6.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,587 @@
1
+ """
2
+ Registry Integration for MCP Servers.
3
+
4
+ This module provides automatic registration and deregistration of MCP servers
5
+ with the service discovery system. It includes health announcement, capability
6
+ broadcasting, and graceful shutdown handling.
7
+
8
+ Features:
9
+ - Automatic server registration on startup
10
+ - Periodic health announcements
11
+ - Graceful deregistration on shutdown
12
+ - Capability discovery and broadcasting
13
+ - Network announcement protocols
14
+ - Registry backend integration
15
+
16
+ Examples:
17
+ Auto-registration with file registry:
18
+
19
+ >>> from kailash.mcp_server import MCPServer
20
+ >>> from kailash.mcp_server.registry_integration import ServerRegistrar
21
+ >>>
22
+ >>> server = MCPServer("my-server")
23
+ >>> registrar = ServerRegistrar(server)
24
+ >>>
25
+ >>> @server.tool()
26
+ >>> def search(query: str) -> str:
27
+ ... return f"Results: {query}"
28
+ >>>
29
+ >>> # Server will auto-register when started
30
+ >>> registrar.start_with_registration()
31
+
32
+ Custom registry backend:
33
+
34
+ >>> from kailash.mcp_server.discovery import ServiceRegistry, FileBasedDiscovery
35
+ >>>
36
+ >>> registry = ServiceRegistry([FileBasedDiscovery("custom_registry.json")])
37
+ >>> registrar = ServerRegistrar(server, registry=registry)
38
+ >>> registrar.start_with_registration()
39
+ """
40
+
41
+ import asyncio
42
+ import atexit
43
+ import json
44
+ import logging
45
+ import signal
46
+ import socket
47
+ import time
48
+ import uuid
49
+ from pathlib import Path
50
+ from typing import Any, Dict, List, Optional, Set
51
+ from urllib.parse import urlparse
52
+
53
+ from .discovery import (
54
+ NetworkDiscovery,
55
+ ServerInfo,
56
+ ServiceRegistry,
57
+ create_default_registry,
58
+ )
59
+ from .errors import ServiceDiscoveryError
60
+
61
+ logger = logging.getLogger(__name__)
62
+
63
+
64
+ class ServerRegistrar:
65
+ """Handles automatic registration and lifecycle management for MCP servers."""
66
+
67
+ def __init__(
68
+ self,
69
+ server: Any, # MCPServer instance
70
+ registry: Optional[ServiceRegistry] = None,
71
+ auto_announce: bool = True,
72
+ announce_interval: float = 30.0,
73
+ enable_network_discovery: bool = False,
74
+ server_metadata: Optional[Dict[str, Any]] = None,
75
+ ):
76
+ """Initialize server registrar.
77
+
78
+ Args:
79
+ server: MCP server instance to register
80
+ registry: Service registry to use (creates default if None)
81
+ auto_announce: Enable periodic health announcements
82
+ announce_interval: Health announcement interval in seconds
83
+ enable_network_discovery: Enable UDP network announcements
84
+ server_metadata: Additional server metadata
85
+ """
86
+ self.server = server
87
+ self.registry = registry or create_default_registry()
88
+ self.auto_announce = auto_announce
89
+ self.announce_interval = announce_interval
90
+ self.enable_network_discovery = enable_network_discovery
91
+ self.server_metadata = server_metadata or {}
92
+
93
+ # Server info
94
+ self.server_id = str(uuid.uuid4())
95
+ self.server_info: Optional[ServerInfo] = None
96
+ self.registered = False
97
+
98
+ # Announcement tracking
99
+ self.announcement_task: Optional[asyncio.Task] = None
100
+ self.network_announcer: Optional[NetworkAnnouncer] = None
101
+
102
+ # Setup cleanup handlers
103
+ self._setup_cleanup_handlers()
104
+
105
+ def _setup_cleanup_handlers(self):
106
+ """Setup cleanup handlers for graceful shutdown."""
107
+
108
+ def cleanup():
109
+ if self.registered:
110
+ try:
111
+ asyncio.run(self.deregister())
112
+ except Exception as e:
113
+ logger.error(f"Error during cleanup: {e}")
114
+
115
+ atexit.register(cleanup)
116
+
117
+ # Handle signals
118
+ for sig in [signal.SIGTERM, signal.SIGINT]:
119
+ try:
120
+ signal.signal(sig, lambda s, f: cleanup())
121
+ except (ValueError, OSError):
122
+ # Signal handling not available (e.g., in threads)
123
+ pass
124
+
125
+ async def register(self) -> bool:
126
+ """Register server with the discovery system.
127
+
128
+ Returns:
129
+ True if registration succeeded
130
+ """
131
+ try:
132
+ # Discover server capabilities
133
+ capabilities = await self._discover_capabilities()
134
+
135
+ # Create server info
136
+ self.server_info = ServerInfo(
137
+ id=self.server_id,
138
+ name=self.server.name,
139
+ transport=self._determine_transport(),
140
+ endpoint=self._determine_endpoint(),
141
+ capabilities=capabilities,
142
+ metadata=self._create_metadata(),
143
+ health_status="healthy",
144
+ version=getattr(self.server, "version", "1.0.0"),
145
+ auth_required=getattr(self.server, "auth_manager", None) is not None,
146
+ )
147
+
148
+ # Register with registry
149
+ success = await self.registry.register_server(self.server_info.to_dict())
150
+
151
+ if success:
152
+ self.registered = True
153
+ logger.info(
154
+ f"Successfully registered server {self.server.name} ({self.server_id})"
155
+ )
156
+
157
+ # Start announcements if enabled
158
+ if self.auto_announce:
159
+ await self._start_announcements()
160
+
161
+ # Start network discovery if enabled
162
+ if self.enable_network_discovery:
163
+ self.network_announcer = NetworkAnnouncer(self.server_info)
164
+ await self.network_announcer.start()
165
+
166
+ return True
167
+ else:
168
+ logger.error(f"Failed to register server {self.server.name}")
169
+ return False
170
+
171
+ except Exception as e:
172
+ logger.error(f"Error registering server: {e}")
173
+ return False
174
+
175
+ async def deregister(self) -> bool:
176
+ """Deregister server from the discovery system.
177
+
178
+ Returns:
179
+ True if deregistration succeeded
180
+ """
181
+ if not self.registered:
182
+ return True
183
+
184
+ try:
185
+ # Stop announcements
186
+ if self.announcement_task:
187
+ self.announcement_task.cancel()
188
+ try:
189
+ await self.announcement_task
190
+ except asyncio.CancelledError:
191
+ pass
192
+ self.announcement_task = None
193
+
194
+ # Stop network announcer
195
+ if self.network_announcer:
196
+ await self.network_announcer.stop()
197
+ self.network_announcer = None
198
+
199
+ # Deregister from registry
200
+ success = await self.registry.deregister_server(self.server_id)
201
+
202
+ if success:
203
+ self.registered = False
204
+ logger.info(f"Successfully deregistered server {self.server.name}")
205
+ return True
206
+ else:
207
+ logger.error(f"Failed to deregister server {self.server.name}")
208
+ return False
209
+
210
+ except Exception as e:
211
+ logger.error(f"Error deregistering server: {e}")
212
+ return False
213
+
214
+ async def update_health(
215
+ self, health_status: str = "healthy", response_time: Optional[float] = None
216
+ ):
217
+ """Update server health status.
218
+
219
+ Args:
220
+ health_status: New health status
221
+ response_time: Optional response time measurement
222
+ """
223
+ if not self.registered:
224
+ return
225
+
226
+ try:
227
+ # Update in all registry backends
228
+ for backend in self.registry.backends:
229
+ await backend.update_server_health(
230
+ self.server_id, health_status, response_time
231
+ )
232
+
233
+ # Update local server info
234
+ if self.server_info:
235
+ self.server_info.health_status = health_status
236
+ self.server_info.last_seen = time.time()
237
+ if response_time is not None:
238
+ self.server_info.response_time = response_time
239
+
240
+ except Exception as e:
241
+ logger.error(f"Error updating health: {e}")
242
+
243
+ def start_with_registration(self):
244
+ """Start the server with automatic registration.
245
+
246
+ This is a convenience method that handles registration and then
247
+ starts the server. Use this instead of server.run() for automatic
248
+ service discovery integration.
249
+ """
250
+
251
+ async def startup_sequence():
252
+ # Register server
253
+ success = await self.register()
254
+ if not success:
255
+ logger.warning("Server registration failed, but continuing startup")
256
+
257
+ # Start health monitoring
258
+ if hasattr(self.server, "start_health_checking"):
259
+ self.server.start_health_checking()
260
+
261
+ # Run registration in event loop
262
+ try:
263
+ asyncio.run(startup_sequence())
264
+ except RuntimeError:
265
+ # Already in event loop, schedule as task
266
+ asyncio.create_task(startup_sequence())
267
+
268
+ # Start the actual server
269
+ try:
270
+ self.server.run()
271
+ finally:
272
+ # Ensure cleanup happens
273
+ try:
274
+ asyncio.run(self.deregister())
275
+ except Exception as e:
276
+ logger.error(f"Error during final cleanup: {e}")
277
+
278
+ async def _discover_capabilities(self) -> List[str]:
279
+ """Discover server capabilities by examining registered tools."""
280
+ capabilities = []
281
+
282
+ # Get tools from server registry
283
+ if hasattr(self.server, "_tool_registry"):
284
+ capabilities.extend(self.server._tool_registry.keys())
285
+
286
+ # Get resources
287
+ if hasattr(self.server, "_resource_registry"):
288
+ for uri in self.server._resource_registry.keys():
289
+ capabilities.append(f"resource:{uri}")
290
+
291
+ # Get prompts
292
+ if hasattr(self.server, "_prompt_registry"):
293
+ for name in self.server._prompt_registry.keys():
294
+ capabilities.append(f"prompt:{name}")
295
+
296
+ # Add custom capabilities from metadata
297
+ custom_capabilities = self.server_metadata.get("capabilities", [])
298
+ capabilities.extend(custom_capabilities)
299
+
300
+ return list(set(capabilities)) # Remove duplicates
301
+
302
+ def _determine_transport(self) -> str:
303
+ """Determine the transport type used by the server."""
304
+ # Check server configuration
305
+ if (
306
+ hasattr(self.server, "enable_http_transport")
307
+ and self.server.enable_http_transport
308
+ ):
309
+ return "http"
310
+ elif (
311
+ hasattr(self.server, "enable_sse_transport")
312
+ and self.server.enable_sse_transport
313
+ ):
314
+ return "sse"
315
+ else:
316
+ return "stdio"
317
+
318
+ def _determine_endpoint(self) -> str:
319
+ """Determine the server endpoint."""
320
+ transport = self._determine_transport()
321
+
322
+ if transport in ["http", "sse"]:
323
+ # For HTTP/SSE, construct URL
324
+ host = getattr(self.server, "host", "localhost")
325
+ port = getattr(self.server, "port", 8080)
326
+ return f"http://{host}:{port}"
327
+
328
+ elif transport == "stdio":
329
+ # For stdio, provide command to start server
330
+ # This assumes the server can be started with Python
331
+ server_script = self.server_metadata.get("startup_command")
332
+ if server_script:
333
+ return server_script
334
+ else:
335
+ # Default command
336
+ return f"python -m {self.server.__class__.__module__}"
337
+
338
+ else:
339
+ return "unknown"
340
+
341
+ def _create_metadata(self) -> Dict[str, Any]:
342
+ """Create metadata for server registration."""
343
+ metadata = self.server_metadata.copy()
344
+
345
+ # Add server configuration
346
+ if hasattr(self.server, "config"):
347
+ metadata["config"] = self.server.config.to_dict()
348
+
349
+ # Add feature flags
350
+ metadata["features"] = {
351
+ "caching": getattr(self.server, "cache", None) is not None,
352
+ "metrics": getattr(self.server, "metrics", None) is not None,
353
+ "auth": getattr(self.server, "auth_manager", None) is not None,
354
+ "circuit_breaker": getattr(self.server, "circuit_breaker", None)
355
+ is not None,
356
+ "streaming": getattr(self.server, "enable_streaming", False),
357
+ }
358
+
359
+ # Add authentication config if present
360
+ if hasattr(self.server, "auth_manager") and self.server.auth_manager:
361
+ auth_config = {
362
+ "type": type(self.server.auth_manager.provider)
363
+ .__name__.lower()
364
+ .replace("auth", ""),
365
+ "required": True,
366
+ }
367
+ metadata["auth_config"] = auth_config
368
+
369
+ # Add runtime info
370
+ metadata["runtime"] = {
371
+ "python_version": f"{__import__('sys').version_info.major}.{__import__('sys').version_info.minor}",
372
+ "platform": __import__("platform").system(),
373
+ "registered_at": time.time(),
374
+ }
375
+
376
+ return metadata
377
+
378
+ async def _start_announcements(self):
379
+ """Start periodic health announcements."""
380
+
381
+ async def announce_health():
382
+ while self.registered:
383
+ try:
384
+ # Perform health check
385
+ health_status = await self._check_health()
386
+ await self.update_health(health_status)
387
+
388
+ await asyncio.sleep(self.announce_interval)
389
+
390
+ except asyncio.CancelledError:
391
+ break
392
+ except Exception as e:
393
+ logger.error(f"Error in health announcement: {e}")
394
+ await asyncio.sleep(min(self.announce_interval, 10))
395
+
396
+ self.announcement_task = asyncio.create_task(announce_health())
397
+
398
+ async def _check_health(self) -> str:
399
+ """Check server health status.
400
+
401
+ Returns:
402
+ Health status string
403
+ """
404
+ try:
405
+ # Use server's health check if available
406
+ if hasattr(self.server, "health_check"):
407
+ health_result = self.server.health_check()
408
+ return health_result.get("status", "unknown")
409
+
410
+ # Basic health check - ensure server is running
411
+ if hasattr(self.server, "_running") and self.server._running:
412
+ return "healthy"
413
+ else:
414
+ return "unknown"
415
+
416
+ except Exception as e:
417
+ logger.debug(f"Health check failed: {e}")
418
+ return "unhealthy"
419
+
420
+
421
+ class NetworkAnnouncer:
422
+ """Handles UDP network announcements for MCP servers."""
423
+
424
+ def __init__(self, server_info: ServerInfo, port: int = 8765):
425
+ """Initialize network announcer.
426
+
427
+ Args:
428
+ server_info: Server information to announce
429
+ port: UDP port for announcements
430
+ """
431
+ self.server_info = server_info
432
+ self.port = port
433
+ self.running = False
434
+ self.announcement_task: Optional[asyncio.Task] = None
435
+ self.socket: Optional[socket.socket] = None
436
+
437
+ async def start(self):
438
+ """Start network announcements."""
439
+ self.running = True
440
+
441
+ # Create UDP socket
442
+ self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
443
+ self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
444
+
445
+ # Start announcement task
446
+ self.announcement_task = asyncio.create_task(self._announce_loop())
447
+
448
+ logger.info(f"Started network announcements for {self.server_info.name}")
449
+
450
+ async def stop(self):
451
+ """Stop network announcements."""
452
+ self.running = False
453
+
454
+ if self.announcement_task:
455
+ self.announcement_task.cancel()
456
+ try:
457
+ await self.announcement_task
458
+ except asyncio.CancelledError:
459
+ pass
460
+
461
+ if self.socket:
462
+ self.socket.close()
463
+ self.socket = None
464
+
465
+ logger.info(f"Stopped network announcements for {self.server_info.name}")
466
+
467
+ async def _announce_loop(self):
468
+ """Main announcement loop."""
469
+ while self.running:
470
+ try:
471
+ await self._send_announcement()
472
+ await asyncio.sleep(30) # Announce every 30 seconds
473
+
474
+ except asyncio.CancelledError:
475
+ break
476
+ except Exception as e:
477
+ logger.error(f"Error in network announcement: {e}")
478
+ await asyncio.sleep(10) # Back off on error
479
+
480
+ async def _send_announcement(self):
481
+ """Send UDP announcement."""
482
+ if not self.socket:
483
+ return
484
+
485
+ announcement = {
486
+ "type": "mcp_server_announcement",
487
+ "id": self.server_info.id,
488
+ "name": self.server_info.name,
489
+ "transport": self.server_info.transport,
490
+ "endpoint": self.server_info.endpoint,
491
+ "capabilities": self.server_info.capabilities,
492
+ "metadata": self.server_info.metadata,
493
+ "health_status": self.server_info.health_status,
494
+ "version": self.server_info.version,
495
+ "auth_required": self.server_info.auth_required,
496
+ "timestamp": time.time(),
497
+ }
498
+
499
+ message = json.dumps(announcement).encode()
500
+
501
+ # Broadcast to network
502
+ try:
503
+ self.socket.sendto(message, ("<broadcast>", self.port))
504
+ except Exception as e:
505
+ logger.debug(f"Failed to send broadcast: {e}")
506
+
507
+ # Send to multicast group
508
+ try:
509
+ self.socket.sendto(message, (NetworkDiscovery.MULTICAST_GROUP, self.port))
510
+ except Exception as e:
511
+ logger.debug(f"Failed to send multicast: {e}")
512
+
513
+
514
+ def enable_auto_discovery(server, **kwargs):
515
+ """Enable automatic discovery for an MCP server.
516
+
517
+ This is a convenience function that creates a ServerRegistrar
518
+ and configures it for the given server.
519
+
520
+ Args:
521
+ server: MCP server instance
522
+ **kwargs: Configuration options for ServerRegistrar
523
+
524
+ Returns:
525
+ ServerRegistrar instance
526
+
527
+ Examples:
528
+ >>> from kailash.mcp_server import MCPServer
529
+ >>> from kailash.mcp_server.registry_integration import enable_auto_discovery
530
+ >>>
531
+ >>> server = MCPServer("my-server")
532
+ >>> registrar = enable_auto_discovery(server, enable_network_discovery=True)
533
+ >>> registrar.start_with_registration()
534
+ """
535
+ return ServerRegistrar(server, **kwargs)
536
+
537
+
538
+ def register_server_manually(
539
+ name: str,
540
+ transport: str,
541
+ endpoint: str,
542
+ capabilities: List[str],
543
+ metadata: Optional[Dict[str, Any]] = None,
544
+ registry: Optional[ServiceRegistry] = None,
545
+ ) -> bool:
546
+ """Manually register a server with the discovery system.
547
+
548
+ This is useful for registering external servers that don't use
549
+ the Kailash MCP server framework.
550
+
551
+ Args:
552
+ name: Server name
553
+ transport: Transport type (stdio, http, sse)
554
+ endpoint: Server endpoint
555
+ capabilities: List of capabilities
556
+ metadata: Optional metadata
557
+ registry: Service registry to use
558
+
559
+ Returns:
560
+ True if registration succeeded
561
+
562
+ Examples:
563
+ >>> register_server_manually(
564
+ ... name="external-server",
565
+ ... transport="http",
566
+ ... endpoint="http://external-host:8080",
567
+ ... capabilities=["search", "analyze"],
568
+ ... metadata={"version": "2.0", "external": True}
569
+ ... )
570
+ """
571
+ if registry is None:
572
+ registry = create_default_registry()
573
+
574
+ server_config = {
575
+ "name": name,
576
+ "transport": transport,
577
+ "endpoint": endpoint,
578
+ "capabilities": capabilities,
579
+ "metadata": metadata or {},
580
+ "auth_required": False,
581
+ }
582
+
583
+ try:
584
+ return asyncio.run(registry.register_server(server_config))
585
+ except RuntimeError:
586
+ # Already in event loop
587
+ return asyncio.create_task(registry.register_server(server_config))