Interactive UI Tools for MCP Servers
Project description
Gdansk: React Frontends for Python MCP Servers
[!WARNING] This project is currently in beta. The APIs are subject to change leading up to v1.0. The v1.0 release will coincide with the v2.0 release of the python mcp sdk
Installation
uv add gdansk
Skill for Coding Agents
If you use coding agents such as Claude Code or Cursor, add the gdansk skills to your repository:
npx skills add mplemay/gdansk
Then use:
$use-gdanskto bootstrap gdansk in a new repo or add another widget to an existing integration.$debug-gdanskto diagnose widget path, bundling, SSR, and runtime failures in an existing gdansk setup.
Compatibility
- Python:
gdanskcurrently requires>=3.12,<3.15. - Frontend package: use an ESM package with
@gdansk/vite,vite,@vitejs/plugin-react,react,react-dom, and@modelcontextprotocol/ext-apps. - Runtime tooling: gdansk starts the frontend through
uv run deno .... If you run frontend package scripts directly, the published@gdansk/vitepackage currently declares Node>=22.
Examples
- FastAPI: Mounting the MCP app inside an existing FastAPI service.
- get-time: Small copyable widget example for first-time adoption in another repo.
- ssr: Minimal SSR and hydration example with a single widget tool.
- shadcn: Multi-tool todo app with
structured_output=Trueandshadcn/ui.
Quick Start
Here's a complete example showing how to build a simple greeting tool with a React UI:
Project Structure:
my-mcp-server/
├── server.py
└── frontend/
├── package.json
├── vite.config.ts
└── widgets/
└── hello/
└── widget.tsx
The frontend folder name is only an example. Pass any frontend package root to Ship(..., views=...).
That frontend package owns its own vite.config.ts; import @gdansk/vite there alongside any framework plugins.
server.py:
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from pathlib import Path
import uvicorn
from mcp.server import MCPServer
from mcp.types import TextContent
from starlette.middleware.cors import CORSMiddleware
from gdansk import Ship
frontend_path = Path(__file__).parent / "frontend"
ship = Ship(views=frontend_path)
@ship.widget(path=Path("hello/widget.tsx"), name="greet")
def greet(name: str) -> list[TextContent]:
"""Greet someone by name."""
return [TextContent(type="text", text=f"Hello, {name}!")]
@asynccontextmanager
async def lifespan(app: MCPServer) -> AsyncIterator[None]:
async with ship.mcp(app=app, dev=True):
yield
mcp = MCPServer(name="Hello World Server", lifespan=lifespan)
def main() -> None:
app = mcp.streamable_http_app()
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_methods=["*"],
allow_headers=["*"],
)
app.mount(path="/assets", app=ship.assets)
uvicorn.run(app, port=3000)
if __name__ == "__main__":
main()
frontend/widgets/hello/widget.tsx:
import { useApp } from "@modelcontextprotocol/ext-apps/react";
import { useState } from "react";
export default function App() {
const [name, setName] = useState("");
const [greeting, setGreeting] = useState("");
const { app, error } = useApp({
appInfo: { name: "Greeter", version: "1.0.0" },
capabilities: {},
});
if (error) return <div>Error: {error.message}</div>;
if (!app) return <div>Connecting...</div>;
return (
<main>
<h2>Say Hello</h2>
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="Enter your name..."
/>
<button
onClick={async () => {
const result = await app.callServerTool({
name: "greet",
arguments: { name },
});
const text = result.content?.find((c) => c.type === "text");
if (text && "text" in text) setGreeting(text.text);
}}
>
Greet Me
</button>
{greeting && <p>{greeting}</p>}
</main>
);
}
frontend/package.json:
{
"name": "my-mcp-frontend",
"private": true,
"type": "module",
"dependencies": {
"@gdansk/vite": "^0.1.0",
"@modelcontextprotocol/ext-apps": "^1.5.0",
"@vitejs/plugin-react": "^6.0.1",
"react": "^19.2.5",
"react-dom": "^19.2.5",
"vite": "^8.0.8"
},
"devDependencies": {
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3"
}
}
frontend/vite.config.ts:
import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import gdansk from "@gdansk/vite";
export default defineConfig({
plugins: [gdansk(), react()],
});
Production widgets load their hydration assets from /<assets_dir>/.... Mount ship.assets at that path on the
public app; with the default settings this is /assets.
If you want a different SSR host or port, configure both sides explicitly:
ship = Ship(views=Path(__file__).parent / "frontend", host="127.0.0.1", port=14000)
export default defineConfig({
plugins: [gdansk({ host: "127.0.0.1", port: 14000 }), react()],
});
Install the frontend package dependencies from frontend/ after editing them:
cd frontend
uv run deno install
Gdansk mounts your default export into #root automatically and wraps it with React.StrictMode.
Run the server with uv run python server.py, configure it in your MCP client (like Claude Desktop), and you'll have
an interactive greeting tool ready to use.
Why Use Gdansk?
-
Python Backend, React Frontend — Use familiar technologies you already know. Write your logic in Python with type hints, build your UI in React/TypeScript. No need to learn a new framework-specific language.
-
Built for MCP — Composes with
MCPServerfrom the official Python SDK: register widget tools and HTML resources viaShip, wire them in withship.mcp(app=...), and integrate with Claude Desktop and other MCP clients. -
Fast bundling with Rolldown — The Rolldown bundler processes your TypeScript/JSX automatically. Hot-reload in development mode means you see changes instantly without manual rebuilds.
-
Type-Safe — Full type safety across the stack. Python type hints on the backend, TypeScript on the frontend, with automatic type checking via ruff and TypeScript compiler.
-
Developer-Friendly — Simple decorator API (
@ship.widget()), automatic resource registration, dev mode onship.mcp(...), and comprehensive error messages. Get started in minutes, not hours. -
Production Ready — Comprehensive test suite covering Python 3.12+ across Linux, macOS, and Windows. Used in production MCP servers with proven reliability.
Credits
Gdansk builds on the shoulders of giants:
- Model Context Protocol — Official MCP documentation
- @modelcontextprotocol/ext-apps — React hooks for MCP apps
- Rolldown — Fast JavaScript bundler
- mcp/python-sdk — Python SDK for MCP server development
- Deno — JavaScript/TypeScript runtime used by the embedded Deno tooling
Special thanks to the Model Context Protocol team at Anthropic for creating the MCP standard and the
@modelcontextprotocol/ext-apps package.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gdansk-0.3.1.tar.gz.
File metadata
- Download URL: gdansk-0.3.1.tar.gz
- Upload date:
- Size: 17.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b259d8b1536bf4e634c00cdd434ba96a0aee8e86c69ebdcd1305e4c39c9f244e
|
|
| MD5 |
f329b33d640adeaa6257ac6c959d0cce
|
|
| BLAKE2b-256 |
e945a466326405937cc61147724e16364c4438e0cc538f0ee345c09aebe172a2
|
File details
Details for the file gdansk-0.3.1-py3-none-any.whl.
File metadata
- Download URL: gdansk-0.3.1-py3-none-any.whl
- Upload date:
- Size: 19.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3840890e95e6ffd196ac97bca55a6d7ae3023750bdf001f5e3020d6bafa94b2c
|
|
| MD5 |
e29641bb68209ea2db8a2cd3fac6c2e6
|
|
| BLAKE2b-256 |
602d314c123eea3053766c61f9d250ac3a80cfe7ec23580e98b60e158c3602ba
|