Skip to content

Add SSL Certificate Verification Options to FastMCP Client #870

Closed

Description

@iDataist

Is your feature request related to a problem? Please describe.

When connecting to an MCP server over HTTPS with self-signed or internal certificates, the FastMCP Client fails with SSL certificate verification errors. Currently, there's no straightforward way to disable SSL verification or provide custom certificates when using the Client class.

ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1007)

Describe the solution you'd like
Please add one or more of the following options to the FastMCP Client:

A verify parameter to disable SSL verification or to provide a custom certificate:

client = Client(MCP_URL, verify=False)
client = Client(MCP_URL, verify="/path/to/certificate.pem")

This is needed when working with internal or development environments that use self-signed certificates or private certificate authorities.

Describe alternatives you've considered
Users need to resort to insecure workarounds like patching Python's SSL module.

Additional context

The issue appears to be in how the transport is inferred and created from the URL string. When a URL is provided, the infer_transport function creates a StreamableHttpTransport for HTTPS URLs, but there's no way to configure the SSL verification options for this transport.

According to FastMCP requirements, streamable-http transport is required for web deployments, and the client needs to connect to the /mcp path. The current implementation doesn't provide a way to handle SSL verification for these connections.

This would align with how other HTTP client libraries (like requests) handle SSL verification, making the API more intuitive and flexible for enterprise environments.

