use std::sync::Arc;
use asupersync::Cx;
use fastmcp_client::http_auth::managed::ManagedOAuthSession;
use fastmcp_client::http_auth::rpc::tool_headers::ManagedToolHeaderError;
use fastmcp_client::http_auth::rpc::{ManagedCoreEvent, ManagedCoreLimits};
use fastmcp_client::http_executor::parameter_headers::ReviewedToolHeaders;
use fastmcp_core::{CanonicalHttpUrl, McpContext, McpError, McpResult};
use fastmcp_protocol::http_headers::ParameterHeaderBinding;
use fastmcp_protocol::{CoreRequest, CoreResult, FinalCoreResult, FinalTool, RequestId};
use super::{
BoxFuture, CoreBackend, Forwarder, ManagedOAuthProvider, ManagedOAuthTool, UNEXPECTED_RESULT,
check_cx, forward_notification, upstream_error,
};
const HEADER_FAILURE: &str = "Managed OAuth tool header review or projection failed";
impl ManagedOAuthProvider {
pub async fn tools_with_header_review(
&self,
cx: &Cx,
review: impl FnMut(&FinalTool, &ParameterHeaderBinding) -> bool,
) -> McpResult<Vec<ManagedOAuthTool>> {
let tools = self.tools(cx).await?;
review_tools(cx, self.session.resource(), tools, review)
}
}
fn review_tools(
cx: &Cx,
resource: &CanonicalHttpUrl,
mut tools: Vec<ManagedOAuthTool>,
mut review: impl FnMut(&FinalTool, &ParameterHeaderBinding) -> bool,
) -> McpResult<Vec<ManagedOAuthTool>> {
check_cx(cx)?;
for tool in &mut tools {
check_cx(cx)?;
let mut upstream = tool.definition.clone();
upstream.name.clone_from(&tool.upstream_name);
let reviewed = ReviewedToolHeaders::new(
resource.clone(),
upstream.name.clone(),
upstream.input_schema.clone(),
|binding| {
if check_cx(cx).is_err() {
return false;
}
let approved = review(&upstream, binding);
approved && check_cx(cx).is_ok()
},
);
check_cx(cx)?;
let reviewed = Arc::new(reviewed.map_err(|_| McpError::invalid_params(HEADER_FAILURE))?);
let backend = tool.forwarder.backend.with_reviewed_headers(reviewed)?;
tool.forwarder = Arc::new(Forwarder {
backend,
next_id: Arc::clone(&tool.forwarder.next_id),
limits: tool.forwarder.limits,
});
}
check_cx(cx)?;
Ok(tools)
}
pub(super) fn admit_resource(
resource: &CanonicalHttpUrl,
reviewed: &ReviewedToolHeaders,
) -> McpResult<()> {
if resource != reviewed.resource() {
return Err(McpError::invalid_params(HEADER_FAILURE));
}
Ok(())
}
pub(super) fn native_backend(
session: ManagedOAuthSession,
reviewed: Arc<ReviewedToolHeaders>,
) -> McpResult<Arc<dyn CoreBackend>> {
admit_resource(session.resource(), &reviewed)?;
Ok(Arc::new(ReviewedNativeBackend { session, reviewed }))
}
struct ReviewedNativeBackend {
session: ManagedOAuthSession,
reviewed: Arc<ReviewedToolHeaders>,
}
impl CoreBackend for ReviewedNativeBackend {
fn execute<'a>(
&'a self,
ctx: &'a McpContext,
cx: &'a Cx,
request: CoreRequest,
id: RequestId,
limits: ManagedCoreLimits,
) -> BoxFuture<'a, McpResult<FinalCoreResult>> {
Box::pin(async move {
ctx.checkpoint()?;
check_cx(cx)?;
let cancellation = ctx.request_cancellation();
let mut call = self
.session
.request_tool_with_headers_and_cancellation(
cx,
&cancellation,
request,
id,
&self.reviewed,
limits,
)
.await
.map_err(header_error)?;
while let Some(event) = call.next_event(cx).await.map_err(upstream_error)? {
ctx.checkpoint()?;
check_cx(cx)?;
match event {
ManagedCoreEvent::Result(result) => {
return match *result {
CoreResult::Final(result) => Ok(result),
_ => Err(McpError::invalid_request(UNEXPECTED_RESULT)),
};
}
ManagedCoreEvent::Notification(notification) => {
forward_notification(ctx, *notification)?
}
}
}
Err(McpError::invalid_request(UNEXPECTED_RESULT))
})
}
}
fn header_error(error: ManagedToolHeaderError) -> McpError {
match error {
ManagedToolHeaderError::Core(error) => upstream_error(error),
ManagedToolHeaderError::Headers(_) => McpError::invalid_params(HEADER_FAILURE),
}
}
#[cfg(test)]
mod tests;