Skip to main content
Lesson 7 of 14 · MCP for Developers: Servers and Integrations

Configure a Remote SSE Server

Lesson 7 of 14 Updated November 3, 2026 Developer
On this page
  1. Moving from Local to Remote
  2. The Server Barely Changes
  3. Getting a Public URL with ngrok
  4. Connecting a Python Client
  5. Multiple Servers on a Single Host
  6. Key Takeaways

Update November 2, 2026 — Le Chat is now Vibe. As of May 28, 2026, Le Chat has been rebranded as Vibe (still at chat.mistral.ai): a single agent for work and code, carrying over your conversations, settings, and subscriptions. The legacy Le Chat experience remains available in Chat mode; in Work mode, agents are now skills (invoked with @), memories serve as the knowledge base, and deep search is the Deep Research skill. Plans are now called Mistral Free, Pro, Team, and Enterprise. This lesson uses the name Vibe; some menus described here may have changed names or locations. Addendum November 3: run_async results are read from result.output_as_text; the mistralai SDK 3.7.0 does not have an output attribute.

Update November 2, 2026 — MCP Python SDK v2. This course now follows version 2 of the mcp SDK (2.3.0 as of October 2, 2026): MCPServer replaces FastMCP, transport settings move to run() or streamable_http_app(), and an app exposed under a real hostname must declare that hostname (transport_security). This is also the version required by the Mistral SDK: the mistralai[agents] extra requires mcp>=2.2.0,<3.0. Sources: What’s new in v2 · Migration Guide · Deploy & scale.

Moving from Local to Remote

Until now, your MCP server has lived on your machine, communicating via STDIO. This mode is ideal for development, but it isolates your work: no one else can use it, and Mistral’s Vibe can never launch a process on your local machine. To cross this boundary, you must expose the server over HTTP.

The change is lighter than you might think. A remote MCP server publishes its tools at a URL; the client—Vibe, an SDK, or an IDE—connects over the network instead of spawning a process. Your tool code itself remains unchanged.

The Server Barely Changes

MCPServer supports Streamable HTTP transport natively. The file below is written exactly like a local server: same decorators, same docstrings, same typed signatures.

# remote_server.py
"""Remote MCP server with HTTP transport."""

from mcp.server.mcpserver import MCPServer

# The server is the same as local
app = MCPServer("Remote Demo Server")

@app.tool()
def analyze_text(text: str, language: str = "fr") -> str:
    """Analyze text and return statistics.

    Args:
        text: The text to analyze
        language: Language code (fr, en, es). Default: fr

    Returns:
        Text statistics: word count, character count, sentence count
    """
    words = len(text.split())
    chars = len(text)
    sentences = text.count(".") + text.count("!") + text.count("?")
    return (
        f"Language: {language}\n"
        f"Words: {words}\n"
        f"Characters: {chars}\n"
        f"Sentences: {sentences}"
    )

@app.tool()
def summarize_key_points(text: str, max_points: int = 5) -> str:
    """Extract key points from text (simplified version).

    Args:
        text: The source text
        max_points: Maximum number of points to extract (1-10)

    Returns:
        List of identified key points
    """
    sentences = [s.strip() for s in text.split(".") if len(s.strip()) > 20]
    points = sentences[:max_points]
    return "\n".join(f"- {p}" for p in points) if points else "No key points identified."

The only change is a launch block added to the end of the file: HTTP transport, port, and the list of hostnames the server accepts.

import os
from mcp.server.transport_security import TransportSecuritySettings

if __name__ == "__main__":
    # Public hostname, e.g., the one ngrok assigns you (without https://)
    public_host = os.environ.get("MCP_PUBLIC_HOST")
    hosts = ["localhost:*", "127.0.0.1:*"]
    if public_host:
        hosts += [public_host, f"{public_host}:*"]
    app.run(
        transport="streamable-http",
        port=8000,
        transport_security=TransportSecuritySettings(allowed_hosts=hosts),
    )
python remote_server.py

Your server now listens on http://localhost:8000/mcp. The allowed_hosts list is not a detail: by default, SDK v2 only accepts requests addressed to localhost or 127.0.0.1, a protection against DNS rebinding. Behind a tunnel or a real domain name, any other request receives a 421 Invalid Host header error, which the client displays as a generic transport error; only the server log reveals the reason.

Getting a Public URL with ngrok

To test your server from Vibe without a full deployment, ngrok creates a tunnel between a public URL and your local port. This tool shortens the development loop more than anything else: you modify a tool, restart, and the remote connector sees the new version immediately.

# Install the ngrok agent from https://ngrok.com/download, then authenticate (once)
ngrok config add-authtoken YOUR_NGROK_TOKEN

# Expose port 8000
ngrok http 8000

ngrok assigns you an address like https://abc123.ngrok-free.app. Restart the server declaring this hostname (MCP_PUBLIC_HOST=abc123.ngrok-free.app python remote_server.py). Your MCP endpoint becomes this, simply by appending the /mcp path:

