harn-cli 0.7.27

CLI for the Harn programming language — run, test, REPL, format, and lint
use std::collections::BTreeSet;
use std::sync::Arc;

use harn_serve::{
    ApiKeyAuthConfig, AuthMethodConfig, AuthPolicy, DispatchCore, DispatchCoreConfig,
    HmacAuthConfig, McpHttpServeOptions, McpServer, McpServerConfig,
};
use time::Duration;

use crate::cli::{McpServeTransport, ServeMcpArgs};

pub(crate) async fn run_mcp_server(args: &ServeMcpArgs) -> Result<(), String> {
    if args.transport == McpServeTransport::Stdio
        && (!args.api_key.is_empty() || args.hmac_secret.is_some())
    {
        return Err("HTTP auth flags require `harn serve mcp --transport http`".to_string());
    }

    let mut config = DispatchCoreConfig::for_script(&args.file);
    config.auth_policy = build_auth_policy(args);
    let core = DispatchCore::new(config).map_err(|error| error.to_string())?;
    let server = Arc::new(McpServer::new(McpServerConfig::new(core)));

    match args.transport {
        McpServeTransport::Stdio => server.run_stdio().await,
        McpServeTransport::Http => {
            server
                .run_http(McpHttpServeOptions {
                    bind: args.bind,
                    path: args.path.clone(),
                    sse_path: args.sse_path.clone(),
                    messages_path: args.messages_path.clone(),
                })
                .await
        }
    }
}

fn build_auth_policy(args: &ServeMcpArgs) -> AuthPolicy {
    let mut methods = Vec::new();
    if !args.api_key.is_empty() {
        methods.push(AuthMethodConfig::ApiKey(ApiKeyAuthConfig {
            keys: args.api_key.iter().cloned().collect::<BTreeSet<_>>(),
        }));
    }
    if let Some(secret) = args.hmac_secret.as_ref() {
        methods.push(AuthMethodConfig::Hmac(HmacAuthConfig {
            shared_secret: secret.clone(),
            provider: "harn-serve".to_string(),
            timestamp_window: Duration::seconds(300),
        }));
    }
    AuthPolicy { methods }
}