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

> Master SSE transport mode in grep-mcp for real-time updates via Starlette. Learn seamless integration for your web applications and unlock efficient communication.

- Repository: [gal peretz/grep-mcp](https://github.com/galprz/grep-mcp)
- Tags: deep-dive
- Published: 2026-02-16

---

**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`](https://github.com/galprz/grep-mcp/blob/main/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`](https://github.com/galprz/grep-mcp/blob/main/src/grep_mcp/server.py) at lines 61-66, the transport is instantiated with a base URL path:

```python
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`](https://github.com/galprz/grep-mcp/blob/main/src/grep_mcp/server.py)) constructs the Starlette application and wires the SSE transport:

```python
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`](https://github.com/galprz/grep-mcp/blob/main/src/grep_mcp/server.py) (lines 19-30 and 32-44) handles transport selection:

```python
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:

```javascript
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:

```python
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:

```python
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`](https://github.com/galprz/grep-mcp/blob/main/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`](https://github.com/galprz/grep-mcp/blob/main/src/grep_mcp/__main__.py) | Module entry point (`python -m grep_mcp`). |
| [`src/grep_mcp/__init__.py`](https://github.com/galprz/grep-mcp/blob/main/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`](https://github.com/galprz/grep-mcp/blob/main/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.