MCP工具浏览器UI

为MCP(Model Context Protocol)服务器提供的浏览器工具浏览器,支持工具列表查看、模式检查、尝试执行和cURL导出功能。

  • by aiperceivable
  • Rust
  • 本地部署
  • 更新于
在 GitHub 查看 aipartnerup/mcp-embedded-ui-rust

使用说明

mcp-embedded-ui (Rust)

The Rust implementation of mcp-embedded-ui — a browser-based tool explorer for any MCP (Model Context Protocol) server.

What is this?

If you build an MCP server in Rust, your users interact with tools through raw JSON — no visual feedback, no schema browser, no quick way to test. This library adds a full browser UI to your server with one import and one mount.

┌───────────────────────────────────┐
│  Browser                          │
│  Tool list → Schema → Try it      │
└──────────────┬────────────────────┘
               │ HTTP / JSON
┌──────────────▼────────────────────┐
│  Your Rust MCP Server             │
│  + mcp-embedded-ui                │
│    (Axum)                         │
└───────────────────────────────────┘

What does the UI provide?

  • Tool list — browse all registered tools with descriptions and annotation badges
  • Schema inspector — expand any tool to view its full JSON Schema (inputSchema)
  • Try-it console — type JSON arguments, execute the tool, see results instantly
  • cURL export — copy a ready-made cURL command for any execution
  • Auth support — enter a Bearer token in the UI, sent with all requests

No build step. No CDN. No external dependencies. The entire UI is a single self-contained HTML page embedded in the crate.

Install

Add to your Cargo.toml:

[dependencies]
mcp-embedded-ui = "0.4"

Requires Axum 0.8+ and Tokio 1.x.

Quick Start

Axum

use std::sync::Arc;
use mcp_embedded_ui::{create_mount, ToolsProvider, UiConfig};

// Mount at /explorer (default), enable tool execution
let tools: Arc<dyn ToolsProvider> = Arc::new(my_tools);
let config = UiConfig {
    allow_execute: true,
    ..UiConfig::default()
};
let app = create_mount(None, tools, my_handler, config);

// Or specify a custom prefix
let app = create_mount(Some("/mcp-ui"), tools, my_handler, config);

// Visit http://localhost:8000/explorer/

Standalone router

use mcp_embedded_ui::{create_app, UiConfig};

// Returns an Axum Router — nest in any Axum application
let app = create_app(tools, my_handler, UiConfig::default());

Full working example

use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use mcp_embedded_ui::{
    create_mount, Content, HandlerResult, Tool, ToolCallError,
    ToolCallHandler, ToolsProvider, UiConfig,
};

// 1. Define your tools (implement the Tool trait)
struct GreetTool;
impl Tool for GreetTool {
    fn name(&self) -> &str { "greet" }
    fn description(&self) -> &str { "Say hello" }
    fn input_schema(&self) -> serde_json::Value {
        serde_json::json!({
            "type": "object",
            "properties": { "name": { "type": "string" } }
        })
    }
}

// 2. Define a handler: (name, args) -> (content, is_error, trace_id)
let handler = ToolCallHandler::Basic(Arc::new(|name, args| -> Pin<Box<dyn Future<Output = HandlerResult> + Send>> {
    Box::pin(async move {
        let msg = args.get("name").and_then(|v| v.as_str()).unwrap_or("world");
        Ok((
            vec![Content { content_type: "text".into(), text: Some(format!("Hello, {}!", msg)), mime_type: None, data: None }],
            false,
            None,
        ))
    })
}));

// 3. Mount the UI
let tools: Arc<dyn ToolsProvider> = Arc::new(vec![Arc::new(GreetTool) as Arc<dyn Tool>]);
let config = UiConfig { allow_execute: true, ..UiConfig::default() };
let app = create_mount(None, tools, handler, config);

With auth hook

use mcp_embedded_ui::{AuthHook, AuthError};

let config = UiConfig {
    allow_execute: true,
    auth_hook: AuthHook(Some(Arc::new(|parts| {
        Box::pin(async move {
            let auth = parts.headers
                .get("authorization")
                .and_then(|v| v.to_str().ok())
                .unwrap_or("");
            if auth.starts_with("Bearer ") {
                Ok(())
            } else {
                Err(AuthError)
            }
        })
    }))),
    ..UiConfig::default()
};

Auth only guards POST /tools/{name}/call. Discovery endpoints are always public. The UI has a built-in token input field — enter your Bearer token there and it's sent with every execution request.

The included demo (examples/axum_demo.rs) uses a hardcoded Bearer demo-secret-token — the token is printed at startup so you know what to paste into the UI.

Dynamic tools

use mcp_embedded_ui::DynamicToolsProvider;

// Async callable — re-evaluated on every request
let provider = DynamicToolsProvider::new(|| {
    Box::pin(async { registry.list_tools().await })
});
let tools: Arc<dyn ToolsProvider> = Arc::new(provider);

API

Three-tier API

Function Returns Use case
create_mount(prefix, tools, handler, config) Router Axum — nest under a URL prefix
create_app(tools, handler, config) Router Standalone Axum router
build_ui_routes(tools, handler, config) Router Power users — fine-grained route control

Parameters (UiConfig)

Field Type Default Description
allow_execute bool false Enable/disable tool execution (enforced server-side)
title String "MCP Tool Explorer" Page title (HTML-escaped automatically)
project_name Option<String> None Project name shown in footer
project_url Option<String> None Project URL linked in footer (requires project_name)
auth_hook AuthHook None Legacy async auth guard (validation only)
authenticator Option<Arc<dyn Authenticator>> None Full auth with identity propagation (recommended)

Authenticator (recommended)

Implement the Authenticator trait to authenticate requests and propagate an Identity to tool call handlers via the AUTH_IDENTITY task-local:

use std::collections::HashMap;
use std::sync::Arc;
use async_trait::async_trait;
use mcp_embedded_ui::{Authenticator, Identity, AUTH_IDENTITY, UiConfig};

struct MyAuth;

#[async_trait]
impl Authenticator for MyAuth {
    async fn authenticate(&self, headers: &HashMap<String, String>) -> Option<Identity> {
        let token = headers.get("authorization")?.strip_prefix("Bearer ")?;
        // Validate token, return Identity on success
        Some(Identity {
            id: "user-123".into(),
            identity_type: "human".into(),
            roles: vec!["user".into()],
            attrs: Default::default(),
        })
    }
}

let config = UiConfig {
    allow_execute: true,
    authenticator: Some(Arc::new(MyAuth)),
    ..UiConfig::default()
};

Inside tool call handlers, read the authenticated identity:

let identity = AUTH_IDENTITY.try_with(|id| id.clone()).ok().flatten();

When authenticator is set, it takes precedence over auth_hook. Returning None from authenticate() responds with 401.

Auth Hook (legacy)

The auth_hook receives the request Parts (headers, URI, method) and returns a future resolving to Result<(), AuthError>. Return Err(AuthError) to reject with 401. Unlike Authenticator, it does not propagate identity to handlers. The error response is always {"error": "Unauthorized"} — internal details are never leaked.

Auth only guards POST /tools/{name}/call. Discovery endpoints (GET /tools, GET /tools/{name}) are always public.

Endpoints

Method Path Description
GET / Self-contained HTML explorer page
GET /tools Summary list of all tools
GET /tools/{name} Full tool detail with inputSchema
POST /tools/{name}/call Execute a tool, returns MCP CallToolResult

Development

# Run the demo (auth enabled with a demo token)
cargo run --example axum_demo
# Visit http://localhost:8000/explorer/
# Paste "Bearer demo-secret-token" in the UI's token field to execute tools

# Format check
cargo fmt --all -- --check

# Run tests
cargo test --all-features

# Lint
cargo clippy --all-targets --all-features -- -D warnings

Cross-Language Specification

This crate implements the mcp-embedded-ui specification. The spec repo contains:

  • PROTOCOL.md — endpoint spec, data shapes, security checklist
  • explorer.html — shared HTML template (identical across all language implementations)
  • Feature specs — detailed requirements and test criteria

License

Apache-2.0