Skip to content

Add verify parameter for SSL certificate configuration#3487

Merged
jlowin merged 7 commits intomainfrom
feat/ssl-verify
Mar 14, 2026
Merged

Add verify parameter for SSL certificate configuration#3487
jlowin merged 7 commits intomainfrom
feat/ssl-verify

Conversation

@jlowin
Copy link
Copy Markdown
Member

@jlowin jlowin commented Mar 14, 2026

Connecting to MCP servers with self-signed certificates or internal CAs currently requires constructing a custom httpx_client_factory — boilerplate that shouldn't be necessary for such a common enterprise/development scenario.

The Client, StreamableHttpTransport, and SSETransport now accept a verify parameter matching httpx conventions (bool, cert path string, or ssl.SSLContext):

from fastmcp import Client

# Self-signed certs in development
client = Client("https://dev-server.internal/mcp", verify=False)

# Custom CA bundle
client = Client("https://corp-server.internal/mcp", verify="/path/to/ca-bundle.pem")

# Full SSL context control
import ssl
ctx = ssl.create_default_context()
ctx.load_verify_locations("/path/to/internal-ca.pem")
client = Client("https://corp-server.internal/mcp", verify=ctx)

The parameter is applied after transport inference rather than being threaded through infer_transport, so it only touches HTTP-based transports — passing verify with an in-memory or stdio transport raises a clear error.

Closes #717

@marvin-context-protocol marvin-context-protocol Bot added enhancement Improvement to existing functionality. For issues and smaller PR improvements. client Related to the FastMCP client SDK or client-side functionality. http Related to HTTP transport, networking, or web server functionality. labels Mar 14, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d93b8f28a9

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/fastmcp/client/transports/http.py Outdated
Comment on lines +126 to +130
elif self.verify is not None:
# create_mcp_http_client doesn't support verify, so construct
# the client directly with the same MCP defaults
if timeout is None:
timeout = httpx.Timeout(30.0, read=300.0)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Propagate verify to OAuth preflight client for streamable HTTP

When verify is provided and no custom httpx_client_factory is set, this code path builds a one-off httpx.AsyncClient for transport requests but leaves self.httpx_client_factory as None; auth="oauth" therefore still creates OAuth with its default factory. OAuth.redirect_handler uses auth.httpx_client_factory() for the authorization preflight request, so self-signed/internal-CA servers still fail certificate validation before OAuth completes, despite verify=False or a custom CA being passed.

Useful? React with 👍 / 👎.

Comment thread src/fastmcp/client/transports/sse.py Outdated
Comment on lines +94 to +97
def _factory(**kwargs: Any) -> httpx.AsyncClient:
return httpx.AsyncClient(verify=verify, **kwargs)

client_kwargs["httpx_client_factory"] = _factory
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reuse verify-aware factory for OAuth in SSE transport

