feat(remote): add --verify flag for TLS certificate verification (#4369)

This commit is contained in:
Jeremiah Lowin 2026-06-24 12:09:22 -04:00 committed by GitHub
commit a13e48ea6f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 147 additions and 3 deletions

View file

@ -73,6 +73,24 @@ Use `--auth none` for unauthenticated development servers:
uvx fastmcp-remote http://localhost:8000/mcp --auth none
```
For servers behind a self-signed certificate, point `--verify` at a CA bundle that trusts the certificate:
```bash
uvx fastmcp-remote https://internal.example.com/mcp --verify /path/to/ca-bundle.pem
```
To disable certificate verification entirely (insecure, only for trusted private networks), pass `--verify false`:
```bash
uvx fastmcp-remote https://internal.example.com/mcp --verify false
```
A CA bundle can also be supplied through the standard `SSL_CERT_FILE` environment variable, which OpenSSL reads automatically:
```bash
SSL_CERT_FILE=/path/to/ca-bundle.pem uvx fastmcp-remote https://internal.example.com/mcp
```
## Options
- `--transport`: Choose `http` or `sse`. Defaults to `http`.
@ -82,5 +100,6 @@ uvx fastmcp-remote http://localhost:8000/mcp --auth none
- `--auth-timeout`: Set how long to wait for the OAuth callback. Defaults to 300 seconds.
- `--ignore-tool`: Hide tools whose names match a glob pattern.
- `--auth`: Choose `oauth` or `none`. The default uses OAuth unless an `Authorization` header is provided.
- `--verify`: Control TLS certificate verification. Pass a path to a CA bundle to trust a self-signed certificate, or `false` to disable verification (insecure). Defaults to verification enabled.
OAuth tokens are stored under `~/.fastmcp/remote` by default. Set `FASTMCP_REMOTE_CONFIG_DIR` to use another directory.

View file

@ -45,6 +45,7 @@ class RemoteConfig:
ignore_tools: tuple[str, ...]
show_banner: bool
log_level: str | None
verify: bool | str | None
class IgnoreTools(Transform):
@ -84,6 +85,16 @@ def parse_header(value: str) -> tuple[str, str]:
return name.strip(), expanded_value.strip()
def parse_verify(value: str) -> bool | str:
"""Interpret the --verify value as a boolean toggle or a CA bundle path."""
lowered = value.strip().lower()
if lowered in {"false", "0", "no", "off"}:
return False
if lowered in {"true", "1", "yes", "on"}:
return True
return value
def default_storage_dir(resource: str | None = None) -> Path:
if config_dir := os.environ.get("FASTMCP_REMOTE_CONFIG_DIR"):
base = Path(config_dir).expanduser()
@ -147,6 +158,17 @@ def build_parser() -> argparse.ArgumentParser:
default=[],
help="Hide tools matching this glob pattern. Repeat for multiple patterns.",
)
parser.add_argument(
"--verify",
type=parse_verify,
default=None,
metavar="VERIFY",
help=(
"SSL certificate verification. Pass a path to a CA bundle file, or "
"'false' to disable verification (insecure, for self-signed "
"certificates). Defaults to verification enabled."
),
)
parser.add_argument(
"--debug",
action="store_true",
@ -190,6 +212,7 @@ def parse_args(argv: Sequence[str] | None = None) -> RemoteConfig:
ignore_tools=tuple(args.ignore_tool),
show_banner=not args.silent,
log_level=log_level,
verify=args.verify,
)
@ -228,8 +251,12 @@ def resolve_auth(config: RemoteConfig) -> OAuth | None:
def build_transport(config: RemoteConfig) -> SSETransport | StreamableHttpTransport:
auth = resolve_auth(config)
if config.transport == "sse":
return SSETransport(config.url, headers=config.headers, auth=auth)
return StreamableHttpTransport(config.url, headers=config.headers, auth=auth)
return SSETransport(
config.url, headers=config.headers, auth=auth, verify=config.verify
)
return StreamableHttpTransport(
config.url, headers=config.headers, auth=auth, verify=config.verify
)
async def run(config: RemoteConfig) -> None: