turbomcp-proxy 3.0.12

Universal MCP adapter/generator - introspection, proxying, and code generation for any MCP server
//! Proxy implementation for {{server_name}}
//!
//! This module provides the proxy logic for forwarding requests between
//! the {{frontend_type}} frontend and {{backend_type}} backend.

use std::collections::HashMap;
use std::sync::Arc;
use serde_json::Value;
use turbomcp_client::Client;
use turbomcp_transport::ChildProcessTransport;

/// Proxy router that forwards MCP requests to the backend server
pub struct ProxyRouter {
    /// Backend MCP client
    backend: Arc<Client<ChildProcessTransport>>,
}

impl ProxyRouter {
    /// Create a new proxy router
    ///
    /// # Arguments
    ///
    /// * `backend` - The backend MCP client (must be initialized)
    pub fn new(backend: Arc<Client<ChildProcessTransport>>) -> Self {
        Self { backend }
    }

    /// Get a reference to the backend client
    pub fn backend(&self) -> &Arc<Client<ChildProcessTransport>> {
        &self.backend
    }

    /// Route a tool call to the appropriate handler
    ///
    /// # Arguments
    ///
    /// * `tool_name` - The name of the tool to call
    /// * `arguments` - The arguments for the tool
    ///
    /// # Returns
    ///
    /// Returns the tool result as JSON value
    pub async fn route_tool(
        &self,
        tool_name: &str,
        arguments: Option<HashMap<String, Value>>,
    ) -> Result<Value, Box<dyn std::error::Error>> {
        tracing::debug!(tool = tool_name, "Routing tool call");

        match tool_name {
            {{#each tools}}
            "{{this.name}}" => self.handle_{{snake_case this.name}}(arguments).await,
            {{/each}}
            _ => Err(format!("Unknown tool: {}", tool_name).into()),
        }
    }

    {{#each tools}}
    /// Handle {{this.name}} tool
    ///
    /// Description: {{this.description}}
    {{#if this.input_type}}
    ///
    /// # Input Type
    /// `{{this.input_type}}`
    {{/if}}
    async fn handle_{{snake_case this.name}}(
        &self,
        arguments: Option<HashMap<String, Value>>,
    ) -> Result<Value, Box<dyn std::error::Error>> {
        tracing::debug!(tool = "{{this.name}}", "Handling tool call");

        // Forward to backend and return result
        let result = self.backend.call_tool("{{this.name}}", arguments).await
            .map_err(|e| format!("Backend call failed for {{this.name}}: {}", e))?;

        // Convert result to JSON
        let result_json = serde_json::to_value(&result)
            .map_err(|e| format!("Failed to serialize tool result: {}", e))?;

        tracing::debug!(tool = "{{this.name}}", "Tool call successful");
        Ok(result_json)
    }

    {{/each}}

    /// Route a resource read request to the appropriate handler
    ///
    /// # Arguments
    ///
    /// * `uri` - The URI of the resource to read
    ///
    /// # Returns
    ///
    /// Returns the resource content as JSON value
    pub async fn route_resource(
        &self,
        uri: &str,
    ) -> Result<Value, Box<dyn std::error::Error>> {
        tracing::debug!(resource_uri = uri, "Routing resource read");

        match uri {
            {{#each resources}}
            "{{this.uri}}" => self.read_{{snake_case this.name}}().await,
            {{/each}}
            _ => Err(format!("Unknown resource: {}", uri).into()),
        }
    }

    {{#each resources}}
    /// Read {{this.name}} resource
    ///
    /// URI: {{this.uri}}
    /// Description: {{this.description}}
    async fn read_{{snake_case this.name}}(&self) -> Result<Value, Box<dyn std::error::Error>> {
        tracing::debug!(resource = "{{this.uri}}", "Reading resource");

        // Forward to backend
        let result = self.backend.read_resource("{{this.uri}}").await
            .map_err(|e| format!("Backend read failed for {{this.uri}}: {}", e))?;

        // Convert result to JSON
        let result_json = serde_json::to_value(&result)
            .map_err(|e| format!("Failed to serialize resource result: {}", e))?;

        tracing::debug!(resource = "{{this.uri}}", "Resource read successful");
        Ok(result_json)
    }

    {{/each}}

    /// Route a prompt request to the appropriate handler
    ///
    /// # Arguments
    ///
    /// * `name` - The name of the prompt
    /// * `arguments` - Optional arguments for the prompt
    ///
    /// # Returns
    ///
    /// Returns the prompt result as JSON value
    pub async fn route_prompt(
        &self,
        name: &str,
        arguments: Option<HashMap<String, Value>>,
    ) -> Result<Value, Box<dyn std::error::Error>> {
        tracing::debug!(prompt = name, "Routing prompt request");

        match name {
            {{#each prompts}}
            "{{this.name}}" => self.get_{{snake_case this.name}}(arguments).await,
            {{/each}}
            _ => Err(format!("Unknown prompt: {}", name).into()),
        }
    }

    {{#each prompts}}
    /// Get {{this.name}} prompt
    ///
    /// Description: {{this.description}}
    async fn get_{{snake_case this.name}}(
        &self,
        arguments: Option<HashMap<String, Value>>,
    ) -> Result<Value, Box<dyn std::error::Error>> {
        tracing::debug!(prompt = "{{this.name}}", "Getting prompt");

        // Forward to backend
        let result = self.backend.get_prompt("{{this.name}}", arguments).await
            .map_err(|e| format!("Backend prompt failed for {{this.name}}: {}", e))?;

        // Convert result to JSON
        let result_json = serde_json::to_value(&result)
            .map_err(|e| format!("Failed to serialize prompt result: {}", e))?;

        tracing::debug!(prompt = "{{this.name}}", "Prompt successful");
        Ok(result_json)
    }

    {{/each}}
}

/// Helper function to list all available tools
pub async fn list_tools(backend: &Client<ChildProcessTransport>) -> Result<Value, Box<dyn std::error::Error>> {
    tracing::debug!("Listing all tools");

    let tools = backend.list_tools().await
        .map_err(|e| format!("Failed to list tools: {}", e))?;

    Ok(serde_json::json!({
        "tools": tools
    }))
}

/// Helper function to list all available resources
pub async fn list_resources(backend: &Client<ChildProcessTransport>) -> Result<Value, Box<dyn std::error::Error>> {
    tracing::debug!("Listing all resources");

    let resources = backend.list_resources().await
        .map_err(|e| format!("Failed to list resources: {}", e))?;

    Ok(serde_json::json!({
        "resources": resources
    }))
}

/// Helper function to list all available prompts
pub async fn list_prompts(backend: &Client<ChildProcessTransport>) -> Result<Value, Box<dyn std::error::Error>> {
    tracing::debug!("Listing all prompts");

    let prompts = backend.list_prompts().await
        .map_err(|e| format!("Failed to list prompts: {}", e))?;

    Ok(serde_json::json!({
        "prompts": prompts
    }))
}