use std::sync::Arc; use axum::Router; use axum::extract::State; use axum::response::{Html, IntoResponse}; use axum::routing::{get, post}; use crate::cache::SharedCache; use crate::config::LocalConfig; use crate::remote::RemoteClient; #[derive(Clone)] pub struct AppState { pub config: LocalConfig, pub cache: SharedCache, pub remote: Arc, } async fn index(State(state): State) -> impl IntoResponse { Html(format!( r#" secrets-mcp-local onboarding

secrets-mcp-local

本地 MCP 地址:http://{bind}/mcp

远端服务地址:{remote}

当前状态

loading...
打开解锁页

步骤 1:远端授权

点击“开始绑定”后,这里会显示授权地址。

步骤 2:本地解锁

授权完成后,本页会自动切换到解锁阶段。你也可以直接在下方完成解锁。

接入 Cursor

把 MCP 地址配置为 http://{bind}/mcp。在未就绪时,AI 只会看到 bootstrap 工具;完成授权和解锁后会自动暴露业务工具。

"#, bind = state.config.bind, remote = state.config.remote_base_url, )) } pub fn router(state: AppState) -> Router { Router::new() .route("/", get(index)) .route("/mcp", axum::routing::any(crate::mcp::handle_mcp)) .route("/local/bind/start", post(crate::bind::bind_start)) .route("/local/bind/exchange", post(crate::bind::bind_exchange)) .route("/local/unbind", post(crate::bind::unbind)) .route("/unlock", get(crate::unlock::unlock_page)) .route( "/local/unlock/complete", post(crate::unlock::unlock_complete), ) .route("/local/lock", post(crate::unlock::lock)) .route("/local/status", get(crate::unlock::status)) .layer(axum::extract::DefaultBodyLimit::max(10 * 1024 * 1024)) .with_state(state) }