Skip to main content
Glama
Miragon
by Miragon

my-mcp-server: a minimal Miragon MCP Toolkit starter

A self-contained MCP server built on the Miragon MCP Toolkit (the published @miragon/mcp-toolkit-* packages): a plain mcp-use project with the toolkit installed on top. One module registers its own tools plus a widget (the tasks module), and the mcp-use CLI builds and serves the views. Two commands take you from clone to a rendered widget.

Quickstart

You need Node.js 22.22.2 or newer and pnpm. The @miragon packages are on the public npm registry, so no authentication is required.

  1. Install:

    pnpm install
  2. Run (mcp-use dev — builds the views with HMR, boots the server):

    pnpm dev
  3. See it work: the terminal prints the built-in inspector URL (…/mcp/inspector). Open it and call show_tasks_board — the task-board widget renders. That is the full loop: an MCP tool returning a rendered UI. Edit views/shared/TaskListCard.tsx and the view hot-reloads.

Related MCP server: task-manager-mcp

Dev loop

  • pnpm dev — mcp-use dev: server + views with HMR + the built-in inspector. This is the whole loop; no bundle step, no restart after widget edits.

  • pnpm build / pnpm start — mcp-use build (server + views into .mcp-use/build) and mcp-use start (serve the production build).

  • pnpm typecheck — tsc --noEmit.

Optional config lives in .env (PORT; MCP_URL only for deployed servers) — copy the template first:

cp env.example .env

Project layout

├── package.json                   # pinned versions; main: src/index.ts (the CLI reads it)
├── .npmrc                         # save-exact: pin dependency versions
├── env.example                    # PORT / MCP_URL — copy to .env
├── src/
│   ├── index.ts                   # default-exported MCPServer + installToolkit(...)
│   └── modules/tasks/
│       ├── definition.ts          # static contract: module name + widget ids
│       ├── tool-names.ts          # tool-name constants (server ↔ views agree here)
│       ├── store.ts               # in-memory domain layer; TasksBoardData view-model
│       └── plugin.ts              # tools: list_tasks, create_task, show_tasks_board, tasks_board_data
└── views/                         # CLI convention: one dir per view-bound tool
    ├── render-view/view.tsx       # the toolkit composer view
    ├── show_tasks_board/view.tsx  # the tasks widget tool's view
    └── shared/                    # shared browser modules — MUST live under views/
        ├── widgets.tsx            # widget-id map → React components
        ├── TaskListCard.tsx       # the widget: {data: TasksBoardData} → UI primitives
        ├── messages.ts            # the widget's words: typed en/de catalog
        └── styles.css             # Tailwind entry: globals.css + fonts.css + @source scan paths

How the views work

Every model-visible widget tool is bound to a view named after the tool (view: { name: "show_tasks_board" } in plugin.ts); installToolkit binds render-view the same way. The mcp-use CLI discovers each views/<name>/view.tsx by convention, builds it, serves it as the MCP resource ui://views/<name>.html, and emits the _meta.ui wire keys — no bundle wiring in this project at all.

Each view renders the same McpToolkitApp with the shared widget map:

  • Every widget is registered twice: once in the plugin's definition.ts (the id + the consumes dataType the server pushes) and once in views/shared/widgets.tsx (the id + the component). The two sides meet on the widget id — "tasks:board" here.

  • adaptDataWidget(TaskListCard, "tasks:board") resolves the step whose _dataType is "tasks:board" (set by buildSingleWidgetView in show_tasks_board) and forwards its data to the component's data prop.

