# /mcp/notifications/{channel}

> Kind 4: the notifications the caller’s servers send on their own account; the server sends one message per notification for the life of the path, an error as the last message when there is one, and closes the connection.

Canonical: https://provider.diverge.network/2.3.0/proxy/mcp/notifications/
Specification revision: 2.3.0

The ask is kind `4` on [`/requests`](/2.3.0/proxy/requests/). Its payload
is empty. The server answers on `/mcp/notifications/{channel}` by
sending one message per notification for as long as the path lives.
Each message is the response frame defined below: its first byte is
`0`, followed by the notification as JSON. A message whose first byte
is `1`, followed by an error as JSON, is the last message the server
sends. The server closes the connection when it has no more to send.
A path that the proxy or the server closes cleanly with no message
before the close states that the ask was not served.

- **When the proxy asks.** The proxy asks for notifications after
  its first exchange of kinds `0` to `3`, and it keeps one such ask
  open for the rest of its life.
- **A stream that ended.** When a notification path ends, cleanly,
  by an error, or abruptly, the proxy asks for notifications again on
  the next `/requests` connection. The server does not replay a
  notification that occurred while no notification path was open.

The ask is defined by `diverge-provider-sdk/src/shared/mcp/notifications/request/request.rs`:

```rust
//! Asking to hear what a server says.

use std::convert::Infallible;

use crate::decode::Decode;
use crate::encode::{Encode, Writer};

/// Open the notification stream.
///
/// It carries nothing, and MCP is the reason rather than an economy.
/// Every other exchange here is a JSON-RPC method, and a request naming
/// one carries its params. This is not a method: in Streamable HTTP a
/// client opens the notification stream with a bare `GET` on the same
/// url it POSTs everything else to — no method name, no body, nothing
/// to say. There is no `notifications/subscribe` to mirror.
///
/// So the empty payload is the request, whole.
///
/// # It exists anyway
///
/// Rather than the channel request simply carrying no payload for this
/// one case. A frame that names five things should name them the same
/// way, and a variant with nothing in it is a variant a reader has to
/// check twice — once for what it means, once for why it is shaped
/// differently.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct Request;

/// Nothing at all. Which channel it arrives on says what it is, and
/// there is nothing else to say.
impl Encode for Request {
    /// [`Infallible`]: no bytes.
    type Error = Infallible;

    fn encode(&self, _out: &mut Writer<'_>) -> Result<(), Infallible> {
        Ok(())
    }
}

impl Decode<'_> for Request {
    /// [`Infallible`]: nothing is read, so nothing can be wrong.
    type Error = Infallible;

    /// Whatever bytes are there are ignored rather than rejected. There
    /// is nothing this could carry, so a reader that found something
    /// has met a writer from a version that gave it one — and the
    /// channel already said what was meant.
    fn decode(_bytes: &[u8]) -> Result<Self, Infallible> {
        Ok(Request)
    }
}
```

The messages are defined by `diverge-provider-sdk/src/shared/mcp/notifications/response/frame.rs`:

```rust
//! What arrives on a notification channel.

use rmcp::ErrorData;
use rmcp::model::ServerNotification;

use super::super::super::FrameError;
use crate::decode::Decode;
use crate::encode::{Encode, Writer};

/// One thing the server said on its own account, or the reason it will
/// say nothing further.
///
/// The payload of a
/// [`ClientFrame::ChannelResponse`](crate::frame::client::ClientFrame::ChannelResponse)
/// on a channel opened by a
/// [`request::Request`](super::super::request::Request).
///
/// A payload leads with one byte saying which — `0` for
/// [`Notification`](Self::Notification), `1` for
/// [`Error`](Self::Error) — and the rest is that variant's own JSON.
///
/// # Many frames, unlike the other four
///
/// The other exchanges answer once and finish. This one carries a frame
/// per notification for as long as the channel lives, because that is
/// what it is: not an answer, but the stream a server pushes into when
/// something changes.
///
/// Nothing terminates it in the payload. The channel's own finish says
/// there will be no more, which is what a finish already means and what
/// makes a second signal for one fact a second thing to disagree about.
///
/// # An error is the last thing on it
///
/// A notification stream that stops is a stream that stopped, and a
/// caller that can no longer keep one open says so here rather than
/// finishing in silence. Nothing follows it; the channel finishes
/// after.
///
/// It is [`ErrorData`] rather than
/// [`shared::error::Error`](crate::shared::error::Error) for the reason
/// the other four give: what is being relayed is an MCP server's
/// failure, not the provider's, and a JSON-RPC code is content rather
/// than detail.
///
/// # [`ServerNotification`] is the whole union, deliberately
///
/// Every notification a server can send, including the ones this crate
/// has no opinion about. Narrowing it to the ones an agent obviously
/// acts on — tools changed, resources changed — would mean deciding on
/// an agent's behalf what is worth hearing, and would need revising
/// every time MCP adds one.
///
/// It deserializes by its `method`, which each variant fixes to a
/// constant that rejects every other value. So the union is untagged in
/// serde's terms and discriminated in practice, and a notification this
/// crate's `rmcp` is too old to know arrives as
/// `CustomNotification` rather than as a failure.
///
/// # It does not derive [`PartialEq`]
///
/// Alone among the five, because
/// [`ServerNotification`] does not. Comparing two is not something
/// anything here does, and wrapping the union to add it would be
/// keeping a second copy of `rmcp`'s type for the sake of a derive.
#[derive(Debug, Clone)]
pub enum Frame {
    /// One notification, as the server sent it. Tag `0`.
    Notification(ServerNotification),
    /// The stream ended early, and this is why. Tag `1`.
    Error(ErrorData),
}

/// Tag for [`Frame::Notification`].
const NOTIFICATION: u8 = 0;

/// Tag for [`Frame::Error`].
const ERROR: u8 = 1;

impl Encode for Frame {
    /// The ordinary JSON failure. Both variants are serialized and the
    /// tag cannot fail.
    type Error = serde_json::Error;

    // Spelled out rather than `Self::Error`: this enum has a variant
    // called `Error`, so the associated type is ambiguous by that name.
    fn encode(&self, out: &mut Writer<'_>) -> Result<(), serde_json::Error> {
        match self {
            Frame::Notification(notification) => {
                out.extend_from_slice(&[NOTIFICATION]);
                serde_json::to_writer(out, notification)
            }
            Frame::Error(error) => {
                out.extend_from_slice(&[ERROR]);
                serde_json::to_writer(out, error)
            }
        }
    }
}

impl Decode<'_> for Frame {
    /// Three ways to fail, and only one of them is JSON.
    type Error = FrameError;

    // Spelled out for the same reason as `encode` above.
    fn decode(bytes: &[u8]) -> Result<Self, FrameError> {
        let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
        match *tag {
            NOTIFICATION => serde_json::from_slice(rest)
                .map(Frame::Notification)
                .map_err(FrameError::Body),
            ERROR => serde_json::from_slice(rest)
                .map(Frame::Error)
                .map_err(FrameError::Body),
            tag => Err(FrameError::UnknownTag(tag)),
        }
    }
}
```
