Skip to content

Commit 1d60a8f

Browse files
authored
Merge pull request #8 from dprince/tls
Add TLS support for incoming MCP server connections
2 parents b0c6d04 + c026e28 commit 1d60a8f

4 files changed

Lines changed: 50 additions & 3 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,7 @@ These go under the `mcp_transport_security` key:
9797
- `token`: Token to use for basic authentication. The caller must send this value in request header `Authorization`. For example: `Authorization: Bearer supersecret`. Default `""`.
9898
- `enable_dns_rebinding_protection`: Enable DNS rebinding protection")
9999
- `allowed_hosts`: List of allowed hosts. Default `["*:*"]`
100-
- `allowed_origins`: list of allowed origins. Default `["http://*:*"]`.
100+
- `allowed_origins`: list of allowed origins. Default `["http://*:*", "https://*:*"]`.
101101

102102
With the exception of the `token` field the rest comes from the [Anthropic MCP SDK](https://github.com/modelcontextprotocol/python-sdk).
103103

config.yaml.sample

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ openshift:
1616
allow_write: false
1717
insecure: false
1818

19+
# Incoming TLS settings for the MCP server
20+
#tls:
21+
# ssl_certfile: ./cert.pem
22+
# ssl_keyfile: ./key.pem
23+
# ssl_keyfile_password: null
24+
# ssl_ca_certs: null
25+
1926
mcp_transport_security:
2027
token: supersecret
2128
enable_dns_rebinding_protection: false

src/rhos_ls_mcps/main.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
import contextlib
88
import logging
9+
import ssl
910

1011
from mcp.server.fastmcp import FastMCP
1112
from starlette.applications import Starlette
@@ -108,6 +109,16 @@ def main():
108109

109110
# Pass string instead of an instance to support multiple workers.
110111
# Pass the factory=True argument to use a function (create_app) instead of a variable.
112+
ssl_kwargs = {}
113+
if config.tls.ssl_certfile:
114+
ssl_kwargs["ssl_certfile"] = config.tls.ssl_certfile
115+
ssl_kwargs["ssl_keyfile"] = config.tls.ssl_keyfile
116+
if config.tls.ssl_keyfile_password:
117+
ssl_kwargs["ssl_keyfile_password"] = config.tls.ssl_keyfile_password
118+
if config.tls.ssl_ca_certs:
119+
ssl_kwargs["ssl_ca_certs"] = config.tls.ssl_ca_certs
120+
ssl_kwargs["ssl_cert_reqs"] = ssl.CERT_REQUIRED
121+
111122
uvicorn.run(
112123
"rhos_ls_mcps.main:create_app",
113124
host=config.ip,
@@ -118,6 +129,7 @@ def main():
118129
access_log=False,
119130
factory=True,
120131
log_config=log_config,
132+
**ssl_kwargs,
121133
)
122134

123135

src/rhos_ls_mcps/settings.py

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import yaml
44
from typing import Optional
55

6-
from pydantic import Field
6+
from pydantic import Field, model_validator
77
from pydantic_settings import BaseSettings
88

99
from rhos_ls_mcps import oc_defaults
@@ -40,6 +40,31 @@ class OpenShiftSettings(BaseSettings):
4040
)
4141

4242

43+
class TLSSettings(BaseSettings):
44+
ssl_certfile: Optional[str] = Field(
45+
default=None, description="Path to SSL certificate file for incoming TLS"
46+
)
47+
ssl_keyfile: Optional[str] = Field(
48+
default=None, description="Path to SSL private key file for incoming TLS"
49+
)
50+
ssl_keyfile_password: Optional[str] = Field(
51+
default=None, description="Password for encrypted SSL key file"
52+
)
53+
ssl_ca_certs: Optional[str] = Field(
54+
default=None,
55+
description="Path to CA certs file for client certificate verification (mutual TLS)",
56+
)
57+
58+
@model_validator(mode="after")
59+
def check_certfile_required(self) -> "TLSSettings":
60+
dependent = [self.ssl_keyfile, self.ssl_keyfile_password, self.ssl_ca_certs]
61+
if any(dependent) and not self.ssl_certfile:
62+
raise ValueError(
63+
"tls.ssl_certfile is required when other tls.ssl_* fields are set"
64+
)
65+
return self
66+
67+
4368
class TransportSecuritySettings(BaseSettings):
4469
token: Optional[str] = Field(
4570
default=os.environ.get("MCP_SECURITY_TOKEN"),
@@ -50,7 +75,7 @@ class TransportSecuritySettings(BaseSettings):
5075
)
5176
allowed_hosts: list[str] = Field(default=["*:*"], description="Allowed hosts")
5277
allowed_origins: list[str] = Field(
53-
default=["http://*:*"], description="Allowed origins"
78+
default=["http://*:*", "https://*:*"], description="Allowed origins"
5479
)
5580

5681

@@ -79,6 +104,9 @@ class Settings(BaseSettings):
79104
mcp_transport_security: TransportSecuritySettings = Field(
80105
default=TransportSecuritySettings(), description="Transport security settings"
81106
)
107+
tls: TLSSettings = Field(
108+
default=TLSSettings(), description="Incoming TLS settings for the MCP server"
109+
)
82110

83111

84112
def load_config():

0 commit comments

Comments
 (0)