mcp-hangar 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.
Files changed (160) hide show
  1. mcp_hangar/__init__.py +139 -0
  2. mcp_hangar/application/__init__.py +1 -0
  3. mcp_hangar/application/commands/__init__.py +67 -0
  4. mcp_hangar/application/commands/auth_commands.py +118 -0
  5. mcp_hangar/application/commands/auth_handlers.py +296 -0
  6. mcp_hangar/application/commands/commands.py +59 -0
  7. mcp_hangar/application/commands/handlers.py +189 -0
  8. mcp_hangar/application/discovery/__init__.py +21 -0
  9. mcp_hangar/application/discovery/discovery_metrics.py +283 -0
  10. mcp_hangar/application/discovery/discovery_orchestrator.py +497 -0
  11. mcp_hangar/application/discovery/lifecycle_manager.py +315 -0
  12. mcp_hangar/application/discovery/security_validator.py +414 -0
  13. mcp_hangar/application/event_handlers/__init__.py +50 -0
  14. mcp_hangar/application/event_handlers/alert_handler.py +191 -0
  15. mcp_hangar/application/event_handlers/audit_handler.py +203 -0
  16. mcp_hangar/application/event_handlers/knowledge_base_handler.py +120 -0
  17. mcp_hangar/application/event_handlers/logging_handler.py +69 -0
  18. mcp_hangar/application/event_handlers/metrics_handler.py +152 -0
  19. mcp_hangar/application/event_handlers/persistent_audit_store.py +217 -0
  20. mcp_hangar/application/event_handlers/security_handler.py +604 -0
  21. mcp_hangar/application/mcp/tooling.py +158 -0
  22. mcp_hangar/application/ports/__init__.py +9 -0
  23. mcp_hangar/application/ports/observability.py +237 -0
  24. mcp_hangar/application/queries/__init__.py +52 -0
  25. mcp_hangar/application/queries/auth_handlers.py +237 -0
  26. mcp_hangar/application/queries/auth_queries.py +118 -0
  27. mcp_hangar/application/queries/handlers.py +227 -0
  28. mcp_hangar/application/read_models/__init__.py +11 -0
  29. mcp_hangar/application/read_models/provider_views.py +139 -0
  30. mcp_hangar/application/sagas/__init__.py +11 -0
  31. mcp_hangar/application/sagas/group_rebalance_saga.py +137 -0
  32. mcp_hangar/application/sagas/provider_failover_saga.py +266 -0
  33. mcp_hangar/application/sagas/provider_recovery_saga.py +172 -0
  34. mcp_hangar/application/services/__init__.py +9 -0
  35. mcp_hangar/application/services/provider_service.py +208 -0
  36. mcp_hangar/application/services/traced_provider_service.py +211 -0
  37. mcp_hangar/bootstrap/runtime.py +328 -0
  38. mcp_hangar/context.py +178 -0
  39. mcp_hangar/domain/__init__.py +117 -0
  40. mcp_hangar/domain/contracts/__init__.py +57 -0
  41. mcp_hangar/domain/contracts/authentication.py +225 -0
  42. mcp_hangar/domain/contracts/authorization.py +229 -0
  43. mcp_hangar/domain/contracts/event_store.py +178 -0
  44. mcp_hangar/domain/contracts/metrics_publisher.py +59 -0
  45. mcp_hangar/domain/contracts/persistence.py +383 -0
  46. mcp_hangar/domain/contracts/provider_runtime.py +146 -0
  47. mcp_hangar/domain/discovery/__init__.py +20 -0
  48. mcp_hangar/domain/discovery/conflict_resolver.py +267 -0
  49. mcp_hangar/domain/discovery/discovered_provider.py +185 -0
  50. mcp_hangar/domain/discovery/discovery_service.py +412 -0
  51. mcp_hangar/domain/discovery/discovery_source.py +192 -0
  52. mcp_hangar/domain/events.py +433 -0
  53. mcp_hangar/domain/exceptions.py +525 -0
  54. mcp_hangar/domain/model/__init__.py +70 -0
  55. mcp_hangar/domain/model/aggregate.py +58 -0
  56. mcp_hangar/domain/model/circuit_breaker.py +152 -0
  57. mcp_hangar/domain/model/event_sourced_api_key.py +413 -0
  58. mcp_hangar/domain/model/event_sourced_provider.py +423 -0
  59. mcp_hangar/domain/model/event_sourced_role_assignment.py +268 -0
  60. mcp_hangar/domain/model/health_tracker.py +183 -0
  61. mcp_hangar/domain/model/load_balancer.py +185 -0
  62. mcp_hangar/domain/model/provider.py +810 -0
  63. mcp_hangar/domain/model/provider_group.py +656 -0
  64. mcp_hangar/domain/model/tool_catalog.py +105 -0
  65. mcp_hangar/domain/policies/__init__.py +19 -0
  66. mcp_hangar/domain/policies/provider_health.py +187 -0
  67. mcp_hangar/domain/repository.py +249 -0
  68. mcp_hangar/domain/security/__init__.py +85 -0
  69. mcp_hangar/domain/security/input_validator.py +710 -0
  70. mcp_hangar/domain/security/rate_limiter.py +387 -0
  71. mcp_hangar/domain/security/roles.py +237 -0
  72. mcp_hangar/domain/security/sanitizer.py +387 -0
  73. mcp_hangar/domain/security/secrets.py +501 -0
  74. mcp_hangar/domain/services/__init__.py +20 -0
  75. mcp_hangar/domain/services/audit_service.py +376 -0
  76. mcp_hangar/domain/services/image_builder.py +328 -0
  77. mcp_hangar/domain/services/provider_launcher.py +1046 -0
  78. mcp_hangar/domain/value_objects.py +1138 -0
  79. mcp_hangar/errors.py +818 -0
  80. mcp_hangar/fastmcp_server.py +1105 -0
  81. mcp_hangar/gc.py +134 -0
  82. mcp_hangar/infrastructure/__init__.py +79 -0
  83. mcp_hangar/infrastructure/async_executor.py +133 -0
  84. mcp_hangar/infrastructure/auth/__init__.py +37 -0
  85. mcp_hangar/infrastructure/auth/api_key_authenticator.py +388 -0
  86. mcp_hangar/infrastructure/auth/event_sourced_store.py +567 -0
  87. mcp_hangar/infrastructure/auth/jwt_authenticator.py +360 -0
  88. mcp_hangar/infrastructure/auth/middleware.py +340 -0
  89. mcp_hangar/infrastructure/auth/opa_authorizer.py +243 -0
  90. mcp_hangar/infrastructure/auth/postgres_store.py +659 -0
  91. mcp_hangar/infrastructure/auth/projections.py +366 -0
  92. mcp_hangar/infrastructure/auth/rate_limiter.py +311 -0
  93. mcp_hangar/infrastructure/auth/rbac_authorizer.py +323 -0
  94. mcp_hangar/infrastructure/auth/sqlite_store.py +624 -0
  95. mcp_hangar/infrastructure/command_bus.py +112 -0
  96. mcp_hangar/infrastructure/discovery/__init__.py +110 -0
  97. mcp_hangar/infrastructure/discovery/docker_source.py +289 -0
  98. mcp_hangar/infrastructure/discovery/entrypoint_source.py +249 -0
  99. mcp_hangar/infrastructure/discovery/filesystem_source.py +383 -0
  100. mcp_hangar/infrastructure/discovery/kubernetes_source.py +247 -0
  101. mcp_hangar/infrastructure/event_bus.py +260 -0
  102. mcp_hangar/infrastructure/event_sourced_repository.py +443 -0
  103. mcp_hangar/infrastructure/event_store.py +396 -0
  104. mcp_hangar/infrastructure/knowledge_base/__init__.py +259 -0
  105. mcp_hangar/infrastructure/knowledge_base/contracts.py +202 -0
  106. mcp_hangar/infrastructure/knowledge_base/memory.py +177 -0
  107. mcp_hangar/infrastructure/knowledge_base/postgres.py +545 -0
  108. mcp_hangar/infrastructure/knowledge_base/sqlite.py +513 -0
  109. mcp_hangar/infrastructure/metrics_publisher.py +36 -0
  110. mcp_hangar/infrastructure/observability/__init__.py +10 -0
  111. mcp_hangar/infrastructure/observability/langfuse_adapter.py +534 -0
  112. mcp_hangar/infrastructure/persistence/__init__.py +33 -0
  113. mcp_hangar/infrastructure/persistence/audit_repository.py +371 -0
  114. mcp_hangar/infrastructure/persistence/config_repository.py +398 -0
  115. mcp_hangar/infrastructure/persistence/database.py +333 -0
  116. mcp_hangar/infrastructure/persistence/database_common.py +330 -0
  117. mcp_hangar/infrastructure/persistence/event_serializer.py +280 -0
  118. mcp_hangar/infrastructure/persistence/event_upcaster.py +166 -0
  119. mcp_hangar/infrastructure/persistence/in_memory_event_store.py +150 -0
  120. mcp_hangar/infrastructure/persistence/recovery_service.py +312 -0
  121. mcp_hangar/infrastructure/persistence/sqlite_event_store.py +386 -0
  122. mcp_hangar/infrastructure/persistence/unit_of_work.py +409 -0
  123. mcp_hangar/infrastructure/persistence/upcasters/README.md +13 -0
  124. mcp_hangar/infrastructure/persistence/upcasters/__init__.py +7 -0
  125. mcp_hangar/infrastructure/query_bus.py +153 -0
  126. mcp_hangar/infrastructure/saga_manager.py +401 -0
  127. mcp_hangar/logging_config.py +209 -0
  128. mcp_hangar/metrics.py +1007 -0
  129. mcp_hangar/models.py +31 -0
  130. mcp_hangar/observability/__init__.py +54 -0
  131. mcp_hangar/observability/health.py +487 -0
  132. mcp_hangar/observability/metrics.py +319 -0
  133. mcp_hangar/observability/tracing.py +433 -0
  134. mcp_hangar/progress.py +542 -0
  135. mcp_hangar/retry.py +613 -0
  136. mcp_hangar/server/__init__.py +120 -0
  137. mcp_hangar/server/__main__.py +6 -0
  138. mcp_hangar/server/auth_bootstrap.py +340 -0
  139. mcp_hangar/server/auth_cli.py +335 -0
  140. mcp_hangar/server/auth_config.py +305 -0
  141. mcp_hangar/server/bootstrap.py +735 -0
  142. mcp_hangar/server/cli.py +161 -0
  143. mcp_hangar/server/config.py +224 -0
  144. mcp_hangar/server/context.py +215 -0
  145. mcp_hangar/server/http_auth_middleware.py +165 -0
  146. mcp_hangar/server/lifecycle.py +467 -0
  147. mcp_hangar/server/state.py +117 -0
  148. mcp_hangar/server/tools/__init__.py +16 -0
  149. mcp_hangar/server/tools/discovery.py +186 -0
  150. mcp_hangar/server/tools/groups.py +75 -0
  151. mcp_hangar/server/tools/health.py +301 -0
  152. mcp_hangar/server/tools/provider.py +939 -0
  153. mcp_hangar/server/tools/registry.py +320 -0
  154. mcp_hangar/server/validation.py +113 -0
  155. mcp_hangar/stdio_client.py +229 -0
  156. mcp_hangar-0.2.0.dist-info/METADATA +347 -0
  157. mcp_hangar-0.2.0.dist-info/RECORD +160 -0
  158. mcp_hangar-0.2.0.dist-info/WHEEL +4 -0
  159. mcp_hangar-0.2.0.dist-info/entry_points.txt +2 -0
  160. mcp_hangar-0.2.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,1105 @@
