Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
mcp_serve.egg-info
build
dist
certs
env
.eggs
__pycache__
37 changes: 37 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,43 @@ This example is for basic manifests to work in Kind (or Kubernetes/Openshift). N

We will be making a Kubernetes Operator to create this set of stuff soon.

### SSL

Generate keys

```bash
mkdir -p ./certs
openssl req -x509 -newkey rsa:4096 -keyout ./certs/key.pem -out ./certs/cert.pem -sha256 -days 365 -nodes -subj '/CN=localhost'
```

And start the server, indicating you want to use them.

```bash
mcpserver start --transport http --port 8089 --ssl-keyfile ./certs/key.pem --ssl-certfile ./certs/cert.pem
```

For the client, the way that it works is that httpx discovers the certs via [environment variables](https://github.com/modelcontextprotocol/python-sdk/issues/870#issuecomment-3449911720). E.g., try the test first without them:

```bash
python3 examples/ssl/test_ssl_client.py
📡 Connecting to https://localhost:8089/mcp...
❌ Connection failed: Client failed to connect: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self-signed certificate (_ssl.c:1000)
```

Now export the envars:

```bash
export SSL_CERT_DIR=$(pwd)/certs
export SSL_CERT_FILE=$(pwd)/certs/cert.pem
```
```console
📡 Connecting to https://localhost:8089/mcp...
⭐ Discovered tool: simple_echo

✅ Connection successful!
```
And you'll see the server get hit.

### Design Choices

Here are a few design choices (subject to change, of course). I am starting with re-implementing our fractale agents with this framework. For that, instead of agents being tied to specific functions (as classes on their agent functions) we will have a flexible agent class that changes function based on a chosen prompt. It will use mcp functions, prompts, and resources. In addition:
Expand Down
35 changes: 35 additions & 0 deletions examples/ssl/test_ssl_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import asyncio
import sys
from fastmcp import Client
from fastmcp.client.transports import StreamableHttpTransport

port = 8089
if len(sys.argv) > 1:
port = sys.argv[1]

# Use https for SSL stuff
url = f"https://localhost:{port}/mcp"

# Create the transport with the verification
transport = StreamableHttpTransport(url=url)
client = Client(transport)

async def list_tools():
"""
Connects to the Flux MCP server via SSL and lists discovered tools.
WITH SSL! Oohlala.
"""
print(f"📡 Connecting to {url}...")
async with client:
tools = await client.list_tools()
if not tools:
print(" ⚠️ No tools discovered.")
for tool in tools:
print(f" ⭐ Discovered tool: {tool.name}")
print("\n✅ Connection successful!")

if __name__ == "__main__":
try:
asyncio.run(list_tools())
except Exception as e:
print(f"❌ Connection failed: {e}")
3 changes: 3 additions & 0 deletions mcpserver/cli/args.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ def populate_start_args(start):
start.add_argument("--path", help="Server path for mcp", default=default_path)
start.add_argument("--config", help="Configuration file for server.")

# Args for ssl
start.add_argument("--ssl-keyfile", default=None, help="SSL key file (e.g. key.pem)")
start.add_argument("--ssl-certfile", default=None, help="SSL certificate file (e.g. cert.pem)")
start.add_argument(
"--mask-error_details",
help="Mask error details (for higher security deployments)",
Expand Down
4 changes: 4 additions & 0 deletions mcpserver/cli/manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ def get_manager(mcp, cfg):
for tool in manager.load_tools(mcp, cfg.include, cfg.exclude):
print(f" ✅ Registered: {tool.name}")

# Visual to show user we have ssl
if cfg.server.ssl_keyfile is not None and cfg.server.ssl_certfile is not None:
print(f" 🔐 SSL Enabled")


def register(mcp, cfg: MCPConfig):
"""
Expand Down
23 changes: 21 additions & 2 deletions mcpserver/cli/start.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
import warnings

import uvicorn
from fastapi import FastAPI

# Ignore these for now
warnings.filterwarnings("ignore", category=DeprecationWarning, module="websockets.legacy")
warnings.filterwarnings(
"ignore", category=DeprecationWarning, module="uvicorn.protocols.websockets"
)


from mcpserver.app import init_mcp
from mcpserver.cli.manager import get_manager
from mcpserver.core.config import MCPConfig
Expand All @@ -21,7 +31,7 @@ def main(args, extra, **kwargs):

# Get the tool manager and register discovered tools
mcp = init_mcp(cfg.exclude, cfg.include, args.mask_error_details)
manager = get_manager(mcp, cfg)
get_manager(mcp, cfg)

# Create ASGI app from MCP server
mcp_app = mcp.http_app(path=cfg.server.path)
Expand All @@ -34,7 +44,16 @@ def main(args, extra, **kwargs):

# http transports can accept a host and port
if "http" in cfg.server.transport:
mcp.run(transport=cfg.server.transport, port=cfg.server.port, host=cfg.server.host)
# mcp.run(transport=cfg.server.transport, port=cfg.server.port, host=cfg.server.host)
uvicorn.run(
app,
host=cfg.server.host,
port=cfg.server.port,
ssl_keyfile=cfg.server.ssl_keyfile,
ssl_certfile=cfg.server.ssl_certfile,
timeout_graceful_shutdown=75,
timeout_keep_alive=60,
)

# stdio does not!
else:
Expand Down
9 changes: 8 additions & 1 deletion mcpserver/core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class ServerConfig:
"""

transport: str = defaults.transport
ssl_keyfile: str = None
ssl_certfile: str = None
port: int = int(defaults.port)
host: str = defaults.host
path: str = defaults.path
Expand Down Expand Up @@ -90,7 +92,12 @@ def from_args(cls, args):
"""
return cls(
server=ServerConfig(
transport=args.transport, port=args.port, host=args.host, path=args.path
transport=args.transport,
port=args.port,
host=args.host,
path=args.path,
ssl_certfile=args.ssl_certfile,
ssl_keyfile=args.ssl_keyfile,
),
include=args.include,
exclude=args.exclude,
Expand Down
2 changes: 1 addition & 1 deletion mcpserver/version.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.0.12"
__version__ = "0.0.13"
AUTHOR = "Vanessa Sochat"
AUTHOR_EMAIL = "vsoch@users.noreply.github.com"
NAME = "mcp-serve"
Expand Down