#[cfg(feature = "transport-sse")]
mod sse_example {
#![allow(deprecated)]
use actix_web::{App, HttpServer};
use rmcp::{
ErrorData as McpError, RoleServer, ServerHandler,
handler::server::router::tool::ToolRouter, model::*, service::RequestContext, tool,
tool_handler, tool_router,
};
use rmcp_actix_web::transport::{AuthorizationHeader, SseService};
use serde_json::json;
use std::sync::Arc;
#[derive(Clone)]
struct SseProxyService {
tool_router: ToolRouter<SseProxyService>,
}
#[tool_router]
impl SseProxyService {
fn new() -> Self {
Self {
tool_router: Self::tool_router(),
}
}
#[tool(description = "Make authenticated API call using provided token")]
async fn call_api(
&self,
context: RequestContext<RoleServer>,
) -> Result<CallToolResult, McpError> {
if let Some(auth) = context.extensions.get::<AuthorizationHeader>() {
let result = json!({
"success": true,
"auth_used": auth.0,
"api": "generic-api",
"note": "Would call backend API with this token"
});
Ok(CallToolResult::success(vec![Content::text(
result.to_string(),
)]))
} else {
Err(McpError::invalid_request(
"No authorization token provided for this request. Include Authorization: Bearer <token>",
None,
))
}
}
#[tool(description = "Call GitHub API using provided GitHub token")]
async fn github_api(
&self,
context: RequestContext<RoleServer>,
) -> Result<CallToolResult, McpError> {
if let Some(auth) = context.extensions.get::<AuthorizationHeader>() {
let result = json!({
"success": true,
"auth_used": auth.0,
"api": "github",
"note": "Would fetch GitHub data with this token"
});
Ok(CallToolResult::success(vec![Content::text(
result.to_string(),
)]))
} else {
Err(McpError::invalid_request(
"GitHub token required. Include Authorization: Bearer <github-token>",
None,
))
}
}
#[tool(description = "Public API call that doesn't need auth")]
async fn public_api(
&self,
_context: RequestContext<RoleServer>,
) -> Result<CallToolResult, McpError> {
let result = json!({
"success": true,
"data": "Public data accessible without authentication",
"api": "public",
"timestamp": chrono::Utc::now().to_rfc3339(),
});
Ok(CallToolResult::success(vec![Content::text(
result.to_string(),
)]))
}
#[tool(description = "Check authorization token for this request")]
async fn check_request_auth(
&self,
context: RequestContext<RoleServer>,
) -> Result<CallToolResult, McpError> {
let status = if let Some(auth) = context.extensions.get::<AuthorizationHeader>() {
json!({
"has_auth": true,
"auth_header": auth.0,
"type": "Bearer",
"note": "This authorization is for THIS request only"
})
} else {
json!({
"has_auth": false,
"note": "No Authorization header in this request"
})
};
Ok(CallToolResult::success(vec![Content::text(
status.to_string(),
)]))
}
}
#[tool_handler]
impl ServerHandler for SseProxyService {
fn get_info(&self) -> ServerInfo {
ServerInfo {
protocol_version: ProtocolVersion::V_2024_11_05,
capabilities: ServerCapabilities::builder().enable_tools().build(),
server_info: Implementation::from_build_env(),
instructions: Some(
"SSE proxy service demonstrating per-request Authorization. \
Each tool call can include its own Bearer token for different APIs. \
Some tools require auth, others don't."
.to_string(),
),
}
}
async fn initialize(
&self,
request: InitializeRequestParam,
context: RequestContext<RoleServer>,
) -> Result<InitializeResult, McpError> {
if context.peer.peer_info().is_none() {
context.peer.set_peer_info(request);
}
if let Some(auth) = context.extensions.get::<AuthorizationHeader>() {
tracing::info!("Initialize request included Authorization: {}", auth.0);
} else {
tracing::info!("Initialize request without Authorization (normal for SSE)");
}
Ok(self.get_info())
}
}
pub async fn run() -> std::io::Result<()> {
tracing_subscriber::fmt()
.with_env_filter(
tracing_subscriber::EnvFilter::from_default_env()
.add_directive("rmcp_actix_web=info".parse().unwrap())
.add_directive("authorization_proxy_sse_example=info".parse().unwrap()),
)
.init();
println!("đ Starting SSE Authorization Proxy Example Server");
println!(" Server: http://localhost:8080");
println!();
println!("đ Test flow:");
println!();
println!("1. Connect to SSE endpoint:");
println!(" curl -N http://localhost:8080/sse");
println!(" (Note the endpoint URL from the first event)");
println!();
println!("2. Initialize (auth optional):");
println!(" curl -X POST \"http://localhost:8080/message?sessionId=YOUR_SESSION_ID\" \\");
println!(" -H \"Content-Type: application/json\" \\");
println!(
" -d '{{\"jsonrpc\":\"2.0\",\"method\":\"initialize\",\"params\":{{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{{}},\"clientInfo\":{{\"name\":\"test\",\"version\":\"1.0\"}}}},\"id\":1}}'"
);
println!();
println!("3. Call API with token:");
println!(" curl -X POST \"http://localhost:8080/message?sessionId=YOUR_SESSION_ID\" \\");
println!(" -H \"Content-Type: application/json\" \\");
println!(" -H \"Authorization: Bearer your-api-token\" \\");
println!(
" -d '{{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{{\"name\":\"call_api\"}},\"id\":2}}'"
);
println!();
println!("4. Call public API (no auth):");
println!(" curl -X POST \"http://localhost:8080/message?sessionId=YOUR_SESSION_ID\" \\");
println!(" -H \"Content-Type: application/json\" \\");
println!(
" -d '{{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{{\"name\":\"public_api\"}},\"id\":3}}'"
);
println!();
println!("5. Check request auth:");
println!(" curl -X POST \"http://localhost:8080/message?sessionId=YOUR_SESSION_ID\" \\");
println!(" -H \"Content-Type: application/json\" \\");
println!(" -H \"Authorization: Bearer check-this-token\" \\");
println!(
" -d '{{\"jsonrpc\":\"2.0\",\"method\":\"tools/call\",\"params\":{{\"name\":\"check_request_auth\"}},\"id\":4}}'"
);
println!();
println!("âšī¸ Key points:");
println!(" - Each request can have different (or no) Authorization");
println!(" - Only Bearer tokens are forwarded");
println!(" - Perfect for proxy scenarios with multiple backends");
println!();
let service = SseService::builder()
.service_factory(Arc::new(|| Ok(SseProxyService::new())))
.build();
HttpServer::new(move || App::new().service(service.clone().scope()))
.bind("127.0.0.1:8080")?
.run()
.await
}
}
#[cfg(feature = "transport-sse")]
#[actix_web::main]
async fn main() -> std::io::Result<()> {
sse_example::run().await
}
#[cfg(not(feature = "transport-sse"))]
fn main() {
eprintln!("This example requires the 'transport-sse' feature to be enabled.");
eprintln!(
"Run with: cargo run --example authorization_proxy_sse_example --features transport-sse"
);
std::process::exit(1);
}