1
+ """MCP HTTP Server using FastMCP.
2
+
3
+ Provides MCP-over-HTTP with proper dependency injection.
4
+ No global state — all dependencies passed via constructor.
5
+
6
+ Endpoints (HTTP mode):
7
+ - /health : liveness (cheap ping)
8
+ - /ready : readiness (checks internal registry wiring + basic runtime state)
9
+ - /metrics: prometheus metrics
10
+ - /mcp : MCP streamable HTTP endpoint
11
+
12
+ Usage:
13
+ # Recommended: Use MCPServerFactory
14
+ from mcp_hangar.fastmcp_server import MCPServerFactory, RegistryFunctions
15
+
16
+ registry = RegistryFunctions(
17
+ list=my_list_fn,
18
+ start=my_start_fn,
19
+ stop=my_stop_fn,
20
+ invoke=my_invoke_fn,
21
+ tools=my_tools_fn,
22
+ details=my_details_fn,
23
+ health=my_health_fn,
24
+ )
25
+
26
+ factory = MCPServerFactory(registry)
27
+ app = factory.create_asgi_app()
28
+
29
+ # Or use the builder pattern:
30
+ factory = (MCPServerFactory.builder()
31
+ .with_registry(list_fn, start_fn, stop_fn, invoke_fn, tools_fn, details_fn, health_fn)
32
+ .with_discovery(discover_fn=discover_fn)
33
+ .with_config(port=9000)
34
+ .build())
35
+ """
36
+
37
+ from dataclasses import dataclass
38
+ from typing import Any, Dict, Optional, Protocol, TYPE_CHECKING
39
+
40
+ from mcp.server.fastmcp import FastMCP
41
+
42
+ from .logging_config import get_logger
43
+
44
+ if TYPE_CHECKING:
45
+ from .server.auth_bootstrap import AuthComponents
46
+
47
+ logger = get_logger(__name__)
48
+
49
+
50
+ # =============================================================================
51
+ # Protocols for Type Safety
52
+ # =============================================================================
53
+
54
+
55
+ class RegistryListFn(Protocol):
56
+ """Protocol for registry_list function."""
57
+
58
+ def __call__(self, state_filter: Optional[str] = None) -> Dict[str, Any]:
59
+ """List all providers with status and metadata."""
60
+ ...
61
+
62
+
63
+ class RegistryStartFn(Protocol):
64
+ """Protocol for registry_start function."""
65
+
66
+ def __call__(self, provider: str) -> Dict[str, Any]:
67
+ """Start a provider and discover tools."""
68
+ ...
69
+
70
+
71
+ class RegistryStopFn(Protocol):
72
+ """Protocol for registry_stop function."""
73
+
74
+ def __call__(self, provider: str) -> Dict[str, Any]:
75
+ """Stop a provider."""
76
+ ...
77
+
78
+
79
+ class RegistryInvokeFn(Protocol):
80
+ """Protocol for registry_invoke function."""
81
+
82
+ def __call__(
83
+ self,
84
+ provider: str,
85
+ tool: str,
86
+ arguments: Dict[str, Any],
87
+ timeout: float = 30.0,
88
+ ) -> Dict[str, Any]:
89
+ """Invoke a tool on a provider."""
90
+ ...
91
+
92
+
93
+ class RegistryToolsFn(Protocol):
94
+ """Protocol for registry_tools function."""
95
+
96
+ def __call__(self, provider: str) -> Dict[str, Any]:
97
+ """Get tool schemas for a provider."""
98
+ ...
99
+
100
+
101
+ class RegistryDetailsFn(Protocol):
102
+ """Protocol for registry_details function."""
103
+
104
+ def __call__(self, provider: str) -> Dict[str, Any]:
105
+ """Get detailed provider information."""
106
+ ...
107
+
108
+
109
+ class RegistryHealthFn(Protocol):
110
+ """Protocol for registry_health function."""
111
+
112
+ def __call__(self) -> Dict[str, Any]:
113
+ """Get registry health status."""
114
+ ...
115
+
116
+
117
+ # Discovery protocols (optional)
118
+
119
+
120
+ class RegistryDiscoverFn(Protocol):
121
+ """Protocol for registry_discover function (async)."""
122
+
123
+ async def __call__(self) -> Dict[str, Any]:
124
+ """Trigger immediate discovery cycle."""
125
+ ...
126
+
127
+
128
+ class RegistryDiscoveredFn(Protocol):
129
+ """Protocol for registry_discovered function."""
130
+
131
+ def __call__(self) -> Dict[str, Any]:
132
+ """List discovered providers pending registration."""
133
+ ...
134
+
135
+
136
+ class RegistryQuarantineFn(Protocol):
137
+ """Protocol for registry_quarantine function."""
138
+
139
+ def __call__(self) -> Dict[str, Any]:
140
+ """List quarantined providers."""
141
+ ...
142
+
143
+
144
+ class RegistryApproveFn(Protocol):
145
+ """Protocol for registry_approve function (async)."""
146
+
147
+ async def __call__(self, provider: str) -> Dict[str, Any]:
148
+ """Approve a quarantined provider."""
149
+ ...
150
+
151
+
152
+ class RegistrySourcesFn(Protocol):
153
+ """Protocol for registry_sources function."""
154
+
155
+ def __call__(self) -> Dict[str, Any]:
156
+ """List discovery sources with status."""
157
+ ...
158
+
159
+
160
+ class RegistryMetricsFn(Protocol):
161
+ """Protocol for registry_metrics function."""
162
+
163
+ def __call__(self, format: str = "summary") -> Dict[str, Any]:
164
+ """Get registry metrics."""
165
+ ...
166
+
167
+
168
+ # =============================================================================
169
+ # Configuration
170
+ # =============================================================================
171
+
172
+
173
+ @dataclass(frozen=True)
174
+ class RegistryFunctions:
175
+ """Container for all registry function dependencies.
176
+
177
+ Core functions are required. Discovery functions are optional
178
+ and will return appropriate errors if not provided.
179
+
180
+ Attributes:
181
+ list: Function to list all providers.
182
+ start: Function to start a provider.
183
+ stop: Function to stop a provider.
184
+ invoke: Function to invoke a tool on a provider.
185
+ tools: Function to get tool schemas.
186
+ details: Function to get provider details.
187
+ health: Function to get registry health.
188
+ discover: Optional async function to trigger discovery.
189
+ discovered: Optional function to list discovered providers.
190
+ quarantine: Optional function to list quarantined providers.
191
+ approve: Optional async function to approve a quarantined provider.
192
+ sources: Optional function to list discovery sources.
193
+ metrics: Optional function to get registry metrics.
194
+ """
195
+
196
+ # Core (required)
197
+ list: RegistryListFn
198
+ start: RegistryStartFn
199
+ stop: RegistryStopFn
200
+ invoke: RegistryInvokeFn
201
+ tools: RegistryToolsFn
202
+ details: RegistryDetailsFn
203
+ health: RegistryHealthFn
204
+
205
+ # Discovery (optional)
206
+ discover: Optional[RegistryDiscoverFn] = None
207
+ discovered: Optional[RegistryDiscoveredFn] = None
208
+ quarantine: Optional[RegistryQuarantineFn] = None
209
+ approve: Optional[RegistryApproveFn] = None
210
+ sources: Optional[RegistrySourcesFn] = None
211
+ metrics: Optional[RegistryMetricsFn] = None
212
+
213
+
214
+ @dataclass(frozen=True)
215
+ class ServerConfig:
216
+ """HTTP server configuration.
217
+
218
+ Attributes:
219
+ host: Host to bind to.
220
+ port: Port to bind to.
221
+ streamable_http_path: Path for MCP streamable HTTP endpoint.
222
+ sse_path: Path for SSE endpoint.
223
+ message_path: Path for message endpoint.
224
+ auth_enabled: Whether authentication is enabled (opt-in, default False).
225
+ auth_skip_paths: Paths to skip authentication (health, metrics, etc.).
226
+ trusted_proxies: Set of trusted proxy IPs for X-Forwarded-For.
227
+ """
228
+
229
+ host: str = "0.0.0.0"
230
+ port: int = 8000
231
+ streamable_http_path: str = "/mcp"
232
+ sse_path: str = "/sse"
233
+ message_path: str = "/messages/"
234
+ # Auth configuration (opt-in)
235
+ auth_enabled: bool = False
236
+ auth_skip_paths: tuple[str, ...] = ("/health", "/ready", "/_ready", "/metrics")
237
+ trusted_proxies: frozenset[str] = frozenset(["127.0.0.1", "::1"])
238
+
239
+
240
+ # =============================================================================
241
+ # Factory
242
+ # =============================================================================
243
+
244
+
245
+ class MCPServerFactory:
246
+ """Factory for creating configured FastMCP servers.
247
+
248
+ This factory encapsulates all dependencies needed to create an MCP server,
249
+ enabling proper dependency injection and testability.
250
+
251
+ Usage:
252
+ # Direct instantiation
253
+ factory = MCPServerFactory(registry_functions)
254
+ mcp = factory.create_server()
255
+ app = factory.create_asgi_app()
256
+
257
+ # With authentication (opt-in)
258
+ factory = MCPServerFactory(
259
+ registry_functions,
260
+ auth_components=auth_components,
261
+ config=ServerConfig(auth_enabled=True),
262
+ )
263
+ app = factory.create_asgi_app()
264
+
265
+ # Or use the builder pattern
266
+ factory = (MCPServerFactory.builder()
267
+ .with_registry(list_fn, start_fn, ...)
268
+ .with_discovery(discover_fn, ...)
269
+ .with_auth(auth_components)
270
+ .with_config(host="0.0.0.0", port=9000, auth_enabled=True)
271
+ .build())
272
+ """
273
+
274
+ def __init__(
275
+ self,
276
+ registry: RegistryFunctions,
277
+ config: Optional[ServerConfig] = None,
278
+ auth_components: Optional["AuthComponents"] = None,
279
+ ):
280
+ """Initialize factory with dependencies.
281
+
282
+ Args:
283
+ registry: Registry function implementations.
284
+ config: Server configuration (uses defaults if None).
285
+ auth_components: Optional auth components for authentication/authorization.
286
+ """
287
+ self._registry = registry
288
+ self._config = config or ServerConfig()
289
+ self._auth_components = auth_components
290
+ self._mcp: Optional[FastMCP] = None
291
+
292
+ @classmethod
293
+ def builder(cls) -> "MCPServerFactoryBuilder":
294
+ """Create a builder for fluent configuration.
295
+
296
+ Returns:
297
+ MCPServerFactoryBuilder instance.
298
+ """
299
+ return MCPServerFactoryBuilder()
300
+
301
+ @property
302
+ def registry(self) -> RegistryFunctions:
303
+ """Get the registry functions."""
304
+ return self._registry
305
+
306
+ @property
307
+ def config(self) -> ServerConfig:
308
+ """Get the server configuration."""
309
+ return self._config
310
+
311
+ def create_server(self) -> FastMCP:
312
+ """Create and configure FastMCP server instance.
313
+
314
+ The server is cached — repeated calls return the same instance.
315
+
316
+ Returns:
317
+ Configured FastMCP server with all tools registered.
318
+ """
319
+ if self._mcp is not None:
320
+ return self._mcp
321
+
322
+ mcp = FastMCP(
323
+ name="mcp-registry",
324
+ host=self._config.host,
325
+ port=self._config.port,
326
+ streamable_http_path=self._config.streamable_http_path,
327
+ sse_path=self._config.sse_path,
328
+ message_path=self._config.message_path,
329
+ )
330
+
331
+ self._register_core_tools(mcp)
332
+ self._register_discovery_tools(mcp)
333
+
334
+ self._mcp = mcp
335
+ logger.info(
336
+ "fastmcp_server_created",
337
+ host=self._config.host,
338
+ port=self._config.port,
339
+ discovery_enabled=self._registry.discover is not None,
340
+ )
341
+
342
+ return mcp
343
+
344
+ def create_asgi_app(self):
345
+ """Create ASGI application with metrics/health endpoints.
346
+
347
+ Creates a combined ASGI app that handles:
348
+ - /health: Liveness endpoint
349
+ - /ready: Readiness endpoint with internal checks
350
+ - /metrics: Prometheus metrics
351
+ - /mcp: MCP streamable HTTP endpoint (and related paths)
352
+
353
+ If auth is enabled (config.auth_enabled=True and auth_components provided),
354
+ the auth middleware will be applied to protect MCP endpoints.
355
+
356
+ Returns:
357
+ Combined ASGI app callable.
358
+ """
359
+ from starlette.applications import Starlette
360
+ from starlette.responses import JSONResponse, PlainTextResponse
361
+ from starlette.routing import Route
362
+
363
+ from .metrics import get_metrics
364
+
365
+ mcp = self.create_server()
366
+ mcp_app = mcp.streamable_http_app()
367
+
368
+ # Log if auth is configured (actual wrapping happens in _create_auth_combined_app)
369
+ if self._config.auth_enabled and self._auth_components:
370
+
371
+ logger.info(
372
+ "auth_middleware_enabled",
373
+ skip_paths=self._config.auth_skip_paths,
374
+ trusted_proxies=list(self._config.trusted_proxies),
375
+ )
376
+
377
+ # Health endpoint (liveness)
378
+ async def health_endpoint(request):
379
+ """Liveness endpoint (cheap ping)."""
380
+ return JSONResponse({"status": "ok", "service": "mcp-registry"})
381
+
382
+ # Readiness endpoint
383
+ async def ready_endpoint(request):
384
+ """Readiness endpoint with internal checks."""
385
+ checks = self._run_readiness_checks()
386
+ ready = all(v is True for k, v in checks.items() if isinstance(v, bool))
387
+ return JSONResponse(
388
+ {"ready": ready, "service": "mcp-registry", "checks": checks},
389
+ status_code=200 if ready else 503,
390
+ )
391
+
392
+ # Metrics endpoint
393
+ async def metrics_endpoint(request):
394
+ """Prometheus metrics endpoint."""
395
+ self._update_metrics()
396
+ return PlainTextResponse(
397
+ get_metrics(),
398
+ media_type="text/plain; version=0.0.4; charset=utf-8",
399
+ )
400
+
401
+ routes = [
402
+ Route("/health", health_endpoint, methods=["GET"]),
403
+ Route("/ready", ready_endpoint, methods=["GET"]),
404
+ Route("/metrics", metrics_endpoint, methods=["GET"]),
405
+ ]
406
+
407
+ aux_app = Starlette(routes=routes)
408
+
409
+ # Create auth-aware combined app
410
+ if self._config.auth_enabled and self._auth_components:
411
+ combined_app = self._create_auth_combined_app(aux_app, mcp_app)
412
+ else:
413
+
414
+ async def combined_app(scope, receive, send):
415
+ """Combined ASGI app that routes to metrics/health or MCP."""
416
+ if scope["type"] == "http":
417
+ path = scope.get("path", "")
418
+ if path in ("/health", "/ready", "/metrics"):
419
+ await aux_app(scope, receive, send)
420
+ return
421
+ await mcp_app(scope, receive, send)
422
+
423
+ return combined_app
424
+
425
+ def _create_auth_combined_app(self, aux_app, mcp_app):
426
+ """Create auth-enabled combined ASGI app.
427
+
428
+ This wraps the MCP app with authentication middleware while
429
+ keeping health/metrics endpoints unprotected.
430
+
431
+ Args:
432
+ aux_app: Starlette app for health/metrics endpoints.
433
+ mcp_app: FastMCP ASGI app.
434
+
435
+ Returns:
436
+ Combined ASGI app with auth middleware.
437
+ """
438
+ from starlette.responses import JSONResponse
439
+
440
+ from .domain.contracts.authentication import AuthRequest
441
+ from .domain.exceptions import AccessDeniedError, AuthenticationError
442
+
443
+ auth_components = self._auth_components
444
+ skip_paths = set(self._config.auth_skip_paths)
445
+ trusted_proxies = self._config.trusted_proxies
446
+
447
+ async def auth_combined_app(scope, receive, send):
448
+ """Combined ASGI app with authentication for MCP endpoints."""
449
+ if scope["type"] != "http":
450
+ # Non-HTTP (e.g., lifespan) - pass through
451
+ await mcp_app(scope, receive, send)
452
+ return
453
+
454
+ path = scope.get("path", "")
455
+
456
+ # Skip auth for health/metrics endpoints
457
+ if path in skip_paths:
458
+ await aux_app(scope, receive, send)
459
+ return
460
+
461
+ # For MCP endpoints, apply authentication
462
+ # Build headers dict from scope
463
+ headers = {}
464
+ for key, value in scope.get("headers", []):
465
+ headers[key.decode("latin-1").lower()] = value.decode("latin-1")
466
+
467
+ # Get client IP
468
+ client = scope.get("client")
469
+ source_ip = client[0] if client else "unknown"
470
+
471
+ # Trust X-Forwarded-For only from trusted proxies
472
+ if source_ip in trusted_proxies:
473
+ forwarded_for = headers.get("x-forwarded-for")
474
+ if forwarded_for:
475
+ source_ip = forwarded_for.split(",")[0].strip()
476
+
477
+ # Create auth request
478
+ auth_request = AuthRequest(
479
+ headers=headers,
480
+ source_ip=source_ip,
481
+ method=scope.get("method", ""),
482
+ path=path,
483
+ )
484
+
485
+ try:
486
+ # Authenticate
487
+ auth_context = auth_components.authn_middleware.authenticate(auth_request)
488
+
489
+ # Store auth context in scope for downstream handlers
490
+ scope["auth"] = auth_context
491
+
492
+ # Pass to MCP app
493
+ await mcp_app(scope, receive, send)
494
+
495
+ except AuthenticationError as e:
496
+ response = JSONResponse(
497
+ status_code=401,
498
+ content={
499
+ "error": "authentication_failed",
500
+ "message": e.message,
501
+ },
502
+ headers={"WWW-Authenticate": "Bearer, ApiKey"},
503
+ )
504
+ await response(scope, receive, send)
505
+
506
+ except AccessDeniedError as e:
507
+ response = JSONResponse(
508
+ status_code=403,
509
+ content={
510
+ "error": "access_denied",
511
+ "message": str(e),
512
+ },
513
+ )
514
+ await response(scope, receive, send)
515
+
516
+ return auth_combined_app
517
+
518
+ def _register_core_tools(self, mcp: FastMCP) -> None:
519
+ """Register core registry tools.
520
+
521
+ Args:
522
+ mcp: FastMCP server instance.
523
+ """
524
+ reg = self._registry
525
+
526
+ @mcp.tool()
527
+ def registry_list(state_filter: str = None) -> dict:
528
+ """List all providers with status and metadata.
529
+
530
+ Args:
531
+ state_filter: Optional filter by state (cold, ready, degraded, dead)
532
+ """
533
+ return reg.list(state_filter=state_filter)
534
+
535
+ @mcp.tool()
536
+ def registry_start(provider: str) -> dict:
537
+ """Explicitly start a provider and discover tools.
538
+
539
+ Args:
540
+ provider: Provider ID to start
541
+ """
542
+ return reg.start(provider=provider)
543
+
544
+ @mcp.tool()
545
+ def registry_stop(provider: str) -> dict:
546
+ """Stop a provider.
547
+
548
+ Args:
549
+ provider: Provider ID to stop
550
+ """
551
+ return reg.stop(provider=provider)
552
+
553
+ @mcp.tool()
554
+ def registry_invoke(
555
+ provider: str,
556
+ tool: str,
557
+ arguments: Optional[dict] = None,
558
+ timeout: float = 30.0,
559
+ ) -> dict:
560
+ """Invoke a tool on a provider.
561
+
562
+ Args:
563
+ provider: Provider ID
564
+ tool: Tool name to invoke
565
+ arguments: Tool arguments as dictionary (default: empty)
566
+ timeout: Timeout in seconds (default 30)
567
+ """
568
+ return reg.invoke(
569
+ provider=provider,
570
+ tool=tool,
571
+ arguments=arguments or {},
572
+ timeout=timeout,
573
+ )
574
+
575
+ @mcp.tool()
576
+ def registry_tools(provider: str) -> dict:
577
+ """Get detailed tool schemas for a provider.
578
+
579
+ Args:
580
+ provider: Provider ID
581
+ """
582
+ return reg.tools(provider=provider)
583
+
584
+ @mcp.tool()
585
+ def registry_details(provider: str) -> dict:
586
+ """Get detailed information about a provider.
587
+
588
+ Args:
589
+ provider: Provider ID
590
+ """
591
+ return reg.details(provider=provider)
592
+
593
+ @mcp.tool()
594
+ def registry_health() -> dict:
595
+ """Get registry health status including provider counts and metrics."""
596
+ return reg.health()
597
+
598
+ def _register_discovery_tools(self, mcp: FastMCP) -> None:
599
+ """Register discovery tools (if enabled).
600
+
601
+ Args:
602
+ mcp: FastMCP server instance.
603
+ """
604
+ reg = self._registry
605
+
606
+ @mcp.tool()
607
+ async def registry_discover() -> dict:
608
+ """Trigger immediate discovery cycle.
609
+
610
+ Runs discovery across all configured sources and returns
611
+ statistics about discovered, registered, and quarantined providers.
612
+ """
613
+ if reg.discover is None:
614
+ return {"error": "Discovery not configured"}
615
+ return await reg.discover()
616
+
617
+ @mcp.tool()
618
+ def registry_discovered() -> dict:
619
+ """List all discovered providers pending registration.
620
+
621
+ Shows providers found by discovery but not yet registered,
622
+ typically due to auto_register=false or pending approval.
623
+ """
624
+ if reg.discovered is None:
625
+ return {"error": "Discovery not configured"}
626
+ return reg.discovered()
627
+
628
+ @mcp.tool()
629
+ def registry_quarantine() -> dict:
630
+ """List quarantined providers with failure reasons.
631
+
632
+ Shows providers that failed validation and are waiting
633
+ for manual approval or rejection.
634
+ """
635
+ if reg.quarantine is None:
636
+ return {"error": "Discovery not configured"}
637
+ return reg.quarantine()
638
+
639
+ @mcp.tool()
640
+ async def registry_approve(provider: str) -> dict:
641
+ """Approve a quarantined provider for registration.
642
+
643
+ Args:
644
+ provider: Name of the quarantined provider to approve
645
+ """
646
+ if reg.approve is None:
647
+ return {"error": "Discovery not configured"}
648
+ return await reg.approve(provider=provider)
649
+
650
+ @mcp.tool()
651
+ def registry_sources() -> dict:
652
+ """List configured discovery sources with health status.
653
+
654
+ Shows all discovery sources (kubernetes, docker, filesystem, entrypoint)
655
+ with their current health and last discovery timestamp.
656
+ """
657
+ if reg.sources is None:
658
+ return {"error": "Discovery not configured"}
659
+ return reg.sources()
660
+
661
+ @mcp.tool()
662
+ def registry_metrics(format: str = "summary") -> dict:
663
+ """Get registry metrics and statistics.
664
+
665
+ Args:
666
+ format: Output format - "summary" (default), "prometheus", or "detailed"
667
+
668
+ Returns metrics including provider states, tool call counts, errors,
669
+ discovery statistics, and performance data.
670
+ """
671
+ if reg.metrics is None:
672
+ return {"error": "Metrics not available"}
673
+ return reg.metrics(format=format)
674
+
675
+ def _run_readiness_checks(self) -> Dict[str, Any]:
676
+ """Run readiness checks.
677
+
678
+ Returns:
679
+ Dictionary of check names to results.
680
+ """
681
+ checks: Dict[str, Any] = {}
682
+
683
+ # Check registry wiring
684
+ checks["registry_wired"] = True
685
+
686
+ # Check registry list
687
+ try:
688
+ data = self._registry.list()
689
+ checks["registry_list_ok"] = isinstance(data, dict) and "providers" in data
690
+ except Exception as e:
691
+ checks["registry_list_ok"] = False
692
+ checks["registry_list_error"] = str(e)
693
+
694
+ # Check registry health
695
+ try:
696
+ h = self._registry.health()
697
+ checks["registry_health_ok"] = isinstance(h, dict) and "status" in h
698
+ except Exception as e:
699
+ checks["registry_health_ok"] = False
700
+ checks["registry_health_error"] = str(e)
701
+
702
+ return checks
703
+
704
+ def _update_metrics(self) -> None:
705
+ """Update provider state metrics."""
706
+ from .metrics import update_provider_state
707
+
708
+ try:
709
+ data = self._registry.list()
710
+ if isinstance(data, dict) and "providers" in data:
711
+ for p in data.get("providers", []):
712
+ pid = p.get("provider_id") or p.get("name") or p.get("id")
713
+ if pid:
714
+ update_provider_state(
715
+ pid,
716
+ p.get("state", "cold"),
717
+ p.get("mode", "subprocess"),
718
+ )
719
+ except Exception as e:
720
+ logger.debug("metrics_update_failed", error=str(e))
721
+
722
+
723
+ # =============================================================================
724
+ # Builder (Optional Fluent API)
725
+ # =============================================================================
726
+
727
+
728
+ class MCPServerFactoryBuilder:
729
+ """Builder for MCPServerFactory with fluent API.
730
+
731
+ Provides a convenient way to construct an MCPServerFactory
732
+ with optional components.
733
+
734
+ Usage:
735
+ factory = (MCPServerFactory.builder()
736
+ .with_registry(list_fn, start_fn, stop_fn, invoke_fn, tools_fn, details_fn, health_fn)
737
+ .with_discovery(discover_fn=my_discover)
738
+ .with_config(port=9000)
739
+ .build())
740
+ """
741
+
742
+ def __init__(self):
743
+ """Initialize builder with empty state."""
744
+ self._list_fn: Optional[RegistryListFn] = None
745
+ self._start_fn: Optional[RegistryStartFn] = None
746
+ self._stop_fn: Optional[RegistryStopFn] = None
747
+ self._invoke_fn: Optional[RegistryInvokeFn] = None
748
+ self._tools_fn: Optional[RegistryToolsFn] = None
749
+ self._details_fn: Optional[RegistryDetailsFn] = None
750
+ self._health_fn: Optional[RegistryHealthFn] = None
751
+
752
+ self._discover_fn: Optional[RegistryDiscoverFn] = None
753
+ self._discovered_fn: Optional[RegistryDiscoveredFn] = None
754
+ self._quarantine_fn: Optional[RegistryQuarantineFn] = None
755
+ self._approve_fn: Optional[RegistryApproveFn] = None
756
+ self._sources_fn: Optional[RegistrySourcesFn] = None
757
+ self._metrics_fn: Optional[RegistryMetricsFn] = None
758
+
759
+ self._config: Optional[ServerConfig] = None
760
+ self._auth_components: Optional["AuthComponents"] = None
761
+
762
+ def with_registry(
763
+ self,
764
+ list_fn: RegistryListFn,
765
+ start_fn: RegistryStartFn,
766
+ stop_fn: RegistryStopFn,
767
+ invoke_fn: RegistryInvokeFn,
768
+ tools_fn: RegistryToolsFn,
769
+ details_fn: RegistryDetailsFn,
770
+ health_fn: RegistryHealthFn,
771
+ ) -> "MCPServerFactoryBuilder":
772
+ """Set core registry functions.
773
+
774
+ Args:
775
+ list_fn: Function to list providers.
776
+ start_fn: Function to start a provider.
777
+ stop_fn: Function to stop a provider.
778
+ invoke_fn: Function to invoke a tool.
779
+ tools_fn: Function to get tool schemas.
780
+ details_fn: Function to get provider details.
781
+ health_fn: Function to get registry health.
782
+
783
+ Returns:
784
+ Self for chaining.
785
+ """
786
+ self._list_fn = list_fn
787
+ self._start_fn = start_fn
788
+ self._stop_fn = stop_fn
789
+ self._invoke_fn = invoke_fn
790
+ self._tools_fn = tools_fn
791
+ self._details_fn = details_fn
792
+ self._health_fn = health_fn
793
+ return self
794
+
795
+ def with_discovery(
796
+ self,
797
+ discover_fn: Optional[RegistryDiscoverFn] = None,
798
+ discovered_fn: Optional[RegistryDiscoveredFn] = None,
799
+ quarantine_fn: Optional[RegistryQuarantineFn] = None,
800
+ approve_fn: Optional[RegistryApproveFn] = None,
801
+ sources_fn: Optional[RegistrySourcesFn] = None,
802
+ metrics_fn: Optional[RegistryMetricsFn] = None,
803
+ ) -> "MCPServerFactoryBuilder":
804
+ """Set discovery functions (all optional).
805
+
806
+ Args:
807
+ discover_fn: Async function to trigger discovery.
808
+ discovered_fn: Function to list discovered providers.
809
+ quarantine_fn: Function to list quarantined providers.
810
+ approve_fn: Async function to approve a provider.
811
+ sources_fn: Function to list discovery sources.
812
+ metrics_fn: Function to get metrics.
813
+
814
+ Returns:
815
+ Self for chaining.
816
+ """
817
+ self._discover_fn = discover_fn
818
+ self._discovered_fn = discovered_fn
819
+ self._quarantine_fn = quarantine_fn
820
+ self._approve_fn = approve_fn
821
+ self._sources_fn = sources_fn
822
+ self._metrics_fn = metrics_fn
823
+ return self
824
+
825
+ def with_config(
826
+ self,
827
+ host: str = "0.0.0.0",
828
+ port: int = 8000,
829
+ streamable_http_path: str = "/mcp",
830
+ sse_path: str = "/sse",
831
+ message_path: str = "/messages/",
832
+ auth_enabled: bool = False,
833
+ auth_skip_paths: tuple[str, ...] = ("/health", "/ready", "/_ready", "/metrics"),
834
+ trusted_proxies: frozenset[str] = frozenset(["127.0.0.1", "::1"]),
835
+ ) -> "MCPServerFactoryBuilder":
836
+ """Set server configuration.
837
+
838
+ Args:
839
+ host: Host to bind to.
840
+ port: Port to bind to.
841
+ streamable_http_path: Path for MCP streamable HTTP endpoint.
842
+ sse_path: Path for SSE endpoint.
843
+ message_path: Path for message endpoint.
844
+ auth_enabled: Whether to enable authentication (default: False).
845
+ auth_skip_paths: Paths to skip authentication.
846
+ trusted_proxies: Trusted proxy IPs for X-Forwarded-For.
847
+
848
+ Returns:
849
+ Self for chaining.
850
+ """
851
+ self._config = ServerConfig(
852
+ host=host,
853
+ port=port,
854
+ streamable_http_path=streamable_http_path,
855
+ sse_path=sse_path,
856
+ message_path=message_path,
857
+ auth_enabled=auth_enabled,
858
+ auth_skip_paths=auth_skip_paths,
859
+ trusted_proxies=trusted_proxies,
860
+ )
861
+ return self
862
+
863
+ def with_auth(
864
+ self,
865
+ auth_components: "AuthComponents",
866
+ ) -> "MCPServerFactoryBuilder":
867
+ """Set authentication components.
868
+
869
+ Args:
870
+ auth_components: Auth components from bootstrap_auth().
871
+
872
+ Returns:
873
+ Self for chaining.
874
+
875
+ Note:
876
+ You also need to set auth_enabled=True in with_config() for
877
+ authentication to be active.
878
+ """
879
+ self._auth_components = auth_components
880
+ return self
881
+
882
+ def build(self) -> MCPServerFactory:
883
+ """Build the factory.
884
+
885
+ Returns:
886
+ Configured MCPServerFactory instance.
887
+
888
+ Raises:
889
+ ValueError: If required registry functions not provided.
890
+ """
891
+ if not all(
892
+ [
893
+ self._list_fn,
894
+ self._start_fn,
895
+ self._stop_fn,
896
+ self._invoke_fn,
897
+ self._tools_fn,
898
+ self._details_fn,
899
+ self._health_fn,
900
+ ]
901
+ ):
902
+ raise ValueError("All core registry functions must be provided via with_registry()")
903
+
904
+ registry = RegistryFunctions(
905
+ list=self._list_fn,
906
+ start=self._start_fn,
907
+ stop=self._stop_fn,
908
+ invoke=self._invoke_fn,
909
+ tools=self._tools_fn,
910
+ details=self._details_fn,
911
+ health=self._health_fn,
912
+ discover=self._discover_fn,
913
+ discovered=self._discovered_fn,
914
+ quarantine=self._quarantine_fn,
915
+ approve=self._approve_fn,
916
+ sources=self._sources_fn,
917
+ metrics=self._metrics_fn,
918
+ )
919
+
920
+ return MCPServerFactory(registry, self._config, self._auth_components)
921
+
922
+
923
+ # =============================================================================
924
+ # Backward Compatibility Layer
925
+ # DEPRECATED: Will be removed in v0.3.0
926
+ # =============================================================================
927
+
928
+ _compat_factory: Optional[MCPServerFactory] = None
929
+
930
+
931
+ def setup_fastmcp_server(
932
+ registry_list_fn,
933
+ registry_start_fn,
934
+ registry_stop_fn,
935
+ registry_tools_fn,
936
+ registry_invoke_fn,
937
+ registry_details_fn,
938
+ registry_health_fn,
939
+ # Discovery functions (optional)
940
+ registry_discover_fn=None,
941
+ registry_discovered_fn=None,
942
+ registry_quarantine_fn=None,
943
+ registry_approve_fn=None,
944
+ registry_sources_fn=None,
945
+ registry_metrics_fn=None,
946
+ ):
947
+ """DEPRECATED: Use MCPServerFactory instead.
948
+
949
+ This function exists for backward compatibility only.
950
+ Will be removed in v0.3.0.
951
+
952
+ Args:
953
+ registry_list_fn: Function to list providers.
954
+ registry_start_fn: Function to start a provider.
955
+ registry_stop_fn: Function to stop a provider.
956
+ registry_tools_fn: Function to get tool schemas.
957
+ registry_invoke_fn: Function to invoke a tool.
958
+ registry_details_fn: Function to get provider details.
959
+ registry_health_fn: Function to get registry health.
960
+ registry_discover_fn: Optional async function to trigger discovery.
961
+ registry_discovered_fn: Optional function to list discovered providers.
962
+ registry_quarantine_fn: Optional function to list quarantined providers.
963
+ registry_approve_fn: Optional async function to approve a provider.
964
+ registry_sources_fn: Optional function to list discovery sources.
965
+ registry_metrics_fn: Optional function to get metrics.
966
+ """
967
+ import warnings
968
+
969
+ warnings.warn(
970
+ "setup_fastmcp_server() is deprecated. Use MCPServerFactory instead.",
971
+ DeprecationWarning,
972
+ stacklevel=2,
973
+ )
974
+
975
+ global _compat_factory
976
+
977
+ registry = RegistryFunctions(
978
+ list=registry_list_fn,
979
+ start=registry_start_fn,
980
+ stop=registry_stop_fn,
981
+ invoke=registry_invoke_fn,
982
+ tools=registry_tools_fn,
983
+ details=registry_details_fn,
984
+ health=registry_health_fn,
985
+ discover=registry_discover_fn,
986
+ discovered=registry_discovered_fn,
987
+ quarantine=registry_quarantine_fn,
988
+ approve=registry_approve_fn,
989
+ sources=registry_sources_fn,
990
+ metrics=registry_metrics_fn,
991
+ )
992
+
993
+ _compat_factory = MCPServerFactory(registry)
994
+ logger.info("fastmcp_server_configured_via_deprecated_api")
995
+
996
+
997
+ def create_fastmcp_server():
998
+ """DEPRECATED: Use MCPServerFactory.create_server() instead.
999
+
1000
+ Returns:
1001
+ Configured FastMCP server instance.
1002
+
1003
+ Raises:
1004
+ RuntimeError: If setup_fastmcp_server() was not called first.
1005
+ """
1006
+ import warnings
1007
+
1008
+ warnings.warn(
1009
+ "create_fastmcp_server() is deprecated. Use MCPServerFactory instead.",
1010
+ DeprecationWarning,
1011
+ stacklevel=2,
1012
+ )
1013
+
1014
+ if _compat_factory is None:
1015
+ raise RuntimeError(
1016
+ "setup_fastmcp_server() must be called before create_fastmcp_server(). "
1017
+ "Consider migrating to MCPServerFactory."
1018
+ )
1019
+
1020
+ return _compat_factory.create_server()
1021
+
1022
+
1023
+ def run_fastmcp_server():
1024
+ """DEPRECATED: Use MCPServerFactory.create_asgi_app() with uvicorn.
1025
+
1026
+ Runs the FastMCP HTTP server. Blocks until shutdown.
1027
+
1028
+ Raises:
1029
+ RuntimeError: If setup_fastmcp_server() was not called first.
1030
+ """
1031
+ import warnings
1032
+
1033
+ warnings.warn(
1034
+ "run_fastmcp_server() is deprecated. Use MCPServerFactory instead.",
1035
+ DeprecationWarning,
1036
+ stacklevel=2,
1037
+ )
1038
+
1039
+ import uvicorn
1040
+
1041
+ from .metrics import init_metrics
1042
+
1043
+ if _compat_factory is None:
1044
+ raise RuntimeError(
1045
+ "setup_fastmcp_server() must be called before run_fastmcp_server(). Consider migrating to MCPServerFactory."
1046
+ )
1047
+
1048
+ logger.info(
1049
+ "fastmcp_http_server_starting",
1050
+ host=_compat_factory.config.host,
1051
+ port=_compat_factory.config.port,
1052
+ streamable_http_path=_compat_factory.config.streamable_http_path,
1053
+ metrics_path="/metrics",
1054
+ )
1055
+
1056
+ init_metrics(version="1.0.0")
1057
+ app = _compat_factory.create_asgi_app()
1058
+
1059
+ uvicorn.run(
1060
+ app,
1061
+ host=_compat_factory.config.host,
1062
+ port=_compat_factory.config.port,
1063
+ log_level="warning",
1064
+ access_log=False,
1065
+ )
1066
+
1067
+
1068
+ # =============================================================================
1069
+ # Module exports
1070
+ # =============================================================================
1071
+
1072
+ __all__ = [
1073
+ # New API
1074
+ "MCPServerFactory",
1075
+ "MCPServerFactoryBuilder",
1076
+ "RegistryFunctions",
1077
+ "ServerConfig",
1078
+ # Protocols
1079
+ "RegistryListFn",
1080
+ "RegistryStartFn",
1081
+ "RegistryStopFn",
1082
+ "RegistryInvokeFn",
1083
+ "RegistryToolsFn",
1084
+ "RegistryDetailsFn",
1085
+ "RegistryHealthFn",
1086
+ "RegistryDiscoverFn",
1087
+ "RegistryDiscoveredFn",
1088
+ "RegistryQuarantineFn",
1089
+ "RegistryApproveFn",
1090
+ "RegistrySourcesFn",
1091
+ "RegistryMetricsFn",
1092
+ # Deprecated (backward compatibility)
1093
+ "setup_fastmcp_server",
1094
+ "create_fastmcp_server",
1095
+ "run_fastmcp_server",
1096
+ ]
1097
+
1098
+
1099
+ if __name__ == "__main__":
1100
+ from .logging_config import setup_logging
1101
+
1102
+ setup_logging(level="INFO", json_format=False)
1103
+
1104
+ # Example with deprecated API (will emit warning)
1105
+ run_fastmcp_server()