Merge branch 'feature/mcp-ui' into bot_manager

This commit is contained in:
朱潮 2026-05-19 17:46:26 +08:00
commit 35ceca1af3
6 changed files with 333 additions and 298 deletions

View File

@ -397,55 +397,55 @@ def _create_ui_resource(uri: str, html: str, width: str = "100%", height: str =
return {"content": [{"type": "text", "text": resource_json}]}
def _handle_data_viz(uri: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
def _handle_data_viz(uri: str, data: Dict[str, Any]) -> Dict[str, Any]:
"""Unified handler for all data visualization URIs.
Parses URI path to route to the appropriate HTML builder.
All content is self-contained in resource.text.
"""
title = arguments.get("title", "Dashboard")
theme = arguments.get("theme", "light")
title = data.get("title", "Dashboard")
theme = data.get("theme", "light")
path = uri.replace("ui://data-dashboard/", "")
if path == "metrics":
metrics = arguments.get("metrics", [])
metrics = data.get("metrics", [])
if not metrics:
raise ValueError("Missing required parameter: metrics")
raise ValueError("Missing required parameter: data.metrics")
html = _build_dashboard_html(title, metrics)
return _create_ui_resource(uri, html, "100%", "auto")
elif path == "chart":
chart_type = arguments.get("chart_type")
data = arguments.get("data", {})
chart_type = data.get("chart_type")
chart_data = data.get("data", {})
if not chart_type:
raise ValueError("Missing required parameter: chart_type")
if not data or not data.get("series"):
raise ValueError("Missing required parameter: data.series")
raise ValueError("Missing required parameter: data.chart_type")
if not chart_data or not chart_data.get("series"):
raise ValueError("Missing required parameter: data.data.series")
width = arguments.get("width", "100%")
height = arguments.get("height", "400px")
stacked = arguments.get("stacked", False)
smooth = arguments.get("smooth", False)
show_label = arguments.get("show_label", False)
width = data.get("width", "100%")
height = data.get("height", "400px")
stacked = data.get("stacked", False)
smooth = data.get("smooth", False)
show_label = data.get("show_label", False)
html = _build_chart_html(
title, chart_type, data,
title, chart_type, chart_data,
width=width, height=height, theme=theme,
stacked=stacked, smooth=smooth, show_label=show_label,
)
return _create_ui_resource(uri, html, width, height)
elif path == "multi-chart":
charts = arguments.get("charts", [])
charts = data.get("charts", [])
if not charts:
raise ValueError("Missing required parameter: charts")
raise ValueError("Missing required parameter: data.charts")
for i, c in enumerate(charts):
if not c.get("chart_type"):
raise ValueError(f"charts[{i}]: missing chart_type")
raise ValueError(f"data.charts[{i}]: missing chart_type")
if not c.get("data", {}).get("series"):
raise ValueError(f"charts[{i}]: missing data.series")
raise ValueError(f"data.charts[{i}]: missing data.series")
columns = arguments.get("columns", 2)
columns = data.get("columns", 2)
html = _build_multi_chart_html(title, charts, columns=columns, theme=theme)
num_rows = (len(charts) + columns - 1) // columns
total_height = f"{80 + num_rows * 420}px"
@ -493,6 +493,7 @@ async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]:
if tool_name == "render_data_viz":
uri = arguments.get("uri", "")
data = arguments.get("data", {})
if not uri or not uri.startswith("ui://data-dashboard/"):
return create_error_response(
request_id, -32602,
@ -500,7 +501,7 @@ async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]:
)
try:
result = _handle_data_viz(uri, arguments)
result = _handle_data_viz(uri, data)
except ValueError as e:
return create_error_response(request_id, -32602, str(e))

View File

@ -1,7 +1,7 @@
[
{
"name": "render_data_viz",
"description": "Render data visualization resources. Use the `uri` field to specify the visualization type. All charts use ECharts library.\n\nSupported URIs:\n- `ui://data-dashboard/metrics` — Metric card dashboard (provide metrics array)\n- `ui://data-dashboard/chart` — Single chart: line, bar, pie, radar, scatter, or gauge (provide chart_type and data)\n- `ui://data-dashboard/multi-chart` — Multiple charts in a grid layout (provide charts array)",
"description": "Render data visualization resources. Use the `uri` field to specify the visualization type, and pass all parameters in the `data` object. All charts use ECharts library.\n\nSupported URIs:\n- `ui://data-dashboard/metrics` — Metric card dashboard. data: {title, metrics: [{label, value, change?, change_type?}]}\n- `ui://data-dashboard/chart` — Single chart. data: {title, chart_type, data: {categories?, series}, width?, height?, stacked?, smooth?, show_label?, theme?}\n- `ui://data-dashboard/multi-chart` — Multiple charts in grid. data: {title, charts: [{title, chart_type, data, height?, stacked?, smooth?, show_label?}], columns?, theme?}",
"inputSchema": {
"type": "object",
"properties": {
@ -10,128 +10,134 @@
"enum": ["ui://data-dashboard/metrics", "ui://data-dashboard/chart", "ui://data-dashboard/multi-chart"],
"description": "Resource URI that determines the visualization type"
},
"title": {
"type": "string",
"description": "Title displayed at the top of the visualization"
},
"metrics": {
"type": "array",
"description": "[uri=ui://data-dashboard/metrics] Array of metric objects to display as cards",
"items": {
"type": "object",
"properties": {
"label": { "type": "string", "description": "Metric name" },
"value": { "type": "string", "description": "Metric value" },
"change": { "type": "string", "description": "Change indicator, e.g. '+12.5%'" },
"change_type": { "type": "string", "enum": ["up", "down", "neutral"], "description": "Direction of change" }
},
"required": ["label", "value"]
}
},
"chart_type": {
"type": "string",
"enum": ["line", "bar", "pie", "radar", "scatter", "gauge"],
"description": "[uri=ui://data-dashboard/chart] Type of chart to render"
},
"data": {
"type": "object",
"description": "[uri=ui://data-dashboard/chart] Chart data object",
"description": "Parameters for the specified URI. Structure depends on uri:\n- metrics: {title, metrics: [{label, value, change?, change_type?}]}\n- chart: {title, chart_type, data: {categories?, series}, width?, height?, stacked?, smooth?, show_label?, theme?}\n- multi-chart: {title, charts: [{title, chart_type, data, height?, stacked?, smooth?, show_label?}], columns?, theme?}",
"properties": {
"categories": {
"type": "array",
"items": { "type": "string" },
"description": "X-axis labels (for line, bar, radar)"
"title": {
"type": "string",
"description": "Title displayed at the top of the visualization"
},
"series": {
"metrics": {
"type": "array",
"description": "Array of data series",
"description": "[uri=metrics] Array of metric objects to display as cards",
"items": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Series name" },
"data": { "type": "array", "description": "Data values. For line/bar/radar: numbers. For pie: {name,value}. For scatter: [x,y]. For gauge: [value]." }
"label": { "type": "string", "description": "Metric name" },
"value": { "type": "string", "description": "Metric value" },
"change": { "type": "string", "description": "Change indicator, e.g. '+12.5%'" },
"change_type": { "type": "string", "enum": ["up", "down", "neutral"], "description": "Direction of change" }
},
"required": ["name", "data"]
"required": ["label", "value"]
}
},
"chart_type": {
"type": "string",
"enum": ["line", "bar", "pie", "radar", "scatter", "gauge"],
"description": "[uri=chart] Type of chart to render"
},
"data": {
"type": "object",
"description": "[uri=chart] Chart data object",
"properties": {
"categories": {
"type": "array",
"items": { "type": "string" },
"description": "X-axis labels (for line, bar, radar)"
},
"series": {
"type": "array",
"description": "Array of data series",
"items": {
"type": "object",
"properties": {
"name": { "type": "string", "description": "Series name" },
"data": { "type": "array", "description": "Data values" }
},
"required": ["name", "data"]
}
}
},
"required": ["series"]
},
"width": {
"type": "string",
"description": "[uri=chart] CSS width. Default: '100%'",
"default": "100%"
},
"height": {
"type": "string",
"description": "[uri=chart] CSS height. Default: '400px'",
"default": "400px"
},
"stacked": {
"type": "boolean",
"description": "[uri=chart] Stack series (line/bar). Default: false",
"default": false
},
"smooth": {
"type": "boolean",
"description": "[uri=chart] Smooth curves (line). Default: false",
"default": false
},
"show_label": {
"type": "boolean",
"description": "[uri=chart] Show data labels. Default: false",
"default": false
},
"theme": {
"type": "string",
"enum": ["light", "dark"],
"description": "Color theme. Default: 'light'",
"default": "light"
},
"columns": {
"type": "integer",
"description": "[uri=multi-chart] Grid columns (1-4). Default: 2",
"default": 2,
"minimum": 1,
"maximum": 4
},
"charts": {
"type": "array",
"description": "[uri=multi-chart] Array of chart objects",
"items": {
"type": "object",
"properties": {
"title": { "type": "string", "description": "Chart title" },
"chart_type": { "type": "string", "enum": ["line", "bar", "pie", "radar", "scatter", "gauge"], "description": "Type of chart" },
"data": {
"type": "object",
"description": "Chart data",
"properties": {
"categories": { "type": "array", "items": { "type": "string" } },
"series": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"data": { "type": "array" }
},
"required": ["name", "data"]
}
}
},
"required": ["series"]
},
"height": { "type": "string", "default": "350px" },
"stacked": { "type": "boolean", "default": false },
"smooth": { "type": "boolean", "default": false },
"show_label": { "type": "boolean", "default": false }
},
"required": ["title", "chart_type", "data"]
}
}
},
"required": ["series"]
},
"width": {
"type": "string",
"description": "[uri=ui://data-dashboard/chart] CSS width. Default: '100%'",
"default": "100%"
},
"height": {
"type": "string",
"description": "[uri=ui://data-dashboard/chart] CSS height. Default: '400px'",
"default": "400px"
},
"stacked": {
"type": "boolean",
"description": "[uri=ui://data-dashboard/chart] Stack series (line/bar). Default: false",
"default": false
},
"smooth": {
"type": "boolean",
"description": "[uri=ui://data-dashboard/chart] Smooth curves (line). Default: false",
"default": false
},
"show_label": {
"type": "boolean",
"description": "[uri=ui://data-dashboard/chart] Show data labels. Default: false",
"default": false
},
"theme": {
"type": "string",
"enum": ["light", "dark"],
"description": "Color theme. Default: 'light'",
"default": "light"
},
"columns": {
"type": "integer",
"description": "[uri=ui://data-dashboard/multi-chart] Grid columns (1-4). Default: 2",
"default": 2,
"minimum": 1,
"maximum": 4
},
"charts": {
"type": "array",
"description": "[uri=ui://data-dashboard/multi-chart] Array of chart objects",
"items": {
"type": "object",
"properties": {
"title": { "type": "string", "description": "Chart title" },
"chart_type": { "type": "string", "enum": ["line", "bar", "pie", "radar", "scatter", "gauge"], "description": "Type of chart" },
"data": {
"type": "object",
"description": "Chart data",
"properties": {
"categories": { "type": "array", "items": { "type": "string" } },
"series": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string" },
"data": { "type": "array" }
},
"required": ["name", "data"]
}
}
},
"required": ["series"]
},
"height": { "type": "string", "default": "350px" },
"stacked": { "type": "boolean", "default": false },
"smooth": { "type": "boolean", "default": false },
"show_label": { "type": "boolean", "default": false }
},
"required": ["title", "chart_type", "data"]
}
}
},
"required": ["uri", "title"]
"required": ["uri", "data"]
}
}
]