Shared browser modules live under views/ on purpose (views/shared/): the CLI dev server routes only views/* through its Vite middleware, so a browser module anywhere else in the project 404s in dev. Server-side code (src/) is unaffected — it runs in Node. Type-only imports from src/ into a view are fine; they are erased at build time.

Styling: views/shared/styles.css imports the toolkit's @miragon/mcp-toolkit-ui/globals.css (Tailwind theme + tokens) and adds @source lines so Tailwind generates classes used outside the CSS file's own tree. If a class "does nothing" in the rendered widget, check that the file using it is covered by an @source line. tailwindcss is a dev dependency because @miragon/mcp-toolkit-ui declares it as a peer; the Vite plugin that compiles it ships inside the mcp-use CLI, so this project needs no Vite dependency or config of its own.

Version pinning: mcp-use here must be exactly the version the pinned @miragon packages name as their peer (a second copy breaks the widgets' host context); react, react-dom and zod only have to satisfy the peer ranges (see the packages' peerDependencies). Keep them in step when bumping either side. lucide-react is pinned to the exact version @miragon/mcp-toolkit-ui depends on, on purpose: a second copy in the graph splits mcp-use into two peer instances and crashes widgets at render time (see the toolkit's migration guide), and your own icons then miss the stroke the toolkit sets for its views.

Between toolkit releases the @miragon pins here still point at the previous release, so pnpm install can report an unmet mcp-use peer until the next release lifts them. It is a warning, not a break — install, typecheck and mcp-use build all succeed.

Look and language

The widgets render in the Miragon design system, the toolkit's default theme, light or dark as the host is, in Geist, with their words in English or German as the host's locale is. Out of the box you change none of it.

Your brand

To give the widgets your brand, override role variables at the end of views/shared/styles.css, below the two toolkit imports (the commented block there is the skeleton). Use the selectors of the toolkit's theme, one light and one dark block, and declare every colour you change in both:

:root,
[data-theme-mode="light"] {
  --primary: #0f766e; /* button fill; --primary-foreground is its label */
  --primary-hover: #115e59;
  --link: #0f766e;
  --focus: #0f766e; /* the focus ring */
  --ring: #0f766e;
  --radius-md: 6px; /* mode-independent: the light block is enough */
}

.dark,
[data-theme="dark"],
[data-theme-mode="dark"] {
  --primary: #0f766e;
  --primary-hover: #115e59;
  --link: #5eead4; /* light enough to read on a dark ground */
  --focus: #5eead4;
  --ring: #5eead4;
}

Keep every text colour at 4.5:1 on its surface and the focus ring at 3:1, in both modes. Override roles only, never the --cd-* tokens (the Miragon palette the roles default to) and never a component class: your brand then needs no toolkit release and survives every update. The white-labeling guide lists every role (status tones, surfaces, radii, fonts) and the alternative in code, createTheme + ThemeProvider.

Fonts and language

  • Fonts. Geist and Geist Mono are on by default: the theme names them first, and views/shared/styles.css loads both through the fonts.css import (the files come from npm through the bundler, no font CDN). The view build inlines them into every view; to keep views small, drop the import and serve the files by URL with fontFaces({ baseUrl }) (allow that origin in the view's CSP resourceDomains), or let the host's font stand in.

  • Language. The toolkit's own text (list footers, empty and error states, toasts, the view toolbar) follows the host's locale, in English or German, and so does TaskListCard: every word comes from the typed en/de catalog in views/shared/messages.ts (German with du), picked by useLocale(). Each views/<tool>/view.tsx hands the host's locale to a LocaleProvider. Add a widget's words to the catalog, never into the component, and format counts and dates with useLocale().

CI

The included CI (.github/workflows/ci.yml) typechecks and runs mcp-use build. The @miragon packages are public on npm, so it installs them with no token or registry configuration.

Where this project comes from

This project is maintained as templates/minimal-server in the mcp-toolkit monorepo and auto-mirrored to Miragon/mcp-toolkit-starter, the "Use this template" repo. Nothing in it depends on the monorepo — it installs only published packages. If you run it in place inside the monorepo checkout, install with pnpm install --ignore-workspace (the directory sits inside the monorepo's pnpm workspace but is not part of it).

Where to go next

  • Docs — concepts, guides, and the API reference for every package; the migration guide if you are coming from a 0.10.x scaffold.

  • The standalone-host example — the in-repo reference for this exact shape.

  • The tasks example — the full-size version of this module (complete_task, filterable board widget, in-widget refresh, tests).

  • Agent skills — the repo's coding-agent skills (build-mcp-server, add-mcp-tool, build-mcp-widget, compose-a-view, white-label-client) encode the house patterns; copy them into this project's .claude/skills/ so your coding agent builds on them.

Related MCP Connectors

Related MCP Servers

  • F
    license
    Not graded
    quality
    D
    maintenance
    A minimal MCP server that provides an interactive to-do list with checkboxes in chat, demonstrating MCP Apps UI resource integration and tool-based state updates.
    -
  • F
    license
    Not graded
    quality
    D
    maintenance
    A task manager MCP server that demonstrates all three MCP primitives (tools, resources, prompts). Enables users to manage tasks, read task summaries and details, and run structured planning/review prompts through natural language.
    -
  • F
    license
    Not graded
    quality
    B
    maintenance
    A lightweight task management MCP server that enables CRUD operations on tasks stored in a single JSON file, including listing, creating, updating progress, and setting priorities.
    172 npm
    -