gadget

package module
v0.4.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 24, 2026 License: MIT Imports: 10 Imported by: 0

README

gadget

Prebuilt, parameterized, interactive HTML widgets for MCP Apps — in Go, out of the box.

gadget lets an MCP server ship CRUD-style UI — data tables, card grids, forms — as fully self-contained HTML template resources: inline CSS, inline JavaScript, zero external files, everything embedded in your single Go binary. Widgets speak the official MCP Apps extension (io.modelcontextprotocol/ui, spec 2026-01-26) and render in any compliant host (Claude, ChatGPT, VS Code, Cursor, Goose, Postman, …).

Status: pre-release. APIs are not stable yet.

The same Table widget rendered in the host's dark theme   

Table and Form widgets rendered by the examples/harness fake host — light and host dark themes.

CardList lays a collection out as a card grid (same filter/sort/pagination/selection as Table); Card renders a single record.

Quickstart

package main

import (
    "context"
    "net/http"

    "github.com/modelcontextprotocol/go-sdk/mcp"
    "github.com/techthos/gadget"
    "github.com/techthos/gadget/gosdk"
)

func main() {
    table := &gadget.Table{
        URI:   "ui://myapp/users",
        Title: "Users",
        Columns: []gadget.Column{
            gadget.Text("name", "Name"),
            gadget.Number("balance", "Balance", "currency:EUR"),
            gadget.Badge("status", "Status", map[string]gadget.BadgeVariant{
                "active": gadget.BadgeSuccess,
            }),
        },
        Filterable: true,
        PageSize:   10,
    }

    server := mcp.NewServer(&mcp.Implementation{Name: "myapp"}, gosdk.EnableUI(nil))

    type in struct{}
    type out struct {
        Rows []map[string]any `json:"rows"`
    }
    gosdk.AddWidgetToolFor(server, table,
        &mcp.Tool{Name: "list_users", Description: "List users in a table."},
        func(context.Context, *mcp.CallToolRequest, in) (*mcp.CallToolResult, out, error) {
            rows, _ := gadget.RowsOf(loadUsers())
            return nil, out{Rows: rows}, nil
        })

    h := mcp.NewStreamableHTTPHandler(func(*http.Request) *mcp.Server { return server }, nil)
    http.ListenAndServe(":8080", h)
}

Ask a connected assistant to "list the users" and it renders an interactive, host-themed table — sortable, filterable, paginated — inside the chat.

Features

  • Table: typed columns (text/number/date/badge/link/actions), client-side sort/filter/pagination, row selection with bulk actions, per-row actions → MCP tool calls, inline destructive-action confirmation, empty/loading states.
  • Form: 10 field types, native + inline client validation, submit as a tool call, server-side field errors mapped inline, prefill for edit flows.
  • Host-aware theming: --gadget-* design tokens defaulting to host-injected CSS variables (Claude/ChatGPT look automatic), Theme struct overrides, dark mode.
  • Locale-aware: numbers/dates formatted via Intl with the host's locale and time zone.
  • SDK-agnostic core + adapter for the official go-sdk; the core works with any Go MCP implementation.
  • Self-contained by construction: documents satisfy the spec's default locked-down CSP; no CDN, no network, no files on disk.

Documentation

Examples

  • examples/demo — complete MCP server (streamable HTTP or -stdio): list/edit/save/delete/archive users. Point MCPJam or any MCP Apps host at http://localhost:8080/mcp.
  • examples/harness — a fake MCP Apps host in one HTML page: renders widgets in a sandboxed iframe, answers the JSON-RPC handshake, logs all traffic, simulates tool results/errors and theme changes. go run ./examples/harness, open http://localhost:8090.

Development

The TypeScript/CSS runtime lives in ui/ and is bundled with esbuild into internal/assets/dist/ (committed, go:embed-ed — consumers never need Node).

make assets       # npm ci + build the runtime bundle
make test         # go test ./... + vitest
make verify-dist  # fail if committed dist doesn't match ui/ sources

Golden-file tests: go test ./ -update regenerates testdata/golden/.

License

MIT

Documentation

Overview

