# MCP Exchanges

> Kinds 0 to 4: the five MCP exchanges the proxy asks; the server answers four of them with exactly one message each, and the fifth with one message per notification for the life of its path.

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

Five asks carry the MCP exchanges of the container. The server
answers four of them with exactly one message each. It answers the
fifth with one message per notification for as long as the path
lives.

| Kind | Ask | Payload | Answered on | Answer |
|------|-----|---------|-------------|--------|
| `0` | list tools | the params as JSON | [`/mcp/list-tools/{channel}`](/2.3.0/proxy/mcp/list-tools/) | exactly one message |
| `1` | list resources | the params as JSON | [`/mcp/list-resources/{channel}`](/2.3.0/proxy/mcp/list-resources/) | exactly one message |
| `2` | call tool | the params as JSON | [`/mcp/call-tool/{channel}`](/2.3.0/proxy/mcp/call-tool/) | exactly one message |
| `3` | read resource | the params as JSON | [`/mcp/read-resource/{channel}`](/2.3.0/proxy/mcp/read-resource/) | exactly one message |
| `4` | notifications | nothing | [`/mcp/notifications/{channel}`](/2.3.0/proxy/mcp/notifications/) | one message per notification |

- **The payloads.** A params payload is the JSON of the request type
  its page shows, read as [Notation](/2.3.0/endpoints/#notation) provides.
  The types named from `rmcp::model` are those of the Model Context
  Protocol, incorporated by reference.
- **The answer.** For kinds `0` to `3`, the server sends exactly one
  message on the path — the response frame of the exchange, carrying
  a result or an error — and closes the connection. For kind `4`,
  the server sends one message per notification for as long as the
  path lives; when it sends an error, the error is the last message;
  the server closes the connection when it has no more to send.
- **When an exchange is answered.** An exchange is answered when its
  message has arrived and its path has closed cleanly. An exchange
  whose `/requests` connection ended before the server opened its
  path, and an exchange whose path ended abruptly, are not answered;
  the proxy asks such an exchange again on the next `/requests`
  connection, under a fresh channel. A server may therefore receive
  one exchange more than once. A path that the proxy or the server closes cleanly with no message
before the close states that the ask was not served. The proxy does not ask an
  unserved exchange again.
- **The notification stream.** The proxy asks for notifications only
  after it has made its first exchange of kinds `0` to `3`. 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 ways in which a response fails to decode are shared by the five
exchanges and are defined by `diverge-provider-sdk/src/shared/mcp/frame_error.rs`:

```rust
//! An MCP answer that could not be read.

use std::error;
use std::fmt;

/// What went wrong reading one of the five answers.
///
/// # One type for five frames
///
/// Which is unusual here: this crate writes an error per frame, so that
/// what can go wrong reading one is spelled out beside it.
///
/// These five have nothing to spell out separately. Every one is a tag
/// and then JSON, so every one fails in the same three ways, and five
/// copies would differ only in the module they sat in — while every
/// consumer that handles more than one of them would need a variant per
/// copy to say the same thing.
///
/// A caller reading a tool listing already knows it was reading a tool
/// listing. The error does not have to tell it.
#[derive(Debug)]
pub enum FrameError {
    /// No bytes at all, so not even a tag.
    Empty,
    /// A tag that is neither of the frame's two.
    ///
    /// What a caller newer than this one produces, which is the case a
    /// tag exists to make survivable: a reader that does not know a
    /// variant says so, rather than reading somebody else's bytes as
    /// its own.
    UnknownTag(u8),
    /// The payload after the tag did not parse.
    Body(serde_json::Error),
}

impl fmt::Display for FrameError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FrameError::Empty => f.write_str("mcp answer frame is empty"),
            FrameError::UnknownTag(tag) => {
                write!(f, "unknown mcp answer frame tag {tag}")
            }
            FrameError::Body(error) => {
                write!(f, "mcp answer did not parse: {error}")
            }
        }
    }
}

impl error::Error for FrameError {
    fn source(&self) -> Option<&(dyn error::Error + 'static)> {
        match self {
            FrameError::Body(error) => Some(error),
            FrameError::Empty | FrameError::UnknownTag(_) => None,
        }
    }
}
```
