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}