55SPDX-License-Identifier: Apache-2.0
66Authors: Fred Araujo, Teryl Taylor
77
8- MCP Plugin Runtime using FastMCP with SSL/TLS support.
8+ MCP Plugin Runtime using MCPServer with SSL/TLS support.
99
1010This runtime does the following:
11- - Uses FastMCP from the MCP Python SDK
11+ - Uses MCPServer from the MCP Python SDK
1212- Supports both mTLS and non-mTLS configurations
1313- Reads configuration from PLUGINS_SERVER_* environment variables or uses configurations
1414 the plugin config.yaml
1919
2020 >>> from cpex.framework.models import MCPServerConfig
2121 >>> config = MCPServerConfig(host="localhost", port=8000)
22- >>> server = SSLCapableFastMCP (server_config=config, name="TestServer")
22+ >>> server = SSLCapableMCPServer (server_config=config, name="TestServer")
2323 >>> server.settings.host
2424 'localhost'
2525 >>> server.settings.port
2929
3030 >>> from cpex.framework.models import MCPServerConfig
3131 >>> config = MCPServerConfig(host="127.0.0.1", port=8000, tls=None)
32- >>> server = SSLCapableFastMCP (server_config=config, name="NoTLSServer")
32+ >>> server = SSLCapableMCPServer (server_config=config, name="NoTLSServer")
3333 >>> ssl_config = server._get_ssl_config()
3434 >>> ssl_config
3535 {}
3838
3939 >>> from cpex.framework.models import MCPServerConfig
4040 >>> config = MCPServerConfig(host="localhost", port=9000)
41- >>> server = SSLCapableFastMCP (server_config=config, name="ConfigTest")
41+ >>> server = SSLCapableMCPServer (server_config=config, name="ConfigTest")
4242 >>> server.server_config.host
4343 'localhost'
4444 >>> server.server_config.port
4545 9000
4646
47- Settings are properly passed to FastMCP :
47+ Settings are properly passed to MCPServer :
4848
4949 >>> from cpex.framework.models import MCPServerConfig
5050 >>> config = MCPServerConfig(host="0.0.0.0", port=8080)
51- >>> server = SSLCapableFastMCP (server_config=config, name="SettingsTest")
51+ >>> server = SSLCapableMCPServer (server_config=config, name="SettingsTest")
5252 >>> server.settings.host
5353 '0.0.0.0'
5454 >>> server.settings.port
6666
6767# Third-Party
6868from fastapi import Response , status
69- from mcp .server .fastmcp import FastMCP
69+ from mcp .server .mcpserver import MCPServer
7070from mcp .server .transport_security import TransportSecuritySettings
7171from prometheus_client import REGISTRY , Gauge , generate_latest
7272
@@ -185,15 +185,15 @@ async def invoke_hook(hook_type: str, plugin_name: str, payload: Dict[str, Any],
185185 return await SERVER .invoke_hook (hook_type , plugin_name , payload , context )
186186
187187
188- class SSLCapableFastMCP ( FastMCP ):
189- """FastMCP server with SSL/TLS support using MCPServerConfig.
188+ class SSLCapableMCPServer ( MCPServer ):
189+ """MCPServer with SSL/TLS support using MCPServerConfig.
190190
191191 Examples:
192- Create an SSL-capable FastMCP server :
192+ Create an SSL-capable MCPServer :
193193
194194 >>> from cpex.framework.models import MCPServerConfig
195195 >>> config = MCPServerConfig(host="127.0.0.1", port=8000)
196- >>> server = SSLCapableFastMCP (server_config=config, name="TestServer")
196+ >>> server = SSLCapableMCPServer (server_config=config, name="TestServer")
197197 >>> server.settings.host
198198 '127.0.0.1'
199199 >>> server.settings.port
@@ -205,13 +205,13 @@ def __init__(self, server_config: MCPServerConfig, *args, **kwargs):
205205
206206 Args:
207207 server_config: the MCP server configuration including mTLS information.
208- *args: Additional positional arguments passed to FastMCP .
209- **kwargs: Additional keyword arguments passed to FastMCP .
208+ *args: Additional positional arguments passed to MCPServer .
209+ **kwargs: Additional keyword arguments passed to MCPServer .
210210
211211 Examples:
212212 >>> from cpex.framework.models import MCPServerConfig
213213 >>> config = MCPServerConfig(host="0.0.0.0", port=9000)
214- >>> server = SSLCapableFastMCP (server_config=config, name="PluginServer")
214+ >>> server = SSLCapableMCPServer (server_config=config, name="PluginServer")
215215 >>> server.server_config.host
216216 '0.0.0.0'
217217 >>> server.server_config.port
@@ -220,13 +220,14 @@ def __init__(self, server_config: MCPServerConfig, *args, **kwargs):
220220 # Load server config from environment
221221
222222 self .server_config = server_config
223- # Override FastMCP settings with our server config
224- if "host" not in kwargs :
225- kwargs ["host" ] = self .server_config .host
226- if "port" not in kwargs :
227- kwargs ["port" ] = self .server_config .port
228- if self .server_config .uds and kwargs .get ("transport_security" ) is None :
229- kwargs ["transport_security" ] = TransportSecuritySettings (
223+ # MCPServer v2 does not accept host/port/transport_security in __init__;
224+ # transport_security is passed to streamable_http_app(), host/port to run methods.
225+ kwargs .pop ("host" , None )
226+ kwargs .pop ("port" , None )
227+
228+ transport_security = kwargs .pop ("transport_security" , None )
229+ if self .server_config .uds and transport_security is None :
230+ transport_security = TransportSecuritySettings (
230231 enable_dns_rebinding_protection = True ,
231232 allowed_hosts = [
232233 "127.0.0.1" ,
@@ -245,6 +246,7 @@ def __init__(self, server_config: MCPServerConfig, *args, **kwargs):
245246 "http://[::1]:*" ,
246247 ],
247248 )
249+ self ._transport_security = transport_security
248250
249251 super ().__init__ (* args , ** kwargs )
250252
@@ -257,7 +259,7 @@ def _get_ssl_config(self) -> dict:
257259 Examples:
258260 >>> from cpex.framework.models import MCPServerConfig
259261 >>> config = MCPServerConfig(host="127.0.0.1", port=8000, tls=None)
260- >>> server = SSLCapableFastMCP (server_config=config, name="TestServer")
262+ >>> server = SSLCapableMCPServer (server_config=config, name="TestServer")
261263 >>> ssl_config = server._get_ssl_config()
262264 >>> ssl_config
263265 {}
@@ -361,10 +363,10 @@ async def metrics_disabled():
361363 # Create a minimal Starlette app with only the health endpoint
362364 health_app = Starlette (routes = routes )
363365
364- logger .info (f"Starting HTTP health check server on { self .settings .host } :{ health_port } " )
366+ logger .info (f"Starting HTTP health check server on { self .server_config .host } :{ health_port } " )
365367 config = uvicorn .Config (
366368 app = health_app ,
367- host = self .settings .host ,
369+ host = self .server_config .host ,
368370 port = health_port ,
369371 log_level = "warning" , # Reduce noise from health checks
370372 )
@@ -379,13 +381,13 @@ async def run_streamable_http_async(self) -> None:
379381
380382 >>> from cpex.framework.models import MCPServerConfig
381383 >>> config = MCPServerConfig(host="0.0.0.0", port=9000)
382- >>> server = SSLCapableFastMCP (server_config=config, name="HTTPServer")
384+ >>> server = SSLCapableMCPServer (server_config=config, name="HTTPServer")
383385 >>> server.settings.host
384386 '0.0.0.0'
385387 >>> server.settings.port
386388 9000
387389 """
388- starlette_app = self .streamable_http_app ()
390+ starlette_app = self .streamable_http_app (transport_security = getattr ( self , '_transport_security' , None ) )
389391
390392 # Add health check endpoint to main app
391393 # Third-Party
@@ -438,8 +440,8 @@ async def metrics_disabled():
438440 ssl_config = self ._get_ssl_config ()
439441 config_kwargs = {
440442 "app" : starlette_app ,
441- "host" : self .settings .host ,
442- "port" : self .settings .port ,
443+ "host" : self .server_config .host ,
444+ "port" : self .server_config .port ,
443445 "log_level" : self .settings .log_level .lower (),
444446 }
445447 config_kwargs .update (ssl_config )
@@ -450,13 +452,13 @@ async def metrics_disabled():
450452 config_kwargs ["uds" ] = self .server_config .uds
451453 logger .info (f"Starting plugin server on unix socket { self .server_config .uds } " )
452454 else :
453- logger .info (f"Starting plugin server on { self .settings .host } :{ self .settings .port } " )
455+ logger .info (f"Starting plugin server on { self .server_config .host } :{ self .server_config .port } " )
454456 config = uvicorn .Config (** config_kwargs ) # type: ignore[arg-type]
455457 server = uvicorn .Server (config )
456458
457459 # If SSL is enabled, start a separate HTTP health check server
458460 if ssl_config and not self .server_config .uds :
459- health_port = self .settings .port + 1000 # Use port+1000 for health checks
461+ health_port = self .server_config .port + 1000 # Use port+1000 for health checks
460462 logger .info (f"SSL enabled - starting separate HTTP health check on port { health_port } " )
461463 # Run both servers concurrently
462464 await asyncio .gather (server .serve (), self ._start_health_check_server (health_port ))
@@ -466,7 +468,7 @@ async def metrics_disabled():
466468
467469
468470async def run () -> None :
469- """Run the external plugin server with FastMCP .
471+ """Run the external plugin server with MCPServer .
470472
471473 Supports both stdio and HTTP transports. Auto-detects transport based on stdin
472474 (if stdin is not a TTY, uses stdio mode), or you can explicitly set PLUGINS_TRANSPORT.
@@ -491,7 +493,7 @@ async def run() -> None:
491493 >>> SERVER is None
492494 True
493495
494- FastMCP server names are defined as constants:
496+ MCPServer names are defined as constants:
495497
496498 >>> from cpex.framework.constants import MCP_SERVER_NAME
497499 >>> isinstance(MCP_SERVER_NAME, str)
@@ -524,33 +526,33 @@ async def run() -> None:
524526
525527 try :
526528 if transport == "stdio" :
527- # Create basic FastMCP server for stdio (no SSL support needed for stdio)
528- mcp = FastMCP (
529+ # Create basic MCPServer for stdio (no SSL support needed for stdio)
530+ mcp = MCPServer (
529531 name = MCP_SERVER_NAME ,
530532 instructions = MCP_SERVER_INSTRUCTIONS ,
531533 )
532534
533- # Register module-level tool functions with FastMCP
535+ # Register module-level tool functions with MCPServer
534536 mcp .tool (name = GET_PLUGIN_CONFIGS )(get_plugin_configs )
535537 mcp .tool (name = GET_PLUGIN_CONFIG )(get_plugin_config )
536538 mcp .tool (name = INVOKE_HOOK )(invoke_hook )
537539 # set the plugin_info gauge on startup
538540 PLUGIN_INFO .labels (server_name = MCP_SERVER_NAME , transport = "stdio" , ssl_enabled = "false" ).set (1 )
539541
540542 # Run with stdio transport
541- logger .info ("Starting MCP plugin server with FastMCP (stdio transport)" )
543+ logger .info ("Starting MCP plugin server with MCPServer (stdio transport)" )
542544 await mcp .run_stdio_async ()
543545
544546 else : # http or streamablehttp
545547 server_config : MCPServerConfig = SERVER .get_server_config ()
546- # Create FastMCP server with SSL support
547- mcp = SSLCapableFastMCP (
548+ # Create MCPServer with SSL support
549+ mcp = SSLCapableMCPServer (
548550 server_config ,
549551 name = MCP_SERVER_NAME ,
550552 instructions = MCP_SERVER_INSTRUCTIONS ,
551553 )
552554
553- # Register module-level tool functions with FastMCP
555+ # Register module-level tool functions with MCPServer
554556 mcp .tool (name = GET_PLUGIN_CONFIGS )(get_plugin_configs )
555557 mcp .tool (name = GET_PLUGIN_CONFIG )(get_plugin_config )
556558 mcp .tool (name = INVOKE_HOOK )(invoke_hook )
@@ -564,7 +566,7 @@ async def run() -> None:
564566 f"Prometheus metrics available at http://{ server_config .host } :{ server_config .port } /metrics/prometheus"
565567 )
566568 # Run with streamable-http transport
567- logger .info ("Starting MCP plugin server with FastMCP (HTTP transport)" )
569+ logger .info ("Starting MCP plugin server with MCPServer (HTTP transport)" )
568570 await mcp .run_streamable_http_async ()
569571
570572 except Exception :
0 commit comments