View Source MCP over SSE

This library provides a simple implementation of the Model Context Protocol (MCP) over Server-Sent Events (SSE).

For more information about the Model Context Protocol, visit: Model Context Protocol Documentation

Installation

For Phoenix Applications:

  1. Add the required configuration to config/config.exs:
# Configure MIME types for SSE
config :mime, :types, %{
  "text/event-stream" => ["sse"]
}

# Configure the MCP Server
config :nws_mcp_server, :mcp_server, MCP.DefaultServer
# config :nws_mcp_server, :mcp_server, YourApp.YourMCPServer
  1. Add to your dependencies in mix.exs:
def deps do
  [
    {:mcp_sse, "~> 0.1.0"}
  ]
end
  1. Configure your router (lib/your_app_web/router.ex):
pipeline :sse do
  plug :accepts, ["sse"]
end

scope "/" do
  pipe_through :sse
  get "/sse", SSE.ConnectionPlug, :call
  
  pipe_through :api
  post "/message", SSE.ConnectionPlug, :call
end

For Plug Applications with Bandit:

  1. Create a new Plug application with supervision:

    mix new your_app --sup
    
  2. Add the required configuration to config/config.exs:

    import Config
    
    # Configure MIME types for SSE
    config :mime, :types, %{
    "text/event-stream" => ["sse"]
    }
    
    # Configure the MCP Server
    config :your_app, :mcp_server, YourApp.MCPServer
  3. Add dependencies to mix.exs:

    def deps do
    [
     {:mcp_sse, "~> 0.1.0"},
     {:plug, "~> 1.14"},
     {:bandit, "~> 1.2"}
    ]
    end
  4. Configure your router (lib/your_app/router.ex):

    defmodule YourApp.Router do
    use Plug.Router
    
    plug Plug.Parsers,
     parsers: [:urlencoded, :json],
     pass: ["text/*"],
     json_decoder: Jason
    
    plug :match
    plug :ensure_session_id
    plug :dispatch
    
    # Middleware to ensure session ID exists
    def ensure_session_id(conn, _opts) do
     case get_session_id(conn) do
       nil ->
         # Generate a new session ID if none exists
         session_id = generate_session_id()
         %{conn | query_params: Map.put(conn.query_params, "sessionId", session_id)}
       _session_id ->
         conn
     end
    end
    
    # Helper to get session ID from query params
    defp get_session_id(conn) do
     conn.query_params["sessionId"]
    end
    
    # Generate a unique session ID
    defp generate_session_id do
     Base.encode16(:crypto.strong_rand_bytes(8), case: :lower)
    end
    
    forward "/sse", to: SSE.ConnectionPlug
    forward "/message", to: SSE.ConnectionPlug
    
    match _ do
     send_resp(conn, 404, "Not found")
    end
    end
  5. Set up your application supervision (lib/your_app/application.ex):

    defmodule YourApp.Application do
    use Application
    
    @impl true
    def start(_type, _args) do
     children = [
       {Bandit, plug: YourApp.Router, port: 4000}
     ]
    
     opts = [strategy: :one_for_one, name: YourApp.Supervisor]
     Supervisor.start_link(children, opts)
    end
    end

Session Management

The MCP SSE server requires a session ID for each connection. The router automatically:

  • Uses an existing session ID from query parameters if provided
  • Generates a new session ID if none exists
  • Ensures all requests to /sse and /message endpoints have a valid session ID

Configuration Options

The Bandit server can be configured with additional options in your application module:

# Example with custom port and HTTPS
children = [
  {Bandit,
    plug: YourApp.Router,
    port: System.get_env("PORT", "4000") |> String.to_integer(),
    scheme: :https,
    certfile: "priv/cert/selfsigned.pem",
    keyfile: "priv/cert/selfsigned_key.pem"
  }
]

The use MCPServer macro provides:

  • Built-in message routing
  • Protocol version validation
  • Default implementations for optional callbacks
  • JSON-RPC error handling
  • Logging

You only need to implement the required callbacks (handle_ping/1 and handle_initialize/2) and any optional callbacks for features you want to support.

Protocol Documentation

For detailed information about the Model Context Protocol, visit: Model Context Protocol Documentation

Features

  • Full MCP server implementation
  • SSE connection management
  • JSON-RPC message handling
  • Tool registration and execution
  • Session management
  • Automatic ping/keepalive
  • Error handling and validation

Contributing

...

Quick Demo

To see the MCP server in action:

  1. Start the Phoenix server:

    mix phx.server
    
  2. In another terminal, run the demo client script:

    elixir examples/mcp_client.exs
    

The client script will:

  • Connect to the SSE endpoint
  • Initialize the connection
  • List available tools
  • Call the upcase tool with example input
  • Display the results of each step

This provides a practical demonstration of the MCP protocol flow and server capabilities.

Other Notes

Example Client Usage

// Connect to SSE endpoint
const sse = new EventSource('/sse');

// Handle endpoint message
sse.addEventListener('endpoint', (e) => {
  const messageEndpoint = e.data;
  // Use messageEndpoint for subsequent JSON-RPC requests
});

// Send initialize request
fetch('/message?sessionId=YOUR_SESSION_ID', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    jsonrpc: '2.0',
    id: 1,
    method: 'initialize',
    params: {
      protocolVersion: '2024-11-05',
      capabilities: {}
    }
  })
});