Skip to content

Rich Content — MCP Apps Inline UI

Source: python/dcc_mcp_core/rich_content.py · Issue #409 · MCP Apps overview

中文版

MCP Apps is the first official MCP protocol extension. A tool can return an interactive interface — chart, form, dashboard, image, table — rendered inline in the chat interface, without hitting the model context. Servers that return rich content see meaningfully higher adoption than text-only servers.

When rich content pays off for DCC tools

ToolRich returnValue
render_framesThumbnail gallery + stats tableVisual verification without leaving chat
get_scene_hierarchyInteractive treeBrowse 10k-node scene
diagnostics__screenshotInline screenshotMore useful than a file path
analyze_keyframesVega-Lite curve chartVisual timing debug
get_render_statsBar chart per layerFaster than raw JSON
list_materialsMaterial swatch gridVisual selection

Imports

python
from dcc_mcp_core import (
    RichContent,
    RichContentKind,
    attach_rich_content,
    skill_success_with_chart,
    skill_success_with_image,
    skill_success_with_table,
)

RichContentKind (enum)

ValueRendered as
"chart"Vega-Lite / Chart.js spec
"form"Interactive JSON-Schema form
"dashboard"Composite layout of multiple components
"image"Inline PNG / JPEG / WebP (base64)
"table"Headers + rows grid

RichContent (dataclass)

Prefer the class-method constructors over the raw dataclass.

RichContent.chart(spec) -> RichContent

Vega-Lite v5 or Chart.js specification dict.

python
RichContent.chart({
    "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
    "data": {"values": [{"x": 1, "y": 2}]},
    "mark": "line",
    "encoding": {"x": {"field": "x"}, "y": {"field": "y"}},
})

RichContent.form(schema, *, title=None) -> RichContent

Interactive form rendered from a JSON Schema. Distinct from Elicitation — this form is part of the tool result (one-shot display), whereas elicitation pauses the tool call for user input.

RichContent.image(data, mime="image/png", *, alt=None) -> RichContent

Raw image bytes encoded to base64.

RichContent.image_from_file(path, mime=None, *, alt=None) -> RichContent

Convenience loader. MIME type auto-detected from extension (.png, .jpg, .jpeg, .webp, .gif).

RichContent.table(headers, rows, *, title=None) -> RichContent

Grid with headers: list[str] and rows: list[list]. Every inner row list must have the same length as headers.

RichContent.dashboard(components) -> RichContent

Composite layout containing an ordered list of other RichContent items.

.to_dict() -> dict

Flattens to {"kind": <value>, **payload} — safe for JSON serialization.

attach_rich_content(result, content) -> dict

Stash a RichContent on an existing skill result dict. Stored under result["context"]["__rich__"] — MCP-Apps-aware clients render it; plain clients ignore it gracefully (backward-compatible).

python
result = skill_success("Render complete", total_frames=250)
return attach_rich_content(result, RichContent.chart({...}))

Skill-script helpers

These return ready-to-use skill result dicts. Additional keyword arguments are forwarded into the context dict.

skill_success_with_chart(message, chart_spec, **context) -> dict

python
return skill_success_with_chart(
    "Render complete",
    chart_spec={
        "$schema": "https://vega.github.io/schema/vega-lite/v5.json",
        "data": {"values": render_stats},
        "mark": "bar",
        "encoding": {
            "x": {"field": "layer"},
            "y": {"field": "time_secs"},
        },
    },
    total_frames=250,
)

skill_success_with_table(message, headers, rows, *, title=None, **context) -> dict

python
return skill_success_with_table(
    "Scene objects",
    headers=["Name", "Type", "Vertices"],
    rows=[["pCube1", "mesh", 8], ["nurbsSphere1", "nurbs", 0]],
)

skill_success_with_image(message, image_data=None, image_path=None, mime="image/png", *, alt=None, **context) -> dict

Provide either image_data (raw bytes) or image_path (file). Raises ValueError if neither is given.

python
return skill_success_with_image(
    "Viewport captured",
    image_data=capture_viewport(),
    alt="Maya viewport",
)

Current status — context-side storage, Rust-side wiring pending

Rich content is stored today in result.context["__rich__"] as a JSON-serialisable dict. Full wiring into tools/call responses using the MCP Apps canonical envelope is tracked in issue #409.

Skills written against these helpers today will automatically surface rich content to MCP-Apps clients once the Rust layer ships.

See also

Released under the MIT License.