The verify-specific factory created here is only passed to sse_client via local client_kwargs; it is not assigned to self.httpx_client_factory, so auth="oauth" still keeps OAuth's default httpx.AsyncClient factory. In OAuth flows, OAuth.redirect_handler uses that factory for its preflight GET, which means TLS verification ignores the transport verify setting and fails against self-signed/internal-CA auth servers.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0095c5d01d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +88 to +91
resolved = OAuth(
self.url,
httpx_client_factory=self.httpx_client_factory
or self._make_verify_factory(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Propagate verify for OAuth auth objects

This branch applies a verify-aware httpx_client_factory only when auth == "oauth"; when callers use the documented auth=OAuth(...) form, _set_auth falls through to isinstance(auth, OAuth) and keeps the provider’s default httpx.AsyncClient, so OAuth preflight requests still enforce normal certificate validation and fail against self-signed/internal-CA servers even if verify=False or a custom CA was passed. I confirmed this in the same transport logic and the mirrored _set_auth implementation in src/fastmcp/client/transports/sse.py.

Useful? React with 👍 / 👎.

Comment on lines +272 to +273
if isinstance(self.transport, StreamableHttpTransport | SSETransport):
self.transport.verify = verify
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Refresh OAuth factory after setting transport verify

When Client receives an already-constructed HTTP transport (a supported input type) with OAuth already configured, this code only mutates transport.verify and does not re-create/rebind OAuth auth, so the existing OAuth provider keeps its old httpx_client_factory and ignores the new verify setting. In practice, Client(StreamableHttpTransport(url, auth="oauth"), verify=False) still performs OAuth preflight with certificate verification enabled.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 61820c49ca

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/fastmcp/client/transports/http.py Outdated
Comment on lines +95 to +96
if auth.httpx_client_factory is httpx.AsyncClient:
verify_factory = (
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Rebind OAuth factory when verify changes

This conditional only rewires an OAuth instance when its factory is exactly httpx.AsyncClient, so pre-constructed transports that already carry a verify-aware factory keep stale TLS settings after Client(..., verify=...) mutates transport.verify. For example, Client(StreamableHttpTransport(url, verify=False, auth="oauth"), verify=True) leaves OAuth preflight requests at CERT_NONE even though the transport verify setting is now strict; the same _set_auth pattern exists in src/fastmcp/client/transports/sse.py.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ad13332052

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/fastmcp/client/transports/http.py Outdated
Comment on lines +95 to +97
factory = self.httpx_client_factory or self._make_verify_factory()
if factory is not None:
auth.httpx_client_factory = factory
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve custom OAuth client factory on bound auth objects

When callers pass an OAuth(...) instance with its own httpx_client_factory, this branch overwrites it whenever the transport has verify (or a transport-level httpx_client_factory). That silently drops OAuth-specific client settings (for example proxies, client certs, or custom transports), so OAuth preflight/token requests can fail even though the caller explicitly configured them on the auth object. This is a regression from the previous behavior where OAuth instances were only bound to the URL.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7808361f48

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/fastmcp/client/transports/sse.py Outdated
Comment on lines +76 to +78
factory = self.httpx_client_factory or self._make_verify_factory()
if factory is not None:
auth.httpx_client_factory = factory
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve caller OAuth factory when binding SSE auth objects

When auth is an OAuth(...) instance, this branch unconditionally overwrites auth.httpx_client_factory with the transport-level factory (or the verify-derived one). That drops any OAuth-specific client configuration the caller attached to the auth object (for example proxies, mTLS/client certs, or custom transports), so OAuth preflight/token requests can fail even though the auth provider was explicitly configured. This is a regression from the previous behavior where passing an OAuth instance only bound the URL.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ddeda28634

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/fastmcp/client/client.py Outdated
Comment on lines +279 to +282
if isinstance(self.transport.auth, OAuth) and auth is None:
verify_factory = self.transport._make_verify_factory()
if verify_factory is not None:
self.transport.auth.httpx_client_factory = verify_factory
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve OAuth factory when Client verify is applied

When Client(..., verify=...) wraps a preconstructed StreamableHttpTransport/SSETransport that already has OAuth configured, this block unconditionally replaces transport.auth.httpx_client_factory with a verify-only factory. That drops any existing transport/client-factory behavior (e.g., proxies, client certs, custom transports) for OAuth preflight/token requests while the transport itself still uses self.transport.httpx_client_factory, so auth and transport traffic diverge and OAuth can fail in environments that rely on the custom factory.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a39f12ca52

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +110 to +111
if auth.httpx_client_factory is httpx.AsyncClient:
factory = self.httpx_client_factory or self._make_verify_factory()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Refresh OAuth verify factory for reused auth objects

This guard only rewires an OAuth instance when its factory is exactly httpx.AsyncClient, so a previously injected verify-aware factory is treated as “custom” and never updated on the next transport. In practice, reusing one OAuth() across transports (e.g. first verify=False, then verify=True) leaves OAuth preflight/token requests on the stale TLS policy from the first transport, so the second transport’s verify setting is ignored; I reproduced this with StreamableHttpTransport, and the same _set_auth pattern exists in sse.py.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are devolving into more and more unlikely edge cases.

@jlowin jlowin merged commit 0b8479a into main Mar 14, 2026
7 of 12 checks passed
@jlowin jlowin deleted the feat/ssl-verify branch March 14, 2026 15:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

client Related to the FastMCP client SDK or client-side functionality. enhancement Improvement to existing functionality. For issues and smaller PR improvements. http Related to HTTP transport, networking, or web server functionality.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add SSL Certificate Verification Options to FastMCP Client

1 participant