# `ConduitMcp.Plugs.MessageRateLimit`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.10.1/lib/conduit_mcp/plugs/message_rate_limit.ex#L1)

Message-level rate limiting plug for MCP servers.

While the HTTP-level rate limit (`ConduitMcp.Plugs.RateLimit`) limits raw HTTP
connections, this plug limits the number of MCP **message interactions** (tool
calls, resource reads, prompt gets) a client can make per time window.

Think of it as: HTTP rate limit = "how fast can you knock on the door",
message rate limit = "how many questions can you ask once inside."

This plug is **completely optional**. If you don't need message-level rate
limiting, simply omit the `:message_rate_limit` option from your transport
config — no additional dependencies are required.

## Dependencies

Message rate limiting requires the [`hammer`](https://hex.pm/packages/hammer)
package. Add it to your `mix.exs` only if you intend to use this plug:

    {:hammer, "~> 7.2"}

## How it works

- Only POST requests are counted (GET/OPTIONS pass through)
- JSON-RPC notifications (requests without an `id` field) are not counted
- Specific methods can be excluded (e.g., `"initialize"`, `"ping"`)
- Keys are prefixed with `"msg:"` to prevent Hammer counter collision when
  both HTTP and message rate limiters share the same backend
- Authenticated users are tracked by user ID; anonymous users by IP

## Options

- `:backend` - **Required when enabled.** A module that implements `hit/3`
  (e.g., a module defined with `use Hammer, backend: :ets`). You must supervise
  this module in your own application supervision tree.
- `:enabled` - Enable/disable message rate limiting (default: `true`)
- `:scale` - Time window in milliseconds (default: `300_000` / 5 minutes)
- `:limit` - Maximum messages per window (default: `50`)
- `:key_func` - Function to derive the rate limit key from the connection
  (default: user-aware with `"msg:"` prefix). Signature: `(Plug.Conn.t()) -> String.t()`
- `:excluded_methods` - List of MCP method names to skip rate limiting for
  (default: `[]`). Example: `["initialize", "ping"]`

## Setup

### 1. Define your Hammer module

    defmodule MyApp.RateLimiter do
      use Hammer, backend: :ets
    end

### 2. Add to your supervision tree

    children = [
      {MyApp.RateLimiter, [clean_period: :timer.minutes(1)]}
    ]

### 3. Pass as `:message_rate_limit` in transport config

    {Bandit,
     plug: {ConduitMcp.Transport.StreamableHTTP,
            server_module: MyApp.MCPServer,
            rate_limit: [
              backend: MyApp.RateLimiter,
              scale: :timer.seconds(60),
              limit: 100
            ],
            message_rate_limit: [
              backend: MyApp.RateLimiter,
              scale: :timer.minutes(5),
              limit: 50
            ]},
     port: 4001}

## Per-user rate limiting

The default key function already supports per-user rate limiting when the
`ConduitMcp.Plugs.Auth` plug is in the pipeline. If `conn.assigns[:current_user]`
is set, it will be used as the key. Otherwise, the client IP is used.

You can also provide a custom key function:

    message_rate_limit: [
      backend: MyApp.RateLimiter,
      limit: 50,
      key_func: fn conn ->
        "msg:custom:" <> get_custom_key(conn)
      end
    ]

## Without message rate limiting

Simply omit the `:message_rate_limit` option from your transport config.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
