use serde::Serialize;
use serde_json::Value;
pub const SUPPORTED_VERSIONS: &[&str] = &["2025-11-25", "2024-11-05"];
#[derive(Debug, Clone)]
pub struct OAuthConfig {
pub enabled: bool,
pub audience: Option<String>,
pub issuer: Option<String>,
pub scopes: Vec<String>,
pub jwks_uri: Option<String>,
pub token_secret: Option<String>,
}
impl Default for OAuthConfig {
fn default() -> Self {
Self {
enabled: false,
audience: None,
issuer: None,
scopes: vec!["mcp:read".to_string(), "mcp:write".to_string()],
jwks_uri: None,
token_secret: None,
}
}
}
impl OAuthConfig {
pub fn enforcement_error(&self) -> Option<&'static str> {
if !self.enabled {
return None;
}
#[cfg(not(feature = "oauth"))]
{
Some(
"MCP_OAUTH_ENABLED is set but this server was built without the `oauth` feature, \
so token signatures cannot be verified",
)
}
#[cfg(feature = "oauth")]
{
if self.token_secret.is_none() {
Some(
"MCP_OAUTH_ENABLED is set but MCP_OAUTH_TOKEN_SECRET is missing, \
so token signatures cannot be verified",
)
} else {
None
}
}
}
pub fn is_enforced(&self) -> bool {
self.enabled && self.enforcement_error().is_none()
}
}
pub fn validate_oauth_config(config: &OAuthConfig) -> Result<(), &'static str> {
match config.enforcement_error() {
None => Ok(()),
Some(reason) => Err(reason),
}
}
#[derive(Debug, Serialize)]
pub struct InitializeResult {
#[serde(rename = "protocolVersion")]
pub protocol_version: String,
pub capabilities: Value,
#[serde(rename = "serverInfo")]
pub server_info: Value,
}
#[derive(Debug, Serialize)]
pub struct McpTool {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub description: String,
#[serde(rename = "inputSchema")]
pub input_schema: Value,
}
#[derive(Debug, Serialize, Clone)]
pub struct ToolStub {
pub name: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub title: Option<String>,
pub description: String,
}
impl From<McpTool> for ToolStub {
fn from(tool: McpTool) -> Self {
Self {
name: tool.name,
title: tool.title,
description: tool.description,
}
}
}
#[derive(Debug, Serialize)]
pub struct ListToolsResult {
pub tools: Vec<McpTool>,
}
#[derive(Debug, Serialize)]
pub struct ListToolStubsResult {
pub tools: Vec<ToolStub>,
}
#[derive(Debug, Serialize)]
pub struct DescribeToolResult {
pub tool: McpTool,
}
#[derive(Debug, Serialize)]
pub struct DescribeToolsResult {
pub tools: Vec<McpTool>,
}
#[derive(Debug, Serialize)]
pub struct ProtectedResourceMetadata {
#[serde(rename = "authorizationServers", skip_serializing_if = "Vec::is_empty")]
pub authorization_servers: Vec<String>,
pub resource: String,
#[serde(rename = "scopesSupported", skip_serializing_if = "Vec::is_empty")]
pub scopes_supported: Vec<String>,
#[serde(rename = "resourceMetadata")]
pub resource_metadata: Option<String>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_oauth_disabled_is_not_enforced() {
let config = OAuthConfig::default();
assert!(config.enforcement_error().is_none());
assert!(!config.is_enforced());
assert!(validate_oauth_config(&config).is_ok());
}
#[cfg(feature = "oauth")]
#[test]
fn test_oauth_enabled_with_secret_is_enforced() {
let config = OAuthConfig {
enabled: true,
token_secret: Some("secret".to_string()),
..OAuthConfig::default()
};
assert!(config.enforcement_error().is_none());
assert!(config.is_enforced());
assert!(validate_oauth_config(&config).is_ok());
}
#[cfg(feature = "oauth")]
#[test]
fn test_oauth_enabled_without_secret_fails_closed() {
let config = OAuthConfig {
enabled: true,
token_secret: None,
..OAuthConfig::default()
};
let reason = config
.enforcement_error()
.expect("missing secret must be reported");
assert!(
reason.contains("MCP_OAUTH_TOKEN_SECRET"),
"unexpected: {reason}"
);
assert!(!config.is_enforced());
assert_eq!(validate_oauth_config(&config), Err(reason));
}
#[cfg(not(feature = "oauth"))]
#[test]
fn test_oauth_enabled_without_feature_fails_closed() {
let config = OAuthConfig {
enabled: true,
token_secret: Some("secret".to_string()),
..OAuthConfig::default()
};
let reason = config
.enforcement_error()
.expect("missing feature must be reported");
assert!(reason.contains("oauth"), "unexpected: {reason}");
assert!(!config.is_enforced());
assert_eq!(validate_oauth_config(&config), Err(reason));
}
}