Skip to content

Add HTTPS support for secure local development #482

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
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
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,19 @@ You can paste the Server Entry into your existing `mcp.json` file under your cho

The inspector supports bearer token authentication for SSE connections. Enter your token in the UI when connecting to an MCP server, and it will be sent in the Authorization header. You can override the header name using the input field in the sidebar.

### HTTPS Configuration

oAuth testing in the browser involves security libraries that require serving the Client UI over a secure connection. To enable HTTPS for both the client and server, set these environment variables:

- `INSPECTOR_SSL_CERT_PATH`: Path to SSL certificate file (.crt or .pem)
- `INSPECTOR_SSL_KEY_PATH`: Path to SSL private key file (.key or .pem)

When both variables are set, the inspector will automatically use HTTPS:
- Client UI: `https://localhost:6274`
- Proxy server: `https://localhost:6277`

If these variables are not set, the inspector defaults to HTTP.

### Security Considerations

The MCP Inspector includes a proxy server that can run and communicate with local MCP processes. The proxy server should not be exposed to untrusted networks as it has permissions to spawn local processes and can connect to any specified MCP server.
Expand Down
113 changes: 79 additions & 34 deletions client/bin/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,47 +4,92 @@ import { join, dirname } from "path";
import { fileURLToPath } from "url";
import handler from "serve-handler";
import http from "http";
import https from "https";
import fs from "fs";

const __dirname = dirname(fileURLToPath(import.meta.url));
const distPath = join(__dirname, "../dist");

const server = http.createServer((request, response) => {
const handlerOptions = {
public: distPath,
rewrites: [{ source: "/**", destination: "/index.html" }],
headers: [
{
// Ensure index.html is never cached
source: "index.html",
headers: [
{
key: "Cache-Control",
value: "no-cache, no-store, max-age=0",
},
],
},
{
// Allow long-term caching for hashed assets
source: "assets/**",
headers: [
{
key: "Cache-Control",
value: "public, max-age=31536000, immutable",
},
],
},
],
};
const handlerOptions = {
public: distPath,
rewrites: [{ source: "/**", destination: "/index.html" }],
headers: [
{
// Ensure index.html is never cached
source: "index.html",
headers: [
{
key: "Cache-Control",
value: "no-cache, no-store, max-age=0",
},
],
},
{
// Allow long-term caching for hashed assets
source: "assets/**",
headers: [
{
key: "Cache-Control",
value: "public, max-age=31536000, immutable",
},
],
},
],
};

const requestHandler = (request, response) => {
return handler(request, response, handlerOptions);
});
};

const port = process.env.CLIENT_PORT || 6274;
const INSPECTOR_SSL_CERT_PATH = process.env.INSPECTOR_SSL_CERT_PATH;
const INSPECTOR_SSL_KEY_PATH = process.env.INSPECTOR_SSL_KEY_PATH;

let server;

if (INSPECTOR_SSL_CERT_PATH && INSPECTOR_SSL_KEY_PATH) {
// HTTPS server
try {
const options = {
cert: fs.readFileSync(INSPECTOR_SSL_CERT_PATH),
key: fs.readFileSync(INSPECTOR_SSL_KEY_PATH)
};
server = https.createServer(options, requestHandler);
server.on("listening", () => {
console.log(
`🔍 MCP Inspector is up and running at https://127.0.0.1:${port} 🚀🔒`,
);
});
} catch (error) {
console.error(`❌ Failed to load SSL certificates: ${error instanceof Error ? error.message : String(error)}`);
console.log(`🔄 Falling back to HTTP mode`);
server = http.createServer(requestHandler);
server.on("listening", () => {
console.log(
`🔍 MCP Inspector is up and running at http://127.0.0.1:${port} 🚀 (SSL fallback)`,
);
});
}
} else {
// HTTP server (default)
if (!INSPECTOR_SSL_CERT_PATH && !INSPECTOR_SSL_KEY_PATH) {
console.log(`🔓 No SSL certificates configured - using HTTP`);
console.log(`💡 To enable HTTPS, set INSPECTOR_SSL_CERT_PATH and INSPECTOR_SSL_KEY_PATH environment variables`);
} else {
console.log(`⚠️ Incomplete SSL configuration:`);
if (!INSPECTOR_SSL_CERT_PATH) console.log(` Missing INSPECTOR_SSL_CERT_PATH`);
if (!INSPECTOR_SSL_KEY_PATH) console.log(` Missing INSPECTOR_SSL_KEY_PATH`);
console.log(`🔄 Using HTTP mode`);
}

server = http.createServer(requestHandler);
server.on("listening", () => {
console.log(
`🔍 MCP Inspector is up and running at http://127.0.0.1:${port} 🚀`,
);
});
}