View File

@ -1,6 +1,6 @@
## `render_data_viz` usage guide
This tool renders data visualization resources. Use the `uri` field to specify the visualization type.
This tool renders data visualization resources. Use the `uri` field to specify the visualization type, and pass all parameters in the `data` object.
### Supported URIs:
- `ui://data-dashboard/metrics` — Metric card dashboard
@ -16,12 +16,14 @@ When to use: KPIs, stats, numerical summaries, side-by-side comparisons.
```
render_data_viz(
uri="ui://data-dashboard/metrics",
title="Sales Overview",
metrics=[
{"label": "Revenue", "value": "$12,345", "change": "+12.5%", "change_type": "up"},
{"label": "Users", "value": "1,234", "change": "-3.2%", "change_type": "down"},
{"label": "Conversion", "value": "4.5%", "change_type": "neutral"}
]
data={
"title": "Sales Overview",
"metrics": [
{"label": "Revenue", "value": "$12,345", "change": "+12.5%", "change_type": "up"},
{"label": "Users", "value": "1,234", "change": "-3.2%", "change_type": "down"},
{"label": "Conversion", "value": "4.5%", "change_type": "neutral"}
]
}
)
```
@ -35,16 +37,18 @@ When to use: trends, comparisons, distributions, compositions, single metric gau
```
render_data_viz(
uri="ui://data-dashboard/chart",
title="Monthly Revenue",
chart_type="line",
data={
"categories": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
"series": [
{"name": "2024", "data": [820, 932, 901, 934, 1290, 1330]},
{"name": "2025", "data": [620, 732, 801, 1034, 1190, 1530]}
]
},
smooth=true
"title": "Monthly Revenue",
"chart_type": "line",
"data": {
"categories": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
"series": [
{"name": "2024", "data": [820, 932, 901, 934, 1290, 1330]},
{"name": "2025", "data": [620, 732, 801, 1034, 1190, 1530]}
]
},
"smooth": true
}
)
```
@ -52,17 +56,19 @@ render_data_viz(
```
render_data_viz(
uri="ui://data-dashboard/chart",
title="Sales by Region",
chart_type="bar",
data={
"categories": ["North", "South", "East", "West"],
"series": [
{"name": "Q1", "data": [320, 302, 341, 374]},
{"name": "Q2", "data": [220, 182, 191, 234]}
]
},
stacked=true,
show_label=true
"title": "Sales by Region",
"chart_type": "bar",
"data": {
"categories": ["North", "South", "East", "West"],
"series": [
{"name": "Q1", "data": [320, 302, 341, 374]},
{"name": "Q2", "data": [220, 182, 191, 234]}
]
},
"stacked": true,
"show_label": true
}
)
```
@ -70,18 +76,20 @@ render_data_viz(
```
render_data_viz(
uri="ui://data-dashboard/chart",
title="Traffic Sources",
chart_type="pie",
data={
"series": [{
"name": "Sources",
"data": [
{"name": "Search", "value": 1048},
{"name": "Direct", "value": 735},
{"name": "Email", "value": 580},
{"name": "Social", "value": 484}
]
}]
"title": "Traffic Sources",
"chart_type": "pie",
"data": {
"series": [{
"name": "Sources",
"data": [
{"name": "Search", "value": 1048},
{"name": "Direct", "value": 735},
{"name": "Email", "value": 580},
{"name": "Social", "value": 484}
]
}]
}
}
)
```
@ -90,14 +98,16 @@ render_data_viz(
```
render_data_viz(
uri="ui://data-dashboard/chart",
title="Skill Assessment",
chart_type="radar",
data={
"categories": ["Sales", "Admin", "Tech", "Support", "Marketing"],
"series": [
{"name": "Alice", "data": [4200, 3000, 20000, 35000, 50000]},
{"name": "Bob", "data": [5000, 14000, 28000, 26000, 42000]}
]
"title": "Skill Assessment",
"chart_type": "radar",
"data": {
"categories": ["Sales", "Admin", "Tech", "Support", "Marketing"],
"series": [
{"name": "Alice", "data": [4200, 3000, 20000, 35000, 50000]},
{"name": "Bob", "data": [5000, 14000, 28000, 26000, 42000]}
]
}
}
)
```
@ -106,13 +116,15 @@ render_data_viz(
```
render_data_viz(
uri="ui://data-dashboard/chart",
title="Height vs Weight",
chart_type="scatter",
data={
"series": [
{"name": "Male", "data": [[161, 51], [167, 59], [159, 49], [175, 73]]},
{"name": "Female", "data": [[150, 45], [160, 55], [165, 60], [155, 50]]}
]
"title": "Height vs Weight",
"chart_type": "scatter",
"data": {
"series": [
{"name": "Male", "data": [[161, 51], [167, 59], [159, 49], [175, 73]]},
{"name": "Female", "data": [[150, 45], [160, 55], [165, 60], [155, 50]]}
]
}
}
)
```
@ -121,13 +133,15 @@ render_data_viz(
```
render_data_viz(
uri="ui://data-dashboard/chart",
title="CPU Usage",
chart_type="gauge",
data={"series": [{"name": "CPU", "data": [72.5]}]}
data={
"title": "CPU Usage",
"chart_type": "gauge",
"data": {"series": [{"name": "CPU", "data": [72.5]}]}
}
)
```
#### Chart options:
#### Chart options (inside data):
- `width`: CSS width, default "100%"
- `height`: CSS height, default "400px"
- `theme`: "light" (default) or "dark"
@ -144,48 +158,50 @@ When to use: comprehensive overviews, multiple related charts, business reports.
```
render_data_viz(
uri="ui://data-dashboard/multi-chart",
title="Monthly Business Report",
columns=2,
theme="light",
charts=[
{
"title": "Revenue Trend",
"chart_type": "line",
"data": {
"categories": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
"series": [{"name": "Revenue", "data": [820, 932, 901, 934, 1290, 1330]}]
data={
"title": "Monthly Business Report",
"columns": 2,
"theme": "light",
"charts": [
{
"title": "Revenue Trend",
"chart_type": "line",
"data": {
"categories": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
"series": [{"name": "Revenue", "data": [820, 932, 901, 934, 1290, 1330]}]
},
"smooth": true
},
"smooth": true
},
{
"title": "Sales by Region",
"chart_type": "bar",
"data": {
"categories": ["North", "South", "East", "West"],
"series": [
{"name": "Q1", "data": [320, 302, 341, 374]},
{"name": "Q2", "data": [220, 182, 191, 234]}
]
{
"title": "Sales by Region",
"chart_type": "bar",
"data": {
"categories": ["North", "South", "East", "West"],
"series": [
{"name": "Q1", "data": [320, 302, 341, 374]},
{"name": "Q2", "data": [220, 182, 191, 234]}
]
}
},
{
"title": "Traffic Sources",
"chart_type": "pie",
"data": {
"series": [{"name": "Sources", "data": [
{"name": "Search", "value": 1048},
{"name": "Direct", "value": 735},
{"name": "Social", "value": 484}
]}]
}
},
{
"title": "Server Load",
"chart_type": "gauge",
"data": {"series": [{"name": "CPU", "data": [67]}]},
"height": "300px"
}
},
{
"title": "Traffic Sources",
"chart_type": "pie",
"data": {
"series": [{"name": "Sources", "data": [
{"name": "Search", "value": 1048},
{"name": "Direct", "value": 735},
{"name": "Social", "value": 484}
]}]
}
},
{
"title": "Server Load",
"chart_type": "gauge",
"data": {"series": [{"name": "CPU", "data": [67]}]},
"height": "300px"
}
]
]
}
)
```

View File

@ -17,27 +17,32 @@ Example: If the question is "What is the topic of the PPT?", do NOT leave option
Do NOT call ask_user with empty options arrays.
### How to call render_ui for ask-user
Use `uri` + `data` format:
```json
render_ui(
uri="ui://mcp-ui/ask-user",
data={
"title": "A descriptive title",
"questions": [
{
"question": "Who is the audience?",
"options": ["Leadership", "Team", "Client"]
},
{
"question": "How long is the presentation?",
"options": ["5-10 minutes", "15-20 minutes", "30+ minutes"]
}
]
}
)
```
### How to format options
- Options MUST be placed in the `options` array field, NOT embedded in the question text.
- Keep the question text short and clean — just the question itself.
- Each question MUST have at least 2 options in the options array.
CORRECT example:
```json
{
"questions": [
{
"question": "Who is the audience?",
"options": ["Leadership", "Team", "Client"]
},
{
"question": "How long is the presentation?",
"options": ["5-10 minutes", "15-20 minutes", "30+ minutes"]
}
]
}
```
WRONG example (DO NOT do this):
```json
{

View File

@ -1,7 +1,7 @@
[
{
"name": "render_ui",
"description": "Render an interactive UI resource in the chat. Use the `uri` field to specify the resource type. Supported URIs:\n- `ui://mcp-ui/html` — Render custom HTML/CSS/JS (provide html_content)\n- `ui://mcp-ui/url` — Embed an external URL in an iframe (provide url)\n- `ui://mcp-ui/ask-user` — Present questions with selectable options to the user (provide questions)",
"description": "Render an interactive UI resource in the chat. Use the `uri` field to specify the resource type, and pass all parameters in the `data` object.\n\nSupported URIs:\n- `ui://mcp-ui/html` — Render custom HTML/CSS/JS. data: {html_content, title?, width?, height?}\n- `ui://mcp-ui/url` — Embed an external URL in an iframe. data: {url, title?, width?, height?}\n- `ui://mcp-ui/ask-user` — Present questions with selectable options. data: {questions: [{question, options, multi_select?}], title?}",
"inputSchema": {
"type": "object",
"properties": {
@ -10,55 +10,61 @@
"enum": ["ui://mcp-ui/html", "ui://mcp-ui/url", "ui://mcp-ui/ask-user"],
"description": "Resource URI that determines the rendering mode"
},
"title": {
"type": "string",
"description": "A descriptive title for the UI widget"
},
"html_content": {
"type": "string",
"description": "[uri=ui://mcp-ui/html] Complete HTML content to render. Can include inline CSS and JavaScript."
},
"url": {
"type": "string",
"description": "[uri=ui://mcp-ui/url] External URL to embed in an iframe."
},
"width": {
"type": "string",
"description": "[uri=ui://mcp-ui/html|url] CSS width for the iframe. Default: '100%'",
"default": "100%"
},
"height": {
"type": "string",
"description": "[uri=ui://mcp-ui/html|url] CSS height for the iframe. Default: 'auto'",
"default": "auto"
},
"questions": {
"type": "array",
"description": "[uri=ui://mcp-ui/ask-user] Array of questions to ask the user.",
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The question to ask the user"
},
"options": {
"type": "array",
"minItems": 2,
"items": { "type": "string" },
"description": "REQUIRED array with at least 2 options."
},
"multi_select": {
"type": "boolean",
"description": "If true, the user can select multiple options. Default: false.",
"default": false
}
"data": {
"type": "object",
"description": "Parameters for the specified URI. Structure depends on uri:\n- html: {html_content: string, title?: string, width?: string, height?: string}\n- url: {url: string, title?: string, width?: string, height?: string}\n- ask-user: {questions: [{question: string, options: string[], multi_select?: boolean}], title?: string}",
"properties": {
"title": {
"type": "string",
"description": "A descriptive title for the UI widget"
},
"required": ["question", "options"]
"html_content": {
"type": "string",
"description": "[uri=html] Complete HTML content to render. Can include inline CSS and JavaScript."
},
"url": {
"type": "string",
"description": "[uri=url] External URL to embed in an iframe."
},
"width": {
"type": "string",
"description": "[uri=html|url] CSS width. Default: '100%'",
"default": "100%"
},
"height": {
"type": "string",
"description": "[uri=html|url] CSS height. Default: 'auto'",
"default": "auto"
},
"questions": {
"type": "array",
"description": "[uri=ask-user] Array of questions to ask the user.",
"items": {
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The question to ask the user"
},
"options": {
"type": "array",
"minItems": 2,
"items": { "type": "string" },
"description": "REQUIRED array with at least 2 options."
},
"multi_select": {
"type": "boolean",
"description": "If true, the user can select multiple options. Default: false.",
"default": false
}
},
"required": ["question", "options"]
}
}
}
}
},
"required": ["uri", "title"]
"required": ["uri", "data"]
}
}
]

View File

@ -26,15 +26,15 @@ def _serialize_ui_resource(ui_resource) -> str:
return json.dumps(ui_resource.model_dump(mode="json"), ensure_ascii=False)
def _handle_render_ui(uri: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
def _handle_render_ui(uri: str, data: Dict[str, Any]) -> Dict[str, Any]:
"""Unified handler for all render_ui URIs.
Content is NOT self-contained the actual html_content/url/questions
stays in the TOOL_CALL args to save tokens. resource.text is a placeholder.
The frontend extracts content from TOOL_CALL args based on URI.
"""
width = arguments.get("width", "100%")
height = arguments.get("height", "auto")
width = data.get("width", "100%")
height = data.get("height", "auto")
if uri == "ui://mcp-ui/ask-user":
resource = create_ui_resource({
@ -46,7 +46,7 @@ def _handle_render_ui(uri: str, arguments: Dict[str, Any]) -> Dict[str, Any]:
},
})
elif uri == "ui://mcp-ui/url":
url = arguments.get("url", "")
url = data.get("url", "")
resource = create_ui_resource({
"uri": uri,
"content": {"type": "externalUrl", "iframeUrl": url},
@ -111,6 +111,7 @@ async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]:
if tool_name == "render_ui":
uri = arguments.get("uri", "")
data = arguments.get("data", {})
if not uri or not uri.startswith("ui://mcp-ui/"):
return create_error_response(
@ -120,20 +121,20 @@ async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]:
path = uri.replace("ui://mcp-ui/", "")
if path == "html" and not arguments.get("html_content"):
if path == "html" and not data.get("html_content"):
return create_error_response(
request_id, -32602, "Missing required parameter: html_content (for uri=ui://mcp-ui/html)"
request_id, -32602, "Missing required parameter: data.html_content (for uri=ui://mcp-ui/html)"
)
elif path == "url" and not arguments.get("url"):
elif path == "url" and not data.get("url"):
return create_error_response(
request_id, -32602, "Missing required parameter: url (for uri=ui://mcp-ui/url)"
request_id, -32602, "Missing required parameter: data.url (for uri=ui://mcp-ui/url)"
)
elif path == "ask-user" and not arguments.get("questions"):
elif path == "ask-user" and not data.get("questions"):
return create_error_response(
request_id, -32602, "Missing required parameter: questions (for uri=ui://mcp-ui/ask-user)"
request_id, -32602, "Missing required parameter: data.questions (for uri=ui://mcp-ui/ask-user)"
)
result = _handle_render_ui(uri, arguments)
result = _handle_render_ui(uri, data)
return {"jsonrpc": "2.0", "id": request_id, "result": result}
else: