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_async(code, &context).await #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 mut response = pmcp_code_mode::ValidationResponse::from_result(result);
344                    if response.result.is_valid {
345                        if dry_run {
346                            response.result.approval_token = None;
347                        }
348                        let risk = response.result.risk_level;
349                        response = response.with_auto_approved(self.config.should_auto_approve(risk));
350                    }
351
352                    let (json, _is_error) = response.to_json_response();
353                    Ok(json)
354                }
355
356                fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
357                    Some(pmcp_code_mode::CodeModeToolBuilder::new(#language_lit).build_validate_tool())
358                }
359            }
360
361            /// Internal state for the `execute_code` tool handler.
362            pub(super) struct ExecuteCodeHandler<E: pmcp_code_mode::CodeExecutor + 'static> {
363                pub(super) pipeline: Arc<pmcp_code_mode::ValidationPipeline>,
364                pub(super) executor: Arc<E>,
365            }
366
367            #[pmcp_code_mode::async_trait]
368            impl<E: pmcp_code_mode::CodeExecutor + 'static> pmcp::ToolHandler for ExecuteCodeHandler<E> {
369                async fn handle(
370                    &self,
371                    args: serde_json::Value,
372                    _extra: pmcp::RequestHandlerExtra,
373                ) -> pmcp::Result<serde_json::Value> {
374                    let input: pmcp_code_mode::ExecuteCodeInput =
375                        serde_json::from_value(args).map_err(|e| {
376                            pmcp::Error::Internal(format!("Invalid arguments: {}", e))
377                        })?;
378
379                    let code = input.code.trim();
380
381                    // Verify the approval token
382                    let token_gen = self.pipeline.token_generator();
383                    let token = pmcp_code_mode::ApprovalToken::decode(&input.approval_token)
384                        .map_err(|e| pmcp::Error::Internal(
385                            format!("Invalid approval token: {}", e),
386                        ))?;
387
388                    // Verify token signature and expiry
389                    token_gen.verify(&token)
390                        .map_err(|e| pmcp::Error::Internal(
391                            format!("Token verification failed: {}", e),
392                        ))?;
393
394                    // Verify code matches the token's code hash
395                    token_gen.verify_code(code, &token)
396                        .map_err(|e| pmcp::Error::Internal(
397                            format!("Code verification failed: {}", e),
398                        ))?;
399
400                    // Execute the validated code
401                    let result = self.executor.execute(code, input.variables.as_ref()).await
402                        .map_err(|e| pmcp::Error::Internal(
403                            format!("Execution error: {}", e),
404                        ))?;
405
406                    Ok(result)
407                }
408
409                fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
410                    Some(pmcp_code_mode::CodeModeToolBuilder::new(#language_lit).build_execute_tool())
411                }
412            }
413        }
414
415        impl #struct_name {
416            /// Register Code Mode tools (`validate_code` + `execute_code`) on the builder.
417            ///
418            /// Uses the `context_from` method to extract real `ValidationContext` from
419            /// each request. Requires `self: &Arc<Self>` to share the server reference
420            /// with the generated handler.
421            ///
422            /// # Errors
423            ///
424            /// Returns [`pmcp_code_mode::TokenError`] if the `token_secret` is too short
425            /// for secure HMAC token generation.
426            ///
427            /// # Example
428            ///
429            /// ```rust,ignore
430            /// let server = Arc::new(my_server);
431            /// let builder = server.register_code_mode_tools(Server::builder())?;
432            /// ```
433            pub fn register_code_mode_tools(
434                self: &std::sync::Arc<Self>,
435                builder: pmcp::ServerBuilder,
436            ) -> Result<pmcp::ServerBuilder, pmcp_code_mode::TokenError> {
437                let pipeline = std::sync::Arc::new(
438                    pmcp_code_mode::ValidationPipeline::from_token_secret_with_policy(
439                        self.code_mode_config.clone(),
440                        &self.token_secret,
441                        std::sync::Arc::clone(&self.policy_evaluator) as std::sync::Arc<dyn pmcp_code_mode::PolicyEvaluator>,
442                    )?
443                );
444
445                let validate_handler = #mod_name::ValidateCodeHandler {
446                    pipeline: std::sync::Arc::clone(&pipeline),
447                    config: self.code_mode_config.clone(),
448                    parent: std::sync::Arc::clone(self),
449                };
450
451                let execute_handler = #mod_name::ExecuteCodeHandler {
452                    pipeline,
453                    executor: std::sync::Arc::clone(&self.code_executor),
454                };
455
456                Ok(builder
457                    .tool("validate_code", validate_handler)
458                    .tool("execute_code", execute_handler))
459            }
460        }
461    }
462}
463
464/// Generate code when `context_from` is NOT specified (backward-compatible path).
465///
466/// The registration method uses `&self` (no `Arc` needed) and the validate handler
467/// uses placeholder `ValidationContext` values with a `#[deprecated]` warning guiding
468/// users toward the `context_from` attribute for production use.
469fn expand_without_context_from(
470    struct_name: &syn::Ident,
471    mod_name: &syn::Ident,
472    language_lit: &syn::LitStr,
473    validation_call: &proc_macro2::TokenStream,
474) -> proc_macro2::TokenStream {
475    quote! {
476        // Send + Sync compile-time assertion (D-08)
477        const _: fn() = || {
478            fn assert_send_sync<T: Send + Sync>() {}
479            assert_send_sync::<#struct_name>();
480        };
481
482        #[doc(hidden)]
483        #[allow(non_snake_case)]
484        mod #mod_name {
485            use super::*;
486            use std::sync::Arc;
487            // Import TokenGenerator trait to bring verify/verify_code into scope
488            use pmcp_code_mode::TokenGenerator as _;
489
490            /// Internal state for the `validate_code` tool handler.
491            pub(super) struct ValidateCodeHandler {
492                pub(super) pipeline: Arc<pmcp_code_mode::ValidationPipeline>,
493                pub(super) config: pmcp_code_mode::CodeModeConfig,
494            }
495
496            #[pmcp_code_mode::async_trait]
497            impl pmcp::ToolHandler for ValidateCodeHandler {
498                async fn handle(
499                    &self,
500                    args: serde_json::Value,
501                    _extra: pmcp::RequestHandlerExtra,
502                ) -> pmcp::Result<serde_json::Value> {
503                    let input: pmcp_code_mode::ValidateCodeInput =
504                        serde_json::from_value(args).map_err(|e| {
505                            pmcp::Error::Internal(format!("Invalid arguments: {}", e))
506                        })?;
507
508                    let code = input.code.trim();
509                    let dry_run = input.dry_run.unwrap_or(false);
510
511                    // WARNING: These are PLACEHOLDER values. The validation context
512                    // uses static strings, so approval tokens are NOT bound to a
513                    // specific user, session, or schema version. An attacker who
514                    // obtains a valid token can replay it across different users and
515                    // sessions until it expires.
516                    //
517                    // Use `#[code_mode(context_from = "method_name")]` for production.
518                    let context = pmcp_code_mode::ValidationContext::new(
519                        "anonymous",
520                        "session",
521                        "schema",
522                        "perms",
523                    );
524
525                    let result = #validation_call;
526
527                    let mut response = pmcp_code_mode::ValidationResponse::from_result(result);
528                    if response.result.is_valid {
529                        if dry_run {
530                            response.result.approval_token = None;
531                        }
532                        let risk = response.result.risk_level;
533                        response = response.with_auto_approved(self.config.should_auto_approve(risk));
534                    }
535
536                    let (json, _is_error) = response.to_json_response();
537                    Ok(json)
538                }
539
540                fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
541                    Some(pmcp_code_mode::CodeModeToolBuilder::new(#language_lit).build_validate_tool())
542                }
543            }
544
545            /// Internal state for the `execute_code` tool handler.
546            pub(super) struct ExecuteCodeHandler<E: pmcp_code_mode::CodeExecutor + 'static> {
547                pub(super) pipeline: Arc<pmcp_code_mode::ValidationPipeline>,
548                pub(super) executor: Arc<E>,
549            }
550
551            #[pmcp_code_mode::async_trait]
552            impl<E: pmcp_code_mode::CodeExecutor + 'static> pmcp::ToolHandler for ExecuteCodeHandler<E> {
553                async fn handle(
554                    &self,
555                    args: serde_json::Value,
556                    _extra: pmcp::RequestHandlerExtra,
557                ) -> pmcp::Result<serde_json::Value> {
558                    let input: pmcp_code_mode::ExecuteCodeInput =
559                        serde_json::from_value(args).map_err(|e| {
560                            pmcp::Error::Internal(format!("Invalid arguments: {}", e))
561                        })?;
562
563                    let code = input.code.trim();
564
565                    // Verify the approval token
566                    let token_gen = self.pipeline.token_generator();
567                    let token = pmcp_code_mode::ApprovalToken::decode(&input.approval_token)
568                        .map_err(|e| pmcp::Error::Internal(
569                            format!("Invalid approval token: {}", e),
570                        ))?;
571
572                    // Verify token signature and expiry
573                    token_gen.verify(&token)
574                        .map_err(|e| pmcp::Error::Internal(
575                            format!("Token verification failed: {}", e),
576                        ))?;
577
578                    // Verify code matches the token's code hash
579                    token_gen.verify_code(code, &token)
580                        .map_err(|e| pmcp::Error::Internal(
581                            format!("Code verification failed: {}", e),
582                        ))?;
583
584                    // Execute the validated code
585                    let result = self.executor.execute(code, input.variables.as_ref()).await
586                        .map_err(|e| pmcp::Error::Internal(
587                            format!("Execution error: {}", e),
588                        ))?;
589
590                    Ok(result)
591                }
592
593                fn metadata(&self) -> Option<pmcp::types::ToolInfo> {
594                    Some(pmcp_code_mode::CodeModeToolBuilder::new(#language_lit).build_execute_tool())
595                }
596            }
597        }
598
599        impl #struct_name {
600            /// Register Code Mode tools (`validate_code` + `execute_code`) on the builder.
601            ///
602            /// **Deprecated:** Uses placeholder `ValidationContext` values. Use
603            /// `#[code_mode(context_from = "method_name")]` for production to bind
604            /// approval tokens to real user identity and session.
605            ///
606            /// # Errors
607            ///
608            /// Returns [`pmcp_code_mode::TokenError`] if the `token_secret` is too short
609            /// for secure HMAC token generation.
610            ///
611            /// # Example
612            ///
613            /// ```rust,ignore
614            /// #[allow(deprecated)]
615            /// let builder = server.register_code_mode_tools(Server::builder())?;
616            /// ```
617            #[deprecated(note = "Use #[code_mode(context_from = \"method_name\")] for production. This uses placeholder ValidationContext.")]
618            pub fn register_code_mode_tools(
619                &self,
620                builder: pmcp::ServerBuilder,
621            ) -> Result<pmcp::ServerBuilder, pmcp_code_mode::TokenError> {
622                let pipeline = std::sync::Arc::new(
623                    pmcp_code_mode::ValidationPipeline::from_token_secret_with_policy(
624                        self.code_mode_config.clone(),
625                        &self.token_secret,
626                        std::sync::Arc::clone(&self.policy_evaluator) as std::sync::Arc<dyn pmcp_code_mode::PolicyEvaluator>,
627                    )?
628                );
629
630                let validate_handler = #mod_name::ValidateCodeHandler {
631                    pipeline: std::sync::Arc::clone(&pipeline),
632                    config: self.code_mode_config.clone(),
633                };
634
635                let execute_handler = #mod_name::ExecuteCodeHandler {
636                    pipeline,
637                    executor: std::sync::Arc::clone(&self.code_executor),
638                };
639
640                Ok(builder
641                    .tool("validate_code", validate_handler)
642                    .tool("execute_code", execute_handler))
643            }
644        }
645    }
646}