Skip to main content

headless_lms_mcp/protocol/
mod.rs

1//! Stateless MCP over HTTP, protocol revision 2026-07-28 only. One POST carries one JSON-RPC
2//! request; everything the server needs is in that request.
3
4pub(crate) mod context;
5pub mod dispatch;
6pub mod headers;
7pub(crate) mod progress;
8
9use headless_lms_utils::http::escape_auth_param;
10use rmcp::model::{ErrorCode, ProtocolVersion};
11
12/// The only protocol revision the server speaks.
13pub(crate) const PROTOCOL_VERSION: ProtocolVersion = ProtocolVersion::V_2026_07_28;
14
15/// Where teachers learn to set up their agent, including Codex's 2026-07-28 opt-in.
16pub(crate) const SETUP_DOCS_URL: &str =
17    "https://github.com/rage/course-material-agent-skills/blob/main/SETUP.md";
18
19/// Our errors, in the implementation-defined range. MCP's reserved codes are used from
20/// [`ErrorCode`] where they apply.
21pub(crate) mod error_codes {
22    use super::ErrorCode;
23
24    /// The bearer token is missing, invalid or lacks a scope.
25    pub const UNAUTHORIZED: ErrorCode = ErrorCode(-32050);
26    /// A sealed `requestState` didn't verify or has expired.
27    pub const INVALID_REQUEST_STATE: ErrorCode = ErrorCode(-32051);
28}
29
30/// The `WWW-Authenticate` value of a 401 or 403 from the MCP server: where the client finds the
31/// authorization server (RFC 9728) and which scopes to ask for. `error` is the RFC 6750 error code
32/// and description, absent when no token was sent.
33pub(crate) fn www_authenticate_challenge(
34    resource_metadata_url: &str,
35    scope: &str,
36    error: Option<(&str, &str)>,
37) -> String {
38    let mut challenge = format!(
39        r#"Bearer resource_metadata="{}", scope="{}""#,
40        escape_auth_param(resource_metadata_url),
41        escape_auth_param(scope)
42    );
43    if let Some((code, description)) = error {
44        challenge.push_str(&format!(
45            r#", error="{}", error_description="{}""#,
46            escape_auth_param(code),
47            escape_auth_param(description)
48        ));
49    }
50    challenge
51}