https://abc123.ngrok-free.app/mcp

Add this URL as-is to Mistral Vibe as a custom connector. Remember that it is temporary and exposes your machine: do not leave a tunnel open on a server touching real data.

Connecting a Python Client

On the client side, the code structure remains the same as in the RunContext lesson. Only the connection class differs: MCPClientStreamableHTTP with StreamableHTTPServerParams replaces the STDIO pair, and you provide a URL rather than a command.

# remote_client.py
import asyncio
from mistralai.client import Mistral
from mistralai.extra.run.context import RunContext
from mistralai.extra.mcp.streamable_http import (
    MCPClientStreamableHTTP,
    StreamableHTTPServerParams,
)

async def main():
    client = Mistral(api_key="YOUR_API_KEY")

    agent = client.beta.agents.create(
        model="mistral-medium-latest",
        name="remote-assistant",
        instructions="You use remote tools to analyze text.",
    )

    # Connect to the remote server
    server_url = "https://abc123.ngrok-free.app/mcp"
    mcp_client = MCPClientStreamableHTTP(
        params=StreamableHTTPServerParams(
            url=server_url,
            timeout=100,  # Timeout in seconds
        )
    )

    async with RunContext(
        agent_id=agent.id,
        continue_on_fn_error=True,
    ) as run_ctx:
        await run_ctx.register_mcp_client(mcp_client=mcp_client)

        result = await client.beta.conversations.run_async(
            run_ctx=run_ctx,
            inputs="Analyze this text: MCP is an open protocol.",
        )
        print(result.output_as_text)

asyncio.run(main())

The timeout parameter deserves a moment of thought. Over STDIO, calls were nearly instant; over the network, a tool querying a third-party API or processing a large document can take several tens of seconds. A timeout that is too short manifests as intermittent, hard-to-reproduce errors, usually on the most useful requests.

When the server expects specific headers—an API token, a routing key, a CORS constraint—you pass them in headers:

mcp_client = MCPClientStreamableHTTP(
    params=StreamableHTTPServerParams(
        url="https://my-server.example.com/mcp",
        timeout=100,
        headers={
            "Authorization": "Bearer my_api_token",
            "X-Custom-Header": "value",
        },
    )
)

Multiple Servers on a Single Host

Lesson 3 recommended focused servers rather than a catch-all catalog. This recommendation does not condemn you to renting a machine per server: with FastAPI, you can mount multiple MCP servers on distinct routes of the same host.

# server.py — Multiple MCP servers on a single host
from contextlib import asynccontextmanager

from fastapi import FastAPI
from mcp.server.mcpserver import MCPServer
from mcp.server.transport_security import TransportSecuritySettings

# MCP server for text analysis
text_mcp = MCPServer("Text Analysis")

@text_mcp.tool()
def analyze(text: str) -> str:
    """Analyze text."""
    return f"Words: {len(text.split())}"

# MCP server for calculations
math_mcp = MCPServer("Math Tools")

@math_mcp.tool()
def calculate(expression: str) -> str:
    """Evaluate a mathematical expression."""
    return str(eval(expression))  # Warning: eval in production!

# Each MCP server produces a standalone ASGI app,
# which must know the hostname under which it is served
security = TransportSecuritySettings(allowed_hosts=["mon-serveur.com", "mon-serveur.com:*"])
text_app = text_mcp.streamable_http_app(transport_security=security)
math_app = math_mcp.streamable_http_app(transport_security=security)

# Combined lifespan: each server must start its session manager
@asynccontextmanager
async def lifespan(app: FastAPI):
    async with text_mcp.session_manager.run(), math_mcp.session_manager.run():
        yield

api = FastAPI(lifespan=lifespan)
api.mount("/text", text_app)
api.mount("/math", math_app)

You get two independent endpoints, https://mon-serveur.com/text/mcp for text analysis tools and https://mon-serveur.com/math/mcp for math tools, which you add separately in Vibe. The critical point is the lifespan: each MCP server has a session manager that must be started explicitly. If you mount the apps without passing this lifespan to FastAPI, the server responds, but every tool call fails with an uninitialized task error. A user who only needs calculation won’t clutter their context with text tools. The comment on eval is no joke: on a public endpoint, this line executes any code sent by the caller—it is a pedagogical example, never a production pattern.

One final reflex: before adding your URL to a chat, run it through the Inspector again. A remote endpoint adds failure sources that local ignored—closed tunnel, missing header, busy port—and it is better to discover them outside a conversation.

Key Takeaways

  • A remote MCP server exposes its tools via HTTP on an /mcp endpoint
  • ngrok lets you get a public URL for development
  • MCPClientStreamableHTTP with StreamableHTTPServerParams connects the client to the remote server (MCPClientSSE is now only for legacy /sse endpoints)
  • The timeout parameter is important for slow servers
  • You can mount multiple MCP servers on the same FastAPI host, provided you pass their lifespan
  • Always test with the Inspector before integrating into a chat