# `ConduitMcp.Protocol`
[🔗](https://github.com/nyo16/conduit_mcp/blob/v0.11.0/lib/conduit_mcp/protocol.ex#L1)

JSON-RPC 2.0 message construction and MCP protocol versions.

ConduitMCP is dual-era. It speaks MCP **2026-07-28** (the *modern* era:
version and capabilities travel in every request's `_meta`, no handshake)
and **2025-11-25** / **2025-06-18** (the *legacy* era, negotiated by
`initialize`). `supported_versions/0` lists all three, newest first — the
list `server/discover` advertises. `modern_versions/0` and
`legacy_versions/0` split it by era; `negotiate_version/1` resolves an
`initialize` request and only ever picks a legacy version.
`protocol_version/0` is the preferred legacy version.

## Building responses

Helpers `success_response/2`, `error_response/3,4`, and `notification/2`
produce the right wire shape — string-keyed maps with `"jsonrpc"`,
`"id"`, and either `"result"` or `"error"`. `validate_request/1`
performs lightweight shape checking on incoming messages.

## Error codes

Standard JSON-RPC 2.0 codes and MCP-specific codes are exposed as
delegating functions to `ConduitMcp.Errors`:

- `parse_error/0`         → -32700
- `invalid_request/0`     → -32600
- `method_not_found/0`    → -32601
- `invalid_params/0`      → -32602
- `internal_error/0`      → -32603
- `server_error/0`        → -32000 (generic server-defined)
- `task_not_ready/0`      → -32004 (legacy `tasks/result` before completion)
- `request_cancelled/0`   → -32800 (`notifications/cancelled`)

Use these instead of hardcoded integers so error code constants stay
in one place.

## Examples

    iex> ConduitMcp.Protocol.success_response(1, %{"value" => 42})
    %{"jsonrpc" => "2.0", "id" => 1, "result" => %{"value" => 42}}

    iex> ConduitMcp.Protocol.error_response(1, -32601, "no such method")
    %{"jsonrpc" => "2.0", "id" => 1, "error" => %{"code" => -32601, "message" => "no such method"}}

# `error_object`

```elixir
@type error_object() :: %{code: integer(), message: String.t(), data: any() | nil}
```

# `error_response`

```elixir
@type error_response() :: %{
  jsonrpc: String.t(),
  id: json_rpc_id(),
  error: error_object()
}
```

# `json_rpc_id`

```elixir
@type json_rpc_id() :: String.t() | integer()
```

# `method`

```elixir
@type method() :: String.t()
```

# `notification`

```elixir
@type notification() :: %{jsonrpc: String.t(), method: method(), params: map() | nil}
```

# `request`

```elixir
@type request() :: %{
  jsonrpc: String.t(),
  id: json_rpc_id(),
  method: method(),
  params: map() | nil
}
```

# `response`

```elixir
@type response() :: success_response() | error_response()
```

# `success_response`

```elixir
@type success_response() :: %{jsonrpc: String.t(), id: json_rpc_id(), result: any()}
```

# `error_response`

Creates an error response.

# `internal_error`

# `invalid_params`

# `invalid_request`

# `legacy_versions`

```elixir
@spec legacy_versions() :: [String.t()]
```

The legacy-era (`initialize`-negotiated) versions: ["2025-11-25", "2025-06-18"].

# `method_not_found`

# `methods`

```elixir
@spec methods() :: %{optional(String.t()) =&gt; atom()}
```

Every MCP method the library routes, mapped to its internal name.

Derived from `ConduitMcp.Handler`'s routing table rather than being a second
hand-maintained copy: the previous list was missing six routed methods
(`resources/templates/list`, all four `tasks/*`, and
`notifications/cancelled`).

# `modern_versions`

```elixir
@spec modern_versions() :: [String.t()]
```

The modern-era (per-request `_meta`) versions: ["2026-07-28"].

# `negotiate_version`

```elixir
@spec negotiate_version(term()) :: String.t() | nil
```

Resolves the version an `initialize` request asks for: the version itself
when it is a legacy version, otherwise `nil`. A modern version is never
negotiated by `initialize`; modern clients send it per request instead.

# `notification`

Creates a notification message.

# `parse_error`

# `protocol_version`

```elixir
@spec protocol_version() :: String.t()
```

The preferred legacy protocol version, `2025-11-25`.

# `request_cancelled`

# `server_error`

# `success_response`

Creates a success response.

# `supported_versions`

```elixir
@spec supported_versions() :: [String.t()]
```

Every supported protocol version, newest first: ["2026-07-28", "2025-11-25", "2025-06-18"].

# `task_not_ready`

# `valid_notification?`

Validates if a message is a valid JSON-RPC 2.0 notification.

# `valid_request?`

Validates if a message is a valid JSON-RPC 2.0 request.

# `valid_response?`

```elixir
@spec valid_response?(term()) :: boolean()
```

Validates if a message is a JSON-RPC 2.0 response: an `id`, no `method`, and
exactly one of `result` and `error`.

---

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