# === MCP Proxy Server (src_mcp_proxy.rs) v1 ===
#  Dependencies: rmcp::model, rmcp::RoleServer, rmcp::ServerHandler, rmcp::ErrorData, rmcp::service::RequestContext, rmcp::transport::child_process::TokioChildProcess, rmcp::serve_client, rmcp::serve_server, rmcp::transport::stdio, serde::Deserialize, std::borrow::Cow, std::collections::HashMap, std::path::PathBuf, std::sync::Arc, tokio::sync::Mutex, tokio::process::Command, std::process::Stdio, serde_json::Value, serde_yaml, crate::policy, crate::vault
# Signet MCP Proxy wraps upstream MCP servers with policy enforcement. Intercepts tool calls, validates them against hot-reloadable policies with cryptographic integrity verification, logs actions to the vault, and forwards allowed calls to the appropriate upstream server. Provides unified tool discovery across multiple upstream servers with namespaced tool names.

# Module invariants:
#   - Tool names exposed by the proxy are always in server__tool format
#   - Policy is reloaded and integrity-checked on every call_tool() invocation
#   - If policy integrity check fails, default_policy() is used as a safety fallback
#   - If vault is None, default_policy() is always used
#   - Actions are logged to vault only if vault exists
#   - Amount logged is 0.0 for any non-Allow decision
#   - Tool detail logged to vault is truncated to 500 characters maximum
#   - Upstream stderr is redirected to null
#   - Individual upstream connection failures do not prevent proxy startup
#   - Proxy returns Ok(()) early if no config file exists or no servers are configured

class ProxyConfig:
    """Configuration structure for the proxy server, deserialized from proxy.yaml"""
    servers: HashMap<String, UpstreamConfig> # required, Map of server names to their upstream configurations

class UpstreamConfig:
    """Configuration for a single upstream MCP server process"""
    command: String                          # required, Executable command to spawn the upstream server
    args: Vec<String>                        # required, Command-line arguments for the upstream server
    env: HashMap<String, String>             # required, Environment variables to set for the upstream server process

class UpstreamConnection:
    """Runtime state for a connected upstream MCP server"""
    name: String                             # required, Server name identifier
    client: rmcp::service::RunningService<rmcp::RoleClient, ()> # required, Active client connection to the upstream server
    tools: Vec<Tool>                         # required, Cached list of tools provided by this upstream server

class ProxyServer:
    """Main proxy server implementation that enforces policies on tool calls to upstream MCP servers"""
    upstreams: Arc<Mutex<Vec<UpstreamConnection>>> # required, Thread-safe collection of active upstream connections
    policy_path: PathBuf                     # required, Filesystem path to the policy YAML file, hot-reloaded on every call_tool()
    vault: Option<Vault>                     # required, Optional cryptographic vault for policy integrity verification and action logging

def proxy_config_path() -> PathBuf:
    """
    Returns the filesystem path to the proxy configuration file (proxy.yaml) within the Signet directory

    Postconditions:
      - Returns vault::signet_dir().join("proxy.yaml")

    Side effects: none
    Idempotent: no
    """
    ...

def get_info(
    self: &ProxyServer,
) -> ServerInfo:
    """
    Returns MCP server metadata including capabilities and instructions. Implements ServerHandler trait method.

    Postconditions:
      - Returns ServerInfo with instructions describing policy enforcement
      - ServerCapabilities includes tools enabled
      - ServerInfo.instructions is set to 'Signet-proxied MCP server. All tool calls pass through policy enforcement.'

    Side effects: none
    Idempotent: no
    """
    ...

async def list_tools(
    self: &ProxyServer,
    params: Option<PaginatedRequestParams> = None,
    context: RequestContext<RoleServer>,
) -> Result<ListToolsResult, McpError>:
    """
    Aggregates tools from all upstream servers, prefixing each tool name with its server name (server__tool format) and prepending server name to descriptions

    Postconditions:
      - Tool names are formatted as {upstream.name}__{tool.name}
      - Tool descriptions are prefixed with [{upstream.name}]
      - next_cursor and meta fields are None

    Side effects: Acquires lock on self.upstreams
    Idempotent: no
    """
    ...

async def call_tool(
    self: &ProxyServer,
    request: CallToolRequestParams,
    context: RequestContext<RoleServer>,
) -> Result<CallToolResult, McpError>:
    """
    Intercepts tool calls, validates against policy with integrity checks, logs to vault, and forwards allowed calls to upstream servers. Implements policy decisions: Deny, Ask, Gate, Ensure all return errors; Allow forwards to upstream.

    Preconditions:
      - request.name must contain '__' separator (server__tool format)

    Postconditions:
      - If vault exists, action is logged with tool_name, decision, category, amount, and detail (truncated to 500 chars)
      - If policy file is tampered (integrity check fails), default_policy() is used
      - If no vault exists, default_policy() is used
      - Amount logged is 0.0 if decision is not Allow

    Errors:
      - invalid_tool_name_format (McpError::invalid_params): Tool name does not contain '__' separator
          message: Tool name must be server__tool format
      - unknown_server (McpError::invalid_params): Server name prefix does not match any upstream connection
          message: Unknown server: {server_name}

    Side effects: Hot-reloads policy from filesystem on every call, Verifies policy integrity using vault session key, Logs action to vault if vault exists, Forwards tool call to upstream server if allowed
    Idempotent: no
    """
    ...

async def connect_upstream(
    name: &str,
    config: &UpstreamConfig,
) -> Result<UpstreamConnection, Box<dyn std::error::Error>>:
    """
    Spawns an upstream MCP server as a child process, establishes client connection via stdio transport, and fetches its tool list

    Preconditions:
      - config.command must be a valid executable path
      - config.args must be valid arguments for the command
      - config.env must be valid environment variables

    Postconditions:
      - Child process is spawned with stdin and stdout piped, stderr set to null
      - Client connection is established via TokioChildProcess transport
      - Tools are fetched from upstream via list_tools() call
      - Returns UpstreamConnection with name, client, and tools

    Errors:
      - process_spawn_error (Box<dyn std::error::Error>): Failed to spawn child process (invalid command, missing file, etc.)
      - transport_error (Box<dyn std::error::Error>): Failed to create TokioChildProcess transport
      - client_connection_error (Box<dyn std::error::Error>): Failed to establish client connection via serve_client()
      - list_tools_error (Box<dyn std::error::Error>): Upstream server failed to respond to list_tools() call

    Side effects: Spawns child process, Establishes stdio transport with child process, Calls list_tools() on upstream server
    Idempotent: no
    """
    ...

async def run_proxy() -> Result<(), Box<dyn std::error::Error>>:
    """
    Main entry point for MCP proxy server. Loads proxy config, connects to all upstream servers, initializes ProxyServer with vault and policy path, and runs MCP server on stdio transport until termination.

    Postconditions:
      - Returns Ok(()) early if proxy config file does not exist
      - Returns Ok(()) early if config.servers is empty
      - Continues on individual upstream connection failures (logs to stderr)
      - Blocks until service terminates

    Errors:
      - config_parse_error (Box<dyn std::error::Error>): proxy.yaml exists but is invalid YAML or does not match ProxyConfig schema
      - service_error (Box<dyn std::error::Error>): rmcp::serve_server() or service.waiting() fails

    Side effects: Reads proxy.yaml from filesystem, Connects to all configured upstream servers, Writes connection status and tool counts to stderr, Initializes vault via vault::try_load_vault(), Runs MCP server on stdio transport, Blocks until service terminates
    Idempotent: no
    """
    ...

# ── REQUIRED EXPORTS ──────────────────────────────────
# Your implementation module MUST export ALL of these names
# with EXACTLY these spellings. Tests import them by name.
# __all__ = ['ProxyConfig', 'UpstreamConfig', 'UpstreamConnection', 'ProxyServer', 'proxy_config_path', 'get_info', 'list_tools', 'call_tool', 'McpError::invalid_params', 'connect_upstream', 'Box<dyn std::error::Error>', 'run_proxy']
