Merge branch 'feature/mcp-ui' into bot_manager
This commit is contained in:
commit
35ceca1af3
@ -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))
|
||||
|
||||
|
||||
@ -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"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -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"
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
@ -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"]
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@ -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:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user