Package gadget provides prebuilt, parameterized, interactive HTML widgets for MCP Apps (the official Model Context Protocol UI extension, io.modelcontextprotocol/ui). Widgets render as fully self-contained HTML documents — inline CSS, inline JavaScript, no external references — ready to serve as ui:// template resources from any Go MCP server.

The core packages are SDK-agnostic; package gosdk adapts widgets to the official modelcontextprotocol/go-sdk.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func RowsOf

func RowsOf(slice any) ([]map[string]any, error)

RowsOf converts a typed slice into row maps via encoding/json, honoring json struct tags. Use it to feed typed application data into Table.InitialData or a tool result's structuredContent:

rows, _ := gadget.RowsOf(users)
table.InitialData = map[string]any{"rows": rows}

Types

type Action

type Action struct {
	Label string
	// Kind defaults to ActionTool.
	Kind ActionKind
	// Tool is the MCP tool name to call (Kind == ActionTool).
	Tool string
	// Args maps tool argument names to their sources.
	Args map[string]ArgSource
	// HrefKey is the row field holding the URL (Kind == ActionLink).
	HrefKey string
	// Confirm, when set, requires a second confirming click showing this
	// text before the action fires. (Rendered inline: native confirm()
	// dialogs are silently disabled in sandboxed MCP Apps iframes.)
	Confirm string
	Variant ActionVariant
}

Action is a user-triggerable operation on a widget: a per-row button, a bulk action over selected rows, or a link.

type ActionKind

type ActionKind string

ActionKind selects what an Action does when triggered.

const (
	// ActionTool calls an MCP tool (the zero-value default).
	ActionTool ActionKind = "tool"
	// ActionLink asks the host to open a URL taken from the row.
	ActionLink ActionKind = "link"
)

type ActionVariant

type ActionVariant string

ActionVariant selects button styling.

const (
	VariantDefault ActionVariant = ""
	VariantPrimary ActionVariant = "primary"
	VariantDanger  ActionVariant = "danger"
)

type Align

type Align string

Align positions cell or field content.

const (
	AlignStart  Align = "start"
	AlignCenter Align = "center"
	AlignEnd    Align = "end"
)

type ArgSource

type ArgSource struct {
	// contains filtered or unexported fields
}

ArgSource declares where a tool-call argument value comes from when an action fires. Construct with Static, FromRow, or FromSelection.

func FromRow

func FromRow(field string) ArgSource

FromRow takes the value of field on the row the action was triggered on.

func FromSelection

func FromSelection(field string) ArgSource

FromSelection collects the values of field across all selected rows (bulk actions).

func Static

func Static(v any) ArgSource

Static supplies a fixed value.

func (ArgSource) MarshalJSON

func (s ArgSource) MarshalJSON() ([]byte, error)

MarshalJSON emits {"static": v} | {"row": "field"} | {"selection": "field"}.

type BadgeVariant

type BadgeVariant string

BadgeVariant colors a badge cell value.

const (
	BadgeNeutral BadgeVariant = "neutral"
	BadgeInfo    BadgeVariant = "info"
	BadgeSuccess BadgeVariant = "success"
	BadgeWarning BadgeVariant = "warning"
	BadgeDanger  BadgeVariant = "danger"
)

type CancelSpec

type CancelSpec struct {
	// Label defaults to "Cancel".
	Label string
}

CancelSpec adds a reset button to the form.

type Card added in v0.4.0

type Card struct {
	// URI is the widget's ui:// resource URI (required).
	URI string
	// Title is shown in the toolbar and the document title.
	Title string
	// Template describes the card content (required).
	Template CardTemplate

	// RowsKey is the structuredContent key holding the rows array; the card
	// renders rows[0]. Defaults to "rows".
	RowsKey string
	// RowID is the record field used for FromRow action args. Defaults to
	// "id".
	RowID string
	// Empty configures the message shown when no record is present.
	Empty EmptyState

	// InitialData is an optional structuredContent-shaped snapshot baked into
	// the document as a JSON island.
	InitialData map[string]any

	// LoadTool, when set, names a read tool the runtime calls once on load to
	// hydrate the card from fresh data, replacing InitialData. It must return
	// the record under RowsKey.
	LoadTool string
	// LoadArgs are optional static arguments passed to LoadTool.
	LoadArgs map[string]any

	// Theme overrides gadget design tokens for this widget.
	Theme *theme.Theme
	// UI overrides resource _meta.ui (CSP, permissions, prefersBorder).
	UI *uispec.ResourceUIMeta
}

