GitHunt
AI

aipartnerup/mcp-embedded-ui-python

A lightweight, minimal-dependency embedded Web UI for any MCP Server.

mcp-embedded-ui (Python)

The Python 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 Python, 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 Python MCP Server           │
│  + mcp-embedded-ui                │
│    (FastAPI / Starlette / ASGI)   │
└───────────────────────────────────┘

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 package.

Install

pip install mcp-embedded-ui

Requires Python 3.10+ and Starlette >= 0.35.

Quick Start

FastAPI / Starlette

from fastapi import FastAPI
from mcp_embedded_ui import create_mount

app = FastAPI()

# Mount at /explorer (default)
app.routes.append(create_mount(tools=my_tools, handle_call=my_handler))

# Or specify a custom prefix
app.routes.append(create_mount("/mcp-ui", tools=my_tools, handle_call=my_handler))

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

Any ASGI framework

from mcp_embedded_ui import create_app

# Returns a standard ASGI app — mount in any ASGI-compatible framework
ui_app = create_app(tools=my_tools, handle_call=my_handler)

Full working example

from fastapi import FastAPI
from mcp_embedded_ui import create_mount

# 1. Define your tools (any object with .name, .description, .inputSchema)
class MyTool:
    def __init__(self, name, description, input_schema):
        self.name = name
        self.description = description
        self.inputSchema = input_schema

tools = [
    MyTool("greet", "Say hello", {
        "type": "object",
        "properties": {"name": {"type": "string"}},
    }),
]

# 2. Define a handler: (name, args) -> (content, is_error, trace_id)
async def handle_call(name, args):
    if name == "greet":
        return [{"type": "text", "text": f"Hello, {args.get('name', 'world')}!"}], False, None
    return [{"type": "text", "text": f"Unknown tool: {name}"}], True, None

# 3. Mount the UI
app = FastAPI()
app.routes.append(create_mount(tools=tools, handle_call=handle_call))

With auth hook

from contextlib import contextmanager
from fastapi import Request

@contextmanager
def my_auth(request: Request):
    token = request.headers.get("authorization", "")
    if not token.startswith("Bearer "):
        raise ValueError("Unauthorized")
    # Verify the token with your own logic (JWT, API key, session, etc.)
    yield

# Pass auth_hook to enable, omit to disable
app.routes.append(create_mount(
    tools=tools,
    handle_call=handle_call,
    auth_hook=my_auth,
))

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/fastapi_demo.py) uses a hardcoded Bearer demo-secret-token — the token is printed at startup so you know what to paste into the UI.

Dynamic tools

# Sync callable — re-evaluated on every request
def get_tools():
    return registry.list_tools()

# Async callable
async def get_tools():
    return await registry.async_list_tools()

app = create_app(tools=get_tools, handle_call=my_handler)

API

Three-tier API

Function Returns Use case
create_mount(prefix, *, tools, handle_call, **config) Mount FastAPI / Starlette — mount under a URL prefix
create_app(tools, handle_call, **config) ASGIApp Any ASGI framework — standalone app
build_ui_routes(tools, handle_call, **config) list[Route] Power users — fine-grained route control

Parameters

Parameter Type Default Description
tools list | Callable | AsyncCallable required MCP Tool objects (.name, .description, .inputSchema)
handle_call ToolCallHandler required async (name, args) -> (content, is_error, trace_id)
allow_execute bool True Enable/disable tool execution (enforced server-side)
auth_hook AuthHook | None None Sync/async context manager factory for auth
title str "MCP Tool Explorer" Page title (HTML-escaped automatically)

Auth Hook

The auth_hook receives a Starlette Request and returns a context manager (sync or async). Raise inside to reject with 401. The error response is always {"error": "Unauthorized"} — internal details are never leaked.

from contextlib import contextmanager

@contextmanager
def my_auth(request):
    token = request.headers.get("Authorization")
    if not valid(token):
        raise ValueError("Bad token")
    my_identity_var.set(decode(token))
    yield

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

Endpoints

Method Path Description
GET / Self-contained HTML explorer page
GET /meta JSON config — { title, allow_execute }
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

# Install in editable mode with dev dependencies
pip install -e ".[dev]"

# Run the demo (auth enabled with a demo token)
python examples/fastapi_demo.py
# Visit http://localhost:8000/explorer/
# Paste "Bearer demo-secret-token" in the UI's token field to execute tools

# Run tests
pytest

Cross-Language Specification

This package 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