const port = process.env.PORT || 6274;
server.on("listening", () => {
console.log(
`🔍 MCP Inspector is up and running at http://127.0.0.1:${port} 🚀`,
);
});
server.on("error", (err) => {
if (err.message.includes(`EADDRINUSE`)) {
console.error(
Expand Down
75 changes: 75 additions & 0 deletions generate-ssl.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
#!/bin/bash

# MCP Inspector SSL Certificate Generator
# This script generates SSL certificates for local HTTPS development

set -e

echo "🔐 Generating SSL certificates for MCP Inspector..."

# Check if mkcert is installed
if ! command -v mkcert &> /dev/null; then
echo "❌ mkcert not found. Installing..."

# Detect OS and install mkcert
if [[ "$OSTYPE" == "darwin"* ]]; then
# macOS
if ! command -v brew &> /dev/null; then
echo "❌ Homebrew not found. Please install Homebrew first: https://brew.sh"
exit 1
fi
brew install mkcert
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
# Linux
if command -v apt-get &> /dev/null; then
sudo apt-get update && sudo apt-get install -y mkcert
elif command -v dnf &> /dev/null; then
sudo dnf install -y mkcert
elif command -v pacman &> /dev/null; then
sudo pacman -S mkcert
elif command -v zypper &> /dev/null; then
sudo zypper install mkcert
else
echo "❌ Package manager not found. Please install mkcert manually: https://github.com/FiloSottile/mkcert"
exit 1
fi
elif [[ "$OSTYPE" == "msys" || "$OSTYPE" == "cygwin" ]]; then
# Windows (Git Bash/WSL)
if command -v choco &> /dev/null; then
choco install mkcert
elif command -v scoop &> /dev/null; then
scoop install mkcert
else
echo "❌ Neither Chocolatey nor Scoop found. Please install one of them or install mkcert manually:"
echo " Chocolatey: https://chocolatey.org"
echo " Scoop: https://scoop.sh"
echo " mkcert: https://github.com/FiloSottile/mkcert"
exit 1
fi
else
echo "❌ Unsupported OS: $OSTYPE"
echo "Please install mkcert manually: https://github.com/FiloSottile/mkcert"
exit 1
fi
fi

# Create ssl directory if it doesn't exist
mkdir -p ssl

# Generate certificates
echo "📜 Generating certificates for localhost and 127.0.0.1..."
mkcert -key-file ssl/key.pem -cert-file ssl/cert.pem localhost 127.0.0.1

# Set permissions
chmod 600 ssl/key.pem
chmod 644 ssl/cert.pem

echo "✅ SSL certificates generated successfully!"
echo ""
echo "📝 To use HTTPS, set these environment variables:"
echo "export INSPECTOR_SSL_CERT_PATH=$(pwd)/ssl/cert.pem"
echo "export INSPECTOR_SSL_KEY_PATH=$(pwd)/ssl/key.pem"
echo ""
echo "💡 To avoid browser warnings, run: sudo mkcert -install"
echo ""
echo "🚀 Start the inspector with: npm run dev"
48 changes: 44 additions & 4 deletions server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import cors from "cors";
import { parseArgs } from "node:util";
import { parse as shellParseArgs } from "shell-quote";
import https from "https";
import fs from "fs";

import {
SSEClientTransport,
Expand Down Expand Up @@ -375,11 +377,49 @@ app.get("/config", (req, res) => {
});

const PORT = process.env.PORT || 6277;
const INSPECTOR_SSL_CERT_PATH = process.env.INSPECTOR_SSL_CERT_PATH;
const INSPECTOR_SSL_KEY_PATH = process.env.INSPECTOR_SSL_KEY_PATH;

let server;

if (INSPECTOR_SSL_CERT_PATH && INSPECTOR_SSL_KEY_PATH) {
// HTTPS server
try {
const options = {
cert: fs.readFileSync(INSPECTOR_SSL_CERT_PATH),
key: fs.readFileSync(INSPECTOR_SSL_KEY_PATH)
};
server = https.createServer(options, app);
server.listen(PORT);
server.on("listening", () => {
console.log(`⚙️ Proxy server listening on https://localhost:${PORT} 🔒`);
});
} catch (error) {
console.error(`❌ Failed to load SSL certificates: ${error instanceof Error ? error.message : String(error)}`);
console.log(`🔄 Falling back to HTTP mode`);
server = app.listen(PORT);
server.on("listening", () => {
console.log(`⚙️ Proxy server listening on http://localhost:${PORT} (SSL fallback)`);
});
}
} else {
// HTTP server (default)
if (!INSPECTOR_SSL_CERT_PATH && !INSPECTOR_SSL_KEY_PATH) {
console.log(`🔓 No SSL certificates configured - using HTTP`);
console.log(`💡 To enable HTTPS, set INSPECTOR_SSL_CERT_PATH and INSPECTOR_SSL_KEY_PATH environment variables`);
} else {
console.log(`⚠️ Incomplete SSL configuration:`);
if (!INSPECTOR_SSL_CERT_PATH) console.log(` Missing INSPECTOR_SSL_CERT_PATH`);
if (!INSPECTOR_SSL_KEY_PATH) console.log(` Missing INSPECTOR_SSL_KEY_PATH`);
console.log(`🔄 Using HTTP mode`);
}

server = app.listen(PORT);
server.on("listening", () => {
console.log(`⚙️ Proxy server listening on http://localhost:${PORT}`);
});
}

const server = app.listen(PORT);
server.on("listening", () => {
console.log(`⚙️ Proxy server listening on port ${PORT}`);
});
server.on("error", (err) => {
if (err.message.includes(`EADDRINUSE`)) {
console.error(`❌ Proxy Server PORT IS IN USE at port ${PORT} ❌ `);
Expand Down