Skip to main content

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, we recommend adding this skill to your repository:

npx skills add mplemay/gdansk

Examples

  • FastAPI: FastAPI-based MCP server integration with mounted app routes.
  • get-time: Feature-rich MCP app covering tool calls, messaging, logging, and links.
  • ssr: Minimal example with a single tool UI.
  • shadcn: Todo app example using shadcn/ui components with Gdansk.

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
└── views/
    ├── package.json
    └── widgets/
        └── hello/
            └── widget.tsx

The views folder name is only an example: pass any directory to Ship(..., views=...) (for example Path(__file__).parent / "frontend"). 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

ship = Ship(views=Path(__file__).parent / "views")


@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=["*"],
    )
    uvicorn.run(app, port=3000)


if __name__ == "__main__":
    main()

views/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>
  );
}

views/vite.config.ts:

import react from "@vitejs/plugin-react";
import { defineConfig } from "vite";
import gdansk from "@gdansk/vite";

export default defineConfig({
  plugins: [gdansk(), react()],
});

If you want a different SSR host or port, configure both sides explicitly:

ship = Ship(views=Path(__file__).parent / "views", 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 views/ after editing them:

cd views
uv run deno install

Gdansk mounts your default export into #root automatically and wraps it with React.StrictMode.

Run the server with 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?

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

  2. Built for MCP — Composes with MCPServer from the official Python SDK: register widget tools and HTML resources via Ship, wire them in with ship.mcp(app=...), and integrate with Claude Desktop and other MCP clients.

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

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

  5. Developer-Friendly — Simple decorator API (@ship.widget()), automatic resource registration, dev mode on ship.mcp(...), and comprehensive error messages. Get started in minutes, not hours.

  6. 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:

Special thanks to the Model Context Protocol team at Anthropic for creating the MCP standard and the @modelcontextprotocol/ext-apps package.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

gdansk-0.2.5.tar.gz (15.9 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

gdansk-0.2.5-py3-none-any.whl (17.9 kB view details)

Uploaded Python 3

File details

Details for the file gdansk-0.2.5.tar.gz.

File metadata

  • Download URL: gdansk-0.2.5.tar.gz
  • Upload date:
  • Size: 15.9 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

Hashes for gdansk-0.2.5.tar.gz
Algorithm Hash digest
SHA256 bf3e82f758264c2ac5760f5218dd4228b92bd5fe461a1185854768c883af519a
MD5 ad52443b9697e374805857cfe57302ad
BLAKE2b-256 2d0c3a738098e6a14c46eff419a2181177044a87d11da548c05d4af14c998c70

See more details on using hashes here.

File details

Details for the file gdansk-0.2.5-py3-none-any.whl.

File metadata

  • Download URL: gdansk-0.2.5-py3-none-any.whl
  • Upload date:
  • Size: 17.9 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

Hashes for gdansk-0.2.5-py3-none-any.whl
Algorithm Hash digest
SHA256 622e4fb64959a21927396fc25bfea9036644f433c04b891bfbd9013890f78c89
MD5 8986577a249d074840c61516f840ebca
BLAKE2b-256 95187e2fdc236e04f662579259da8ae9ce1ecb4fdf8f42adf240a4a83e4e6902

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page