Template

KPI metrics card widget for MCP Apps

A row of key figures with their change against the previous period. Green means good news, even when the number went down.

Add a UI to your MCP server

How did the online shop do in September compared with August?

Used show_metrics

The house design: black outline, butter yellow and a marker swipe under the title.

When to use a KPI metrics widget

This is the widget for "how are we doing?". It puts four or five numbers side by side, each with an arrow and a percentage against the previous period, so the reader sees in one line what moved and whether that is good.

It fits questions like:

  • A monthly business summary: revenue, active customers, average order value.
  • Product health: signups, activation, retention, weekly active users.
  • Support: tickets opened, first response time, satisfaction.
  • Finance: cash in, expenses, days to get paid.

When there is no previous period to compare against and the point is to celebrate, the milestones template is a better fit. When one goal matters more than the rest, try progress rings.

How the model fills it

The schema requires a title and a list of metrics, each with a label and a numeric value. period is a human label for the time span, like a month name. Each metric can also carry:

  • unit, shown after the value: a currency sign, a percent sign.
  • change, the percent change against the previous period, as a number. The model computes it from the two periods its tools returned, or passes it through when the source already has it.
  • lower_is_better, for metrics where a drop is good news, such as churn, costs or response time.

That last flag is the useful one. The arrow always follows the sign of the change, but the color follows its meaning: a fall in churn shows a down arrow in green, a fall in revenue a down arrow in red. The schema description gives churn as the example, which is usually enough for the model to set it on its own.

The tool description mentions KPIs and business, product or financial figures, the words people actually use when they ask for this.

What it does in the chat

This template has no script and loads no library. It is Liquid and CSS only, so it renders the moment the data arrives and needs nothing declared in the resource's CSP. Values are formatted for the host's language, the change gets a sign and one decimal, and each card takes a tint from the design's palette.

The grid fits as many cards per row as the width allows and wraps the rest, so the same widget works in a wide desktop chat and on a phone. Long labels wrap instead of being cut.

No host actions are declared.

Make it yours

In Nordic the figures are light on hairline cards; Aurora gives each card a glow in its own color; Brutalist puts a block of color on top of every card and stamps the deltas; Editorial sets the figures like a magazine stat box split by rules; Neon lights each readout in its color, with bracketed deltas. Those are five of the twenty built-in designs, and your own designs work too.

The comparison label after each change is plain text in the markup, easy to reword ("vs. last year", "vs. target"). You could add a target field to the schema, or a data-wg-message on each card so a click asks the model why that number moved.

Take it to your server

Linking the published widget from your MCP server means a change you publish reaches it with no deploy; the guide to adding a UI to your MCP server has the code and the download path. To have your agent build the widget, see creating widgets with an AI agent.

The data it takes

FieldTypeWhat it is
titlerequiredstring
periodstringHuman label of the period, e.g. "September 2026"
metricsrequiredobject[]
└ labelrequiredstring
└ valuerequirednumber
└ unitstringShown after the value, e.g. "€" or "%"
└ changenumberPercent change against the previous period
└ lower_is_betterbooleanTrue when a drop is good news, e.g. churn

Sample data

{
  "title": "Monthly summary",
  "period": "September 2026",
  "metrics": [
    {
      "label": "Revenue",
      "value": 48250,
      "unit": "€",
      "change": 12.4
    },
    {
      "label": "Active customers",
      "value": 312,
      "change": 4.1
    },
    {
      "label": "Churn",
      "value": 2.3,
      "unit": "%",
      "change": -0.6,
      "lower_is_better": true
    },
    {
      "label": "Average order value",
      "value": 154.6,
      "unit": "€",
      "change": -1.8
    }
  ]
}

The tool that shows it

A widget made from this template is shown by one tool. The model reads its description to decide when to call it and fills the input schema with the data. This is the definition Widgetry exports:

{
  "name": "show_metrics",
  "title": "Metrics",
  "description": "Shows a row of key metrics (KPIs), each with a value and its change against the previous period. Use it for business, product or financial figures.",
  "inputSchema": {
    "type": "object",
    "required": [
      "title",
      "metrics"
    ],
    "properties": {
      "title": {
        "type": "string"
      },
      "period": {
        "type": "string",
        "description": "Human label of the period, e.g. \"September 2026\""
      },
      "metrics": {
        "type": "array",
        "items": {
          "type": "object",
          "required": [
            "label",
            "value"
          ],
          "properties": {
            "label": {
              "type": "string"
            },
            "value": {
              "type": "number"
            },
            "unit": {
              "type": "string",
              "description": "Shown after the value, e.g. \"€\" or \"%\""
            },
            "change": {
              "type": "number",
              "description": "Percent change against the previous period"
            },
            "lower_is_better": {
              "type": "boolean",
              "description": "True when a drop is good news, e.g. churn"
            }
          }
        }
      }
    }
  }
}

Your first widget, in the chat in minutes

Pick a template, drop in your data and see it as ChatGPT or Claude will show it. Then link it from your MCP server, or let your agent build the next one.