修改mcp协议
This commit is contained in:
parent
7f66279311
commit
2a243202c9
@ -397,55 +397,55 @@ def _create_ui_resource(uri: str, html: str, width: str = "100%", height: str =
|
|||||||
return {"content": [{"type": "text", "text": resource_json}]}
|
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.
|
"""Unified handler for all data visualization URIs.
|
||||||
|
|
||||||
Parses URI path to route to the appropriate HTML builder.
|
Parses URI path to route to the appropriate HTML builder.
|
||||||
All content is self-contained in resource.text.
|
All content is self-contained in resource.text.
|
||||||
"""
|
"""
|
||||||
title = arguments.get("title", "Dashboard")
|
title = data.get("title", "Dashboard")
|
||||||
theme = arguments.get("theme", "light")
|
theme = data.get("theme", "light")
|
||||||
path = uri.replace("ui://data-dashboard/", "")
|
path = uri.replace("ui://data-dashboard/", "")
|
||||||
|
|
||||||
if path == "metrics":
|
if path == "metrics":
|
||||||
metrics = arguments.get("metrics", [])
|
metrics = data.get("metrics", [])
|
||||||
if not metrics:
|
if not metrics:
|
||||||
raise ValueError("Missing required parameter: metrics")
|
raise ValueError("Missing required parameter: data.metrics")
|
||||||
html = _build_dashboard_html(title, metrics)
|
html = _build_dashboard_html(title, metrics)
|
||||||
return _create_ui_resource(uri, html, "100%", "auto")
|
return _create_ui_resource(uri, html, "100%", "auto")
|
||||||
|
|
||||||
elif path == "chart":
|
elif path == "chart":
|
||||||
chart_type = arguments.get("chart_type")
|
chart_type = data.get("chart_type")
|
||||||
data = arguments.get("data", {})
|
chart_data = data.get("data", {})
|
||||||
if not chart_type:
|
if not chart_type:
|
||||||
raise ValueError("Missing required parameter: chart_type")
|
raise ValueError("Missing required parameter: data.chart_type")
|
||||||
if not data or not data.get("series"):
|
if not chart_data or not chart_data.get("series"):
|
||||||
raise ValueError("Missing required parameter: data.series")
|
raise ValueError("Missing required parameter: data.data.series")
|
||||||
|
|
||||||
width = arguments.get("width", "100%")
|
width = data.get("width", "100%")
|
||||||
height = arguments.get("height", "400px")
|
height = data.get("height", "400px")
|
||||||
stacked = arguments.get("stacked", False)
|
stacked = data.get("stacked", False)
|
||||||
smooth = arguments.get("smooth", False)
|
smooth = data.get("smooth", False)
|
||||||
show_label = arguments.get("show_label", False)
|
show_label = data.get("show_label", False)
|
||||||
|
|
||||||
html = _build_chart_html(
|
html = _build_chart_html(
|
||||||
title, chart_type, data,
|
title, chart_type, chart_data,
|
||||||
width=width, height=height, theme=theme,
|
width=width, height=height, theme=theme,
|
||||||
stacked=stacked, smooth=smooth, show_label=show_label,
|
stacked=stacked, smooth=smooth, show_label=show_label,
|
||||||
)
|
)
|
||||||
return _create_ui_resource(uri, html, width, height)
|
return _create_ui_resource(uri, html, width, height)
|
||||||
|
|
||||||
elif path == "multi-chart":
|
elif path == "multi-chart":
|
||||||
charts = arguments.get("charts", [])
|
charts = data.get("charts", [])
|
||||||
if not charts:
|
if not charts:
|
||||||
raise ValueError("Missing required parameter: charts")
|
raise ValueError("Missing required parameter: data.charts")
|
||||||
for i, c in enumerate(charts):
|
for i, c in enumerate(charts):
|
||||||
if not c.get("chart_type"):
|
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"):
|
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)
|
html = _build_multi_chart_html(title, charts, columns=columns, theme=theme)
|
||||||
num_rows = (len(charts) + columns - 1) // columns
|
num_rows = (len(charts) + columns - 1) // columns
|
||||||
total_height = f"{80 + num_rows * 420}px"
|
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":
|
if tool_name == "render_data_viz":
|
||||||
uri = arguments.get("uri", "")
|
uri = arguments.get("uri", "")
|
||||||
|
data = arguments.get("data", {})
|
||||||
if not uri or not uri.startswith("ui://data-dashboard/"):
|
if not uri or not uri.startswith("ui://data-dashboard/"):
|
||||||
return create_error_response(
|
return create_error_response(
|
||||||
request_id, -32602,
|
request_id, -32602,
|
||||||
@ -500,7 +501,7 @@ async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = _handle_data_viz(uri, arguments)
|
result = _handle_data_viz(uri, data)
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
return create_error_response(request_id, -32602, str(e))
|
return create_error_response(request_id, -32602, str(e))
|
||||||
|
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "render_data_viz",
|
"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": {
|
"inputSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -10,13 +10,17 @@
|
|||||||
"enum": ["ui://data-dashboard/metrics", "ui://data-dashboard/chart", "ui://data-dashboard/multi-chart"],
|
"enum": ["ui://data-dashboard/metrics", "ui://data-dashboard/chart", "ui://data-dashboard/multi-chart"],
|
||||||
"description": "Resource URI that determines the visualization type"
|
"description": "Resource URI that determines the visualization type"
|
||||||
},
|
},
|
||||||
|
"data": {
|
||||||
|
"type": "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": {
|
||||||
"title": {
|
"title": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "Title displayed at the top of the visualization"
|
"description": "Title displayed at the top of the visualization"
|
||||||
},
|
},
|
||||||
"metrics": {
|
"metrics": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "[uri=ui://data-dashboard/metrics] Array of metric objects to display as cards",
|
"description": "[uri=metrics] Array of metric objects to display as cards",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -31,11 +35,11 @@
|
|||||||
"chart_type": {
|
"chart_type": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"enum": ["line", "bar", "pie", "radar", "scatter", "gauge"],
|
"enum": ["line", "bar", "pie", "radar", "scatter", "gauge"],
|
||||||
"description": "[uri=ui://data-dashboard/chart] Type of chart to render"
|
"description": "[uri=chart] Type of chart to render"
|
||||||
},
|
},
|
||||||
"data": {
|
"data": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"description": "[uri=ui://data-dashboard/chart] Chart data object",
|
"description": "[uri=chart] Chart data object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"categories": {
|
"categories": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
@ -49,7 +53,7 @@
|
|||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
"name": { "type": "string", "description": "Series name" },
|
"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]." }
|
"data": { "type": "array", "description": "Data values" }
|
||||||
},
|
},
|
||||||
"required": ["name", "data"]
|
"required": ["name", "data"]
|
||||||
}
|
}
|
||||||
@ -59,27 +63,27 @@
|
|||||||
},
|
},
|
||||||
"width": {
|
"width": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "[uri=ui://data-dashboard/chart] CSS width. Default: '100%'",
|
"description": "[uri=chart] CSS width. Default: '100%'",
|
||||||
"default": "100%"
|
"default": "100%"
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "[uri=ui://data-dashboard/chart] CSS height. Default: '400px'",
|
"description": "[uri=chart] CSS height. Default: '400px'",
|
||||||
"default": "400px"
|
"default": "400px"
|
||||||
},
|
},
|
||||||
"stacked": {
|
"stacked": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "[uri=ui://data-dashboard/chart] Stack series (line/bar). Default: false",
|
"description": "[uri=chart] Stack series (line/bar). Default: false",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"smooth": {
|
"smooth": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "[uri=ui://data-dashboard/chart] Smooth curves (line). Default: false",
|
"description": "[uri=chart] Smooth curves (line). Default: false",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"show_label": {
|
"show_label": {
|
||||||
"type": "boolean",
|
"type": "boolean",
|
||||||
"description": "[uri=ui://data-dashboard/chart] Show data labels. Default: false",
|
"description": "[uri=chart] Show data labels. Default: false",
|
||||||
"default": false
|
"default": false
|
||||||
},
|
},
|
||||||
"theme": {
|
"theme": {
|
||||||
@ -90,14 +94,14 @@
|
|||||||
},
|
},
|
||||||
"columns": {
|
"columns": {
|
||||||
"type": "integer",
|
"type": "integer",
|
||||||
"description": "[uri=ui://data-dashboard/multi-chart] Grid columns (1-4). Default: 2",
|
"description": "[uri=multi-chart] Grid columns (1-4). Default: 2",
|
||||||
"default": 2,
|
"default": 2,
|
||||||
"minimum": 1,
|
"minimum": 1,
|
||||||
"maximum": 4
|
"maximum": 4
|
||||||
},
|
},
|
||||||
"charts": {
|
"charts": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "[uri=ui://data-dashboard/multi-chart] Array of chart objects",
|
"description": "[uri=multi-chart] Array of chart objects",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -130,8 +134,10 @@
|
|||||||
"required": ["title", "chart_type", "data"]
|
"required": ["title", "chart_type", "data"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"required": ["uri", "title"]
|
"required": ["uri", "data"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
## `render_data_viz` usage guide
|
## `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:
|
### Supported URIs:
|
||||||
- `ui://data-dashboard/metrics` — Metric card dashboard
|
- `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(
|
render_data_viz(
|
||||||
uri="ui://data-dashboard/metrics",
|
uri="ui://data-dashboard/metrics",
|
||||||
title="Sales Overview",
|
data={
|
||||||
metrics=[
|
"title": "Sales Overview",
|
||||||
|
"metrics": [
|
||||||
{"label": "Revenue", "value": "$12,345", "change": "+12.5%", "change_type": "up"},
|
{"label": "Revenue", "value": "$12,345", "change": "+12.5%", "change_type": "up"},
|
||||||
{"label": "Users", "value": "1,234", "change": "-3.2%", "change_type": "down"},
|
{"label": "Users", "value": "1,234", "change": "-3.2%", "change_type": "down"},
|
||||||
{"label": "Conversion", "value": "4.5%", "change_type": "neutral"}
|
{"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(
|
render_data_viz(
|
||||||
uri="ui://data-dashboard/chart",
|
uri="ui://data-dashboard/chart",
|
||||||
title="Monthly Revenue",
|
|
||||||
chart_type="line",
|
|
||||||
data={
|
data={
|
||||||
|
"title": "Monthly Revenue",
|
||||||
|
"chart_type": "line",
|
||||||
|
"data": {
|
||||||
"categories": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
|
"categories": ["Jan", "Feb", "Mar", "Apr", "May", "Jun"],
|
||||||
"series": [
|
"series": [
|
||||||
{"name": "2024", "data": [820, 932, 901, 934, 1290, 1330]},
|
{"name": "2024", "data": [820, 932, 901, 934, 1290, 1330]},
|
||||||
{"name": "2025", "data": [620, 732, 801, 1034, 1190, 1530]}
|
{"name": "2025", "data": [620, 732, 801, 1034, 1190, 1530]}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
smooth=true
|
"smooth": true
|
||||||
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -52,17 +56,19 @@ render_data_viz(
|
|||||||
```
|
```
|
||||||
render_data_viz(
|
render_data_viz(
|
||||||
uri="ui://data-dashboard/chart",
|
uri="ui://data-dashboard/chart",
|
||||||
title="Sales by Region",
|
|
||||||
chart_type="bar",
|
|
||||||
data={
|
data={
|
||||||
|
"title": "Sales by Region",
|
||||||
|
"chart_type": "bar",
|
||||||
|
"data": {
|
||||||
"categories": ["North", "South", "East", "West"],
|
"categories": ["North", "South", "East", "West"],
|
||||||
"series": [
|
"series": [
|
||||||
{"name": "Q1", "data": [320, 302, 341, 374]},
|
{"name": "Q1", "data": [320, 302, 341, 374]},
|
||||||
{"name": "Q2", "data": [220, 182, 191, 234]}
|
{"name": "Q2", "data": [220, 182, 191, 234]}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
stacked=true,
|
"stacked": true,
|
||||||
show_label=true
|
"show_label": true
|
||||||
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -70,9 +76,10 @@ render_data_viz(
|
|||||||
```
|
```
|
||||||
render_data_viz(
|
render_data_viz(
|
||||||
uri="ui://data-dashboard/chart",
|
uri="ui://data-dashboard/chart",
|
||||||
title="Traffic Sources",
|
|
||||||
chart_type="pie",
|
|
||||||
data={
|
data={
|
||||||
|
"title": "Traffic Sources",
|
||||||
|
"chart_type": "pie",
|
||||||
|
"data": {
|
||||||
"series": [{
|
"series": [{
|
||||||
"name": "Sources",
|
"name": "Sources",
|
||||||
"data": [
|
"data": [
|
||||||
@ -83,6 +90,7 @@ render_data_viz(
|
|||||||
]
|
]
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -90,15 +98,17 @@ render_data_viz(
|
|||||||
```
|
```
|
||||||
render_data_viz(
|
render_data_viz(
|
||||||
uri="ui://data-dashboard/chart",
|
uri="ui://data-dashboard/chart",
|
||||||
title="Skill Assessment",
|
|
||||||
chart_type="radar",
|
|
||||||
data={
|
data={
|
||||||
|
"title": "Skill Assessment",
|
||||||
|
"chart_type": "radar",
|
||||||
|
"data": {
|
||||||
"categories": ["Sales", "Admin", "Tech", "Support", "Marketing"],
|
"categories": ["Sales", "Admin", "Tech", "Support", "Marketing"],
|
||||||
"series": [
|
"series": [
|
||||||
{"name": "Alice", "data": [4200, 3000, 20000, 35000, 50000]},
|
{"name": "Alice", "data": [4200, 3000, 20000, 35000, 50000]},
|
||||||
{"name": "Bob", "data": [5000, 14000, 28000, 26000, 42000]}
|
{"name": "Bob", "data": [5000, 14000, 28000, 26000, 42000]}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -106,14 +116,16 @@ render_data_viz(
|
|||||||
```
|
```
|
||||||
render_data_viz(
|
render_data_viz(
|
||||||
uri="ui://data-dashboard/chart",
|
uri="ui://data-dashboard/chart",
|
||||||
title="Height vs Weight",
|
|
||||||
chart_type="scatter",
|
|
||||||
data={
|
data={
|
||||||
|
"title": "Height vs Weight",
|
||||||
|
"chart_type": "scatter",
|
||||||
|
"data": {
|
||||||
"series": [
|
"series": [
|
||||||
{"name": "Male", "data": [[161, 51], [167, 59], [159, 49], [175, 73]]},
|
{"name": "Male", "data": [[161, 51], [167, 59], [159, 49], [175, 73]]},
|
||||||
{"name": "Female", "data": [[150, 45], [160, 55], [165, 60], [155, 50]]}
|
{"name": "Female", "data": [[150, 45], [160, 55], [165, 60], [155, 50]]}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -121,13 +133,15 @@ render_data_viz(
|
|||||||
```
|
```
|
||||||
render_data_viz(
|
render_data_viz(
|
||||||
uri="ui://data-dashboard/chart",
|
uri="ui://data-dashboard/chart",
|
||||||
title="CPU Usage",
|
data={
|
||||||
chart_type="gauge",
|
"title": "CPU Usage",
|
||||||
data={"series": [{"name": "CPU", "data": [72.5]}]}
|
"chart_type": "gauge",
|
||||||
|
"data": {"series": [{"name": "CPU", "data": [72.5]}]}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Chart options:
|
#### Chart options (inside data):
|
||||||
- `width`: CSS width, default "100%"
|
- `width`: CSS width, default "100%"
|
||||||
- `height`: CSS height, default "400px"
|
- `height`: CSS height, default "400px"
|
||||||
- `theme`: "light" (default) or "dark"
|
- `theme`: "light" (default) or "dark"
|
||||||
@ -144,10 +158,11 @@ When to use: comprehensive overviews, multiple related charts, business reports.
|
|||||||
```
|
```
|
||||||
render_data_viz(
|
render_data_viz(
|
||||||
uri="ui://data-dashboard/multi-chart",
|
uri="ui://data-dashboard/multi-chart",
|
||||||
title="Monthly Business Report",
|
data={
|
||||||
columns=2,
|
"title": "Monthly Business Report",
|
||||||
theme="light",
|
"columns": 2,
|
||||||
charts=[
|
"theme": "light",
|
||||||
|
"charts": [
|
||||||
{
|
{
|
||||||
"title": "Revenue Trend",
|
"title": "Revenue Trend",
|
||||||
"chart_type": "line",
|
"chart_type": "line",
|
||||||
@ -186,6 +201,7 @@ render_data_viz(
|
|||||||
"height": "300px"
|
"height": "300px"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
}
|
||||||
)
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@ -17,14 +17,13 @@ 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.
|
Do NOT call ask_user with empty options arrays.
|
||||||
|
|
||||||
### How to format options
|
### How to call render_ui for ask-user
|
||||||
- Options MUST be placed in the `options` array field, NOT embedded in the question text.
|
Use `uri` + `data` format:
|
||||||
- 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
|
```json
|
||||||
{
|
render_ui(
|
||||||
|
uri="ui://mcp-ui/ask-user",
|
||||||
|
data={
|
||||||
|
"title": "A descriptive title",
|
||||||
"questions": [
|
"questions": [
|
||||||
{
|
{
|
||||||
"question": "Who is the audience?",
|
"question": "Who is the audience?",
|
||||||
@ -36,8 +35,14 @@ CORRECT example:
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
WRONG example (DO NOT do this):
|
WRONG example (DO NOT do this):
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
[
|
[
|
||||||
{
|
{
|
||||||
"name": "render_ui",
|
"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": {
|
"inputSchema": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -10,31 +10,35 @@
|
|||||||
"enum": ["ui://mcp-ui/html", "ui://mcp-ui/url", "ui://mcp-ui/ask-user"],
|
"enum": ["ui://mcp-ui/html", "ui://mcp-ui/url", "ui://mcp-ui/ask-user"],
|
||||||
"description": "Resource URI that determines the rendering mode"
|
"description": "Resource URI that determines the rendering mode"
|
||||||
},
|
},
|
||||||
|
"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": {
|
"title": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "A descriptive title for the UI widget"
|
"description": "A descriptive title for the UI widget"
|
||||||
},
|
},
|
||||||
"html_content": {
|
"html_content": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "[uri=ui://mcp-ui/html] Complete HTML content to render. Can include inline CSS and JavaScript."
|
"description": "[uri=html] Complete HTML content to render. Can include inline CSS and JavaScript."
|
||||||
},
|
},
|
||||||
"url": {
|
"url": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "[uri=ui://mcp-ui/url] External URL to embed in an iframe."
|
"description": "[uri=url] External URL to embed in an iframe."
|
||||||
},
|
},
|
||||||
"width": {
|
"width": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "[uri=ui://mcp-ui/html|url] CSS width for the iframe. Default: '100%'",
|
"description": "[uri=html|url] CSS width. Default: '100%'",
|
||||||
"default": "100%"
|
"default": "100%"
|
||||||
},
|
},
|
||||||
"height": {
|
"height": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
"description": "[uri=ui://mcp-ui/html|url] CSS height for the iframe. Default: 'auto'",
|
"description": "[uri=html|url] CSS height. Default: 'auto'",
|
||||||
"default": "auto"
|
"default": "auto"
|
||||||
},
|
},
|
||||||
"questions": {
|
"questions": {
|
||||||
"type": "array",
|
"type": "array",
|
||||||
"description": "[uri=ui://mcp-ui/ask-user] Array of questions to ask the user.",
|
"description": "[uri=ask-user] Array of questions to ask the user.",
|
||||||
"items": {
|
"items": {
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"properties": {
|
"properties": {
|
||||||
@ -57,8 +61,10 @@
|
|||||||
"required": ["question", "options"]
|
"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)
|
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.
|
"""Unified handler for all render_ui URIs.
|
||||||
|
|
||||||
Content is NOT self-contained — the actual html_content/url/questions
|
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.
|
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.
|
The frontend extracts content from TOOL_CALL args based on URI.
|
||||||
"""
|
"""
|
||||||
width = arguments.get("width", "100%")
|
width = data.get("width", "100%")
|
||||||
height = arguments.get("height", "auto")
|
height = data.get("height", "auto")
|
||||||
|
|
||||||
if uri == "ui://mcp-ui/ask-user":
|
if uri == "ui://mcp-ui/ask-user":
|
||||||
resource = create_ui_resource({
|
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":
|
elif uri == "ui://mcp-ui/url":
|
||||||
url = arguments.get("url", "")
|
url = data.get("url", "")
|
||||||
resource = create_ui_resource({
|
resource = create_ui_resource({
|
||||||
"uri": uri,
|
"uri": uri,
|
||||||
"content": {"type": "externalUrl", "iframeUrl": url},
|
"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":
|
if tool_name == "render_ui":
|
||||||
uri = arguments.get("uri", "")
|
uri = arguments.get("uri", "")
|
||||||
|
data = arguments.get("data", {})
|
||||||
|
|
||||||
if not uri or not uri.startswith("ui://mcp-ui/"):
|
if not uri or not uri.startswith("ui://mcp-ui/"):
|
||||||
return create_error_response(
|
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/", "")
|
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(
|
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(
|
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(
|
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}
|
return {"jsonrpc": "2.0", "id": request_id, "result": result}
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user