Activity

  1. cabusar commented on Jun 3, 2025

    @cabusar

    To add more detail to this issue :

    MCP use httpx wich use certifi wich is an external trusted CA store not modifiable by user.

    Certifi does not support any addition/removal or other modification of the CA trust store content. This project is intended to provide a reliable and highly portable root of trust to python deployments.
    Source : https://github.com/certifi/python-certifi

    An issue is open since 2019 for httpx to use the system trusted CA store instead : encode/httpx#302 but didn't seem to progress much.

    Beside the fact it's really annoying to deal with "SSL: CERTIFICATE_VERIFY_FAILED" every time you use a self-signed certificate, it's a real security concern that httpx use a CA store that users can't modify/manage.

    But things could be easy to deal with as httpx provide a simple way to use the system CA store :

    import httpx
    import ssl
    import truststore
    
    ssl_context = truststore.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    client = httpx.Client(verify=ssl_context)
    

    Source :
    encode/httpx#302 (comment)

    This way all python projects using mcp will not have to deal with SSL issues on self-signed certificate and the security side will be handle by the user who will have to manually add the CA certificate to the system's truststore.

    In addition this method is compatible with linux, windows and osx as "trustore" is designed to precisely do it that way :
    https://github.com/sethmlarson/truststore

    Hope this help. :)

    PS :

    One temporary workaround for those who are stuck :

    In ./venv/lib/python3.12/site-packages/certifi/, edit cacert.pem, add the CA you generate with your self-signed certificate.

    -----BEGIN CERTIFICATE-----
    your cert here
    -----END CERTIFICATE-----

  2. iDataist commented on Jun 3, 2025

    @iDataist
    Author

    @cabusar, thank you so much for looking into this ticket! I'm still encountering SSL certificate verification issues with FastMCP. I've tried implementing the truststore approach you suggested, but I'm still facing challenges.

    I have two specific questions:
    Does FastMCP's Client create its own httpx client internally that might bypass our SSL context settings? If so, is there a way to provide the SSL context directly to FastMCP?

    Instead of modifying certifi's cacert.pem directly, is there a way to configure FastMCP to use a custom certificate file path?

  3. cabusar commented on Jun 3, 2025

    @cabusar

    @iDataist : i'm not a contributor of this project, i was facing a similar issue with the python implementation of mcp and tried to help.

    From my understanding of the problem : mcp use httpx with it's default parameters witch imply the use of certifi, a CA store that doesn't allow the modification of the CAs stored internally in ./venv/lib/python3.12/site-packages/certifi/cacert.pem (adapt regarding of your specific configuration).

    mcp didn't offer the possibility to modify the behavior of httpx regarding which CA store to use, so you can't modify the "verify" option of the httpx instance created by mcp.

    So, still in my understanding, and note that i'm not a good dev :

    os.environ['PYTHONHTTPSVERIFY'] = '0'
    import ssl
    ssl._create_default_https_context = ssl._create_unverified_context
    

    Have no effect in you script because certifi didn't take that into account.

    client = Client(MCP_URL)

    Here you create your instance of fastmcp which in the back use mcp, then httpx, but you have no control on how it's done regarding the "verify" parameter.

    So, from here, three options :

    • mcp project implement a solution to control the verify parameter of httpx.
    • httpx project stop using certifi by default (which is a really curious/dumb choice from my adminsys perspective anyway)
    • You bypass entirely all this crap and just add your self-signed/generated CA in the certifi truststore (and froze the certifi dependancy to avoid any break caused by an update of certifi and it's truststore)

    Keep in mind that the last "solution" should be really temporary, CA certificates are renewed regularly for security purposes, if you froze the CA store of certifi it could prevent legitimate certificate to be trusted in the futur.

    The best solution would that httpx to change their approach of course.
    As they seems not willing to do so, mcp dev would be really kind to offer a way to change the "verify" parameter of httpx.

    @dsp-ant : David, if you have some time to look into this discussion it would be really kind (at least to tell if i understand the problem correctly). I would be glad to make a PR about it and save you some time, but given my coding skills you definitely don't want it to happen. ^^'

  4. cabusar commented on Jun 15, 2025

    @cabusar

    Any update on this ?

  5. jwthomp commented on Jun 20, 2025

    @jwthomp

    I work in a corporate environment that has this same problem when accessing any external resources due to the use of zscalar.

  6. aniketwattamwar commented on Jul 3, 2025

    @aniketwattamwar

    Is there a roadmap if this functionality would be added in the near future?
    also @cabusar thank you for the detailed explanation

  7. lucasalvarezlacasa commented on Jul 7, 2025

    @lucasalvarezlacasa

    Any updates on this?

  8. aniketwattamwar commented on Jul 8, 2025

    @aniketwattamwar

    I updated the client library files to pass the verify=False parameter and then tried again and it worked.
    Not the best solution rather temporary
    Happy to raise a PR @ihrpr @dsp-ant

  9. cabusar commented on Jul 8, 2025

    @cabusar

    Hi @aniketwattamwar,

    I would love to see how you implemented this.

    Could you do it by passing "verify=ssl_context" instead, making it safer and a more a long terme solution ?
    Or even letting the user choosing between "default", "false" and "ssl_context" (or local_ca/a more understandable naming) ?

  10. Akash321go commented on Jul 8, 2025

    @Akash321go

    Add import certifi_win32 at the very top, the SSL error went away.

  11. aniketwattamwar commented on Jul 10, 2025

    @aniketwattamwar

    Hi @Akash321go
    Thank you for that input. Looks like the certifi_win32 is replaced with this pip_system_certs

    I tried it and it worked for a deployed on prem application.

    @cabusar
    I did update the local library to accept the local ca .pem file as parameter with verify = True/False but cannot test it though.

  12. eugenejen commented on Jul 10, 2025

    @eugenejen

    I met the same issue when we deploy mcp server internally behind internal CA. I am also interested to allow MCP client to be able to have local CA.

  13. eugenejen commented on Jul 10, 2025

    @eugenejen

    From what i see from _httpx_utils.py. the code is using the httpx._config.create_ssl_context to create ssl context and environment variable SSL_CERT_FILE/SSL_CERT_DIR should allow the us to add extra certs? then the context will use certifi if none of the environment variables are set.

    https://github.com/encode/httpx/blob/master/httpx/_config.py#L23-L70

    Has anyone tried to use SSL_CERT_FILE or SSL_CERT_DIR successfully for mcp client?

  14. suvojitkonar commented on Jul 24, 2025

    @suvojitkonar

    Hey i have figured this out :

    `def custom_httpx_client_factory(**kwargs):
        # return httpx.AsyncClient(verify=False, **kwargs)
        return httpx.AsyncClient(verify=TRUSTBUNDLE_PATH, **kwargs)
    
    async with sse_client(
        "<YOUR_MCP_SERVER>",
        httpx_client_factory=custom_httpx_client_factory
    ) as streams:
        read_stream, write_stream = streams
    
        async with ClientSession(read_stream, write_stream) as session:
            await session.initialize()
            # List available tools
            tools = await session.list_tools()
            print(f"Available tools: {[tool.name for tool in tools.tools]}")`
    
  15. ajainghub commented on Jul 31, 2025

    @ajainghub

    It appears that when using https FastMCP server if you don't put a "/" at the end of the server URL configured in the MCP client then code 307 temporary redirects are sent from the server are not handled by the client. To prevent the redirects put a "/" at the end of the server URL in the MCP client.
    I didn't need the httpx override mentioned above, and below worked.

    async with streamablehttp_client("https://<host_port>/mcp/")

    Same behavior observed when using and configuring a FastMCP client.

    Full basic client code:

    import asyncio
    
    from mcp import ClientSession
    from mcp.client.streamable_http import streamablehttp_client
    
    
    async def main():
        # Connect to a streamable HTTP server
        #async with streamablehttp_client("http://localhost:8000/mcp") as (
        async with streamablehttp_client("https://<host_port>/mcp/") as (
            read_stream,
            write_stream,
            _,
        ):
    
            # Create a session using the client streams
            async with ClientSession(read_stream, write_stream) as session:
                # Initialize the connection
                await session.initialize()
                # List available tools
                tools = await session.list_tools()
                print(f"Available tools: {[tool.name for tool in tools.tools]}")
    
    
    if __name__ == "__main__":
        asyncio.run(main())
    
  16. 3 remaining items

  17. added
    bugSomething isn't working
    ready for workEnough information for someone to start working on
    P0Broken core functionality, security issues, critical missing feature
    on Oct 7, 2025
  18. felixweinberger commented on Oct 7, 2025

    @felixweinberger
    Contributor

    This may be helped by #1177

  19. gserbanut commented on Oct 27, 2025

    @gserbanut

    Here is a simple workaround:

    Server Side

    Override of FastMCP.run_streamable_http_async:

    # your imports here
    # among them, there should be also: 
    # from mcp.server.fastmcp import FastMCP
    
    class SecureFastMCP(FastMCP):
        def __init__(self, *args, **kwargs):
            super().__init__(*args, **kwargs)
    
        async def run_streamable_http_async(self) -> None:
            """Run the server using StreamableHTTP transport."""
            import uvicorn
    
            starlette_app = self.streamable_http_app()
    
            base_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "certs")      # my certs are in the dir certs from where the file resides
            config = uvicorn.Config(
                starlette_app,
                host = self.settings.host,
                port = self.settings.port,
                log_level = self.settings.log_level.lower(),
                ssl_keyfile = os.getenv("SSL_KEY_FILE", os.path.join(base_dir, "key.pem")),     # export SSL_KEY_FILE="/path/to/your/key.pem"
                ssl_certfile = os.getenv("SSL_CERT_FILE", os.path.join(base_dir, "cert.pem")),  # export SSL_CERT_FILE="/path/to/your/cert.pem"
            )
            print(self.streamable_http_app().routes)
            server = uvicorn.Server(config)
            logger.info("Starting secure FastMCP server...")
            await server.serve()
    
    mcp_server = SecureFastMCP(...)
    
    # the rest of your code here
    # don't forget to use @mcp_server.whatever(), where "whatever" can be "tools" and so on
    
    if __name__ == "__main__":
        mcp_server.run(transport = 'streamable-http')
    

    Client Side

    Let HTTPX/SSL use your environment variables (you can set them within Python script as well):

    export SSL_CERT_DIR="/path/to/your/certs"
    export SSL_CERT_FILE="/path/to/your/rootCA.pem"
    

    Here is a working client for MCP v1.19.0

    import asyncio
    # if you would like to set here the env vars, don't forget about:
    # import os
    
    from mcp import ClientSession, Tool
    from mcp.client.streamable_http import streamablehttp_client
    
    async def main():
        # here you should define your environment variables if you prefer them here
        server_url = "https://<your_server>:<your_port>/mcp"
        async with streamablehttp_client(server_url) as (read_stream, write_stream, _):
            async with ClientSession(read_stream, write_stream) as client:
                try:
                    print("Initializing session...")
                    session = await client.initialize()
                    print(f"Session initialized: {session}")
    
                    print("Listing tools...")
                    # tools = await client.list_tools()
                    tools = await client.list_tools()
                    print("Tools metadata keys:", [name for name, _ in tools])
                    print('Raw tools', tools.tools)
                    print(f'Registered tools: {[tool.name for tool in tools.tools]}')
    
                    # Use the 'add' tool, which requires the session ID for authentication.
                    print("Calling <my tool name> tool...")
                    result = await client.call_tool(<tool name>, <tool parameters as dict>)
                    print(f"Result of this call: {result.structuredContent['result']}")
    
                except Exception as e:
                    print(f"An error occurred: {e}")
    
    if __name__ == "__main__":
        asyncio.run(main())
    

    Test SSL

    After starting the server, you can run the following file:

    import os
    import ssl, socket
    
    host = '<your server IP>'
    port = <your server port>
    # PROTOCOL_TLS_CLIENT requires valid cert chain and hostname
    context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)
    context.load_verify_locations('/path/to/your/rootCA.pem')
    
    with socket.create_connection((host, port)) as sock:
        with context.wrap_socket(sock, server_hostname=host) as ssock:
            print(ssock.version())
    

    In my case, I got the output:

    $ python test_ssl.py 
    TLSv1.3
    

    Enjoy your enterprise ready MCP!

  20. josephfahimi commented on Dec 8, 2025

    @josephfahimi

    @felixweinberger any updates on the 2025-11-25 Implementation? And will this implementation allow users to do something like verify=False to address SSL Verification failed?

  21. felixweinberger commented on Dec 8, 2025

    @felixweinberger
    Contributor

    Ack, apologies - we didn't do anything specific in the spec here but I'll chase up the PR to make httpx.AsyncClient usable (#1177), I believe that would help here?

    In the meantime, there is a workaround today: you can pass a custom httpx_client_factory to streamablehttp_client that returns a client with verify=False.

  22. maxisbey commented on Dec 10, 2025

    @maxisbey
    Contributor

    Closing this as #1177 has now been merged. Feel free to reopen if you're still experiencing the issue after the fix!

  23. added
    P1Significant bug affecting many users, highly requested feature
    and removed
    P0Broken core functionality, security issues, critical missing feature
    on Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Significant bug affecting many users, highly requested featurebugSomething isn't workingready for workEnough information for someone to start working on

    Type

    No type

    Projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions