Skip to main content

Refusal

Enum Refusal 

Source
pub enum Refusal {
Show 16 variants MalformedSessionId, MissingSession, UnknownSession, MethodNotAllowed { method: &'static str, reason: &'static str, }, ProtocolHeader { reason: String, }, BodyTooLarge, Unauthenticated, Forbidden, ForeignOrigin, RateLimited { retry_after: NonZeroU64, }, Timeout, MalformedBody { reason: String, }, StreamGap { requested: EventId, evicted_through: EventId, last: EventId, }, UnknownCursor { requested: EventId, last: EventId, }, MalformedCursor, Handler(String),
}
Expand description

A request the adapter refuses before or instead of answering it.

Not an Error in the std::error::Error sense for its own sake — it is one so it composes with ? in a handler whose other failure is a genuine error, and so thiserror writes the Display text that goes in the body.

Variants§

§

MalformedSessionId

The Mcp-Session-Id header was present but not a session id.

§

MissingSession

A stateful request arrived with no session id and was not initialize.

§

UnknownSession

The session id named a session this server does not have.

§

MethodNotAllowed

The endpoint does not offer this method here.

Fields

§method: &'static str

The HTTP method that was refused.

§reason: &'static str

Why it is not offered, in the specification’s own terms.

§

ProtocolHeader

MCP-Protocol-Version disagreed with the negotiated version, or named something this build cannot parse.

Fields

§reason: String

What the domain’s check_protocol_header reported.

§

BodyTooLarge

The body was larger than the configured ceiling.

§

Unauthenticated

The request carried no credentials and the endpoint requires some.

§

Forbidden

The request was authenticated but lacks a required scope.

§

ForeignOrigin

The Origin header named an origin this server does not serve.

The one refusal in this enum that is a specification MUST rather than a JSON-RPC-shaped complaint:

Servers MUST validate the Origin header on all incoming connections to prevent DNS rebinding attacks

Cited: https://modelcontextprotocol.io/specification/2024-11-05/basic/transports

Carries nothing from the header. The value is attacker-chosen, and a refusal that echoes it into a response body is reflecting untrusted input back to whoever sent it — for no diagnostic gain, since the sender knows exactly what they sent. The operator reads the refusal type; the attacker learns nothing they did not send.

Distinct from Forbidden, which is an authorization decision about a credential. This is a decision about where the request came from, made before the request is authenticated, because the point is to refuse the connection rather than to reject an identity.

§

RateLimited

The subject exceeded the configured request rate.

OWASP API Security Top 10:2023, API4:2023 (Unrestricted Resource Consumption). The one refusal here that answers with a delay rather than a denial, which is why it carries retry_after: RFC 9110 §10.2.3 makes Retry-After a 429 SHOULD, and a client told only “429” is a client that retries immediately and keeps the rate up.

Distinct from Forbidden, which is about a credential, and from ForeignOrigin, which is about where the request came from. This is about how often one subject asked.

Fields

§retry_after: NonZeroU64

Whole seconds the caller should wait. Never zero: a Retry-After: 0 is a client told to retry now, which is the opposite of the point.

§

Timeout

The handler did not answer inside the configured request deadline.

503 rather than 504. A 504 names a gateway that timed out talking to an upstream, and this server is the thing that timed out — it is not forwarding to anything. OWASP API Security Top 10:2023, API4:2023.

§

MalformedBody

The body was not a JSON-RPC frame this build can decode.

Fields

§reason: String

What decode reported.

§

StreamGap

A Last-Event-ID names an event the ring has dropped.

The whole point of the variant is the two watermarks. A client that resumes from requested and is told evicted_through knows exactly which of its events are gone and can re-issue them; a client that is handed a short stream and no explanation believes it is current.

Fields

§requested: EventId

The cursor the client quoted.

§evicted_through: EventId

The newest event the ring has dropped.

§last: EventId

The newest event the stream ever issued.

§

UnknownCursor

A Last-Event-ID names an event the stream never issued.

Fields

§requested: EventId

The cursor the client quoted.

§last: EventId

The newest event the stream ever issued.

§

MalformedCursor

The Last-Event-ID header was present but is not a cursor this stream can read.

Separate from UnknownCursor because the two faults have different fixes and the client has to tell them apart to make one: a cursor ahead of the stream is a well-formed id the server cannot serve, while a cursor it cannot parse is a malformed header. A server that answered a corrupt header by quietly starting the client from the top would hand it a stream it believes it has already seen, so this refuses rather than guessing — including for the empty string, which is a client that quoted a cursor and quoted nothing.

§

Handler(String)

The protocol layer failed in a way the adapter does not model.

Implementations§

Source§

impl Refusal

Source

pub const fn status(&self) -> StatusCode

The status this refusal is answered with.

Source

pub const fn code(&self) -> i64

The JSON-RPC error code sent alongside the status.

Separate from status because the two answer different questions and are allowed to disagree: the status is what a proxy, a browser or curl -I sees, and the code is what a conforming MCP client branches on. Collapsing them would force one compromise on both.

Where the JSON-RPC specification reserves a code that fits, the reserved code is used. The rest live in -32000..=-32099, the range JSON-RPC 2.0 defines as “implementation-defined server errors” — which is exactly what a transport-level refusal is: not a malformed JSON-RPC message (nothing was wrong with the message), but a server declining for a reason of its own. Each arm is a distinct code because a client recovering from one of these has a different move for each.

Source

pub fn body(&self) -> Value

The JSON body sent alongside the status.

A well-formed JSON-RPC 2.0 error object, because this is a machine-readable protocol and a client that receives a bare 409 Conflict cannot recover — nothing in it says what was lost. Three members carry that:

  • error.code is the integer from code, which a client switches on.
  • error.message is the human text, which is this type’s Display — the same text the log line carries, so the two can never disagree.
  • error.data.error is the stable slug from slug, the member a client branches on when it wants a name rather than a number, and where the resume watermarks live.

id is always null. A refusal is frequently the answer to a request whose body was never parsed, so there may be no id to echo; sending null rather than omitting the member keeps the document valid against the JSON-RPC schema, which requires the member on an error.

Source

pub fn body_with_id(&self, id: Option<&WireId<'_>>) -> Value

The body, echoing the JSON-RPC id of the request that earned the refusal.

SEP-2575 requires it and JSON-RPC has always allowed it: “the id must be Null if there was an error in detecting the id”. A body this adapter has already read is not that case — the id was detected, and the refusal happened afterwards. Answering id: null for a request that named 4 makes the client’s own pending request unmatchable, which is the one thing an error reply is supposed to prevent.

None still means null, and deliberately so: a GET or a DELETE has no JSON-RPC envelope to take an id from, and a body that would not decode genuinely did not detect one.

Source§

impl Refusal

Source

pub fn into_response_with_id(self, id: Option<&WireId<'_>>) -> Response

The response, echoing the JSON-RPC id of the refused request.

Same rule as body_with_id: an id that has been read off a body is carried into the answer, and an id that was never detected is null.

Trait Implementations§

Source§

impl Clone for Refusal

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Refusal

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Display for Refusal

Source§

fn fmt(&self, __formatter: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Eq for Refusal

Source§

impl Error for Refusal

1.30.0 · Source§

fn source(&self) -> Option<&(dyn Error + 'static)>

Returns the lower-level source of this error, if any. Read more
1.0.0 · Source§

fn description(&self) -> &str

👎Deprecated since 1.42.0:

use the Display impl or to_string()

1.0.0 · Source§

fn cause(&self) -> Option<&dyn Error>

👎Deprecated since 1.33.0:

replaced by Error::source, which can support downcasting

Source§

fn provide<'a>(&'a self, request: &mut Request<'a>)

🔬This is a nightly-only experimental API. (error_generic_member_access)
Provides type-based access to context intended for error reports. Read more
Source§

impl IntoResponse for Refusal

Source§

fn into_response(self) -> Response

Renders the refusal, with the security layer still applied on top.

This impl exists so a handler can ? a Refusal into a Response. It sets only the status, the content type and — for the two refusals where a header is meaningful — Allow and Retry-After; the security headers come from the router layer, which is the only place that can guarantee they are on every response including this one.

Source§

impl PartialEq for Refusal

Source§

fn eq(&self, other: &Self) -> bool

Equality operator ==. Read more
1.0.0 (const: unstable) · Source§

fn ne(&self, other: &Rhs) -> bool

Inequality operator !=. Read more
Source§

impl StructuralPartialEq for Refusal

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<T, S> Handler<IntoResponseHandler, S> for T
where T: IntoResponse + Clone + Send + Sync + 'static,

Source§

type Future = Ready<Response<Body>>

The type of future calling this handler returns.
Source§

fn call( self, _req: Request<Body>, _state: S, ) -> <T as Handler<IntoResponseHandler, S>>::Future

Call the handler with the given request.
Source§

fn layer<L>(self, layer: L) -> Layered<L, Self, T, S>
where L: Layer<HandlerService<Self, T, S>> + Clone, <L as Layer<HandlerService<Self, T, S>>>::Service: Service<Request<Body>>,

Apply a tower::Layer to the handler. Read more
Source§

fn with_state(self, state: S) -> HandlerService<Self, T, S>

Convert the handler into a Service by providing the state
Source§

impl<H, T> HandlerWithoutStateExt<T> for H
where H: Handler<T, ()>,

Source§

fn into_service(self) -> HandlerService<H, T, ()>

Convert the handler into a Service and no state.
Source§

fn into_make_service(self) -> IntoMakeService<HandlerService<H, T, ()>>

Convert the handler into a MakeService and no state. Read more
Source§

fn into_make_service_with_connect_info<C>( self, ) -> IntoMakeServiceWithConnectInfo<HandlerService<H, T, ()>, C>

Convert the handler into a MakeService which stores information about the incoming connection and has no state. Read more
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T> ToString for T
where T: Display + ?Sized,

Source§

fn to_string(&self) -> String

Converts the given value to a String. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more