使用 mcp_ui_server 库构建 UIResource

This commit is contained in:
朱潮 2026-05-18 20:19:10 +08:00
parent cb4a1df0b4
commit add8a1fd18
2 changed files with 55 additions and 31 deletions

View File

@ -144,16 +144,13 @@ async def enhanced_generate_stream_response(
elif isinstance(msg, ToolMessage) and msg.content:
message_tag = "TOOL_RESPONSE"
waiting_for_answer_first_char = False
# Always output UIResource, ask_user and render_ui responses even when tool_response is disabled
# Always output UIResource responses even when tool_response is disabled
is_ui_resource = (
msg.text
and msg.text.lstrip().startswith('{"')
and '"ui://' in msg.text
and ('"text/html' in msg.text or '"text/uri-list' in msg.text)
)
is_ask_user = msg.name == 'ask_user'
is_render_ui = msg.name == 'render_ui'
if config.tool_response or is_ui_resource or is_ask_user or is_render_ui:
if config.tool_response or is_ui_resource:
new_content = f"[{message_tag}] {msg.name}\n{msg.text}\n"
# Collect full content

View File

@ -8,6 +8,8 @@ import json
import sys
from typing import Any, Dict
from mcp_ui_server import create_ui_resource, UIMetadataKey
from mcp_common import (
create_error_response,
create_initialize_response,
@ -18,40 +20,65 @@ from mcp_common import (
)
ASK_USER_RESPONSE = "Questions sent to user."
def _serialize_ui_resource(ui_resource) -> str:
"""Serialize a UIResource to JSON string."""
return json.dumps(ui_resource.model_dump(mode="json"), ensure_ascii=False)
def ask_user() -> Dict[str, Any]:
"""Return a minimal fixed response for ask_user tool.
"""Return a UIResource response for ask_user tool.
The actual questions/options are already in the TOOL_CALL arguments,
so the frontend parses them directly from there. This response only
serves to acknowledge the tool call and minimize token usage in the
subsequent LLM inference round.
The actual questions are in the TOOL_CALL arguments. The frontend
detects ui_type from the UIResource metadata and extracts content
from the corresponding TOOL_CALL args.
"""
return {
"content": [
{"type": "text", "text": ASK_USER_RESPONSE}
]
}
resource = create_ui_resource({
"uri": "ui://mcp-ui/ask-user",
"content": {"type": "rawHtml", "htmlString": "Questions sent to user."},
"encoding": "text",
"uiMetadata": {
"type": "ask_user",
"interactive": True,
},
})
return {"content": [{"type": "text", "text": _serialize_ui_resource(resource)}]}
RENDER_UI_RESPONSE = "UI rendered."
def render_ui(arguments: Dict[str, Any]) -> Dict[str, Any]:
"""Return a UIResource response for render_ui tool.
def render_ui() -> Dict[str, Any]:
"""Return a minimal fixed response for render_ui tool.
The actual html_content/url is already in the TOOL_CALL arguments,
so the frontend parses them directly from there. This response only
serves to acknowledge the tool call and minimize token usage in the
subsequent LLM inference round.
The actual html_content/url is in the TOOL_CALL arguments. The frontend
detects ui_type from the UIResource metadata and extracts content
from the corresponding TOOL_CALL args.
"""
return {
"content": [
{"type": "text", "text": RENDER_UI_RESPONSE}
]
}
html_content = arguments.get("html_content", "")
url = arguments.get("url", "")
width = arguments.get("width", "100%")
height = arguments.get("height", "auto")
if html_content:
resource = create_ui_resource({
"uri": "ui://mcp-ui/render-ui",
"content": {"type": "rawHtml", "htmlString": "UI rendered."},
"encoding": "text",
"uiMetadata": {
UIMetadataKey.PREFERRED_FRAME_SIZE: [width, height],
"type": "render_ui_html",
"interactive": False,
},
})
else:
resource = create_ui_resource({
"uri": "ui://mcp-ui/render-ui",
"content": {"type": "externalUrl", "iframeUrl": url},
"encoding": "text",
"uiMetadata": {
UIMetadataKey.PREFERRED_FRAME_SIZE: [width, height],
"type": "render_ui_url",
"interactive": False,
},
})
return {"content": [{"type": "text", "text": _serialize_ui_resource(resource)}]}
async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]:
@ -109,7 +136,7 @@ async def handle_request(request: Dict[str, Any]) -> Dict[str, Any]:
request_id, -32602, "Missing required parameter: html_content or url"
)
result = render_ui()
result = render_ui(arguments)
return {"jsonrpc": "2.0", "id": request_id, "result": result}
elif tool_name == "ask_user":