Skip to main content

pmcp_code_mode_derive/
lib.rs

1// Allow needless_continue from darling's generated derive code
2#![allow(clippy::needless_continue)]
3
4//! Derive macro for Code Mode validation and execution in MCP servers.
5//!
6//! Provides `#[derive(CodeMode)]` which generates a `register_code_mode_tools`
7//! method that registers `validate_code` and `execute_code` tools on a
8//! [`pmcp::ServerBuilder`].
9//!
10//! # Field Name Convention (v0.1.0)
11//!
12//! The macro identifies required fields by **fixed well-known names**. This is
13//! the v0.1.0 contract -- the field names are the API:
14//!
15//! | Field Name | Required Type | Purpose |
16//! |------------|---------------|---------|
17//! | `code_mode_config` | `CodeModeConfig` | Validation pipeline configuration |
18//! | `token_secret` | `TokenSecret` | HMAC signing secret |
19//! | `policy_evaluator` | `Arc<dyn PolicyEvaluator>` or `Arc<P>` | Policy evaluation |
20//! | `code_executor` | `Arc<dyn CodeExecutor>` or `Arc<E>` | Code execution |
21//!
22//! If any required field is missing, the macro emits a **single** compile error
23//! listing all absent fields.
24//!
25//! # Struct-Level Attributes (v0.2.0)
26//!
27//! | Attribute | Type | Default | Purpose |
28//! |-----------|------|---------|---------|
29//! | `context_from` | `String` | (none) | Method name returning `ValidationContext` |
30//! | `language` | `String` | `"graphql"` | Selects validation path and tool metadata |
31//!
32//! Supported `language` values:
33//!
34//! | Value | Validation Method | Feature Required |
35//! |-------|-------------------|------------------|
36//! | `"graphql"` (default) | `validate_graphql_query_async` | *(none)* |
37//! | `"javascript"` / `"js"` | `validate_javascript_code` | `openapi-code-mode` |
38//! | `"sql"` | `validate_sql_query` | `sql-code-mode` |
39//! | `"mcp"` | `validate_mcp_composition` | `mcp-code-mode` |
40//!
41//! When `context_from` is specified, `register_code_mode_tools` requires
42//! `self: &Arc<Self>` and the generated handler calls `self.parent.{method}(&extra)`
43//! to obtain real `ValidationContext` bound to the current user/session.
44//!
45//! When `context_from` is omitted, `register_code_mode_tools` uses `&self` (no Arc
46//! required) with placeholder context values and a `#[deprecated]` warning guiding
47//! users toward the production path.
48//!
49//! # Generated Code
50//!
51//! The macro generates:
52//!
53//! 1. A `register_code_mode_tools` method on the struct that takes a
54//!    `ServerBuilder` **by value** and returns it (by-value fluent pattern).
55//! 2. Two internal handler structs (`ValidateCodeHandler` and
56//!    `ExecuteCodeHandler`) that implement `pmcp::ToolHandler`.
57//! 3. A `Send + Sync` compile-time assertion (per D-08).
58//!
59//! # Examples
60//!
61//! **Production (with `context_from`):**
62//! ```rust,ignore
63//! #[derive(CodeMode)]
64//! #[code_mode(context_from = "get_context", language = "graphql")]
65//! struct MyServer {
66//!     code_mode_config: CodeModeConfig,
67//!     token_secret: TokenSecret,
68//!     policy_evaluator: Arc<NoopPolicyEvaluator>,
69//!     code_executor: Arc<MyExecutor>,
70//! }
71//!
72//! impl MyServer {
73//!     fn get_context(&self, extra: &RequestHandlerExtra) -> ValidationContext {
74//!         ValidationContext::new("user-1", "session-1", "schema-v1", "perms-v1")
75//!     }
76//! }
77//!
78//! // Generated: MyServer::register_code_mode_tools(self: &Arc<Self>, builder) -> ServerBuilder
79//! ```
80//!
81//! **Testing (without `context_from`, deprecated placeholder path):**
82//! ```rust,ignore
83//! #[derive(CodeMode)]
84//! struct MyServer {
85//!     code_mode_config: CodeModeConfig,
86//!     token_secret: TokenSecret,
87//!     policy_evaluator: Arc<NoopPolicyEvaluator>,
88//!     code_executor: Arc<MyExecutor>,
89//! }
90//!
91//! // Generated: #[deprecated] MyServer::register_code_mode_tools(&self, builder) -> ServerBuilder
92//! ```
93
94use darling::FromDeriveInput;
95use proc_macro::TokenStream;
96use quote::quote;
97use syn::{parse_macro_input, DeriveInput};
98
99/// Required field names for the Code Mode derive macro.
100const REQUIRED_FIELDS: &[&str] = &[
101    "code_mode_config",
102    "token_secret",
103    "policy_evaluator",
104    "code_executor",
105];
106
107/// Parsed attributes from `#[derive(CodeMode)]`.
108///
109/// Struct-level attributes (v0.2.0):
110/// - `context_from`: Optional method name for extracting `ValidationContext`.
111///   When specified, the generated `register_code_mode_tools` requires `self: &Arc<Self>`.
112/// - `language`: Code language for tool metadata. Defaults to `"graphql"`.
113#[derive(Debug, FromDeriveInput)]
114#[darling(attributes(code_mode))]
115struct CodeModeOpts {
116    ident: syn::Ident,
117    data: darling::ast::Data<(), CodeModeField>,
118    /// Optional method name for extracting `ValidationContext` from the struct.
119    /// When specified, the generated registration method requires `self: &Arc<Self>`.
120    #[darling(default)]
121    context_from: Option<String>,
122    /// Code language — selects both the validation method and tool metadata.
123    /// Supported: `"graphql"` (default), `"javascript"`/`"js"`, `"sql"`, `"mcp"`.
124    /// Non-default languages require their respective feature flag on `pmcp-code-mode`.
125    #[darling(default)]
126    language: Option<String>,
127}
128
129/// A single field parsed from the struct.
130#[derive(Debug, Clone, darling::FromField)]
131#[darling(attributes(code_mode))]
132struct CodeModeField {
133    ident: Option<syn::Ident>,
134}
135
136/// Derive macro that generates `register_code_mode_tools` for Code Mode servers.
137///
138/// Requires a named struct with four well-known fields:
139/// `code_mode_config`, `token_secret`, `policy_evaluator`, `code_executor`.
140///
141/// See [crate-level documentation](crate) for the full field name convention.
142#[proc_macro_derive(CodeMode, attributes(code_mode))]
143pub fn code_mode_derive(input: TokenStream) -> TokenStream {
144    let input = parse_macro_input!(input as DeriveInput);
145    expand_code_mode(&input)
146        .unwrap_or_else(|err| err.to_compile_error())
147        .into()
148}
149
150/// Core expansion logic for `#[derive(CodeMode)]`.
151fn expand_code_mode(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
152    let opts = CodeModeOpts::from_derive_input(input)
153        .map_err(|e| syn::Error::new_spanned(input, e.to_string()))?;
154
155    let struct_name = &opts.ident;
156
157    // Extract named fields
158    let fields = match &opts.data {
159        darling::ast::Data::Struct(ref fields) => &fields.fields,
160        darling::ast::Data::Enum(_) => {
161            return Err(syn::Error::new_spanned(
162                input,
163                "#[derive(CodeMode)] can only be applied to structs with named fields",
164            ));
165        },
166    };
167
168    let field_names: Vec<String> = fields
169        .iter()
170        .filter_map(|f| f.ident.as_ref().map(|i| i.to_string()))
171        .collect();
172
173    // Check for missing required fields
174    let missing: Vec<&str> = REQUIRED_FIELDS
175        .iter()
176        .filter(|&&name| !field_names.contains(&name.to_string()))
177        .copied()
178        .collect();
179
180    if !missing.is_empty() {
181        let all_required = REQUIRED_FIELDS.join(", ");
182        let missing_msgs: Vec<String> = missing
183            .iter()
184            .map(|&name| {
185                let type_hint = match name {
186                    "code_mode_config" => "CodeModeConfig",
187                    "token_secret" => "TokenSecret",
188                    "policy_evaluator" => "Arc<dyn PolicyEvaluator>",
189                    "code_executor" => "Arc<dyn CodeExecutor>",
190                    _ => "unknown",
191                };
192                format!(
193                    "#[derive(CodeMode)] requires field `{name}` (type: {type_hint}).\n\
194                     Required fields: {all_required}"
195                )
196            })
197            .collect();
198        let msg = missing_msgs.join("\n\n");
199        return Err(syn::Error::new_spanned(&input.ident, msg));
200    }
201
202    // Generate the handler module name to avoid collision (snake_case to suppress warnings)
203    let mod_name = syn::Ident::new(
204        &format!(
205            "__code_mode_impl_{}",
206            struct_name.to_string().to_lowercase()
207        ),
208        struct_name.span(),
209    );
210
211    // Extract and validate language (defaults to "graphql").
212    // Known values mirror pmcp_code_mode::CodeLanguage — keep in sync.
213    let language = opts.language.as_deref().unwrap_or("graphql");
214    let language_lit = syn::LitStr::new(language, struct_name.span());
215
216    let validation_call = gen_validation_call(language, &input.ident)?;
217
218    // Branch code generation based on context_from presence
219    let expanded = if let Some(ref method_name) = opts.context_from {
220        // Validate that context_from is a valid Rust identifier
221        if method_name.is_empty() || syn::parse_str::<syn::Ident>(method_name).is_err() {
222            return Err(syn::Error::new_spanned(
223                &input.ident,
224                format!("`context_from = \"{method_name}\"` is not a valid Rust identifier"),
225            ));
226        }
227        let method_ident = syn::Ident::new(method_name, struct_name.span());
228        expand_with_context_from(
229            struct_name,
230            &mod_name,
231            &language_lit,
232            &method_ident,
233            &validation_call,
234        )
235    } else {
236        // --- default path: placeholder context with deprecation warning ---
237        expand_without_context_from(struct_name, &mod_name, &language_lit, &validation_call)
238    };
239
240    Ok(expanded)
241}
242
243/// Generate the validation call token stream for the given language.
244///
245/// Each language maps to a specific `ValidationPipeline` method. The method may be
246/// sync or async — the generated handler is always async, so sync methods work fine.
247///
248/// # Supported Languages
249///
250/// | Language | Method | Async | Feature |
251/// |----------|--------|-------|---------|
252/// | `graphql` | `validate_graphql_query_async` | yes | *(none)* |
253/// | `javascript`/`js` | `validate_javascript_code` | no | `openapi-code-mode` |
254/// | `sql` | `validate_sql_query` | no | `sql-code-mode` |
255/// | `mcp` | `validate_mcp_composition` | yes | `mcp-code-mode` |
256///
257/// To add a new language: add a match arm here and a variant to `CodeLanguage` in
258/// `pmcp-code-mode/src/types.rs`.
259fn gen_validation_call(
260    language: &str,
261    error_span: &syn::Ident,
262) -> Result<proc_macro2::TokenStream, syn::Error> {
263    let map_err = quote! {
264        .map_err(|e| pmcp::Error::Internal(format!("Validation error: {}", e)))?
265    };
266    match language {
267        "graphql" => Ok(quote! {
268            self.pipeline.validate_graphql_query_async(code, &context).await #map_err
269        }),
270        "javascript" | "js" => Ok(quote! {
271            self.pipeline.validate_javascript_code(code, &context) #map_err
272        }),
273        "sql" => Ok(quote! {
274            self.pipeline.validate_sql_query(code, &context) #map_err
275        }),
276        "mcp" => Ok(quote! {
277            self.pipeline.validate_mcp_composition(code, &context).await #map_err
278        }),
279        other => Err(syn::Error::new_spanned(
280            error_span,
281            format!(
282                "`language = \"{other}\"` is not a supported language. \
283                 Supported values: \"graphql\" (default), \"javascript\" (requires `openapi-code-mode`), \
284                 \"sql\" (requires `sql-code-mode`), \"mcp\" (requires `mcp-code-mode`)"
285            ),
286        )),
287    }
288}
289
290/// Generate code when `context_from` is specified.
291///
292/// The registration method requires `self: &Arc<Self>` and the validate handler
293/// calls `self.parent.{method_ident}(&extra)` for real `ValidationContext`.
294fn expand_with_context_from(
295    struct_name: &syn::Ident,
296    mod_name: &syn::Ident,
297    language_lit: &syn::LitStr,
298    method_ident: &syn::Ident,
299    validation_call: &proc_macro2::TokenStream,
300) -> proc_macro2::TokenStream {
301    quote! {
302        // Send + Sync compile-time assertion (D-08)
303        const _: fn() = || {
304            fn assert_send_sync<T: Send + Sync>() {}
305            assert_send_sync::<#struct_name>();
306        };
307
308        #[doc(hidden)]
309        #[allow(non_snake_case)]
310        mod #mod_name {
311            use super::*;
312            use std::sync::Arc;
313            // Import TokenGenerator trait to bring verify/verify_code into scope
314            use pmcp_code_mode::TokenGenerator as _;
315
316            /// Internal state for the `validate_code` tool handler.
317            pub(super) struct ValidateCodeHandler {
318                pub(super) pipeline: Arc<pmcp_code_mode::ValidationPipeline>,
319                pub(super) config: pmcp_code_mode::CodeModeConfig,
320                pub(super) parent: Arc<#struct_name>,
321            }
322
323            #[pmcp_code_mode::async_trait]
324            impl pmcp::ToolHandler for ValidateCodeHandler {
325                async fn handle(
326                    &self,
327                    args: serde_json::Value,
328                    extra: pmcp::RequestHandlerExtra,
329                ) -> pmcp::Result<serde_json::Value> {
330                    let input: pmcp_code_mode::ValidateCodeInput =
331                        serde_json::from_value(args).map_err(|e| {
332                            pmcp::Error::Internal(format!("Invalid arguments: {}", e))
333                        })?;
334
335                    let code = input.code.trim();
336                    let dry_run = input.dry_run.unwrap_or(false);
337
338                    // Real ValidationContext from user-defined method
339                    let context = self.parent.#method_ident(&extra);
340
341                    let result = #validation_call;
342
343                    let response = pmcp_code_mode::ValidationResponse::success(
344                        result.explanation.clone(),
345                        result.risk_level,
346                        if dry_run {
347                            String::new()
348                        } else {
349                            result.approval_token.clone().unwrap_or_default()
350                        },
351                        result.metadata.clone(),
352                    )
353                    .with_warnings(result.warnings.clone())
354                    .with_auto_approved(self.config.should_auto_approve(result.risk_level));
355
356                    let (json, _is_error) = response.to_json_response();
357                    Ok(json)
358                }
359
360                fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
361                    Some(pmcp_code_mode::CodeModeToolBuilder::new(#language_lit).build_validate_tool())
362                }
363            }
364
365            /// Internal state for the `execute_code` tool handler.
366            pub(super) struct ExecuteCodeHandler<E: pmcp_code_mode::CodeExecutor + 'static> {
367                pub(super) pipeline: Arc<pmcp_code_mode::ValidationPipeline>,
368                pub(super) executor: Arc<E>,
369            }
370
371            #[pmcp_code_mode::async_trait]
372            impl<E: pmcp_code_mode::CodeExecutor + 'static> pmcp::ToolHandler for ExecuteCodeHandler<E> {
373                async fn handle(
374                    &self,
375                    args: serde_json::Value,
376                    _extra: pmcp::RequestHandlerExtra,
377                ) -> pmcp::Result<serde_json::Value> {
378                    let input: pmcp_code_mode::ExecuteCodeInput =
379                        serde_json::from_value(args).map_err(|e| {
380                            pmcp::Error::Internal(format!("Invalid arguments: {}", e))
381                        })?;
382
383                    let code = input.code.trim();
384
385                    // Verify the approval token
386                    let token_gen = self.pipeline.token_generator();
387                    let token = pmcp_code_mode::ApprovalToken::decode(&input.approval_token)
388                        .map_err(|e| pmcp::Error::Internal(
389                            format!("Invalid approval token: {}", e),
390                        ))?;
391
392                    // Verify token signature and expiry
393                    token_gen.verify(&token)
394                        .map_err(|e| pmcp::Error::Internal(
395                            format!("Token verification failed: {}", e),
396                        ))?;
397
398                    // Verify code matches the token's code hash
399                    token_gen.verify_code(code, &token)
400                        .map_err(|e| pmcp::Error::Internal(
401                            format!("Code verification failed: {}", e),
402                        ))?;
403
404                    // Execute the validated code
405                    let result = self.executor.execute(code, input.variables.as_ref()).await
406                        .map_err(|e| pmcp::Error::Internal(
407                            format!("Execution error: {}", e),
408                        ))?;
409
410                    Ok(result)
411                }
412
413                fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
414                    Some(pmcp_code_mode::CodeModeToolBuilder::new(#language_lit).build_execute_tool())
415                }
416            }
417        }
418
419        impl #struct_name {
420            /// Register Code Mode tools (`validate_code` + `execute_code`) on the builder.
421            ///
422            /// Uses the `context_from` method to extract real `ValidationContext` from
423            /// each request. Requires `self: &Arc<Self>` to share the server reference
424            /// with the generated handler.
425            ///
426            /// # Errors
427            ///
428            /// Returns [`pmcp_code_mode::TokenError`] if the `token_secret` is too short
429            /// for secure HMAC token generation.
430            ///
431            /// # Example
432            ///
433            /// ```rust,ignore
434            /// let server = Arc::new(my_server);
435            /// let builder = server.register_code_mode_tools(Server::builder())?;
436            /// ```
437            pub fn register_code_mode_tools(
438                self: &std::sync::Arc<Self>,
439                builder: pmcp::ServerBuilder,
440            ) -> Result<pmcp::ServerBuilder, pmcp_code_mode::TokenError> {
441                let pipeline = std::sync::Arc::new(
442                    pmcp_code_mode::ValidationPipeline::from_token_secret_with_policy(
443                        self.code_mode_config.clone(),
444                        &self.token_secret,
445                        std::sync::Arc::clone(&self.policy_evaluator) as std::sync::Arc<dyn pmcp_code_mode::PolicyEvaluator>,
446                    )?
447                );
448
449                let validate_handler = #mod_name::ValidateCodeHandler {
450                    pipeline: std::sync::Arc::clone(&pipeline),
451                    config: self.code_mode_config.clone(),
452                    parent: std::sync::Arc::clone(self),
453                };
454
455                let execute_handler = #mod_name::ExecuteCodeHandler {
456                    pipeline,
457                    executor: std::sync::Arc::clone(&self.code_executor),
458                };
459
460                Ok(builder
461                    .tool("validate_code", validate_handler)
462                    .tool("execute_code", execute_handler))
463            }
464        }
465    }
466}
467
468/// Generate code when `context_from` is NOT specified (backward-compatible path).
469///
470/// The registration method uses `&self` (no `Arc` needed) and the validate handler
471/// uses placeholder `ValidationContext` values with a `#[deprecated]` warning guiding
472/// users toward the `context_from` attribute for production use.
473fn expand_without_context_from(
474    struct_name: &syn::Ident,
475    mod_name: &syn::Ident,
476    language_lit: &syn::LitStr,
477    validation_call: &proc_macro2::TokenStream,
478) -> proc_macro2::TokenStream {
479    quote! {
480        // Send + Sync compile-time assertion (D-08)
481        const _: fn() = || {
482            fn assert_send_sync<T: Send + Sync>() {}
483            assert_send_sync::<#struct_name>();
484        };
485
486        #[doc(hidden)]
487        #[allow(non_snake_case)]
488        mod #mod_name {
489            use super::*;
490            use std::sync::Arc;
491            // Import TokenGenerator trait to bring verify/verify_code into scope
492            use pmcp_code_mode::TokenGenerator as _;
493
494            /// Internal state for the `validate_code` tool handler.
495            pub(super) struct ValidateCodeHandler {
496                pub(super) pipeline: Arc<pmcp_code_mode::ValidationPipeline>,
497                pub(super) config: pmcp_code_mode::CodeModeConfig,
498            }
499
500            #[pmcp_code_mode::async_trait]
501            impl pmcp::ToolHandler for ValidateCodeHandler {
502                async fn handle(
503                    &self,
504                    args: serde_json::Value,
505                    _extra: pmcp::RequestHandlerExtra,
506                ) -> pmcp::Result<serde_json::Value> {
507                    let input: pmcp_code_mode::ValidateCodeInput =
508                        serde_json::from_value(args).map_err(|e| {
509                            pmcp::Error::Internal(format!("Invalid arguments: {}", e))
510                        })?;
511
512                    let code = input.code.trim();
513                    let dry_run = input.dry_run.unwrap_or(false);
514
515                    // WARNING: These are PLACEHOLDER values. The validation context
516                    // uses static strings, so approval tokens are NOT bound to a
517                    // specific user, session, or schema version. An attacker who
518                    // obtains a valid token can replay it across different users and
519                    // sessions until it expires.
520                    //
521                    // Use `#[code_mode(context_from = "method_name")]` for production.
522                    let context = pmcp_code_mode::ValidationContext::new(
523                        "anonymous",
524                        "session",
525                        "schema",
526                        "perms",
527                    );
528
529                    let result = #validation_call;
530
531                    let response = pmcp_code_mode::ValidationResponse::success(
532                        result.explanation.clone(),
533                        result.risk_level,
534                        if dry_run {
535                            String::new()
536                        } else {
537                            result.approval_token.clone().unwrap_or_default()
538                        },
539                        result.metadata.clone(),
540                    )
541                    .with_warnings(result.warnings.clone())
542                    .with_auto_approved(self.config.should_auto_approve(result.risk_level));
543
544                    let (json, _is_error) = response.to_json_response();
545                    Ok(json)
546                }
547
548                fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
549                    Some(pmcp_code_mode::CodeModeToolBuilder::new(#language_lit).build_validate_tool())
550                }
551            }
552
553            /// Internal state for the `execute_code` tool handler.
554            pub(super) struct ExecuteCodeHandler<E: pmcp_code_mode::CodeExecutor + 'static> {
555                pub(super) pipeline: Arc<pmcp_code_mode::ValidationPipeline>,
556                pub(super) executor: Arc<E>,
557            }
558
559            #[pmcp_code_mode::async_trait]
560            impl<E: pmcp_code_mode::CodeExecutor + 'static> pmcp::ToolHandler for ExecuteCodeHandler<E> {
561                async fn handle(
562                    &self,
563                    args: serde_json::Value,
564                    _extra: pmcp::RequestHandlerExtra,
565                ) -> pmcp::Result<serde_json::Value> {
566                    let input: pmcp_code_mode::ExecuteCodeInput =
567                        serde_json::from_value(args).map_err(|e| {
568                            pmcp::Error::Internal(format!("Invalid arguments: {}", e))
569                        })?;
570
571                    let code = input.code.trim();
572
573                    // Verify the approval token
574                    let token_gen = self.pipeline.token_generator();
575                    let token = pmcp_code_mode::ApprovalToken::decode(&input.approval_token)
576                        .map_err(|e| pmcp::Error::Internal(
577                            format!("Invalid approval token: {}", e),
578                        ))?;
579
580                    // Verify token signature and expiry
581                    token_gen.verify(&token)
582                        .map_err(|e| pmcp::Error::Internal(
583                            format!("Token verification failed: {}", e),
584                        ))?;
585
586                    // Verify code matches the token's code hash
587                    token_gen.verify_code(code, &token)
588                        .map_err(|e| pmcp::Error::Internal(
589                            format!("Code verification failed: {}", e),
590                        ))?;
591
592                    // Execute the validated code
593                    let result = self.executor.execute(code, input.variables.as_ref()).await
594                        .map_err(|e| pmcp::Error::Internal(
595                            format!("Execution error: {}", e),
596                        ))?;
597
598                    Ok(result)
599                }
600
601                fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
602                    Some(pmcp_code_mode::CodeModeToolBuilder::new(#language_lit).build_execute_tool())
603                }
604            }
605        }
606
607        impl #struct_name {
608            /// Register Code Mode tools (`validate_code` + `execute_code`) on the builder.
609            ///
610            /// **Deprecated:** Uses placeholder `ValidationContext` values. Use
611            /// `#[code_mode(context_from = "method_name")]` for production to bind
612            /// approval tokens to real user identity and session.
613            ///
614            /// # Errors
615            ///
616            /// Returns [`pmcp_code_mode::TokenError`] if the `token_secret` is too short
617            /// for secure HMAC token generation.
618            ///
619            /// # Example
620            ///
621            /// ```rust,ignore
622            /// #[allow(deprecated)]
623            /// let builder = server.register_code_mode_tools(Server::builder())?;
624            /// ```
625            #[deprecated(note = "Use #[code_mode(context_from = \"method_name\")] for production. This uses placeholder ValidationContext.")]
626            pub fn register_code_mode_tools(
627                &self,
628                builder: pmcp::ServerBuilder,
629            ) -> Result<pmcp::ServerBuilder, pmcp_code_mode::TokenError> {
630                let pipeline = std::sync::Arc::new(
631                    pmcp_code_mode::ValidationPipeline::from_token_secret_with_policy(
632                        self.code_mode_config.clone(),
633                        &self.token_secret,
634                        std::sync::Arc::clone(&self.policy_evaluator) as std::sync::Arc<dyn pmcp_code_mode::PolicyEvaluator>,
635                    )?
636                );
637
638                let validate_handler = #mod_name::ValidateCodeHandler {
639                    pipeline: std::sync::Arc::clone(&pipeline),
640                    config: self.code_mode_config.clone(),
641                };
642
643                let execute_handler = #mod_name::ExecuteCodeHandler {
644                    pipeline,
645                    executor: std::sync::Arc::clone(&self.code_executor),
646                };
647
648                Ok(builder
649                    .tool("validate_code", validate_handler)
650                    .tool("execute_code", execute_handler))
651            }
652        }
653    }
654}