Card renders a single record as a card. The record is the first element of the rows array delivered at runtime under RowsKey (the same contract as Table and CardList), or baked into the document via InitialData.

func (*Card) Descriptor added in v0.4.0

func (c *Card) Descriptor() uispec.ResourceDescriptor

Descriptor implements Widget.

func (*Card) Document added in v0.4.0

func (c *Card) Document() (string, error)

Document implements Widget. The shell contains the card chrome only; the record's title/subtitle/fields are rendered by the embedded runtime from tool-result data (and the optional baked InitialData snapshot).

func (*Card) ToolMeta added in v0.4.0

func (c *Card) ToolMeta() map[string]any

ToolMeta implements Widget.

func (*Card) Validate added in v0.4.0

func (c *Card) Validate() error

Validate implements Widget.

type CardList added in v0.4.0

type CardList struct {
	// URI is the widget's ui:// resource URI (required).
	URI string
	// Title is shown in the toolbar and the document title.
	Title string
	// Template describes how each record renders as a card (required).
	Template CardTemplate

	// RowsKey is the structuredContent key holding the rows array. Defaults
	// to "rows".
	RowsKey string
	// RowID uniquely identifies a record, used for selection and
	// FromRow/FromSelection args. Defaults to "id".
	RowID string

	// PageSize enables client-side pagination when > 0.
	PageSize int
	// DefaultSort pre-sorts records on load.
	DefaultSort *SortSpec
	// Filterable adds a client-side text filter box.
	Filterable bool
	// Selection enables per-card checkboxes and bulk actions.
	Selection *SelectionConfig
	// Empty configures the no-data message.
	Empty EmptyState

	// InitialData is an optional structuredContent-shaped snapshot baked into
	// the document as a JSON island.
	InitialData map[string]any

	// LoadTool, when set, names a read tool the runtime calls once on load to
	// hydrate the list from fresh data, replacing InitialData. It must return
	// the records under RowsKey.
	LoadTool string
	// LoadArgs are optional static arguments passed to LoadTool.
	LoadArgs map[string]any

	// Theme overrides gadget design tokens for this widget.
	Theme *theme.Theme
	// UI overrides resource _meta.ui (CSP, permissions, prefersBorder).
	UI *uispec.ResourceUIMeta
}

CardList renders a collection of records as cards in a responsive grid, with client-side filter, sort, pagination, selection with bulk actions, and per-card actions — the same runtime machinery as Table, laid out as cards instead of table rows.

func (*CardList) Descriptor added in v0.4.0

func (l *CardList) Descriptor() uispec.ResourceDescriptor

Descriptor implements Widget.

func (*CardList) Document added in v0.4.0

func (l *CardList) Document() (string, error)

Document implements Widget. The shell contains the list chrome (toolbar, filter, sort, selection, pagination); the cards themselves are rendered by the embedded runtime from tool-result data and the optional snapshot.

func (*CardList) ToolMeta added in v0.4.0

func (l *CardList) ToolMeta() map[string]any

ToolMeta implements Widget.

func (*CardList) Validate added in v0.4.0

func (l *CardList) Validate() error

Validate implements Widget.

type CardTemplate added in v0.4.0

type CardTemplate struct {
	// TitleKey is the row field shown as the card title (required).
	TitleKey string
	// SubtitleKey is an optional row field shown under the title.
	SubtitleKey string
	// Badge is an optional status badge shown in the card header. Construct
	// it with the Badge column constructor; it is present when its Key is
	// set, and must be a badge column.
	Badge Column
	// Fields are the label/value body rows (text/number/date/badge/link
	// columns — not actions).
	Fields []Column
	// Actions renders footer buttons. FromSelection args are invalid here
	// (they belong to CardList bulk actions).
	Actions []Action
}

CardTemplate describes how one record renders as a card. It is shared by the single-record Card widget and the CardList collection widget: a title and optional subtitle pulled from row fields, an optional status badge, a list of label/value body fields (typed and Intl-formatted like table cells), and a footer row of actions.

type Column

type Column struct {
	// Key is the row field this column displays (unused for ColActions).
	Key   string
	Label string
	// Type defaults to ColText.
	Type ColumnType
	// Sortable overrides the default (text/number/date sortable, others not).
	Sortable *bool
	Align    Align
	// Format refines rendering, interpreted by the runtime via Intl:
	// numbers: "int" | "decimal:<digits>" | "percent" | "currency:<code>";
	// dates: "date" | "datetime" | "time" | "relative".
	Format string
	// Badge maps cell values to badge variants (ColBadge).
	Badge map[string]BadgeVariant
	// Link configures ColLink columns.
	Link *LinkSpec
	// Actions renders per-row action buttons (ColActions).
	Actions []Action
	// Width is a CSS width for the column (e.g. "12rem", "20%").
	Width string
}

Column defines one table column.

func ActionsColumn

func ActionsColumn(actions ...Action) Column

ActionsColumn returns a per-row actions column.

func Badge

func Badge(key, label string, variants map[string]BadgeVariant) Column

Badge returns a badge column mapping values to variants.

func Date

func Date(key, label string, format ...string) Column

Date returns a date column with an optional format ("date", "datetime", "time", "relative").

func Link(hrefKey, label string) Column

Link returns a link column whose URL comes from hrefKey.

func Number

func Number(key, label string, format ...string) Column

Number returns a number column with an optional format ("int", "decimal:<digits>", "percent", "currency:<code>").

func Text

func Text(key, label string) Column

Text returns a text column.

type ColumnType

type ColumnType string

ColumnType selects how a column renders its cells.

const (
	ColText    ColumnType = "text" // the zero-value default
	ColNumber  ColumnType = "number"
	ColDate    ColumnType = "date"
	ColBadge   ColumnType = "badge"
	ColLink    ColumnType = "link"
	ColActions ColumnType = "actions"
)

type EmptyState

type EmptyState struct {
	Title string `json:"title,omitempty"`
	Body  string `json:"body,omitempty"`
}

EmptyState configures the message shown when a widget has no data.

type Field

type Field struct {
	// Name is the tool-call argument name (required, unique).
	Name  string
	Label string
	// Description renders as help text under the control.
	Description string
	Placeholder string
	// Type defaults to FText.
	Type FieldType
	// Required marks the field as mandatory.
	Required bool
	// Default is the initial value: string-like for most fields, bool for
	// FCheckbox, []string for FMultiSelect.
	Default any
	// Options are required for FSelect and FMultiSelect.
	Options []Option
	// Validation adds client-side constraints.
	Validation *Validation
	// Rows sets the textarea height (FTextarea).
	Rows int
}

Field defines one form field.

type FieldType

type FieldType string

FieldType selects the control a Field renders.

const (
	FText        FieldType = "text" // the zero-value default
	FTextarea    FieldType = "textarea"
	FNumber      FieldType = "number"
	FCheckbox    FieldType = "checkbox"
	FSelect      FieldType = "select"
	FMultiSelect FieldType = "multiselect"
	FDate        FieldType = "date"
	FTime        FieldType = "time"
	FHidden      FieldType = "hidden"
	FReadonly    FieldType = "readonly"
)

type Form

type Form struct {
	// URI is the widget's ui:// resource URI (required).
	URI string
	// Title is shown above the form and as the document title.
	Title string
	// Fields defines the form fields (required, non-empty).
	Fields []Field
	// Submit configures the submit tool call (required).
	Submit SubmitSpec
	// Cancel, when set, adds a reset button.
	Cancel *CancelSpec

	// PrefillKey is the structuredContent key holding {"field": value}
	// prefill data. Defaults to "values".
	PrefillKey string
	// ErrorsKey is the structuredContent key holding {"field": "message"}
	// validation errors. Defaults to "errors".
	ErrorsKey string

	// InitialData is an optional structuredContent-shaped snapshot baked
	// into the document (e.g. {"values": {...}} for a pre-filled edit form).
	InitialData map[string]any

	// LoadTool, when set, names a read tool the runtime calls once on load
	// (after the host handshake) to hydrate the form's prefill from fresh
	// data, replacing the baked InitialData snapshot. The tool must return
	// the prefill values under PrefillKey in its structuredContent.
	LoadTool string
	// LoadArgs are optional static arguments passed to LoadTool.
	LoadArgs map[string]any

	// Theme overrides gadget design tokens for this widget.
	Theme *theme.Theme
	// UI overrides resource _meta.ui.
	UI *uispec.ResourceUIMeta
}

Form is a create/edit form widget: typed fields with client-side validation, submit as an MCP tool call, and server-side field errors mapped back inline.

For edit forms, prefill values arrive at runtime in the tool result's structuredContent under PrefillKey; the submit call's response may return field errors under ErrorsKey ({"field": "message"}).

func (*Form) Descriptor

func (f *Form) Descriptor() uispec.ResourceDescriptor

Descriptor implements Widget.

func (*Form) Document

func (f *Form) Document() (string, error)

Document implements Widget. Field structure is fully SSR'd (it is static config); prefill values and server-side field errors are runtime state.

func (*Form) ToolMeta

func (f *Form) ToolMeta() map[string]any

ToolMeta implements Widget.

func (*Form) Validate

func (f *Form) Validate() error

Validate implements Widget.

type LinkSpec

type LinkSpec struct {
	HrefKey string `json:"hrefKey"`
	TextKey string `json:"textKey,omitempty"`
	Text    string `json:"text,omitempty"`
}

LinkSpec configures a link column. The URL comes from HrefKey; the link text comes from TextKey, or the fixed Text, or the URL itself.

type Option

type Option struct {
	Value string `json:"value"`
	Label string `json:"label"`
}

Option is a choice in a select or multiselect field.

func Opt

func Opt(value string) Option

Opt returns an Option whose label equals its value.

type SelectionConfig

type SelectionConfig struct {
	// Bulk actions appear in the toolbar while rows are selected.
	// FromSelection args resolve across all selected rows.
	Bulk []Action
}

SelectionConfig enables row selection.

type SortSpec

type SortSpec struct {
	Key  string `json:"key"`
	Desc bool   `json:"desc,omitempty"`
}

SortSpec is a default sort order for a table.

type SubmitSpec

type SubmitSpec struct {
	// Tool is the MCP tool called with {field: value, ...} merged over
	// StaticArgs (required).
	Tool string
	// Label defaults to "Submit".
	Label string
	// StaticArgs are fixed arguments merged under the field values.
	StaticArgs map[string]any
	// SuccessMessage is shown after a successful submit.
	SuccessMessage string
}

SubmitSpec configures form submission.

type Table

type Table struct {
	// URI is the widget's ui:// resource URI (required).
	URI string
	// Title is shown in the toolbar and the document title.
	Title string
	// Columns defines the table's columns (required, non-empty).
	Columns []Column

	// RowsKey is the structuredContent key holding the rows array.
	// Defaults to "rows".
	RowsKey string
	// RowID is the row field that uniquely identifies a row, used for
	// selection and FromRow/FromSelection args. Defaults to "id".
	RowID string

	// PageSize enables client-side pagination when > 0.
	PageSize int
	// DefaultSort pre-sorts rows on load.
	DefaultSort *SortSpec
	// Filterable adds a client-side text filter box.
	Filterable bool
	// Selection enables row checkboxes and bulk actions.
	Selection *SelectionConfig
	// Empty configures the no-data message.
	Empty EmptyState

	// InitialData is an optional structuredContent-shaped snapshot baked
	// into the document as a JSON island.
	InitialData map[string]any

	// LoadTool, when set, names a read tool the runtime calls once on load
	// (after the host handshake) to hydrate the table from fresh data,
	// replacing the baked InitialData snapshot. This keeps a reloaded widget
	// current instead of reverting to the state frozen at render time. The
	// tool must return the rows under RowsKey in its structuredContent.
	LoadTool string
	// LoadArgs are optional static arguments passed to LoadTool.
	LoadArgs map[string]any

	// Theme overrides gadget design tokens for this widget.
	Theme *theme.Theme
	// UI overrides resource _meta.ui (CSP, permissions, prefersBorder).
	UI *uispec.ResourceUIMeta
}

Table is an interactive data table widget: typed columns, client-side sort/filter/pagination, row selection with bulk actions, and per-row actions that call MCP tools.

Rows are string-keyed JSON objects delivered at runtime in the tool result's structuredContent (under RowsKey); the embedded runtime renders them. InitialData optionally bakes a snapshot into the document for instant first paint.

func (*Table) Descriptor

func (t *Table) Descriptor() uispec.ResourceDescriptor

Descriptor implements Widget.

func (*Table) Document

func (t *Table) Document() (string, error)

Document implements Widget. The rendered shell contains the table chrome only; row content is rendered by the embedded runtime from tool-result data (and from the optional baked InitialData snapshot).

func (*Table) ToolMeta

func (t *Table) ToolMeta() map[string]any

ToolMeta implements Widget.

func (*Table) Validate

func (t *Table) Validate() error

Validate implements Widget.

type Validation

type Validation struct {
	// Pattern is a regular expression the value must match (HTML pattern
	// attribute semantics).
	Pattern string
	// Min/Max/Step constrain number, date, and time fields.
	Min  *float64
	Max  *float64
	Step *float64
	// MinLen/MaxLen constrain text lengths.
	MinLen *int
	MaxLen *int
	// Message overrides the browser's validation message.
	Message string
}

Validation declares client-side constraints, rendered as native HTML validation attributes and enforced before submit.

type Widget

type Widget interface {
	// Document renders the complete self-contained HTML document.
	Document() (string, error)
	// Descriptor returns registration data for the template resource.
	Descriptor() uispec.ResourceDescriptor
	// ToolMeta returns the _meta map linking a tool to this widget:
	// {"ui": {"resourceUri": ...}}.
	ToolMeta() map[string]any
	// Validate checks the widget configuration.
	Validate() error
}

Widget is a renderable MCP Apps UI template. The widget's ui:// URI is available as Descriptor().URI.

Directories

Path Synopsis
examples
demo command
Command demo is a runnable MCP server showcasing gadget widgets: a user table with row/bulk actions, the same users as a card grid, and an edit form with server-side validation.
Command demo is a runnable MCP server showcasing gadget widgets: a user table with row/bulk actions, the same users as a card grid, and an edit form with server-side validation.
harness command
Command harness serves a minimal fake MCP Apps host for manually smoke- testing gadget widgets without a real host: it renders standalone widget documents (baked data) and embeds them in an iframe behind a JSON-RPC postMessage host that logs all traffic.
Command harness serves a minimal fake MCP Apps host for manually smoke- testing gadget widgets without a real host: it renders standalone widget documents (baked data) and embeds them in an iframe behind a JSON-RPC postMessage host that logs all traffic.
Package gosdk adapts gadget widgets to the official Go MCP SDK (github.com/modelcontextprotocol/go-sdk).
Package gosdk adapts gadget widgets to the official Go MCP SDK (github.com/modelcontextprotocol/go-sdk).
internal
assets
Package assets embeds the compiled gadget runtime (JavaScript) and stylesheet.
Package assets embeds the compiled gadget runtime (JavaScript) and stylesheet.
htmlx
Package htmlx assembles complete, self-contained MCP Apps HTML documents: inline stylesheet, widget shell markup, JSON data islands, and the inline runtime bundle.
Package htmlx assembles complete, self-contained MCP Apps HTML documents: inline stylesheet, widget shell markup, JSON data islands, and the inline runtime bundle.
Package theme provides global styling overrides for gadget widgets.
Package theme provides global styling overrides for gadget widgets.
Package uispec defines constants and _meta types for the MCP Apps extension (io.modelcontextprotocol/ui), spec version 2026-01-26.
Package uispec defines constants and _meta types for the MCP Apps extension (io.modelcontextprotocol/ui), spec version 2026-01-26.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL