diff --git a/skills/common/data-dashboard/dashboard_server.py b/skills/common/data-dashboard/dashboard_server.py index 0e1e75b..1768cc1 100644 --- a/skills/common/data-dashboard/dashboard_server.py +++ b/skills/common/data-dashboard/dashboard_server.py @@ -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)) diff --git a/skills/common/data-dashboard/dashboard_tools.json b/skills/common/data-dashboard/dashboard_tools.json index aeafc17..248990c 100644 --- a/skills/common/data-dashboard/dashboard_tools.json +++ b/skills/common/data-dashboard/dashboard_tools.json @@ -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"] } } ] diff --git a/skills/common/data-dashboard/hooks/dashboard_guide.md b/skills/common/data-dashboard/hooks/dashboard_guide.md index c2a028c..7d6bb3a 100644 --- a/skills/common/data-dashboard/hooks/dashboard_guide.md +++ b/skills/common/data-dashboard/hooks/dashboard_guide.md @@ -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" - } - ] + ] + } ) ``` diff --git a/skills/common/mcp-ui/hooks/ask_user_guide.md b/skills/common/mcp-ui/hooks/ask_user_guide.md index 5a2d145..7af29f3 100644 --- a/skills/common/mcp-ui/hooks/ask_user_guide.md +++ b/skills/common/mcp-ui/hooks/ask_user_guide.md @@ -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 { diff --git a/skills/common/mcp-ui/mcp_ui_tools.json b/skills/common/mcp-ui/mcp_ui_tools.json index f89af97..4bd3763 100644 --- a/skills/common/mcp-ui/mcp_ui_tools.json +++ b/skills/common/mcp-ui/mcp_ui_tools.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"] } } ] diff --git a/skills/common/mcp-ui/ui_render_server.py b/skills/common/mcp-ui/ui_render_server.py index 2aa822c..7b4b27e 100644 --- a/skills/common/mcp-ui/ui_render_server.py +++ b/skills/common/mcp-ui/ui_render_server.py @@ -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: