SSE Transport Mode in grep-mcp: A Complete Guide to Starlette Integration

The SSE transport mode in grep-mcp enables HTTP-based Server-Sent Events communication, allowing the MCP server to stream real-time updates to clients via a Starlette ASGI application instead of using standard input/output.

The grep-mcp repository provides a Model Context Protocol (MCP) server that supports two transport modes: the default stdio mode and an HTTP-based SSE transport mode in grep-mcp. When running in SSE mode, the server leverages Starlette to handle persistent connections and unidirectional event streaming, making it accessible to web clients and browser-based applications.

Why Use SSE Transport in grep-mcp?

Server-Sent Events provide several advantages for MCP communication:

  • Unidirectional push: The server pushes events (tool calls, responses, diagnostics) to the client without requiring polling.
  • Web-friendly: Any modern browser or JavaScript runtime can consume the stream via the native EventSource API.
  • Scalable: Starlette runs on an ASGI server (e.g., uvicorn) and can handle many simultaneous connections efficiently.

Core Architecture and Integration Points

The SSE transport implementation spans several key components in src/grep_mcp/server.py and related files.

SseServerTransport Implementation

The SseServerTransport class from the MCP library provides the foundational read/write streams over an SSE connection. In src/grep_mcp/server.py at lines 61-66, the transport is instantiated with a base URL path:

from mcp.server.sse import SseServerTransport

# Initialize SSE transport with the messages endpoint path

sse = SseServerTransport("/messages/")

This configuration establishes where clients must POST messages back to the server.

Starlette Application Setup

The create_starlette_app function (lines 61-78 and 98-105 in src/grep_mcp/server.py) constructs the Starlette application and wires the SSE transport:

from starlette.applications import Starlette
from starlette.routing import Route, Mount

async def handle_sse(request):
    async with sse.connect_sse(
        request.scope,
        request.receive,
        request._send,
    ) as (read_stream, write_stream):
        await mcp_server.run(
            read_stream,
            write_stream,
            mcp_server.create_initialization_options(),
        )

def create_starlette_app(mcp_server_instance):
    routes = [
        Route("/sse", endpoint=handle_sse),
        Mount("/messages/", app=sse.handle_post_message),
    ]
    return Starlette(routes=routes)

The handle_sse function manages the connection lifecycle, extracting the read and write streams from the SSE connection and passing them to the MCP server's run method.

CLI Entry Point and Transport Selection

The main function in src/grep_mcp/server.py (lines 19-30 and 32-44) handles transport selection:

import argparse
import uvicorn

def main():
    parser = argparse.ArgumentParser(description="Grep MCP Server")
    parser.add_argument(
        "--transport",
        choices=["stdio", "sse"],
        default="stdio",
        help="Transport mode (default: stdio)"
    )
    # ... host and port arguments ...

    args = parser.parse_args()

    if args.transport == "sse":
        starlette_app = create_starlette_app(mcp._mcp_server)
        uvicorn.run(
            starlette_app,
            host=args.host,
            port=args.port,
            log_level="info"
        )
    else:
        # stdio mode implementation

        mcp.run(transport="stdio")

When --transport sse is specified, the server creates the Starlette application and launches it with uvicorn on the configured host and port.

How the SSE Transport Works in grep-mcp

The message flow follows this sequence:

  1. Client Connection: The client opens an EventSource connection to GET /sse.
  2. Stream Establishment: The handle_sse function calls sse.connect_sse(), which returns a tuple of (read_stream, write_stream).
  3. MCP Protocol Handshake: The mcp_server.run() method initializes the MCP session using these streams.
  4. Bidirectional Communication:
    • Server → Client: Messages flow through the write_stream and are pushed as SSE events.
    • Client → Server: The client POSTs JSON-RPC messages to /messages/, handled by sse.handle_post_message.
  5. Session Management: The connection remains open until the client disconnects or the server shuts down.

Client Integration Examples

Browser-based EventSource

Modern browsers can connect directly using the native EventSource API:

const source = new EventSource('http://localhost:8080/sse');

source.onmessage = (e) => {
  const msg = JSON.parse(e.data);
  console.log('MCP →', msg);
};

// To send a request back to the server:
fetch('http://localhost:8080/messages/', {
  method: 'POST',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({
    jsonrpc: '2.0',
    method: 'grep_query',
    params: {query: 'asyncio'}
  }),
});

Python Async Client with httpx

For Python applications, httpx provides robust async SSE support:

import asyncio
import httpx
import json

async def main():
    async with httpx.AsyncClient(timeout=None) as client:
        # Open SSE stream

        async with client.stream("GET", "http://localhost:8080/sse") as sse:
            async for line in sse.aiter_lines():
                if line.startswith("data:"):
                    data = json.loads(line[5:].strip())
                    print("← Server event:", data)

        # Send a request back to the server

        payload = {
            "jsonrpc": "2.0",
            "method": "grep_query",
            "params": {"query": "asyncio"}
        }
        await client.post(
            "http://localhost:8080/messages/",
            json=payload,
            headers={"Content-Type": "application/json"},
        )

asyncio.run(main())

Direct Tool Invocation

For testing or internal use, you can bypass the transport layer entirely:

from grep_mcp.server import mcp
import asyncio

# Directly call the tool (bypasses transport)

result_json = asyncio.run(mcp.tools['grep_query'](
    query="asyncio", language="python"
))
print(result_json)

Benefits and Limitations of SSE Transport

Benefit Description
Zero-polling Server pushes results as soon as they are ready.
Web-native Works in any browser without extra libraries.
Simple deployment One ASGI process (uvicorn) serves both the SSE stream and POST endpoint.
Limitation Description
Uni-directional SSE only streams from server → client; client → server uses the POST /messages/ endpoint.
No binary SSE payloads are text-based JSON; binary data must be Base64-encoded.
Connection limits Long-standing HTTP connections can be limited by proxies/load-balancers.

Key Files and Implementation Details

File Purpose
src/grep_mcp/server.py Core server implementation – defines create_starlette_app, SSE handling, CLI main, and the grep_query tool.
src/grep_mcp/__main__.py Module entry point (python -m grep_mcp).
src/grep_mcp/__init__.py Exposes the FastMCP instance (mcp).

These files together illustrate how Grep-MCP toggles between stdio and SSE transports and how the SSE transport is seamlessly wrapped inside a Starlette application.

Summary

  • The SSE transport mode in grep-mcp enables HTTP-based communication using Server-Sent Events, providing an alternative to the default stdio transport.
  • The implementation relies on SseServerTransport from the MCP library, instantiated at src/grep_mcp/server.py lines 61-66 with the /messages/ endpoint.
  • Starlette serves as the ASGI framework, with routes defined in create_starlette_app (lines 61-78) handling GET /sse for connections and POST /messages/ for client requests.
  • The server runs via uvicorn when --transport sse is specified in the CLI (lines 32-44), falling back to stdio mode by default.

Frequently Asked Questions

How do I start grep-mcp in SSE mode instead of stdio mode?

Run the server with the --transport sse flag. By default, it binds to 0.0.0.0:8080, but you can customize the host and port with --host and --port arguments. For example: python -m grep_mcp --transport sse --port 3000.

Why does the SSE transport use a separate POST endpoint for client messages?

Server-Sent Events are unidirectional by design, streaming only from server to client. To receive messages from the client, the MCP protocol requires a separate channel. The SseServerTransport creates a POST endpoint at /messages/ that handles incoming JSON-RPC requests from clients, while the /sse endpoint pushes responses and notifications via the SSE stream.

Can I use grep-mcp with browsers or only with Python clients?

You can use both. Modern browsers support the native EventSource API to consume the SSE stream at /sse, and JavaScript's fetch API can POST messages to /messages/. Python clients can use libraries like httpx or requests with SSE extensions to achieve the same result, making the server accessible to any HTTP-capable environment.

What happens if I don't specify the transport mode?

If you run python -m grep_mcp without the --transport argument, it defaults to stdio mode. In this mode, the server communicates over standard input and output using JSON-RPC messages, which is the traditional MCP transport suitable for local process communication with hosts like Claude Desktop or other MCP clients.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →