mcp_kit/server/extract.rs
1/// Extractors for tool/resource/prompt handlers — inspired by axum's extractor pattern.
2use crate::error::{McpError, McpResult};
3use serde::de::DeserializeOwned;
4
5// ─── Json<T> extractor ────────────────────────────────────────────────────────
6
7/// Deserialize the full arguments object as type `T`.
8pub struct Json<T>(pub T);
9
10impl<T: DeserializeOwned> Json<T> {
11 pub fn from_value(v: serde_json::Value) -> McpResult<Self> {
12 serde_json::from_value(v)
13 .map(Json)
14 .map_err(|e| McpError::InvalidParams(e.to_string()))
15 }
16}
17
18impl<T> std::ops::Deref for Json<T> {
19 type Target = T;
20 fn deref(&self) -> &Self::Target {
21 &self.0
22 }
23}
24
25// ─── State<S> extractor ───────────────────────────────────────────────────────
26
27/// Share arbitrary state across handlers.
28#[derive(Clone)]
29pub struct State<S>(pub S);
30
31impl<S> std::ops::Deref for State<S> {
32 type Target = S;
33 fn deref(&self) -> &Self::Target {
34 &self.0
35 }
36}
37
38// ─── Extension<T> extractor ───────────────────────────────────────────────────
39
40/// Type-map extension data attached to a session.
41#[derive(Clone)]
42pub struct Extension<T>(pub T);
43
44impl<T> std::ops::Deref for Extension<T> {
45 type Target = T;
46 fn deref(&self) -> &Self::Target {
47 &self.0
48 }
49}
50
51// ─── Auth extractor ───────────────────────────────────────────────────────────
52
53/// Extracts the [`AuthenticatedIdentity`] for the current request.
54///
55/// Available inside `#[tool]`, `#[resource]`, and `#[prompt]` handlers when
56/// the `auth` feature is enabled and the server is configured with an auth
57/// provider.
58///
59/// Returns [`McpError::Unauthorized`] if the request is unauthenticated.
60///
61/// # Example
62///
63/// ```rust,no_run
64/// use mcp_kit::prelude::*;
65/// use mcp_kit::Auth;
66///
67/// #[mcp_kit::tool(description = "A protected tool")]
68/// async fn my_tool(input: String, auth: Auth) -> McpResult<CallToolResult> {
69/// if !auth.has_scope("tools:execute") {
70/// return Err(McpError::Unauthorized("missing scope".into()));
71/// }
72/// Ok(CallToolResult::text(format!("Hello, {}!", auth.subject)))
73/// }
74/// ```
75///
76/// [`AuthenticatedIdentity`]: crate::auth::AuthenticatedIdentity
77/// [`McpError::Unauthorized`]: crate::error::McpError::Unauthorized
78#[cfg(feature = "auth")]
79pub struct Auth(pub crate::auth::AuthenticatedIdentity);
80
81#[cfg(feature = "auth")]
82impl Auth {
83 /// Pull the current identity from the task-local auth context.
84 /// Returns `Err(McpError::Unauthorized)` if no identity is present.
85 pub fn from_context() -> McpResult<Self> {
86 crate::server::auth_context::current()
87 .map(Auth)
88 .ok_or_else(|| McpError::Unauthorized("unauthenticated request".into()))
89 }
90}
91
92#[cfg(feature = "auth")]
93impl std::ops::Deref for Auth {
94 type Target = crate::auth::AuthenticatedIdentity;
95 fn deref(&self) -> &Self::Target {
96 &self.0
97 }
98}