diff --git a/mcp_servers.d/navi_ui.json b/mcp_servers.d/navi_ui.json index 912c00d..6b8626f 100644 --- a/mcp_servers.d/navi_ui.json +++ b/mcp_servers.d/navi_ui.json @@ -4,5 +4,5 @@ "groups": { "ui": ["render_component"] }, - "instructions": "Internal Navi UI server. Use `render_component` to display structured data in the webclient. The tool accepts `component_name` (string) and `payload` (JSON object); `session_id` is injected automatically by the agent. Only use it when a graphical/structured view is appropriate." + "instructions": "Tool: render_component. Arguments: {\"component_name\": \"card_grid\", \"payload\": {...}}. Only use component_name=\"card_grid\". session_id is injected automatically. For card_grid, payload must contain a non-empty \"cards\" array. Each card must have string \"id\" and string \"title\". Optional card fields: \"subtitle\", \"image\" (URL), \"meta\" (array of {\"label\", \"value\"}), \"description\" (short string), \"details\" (array of {\"label\", \"value\"} shown in modal), \"actions\" (array of {\"label\", \"url\"}). Limit card grid to 4 cards. After rendering the grid, still give a brief text summary and ask what the user wants next." } diff --git a/navi/mcp/ui_server.py b/navi/mcp/ui_server.py index 2445c16..84e106a 100644 --- a/navi/mcp/ui_server.py +++ b/navi/mcp/ui_server.py @@ -34,53 +34,45 @@ # This is separate from the webclient implementation; both must agree on the # same contract, but the server only validates the envelope. SERVER_INSTRUCTIONS = """\ -Internal Navi UI server. Use this to render structured data in the webclient. +Internal Navi UI server. Use render_component to show structured UI in the webclient. Tool: render_component(component_name, payload, session_id) -- component_name: identifier of the UI component to render. -- payload: JSON-serializable object with the data for that component. -- session_id: target Navi session (injected automatically by the agent). +- component_name: only "card_grid" is supported. +- payload: JSON object. See schema below. +- session_id: DO NOT pass manually. It is injected automatically by the agent. -Only call this when the user explicitly asks for a graphical / structured -view, or when a tool response is naturally represented as a component. +## card_grid payload schema -## Supported components - -### card_grid -A responsive grid of clickable cards (default up to 4). Clicking a card opens a -modal with the full `details` object. - -Payload shape: -```json { "title": "Optional section heading", "cards": [ { - "id": "unique-card-id", - "title": "Card title", - "subtitle": "Short secondary line", - "image": "https://optional-image-url", + "id": "required-string-id", + "title": "Required card title", + "subtitle": "Optional secondary line", + "image": "https://optional-direct-image-url", "meta": [ {"label": "Label", "value": "Value"} ], - "description": "One-line summary (optional)", + "description": "Optional short one-line summary", "details": [ - {"label": "Full detail", "value": "Full value"} + {"label": "Detail label", "value": "Detail value"} ], "actions": [ - {"label": "Open", "url": "https://..."} + {"label": "Button label", "url": "https://..."} ] } ] } -``` -Rules: -- `cards` is required and must be a list. -- Each card must contain `id` (string) and `title` (string). -- `image` must be a direct URL if present. -- Keep `description` under 120 characters. -- Put everything the user might want to see after clicking in `details`. +Hard rules: +1. component_name must be "card_grid". +2. payload.cards is required and must be a non-empty list. +3. Every card must have id (string) and title (string). +4. Limit to 4 cards in one call. If there are more, say there are more and ask if the user wants them. +5. image must be a direct URL, never a local path. +6. Put the full information that should appear after a card click into details. +7. After calling render_component, still provide a short text summary and offer next steps. """ mcp = FastMCP( diff --git a/navi/profiles/realtor/system_prompt.txt b/navi/profiles/realtor/system_prompt.txt index 0c2374a..d68aef8 100644 --- a/navi/profiles/realtor/system_prompt.txt +++ b/navi/profiles/realtor/system_prompt.txt @@ -169,48 +169,56 @@ ## UI component output (optional) -When the data is naturally shown as a grid of cards (e.g. several listings, products, items), use the tool: +When the data is naturally shown as a grid of cards (for example, several listings, products, or any items), render it with: -``` -mcp__navi_ui__render_component -component_name: card_grid -payload: - title: "Подходящие варианты" // optional section title - cards: - - id: "unique-id-1" // required, stable identifier - title: "Квартира на Арсенальной" - subtitle: "Печерский район · 5 мин до метро" - image: "https://..." // optional, direct image URL - meta: - - label: "Цена" - value: "$80 000" - - label: "Комнат" - value: "2" - - label: "Площадь" - value: "58 м²" - - label: "Этаж" - value: "4/9" - description: "Краткое описание карточки." - details: // shown in the modal on click - - label: "Адрес" - value: "ул. Примерная, 10" - - label: "Тип дома" - value: "монолит" - - label: "Ремонт" - value: "евро" - - label: "Телефон" - value: "+380 ..." - actions: // optional buttons - - label: "Источник" - url: "https://..." +Tool name: `mcp__navi_ui__render_component` + +Required arguments: +- `component_name`: always `"card_grid"`. +- `payload`: JSON object with the exact shape below. +- `session_id`: DO NOT pass manually. The agent injects it automatically. + +### card_grid payload (exact JSON shape) + +```json +{ + "title": "Подходящие варианты", + "cards": [ + { + "id": "listing-12345", + "title": "2-комнатная квартира на Арсенальной", + "subtitle": "Печерский район, 5 мин до метро", + "image": "https://example.com/image.jpg", + "meta": [ + { "label": "Цена", "value": "$80 000" }, + { "label": "Комнат", "value": "2" }, + { "label": "Площадь", "value": "58 м²" }, + { "label": "Этаж", "value": "4/9" } + ], + "description": "Краткое описание карточки, не длиннее 120 символов.", + "details": [ + { "label": "Адрес", "value": "ул. Примерная, 10" }, + { "label": "Тип дома", "value": "монолит" }, + { "label": "Ремонт", "value": "евро" }, + { "label": "Телефон", "value": "+380 ..." } + ], + "actions": [ + { "label": "Источник", "url": "https://..." } + ] + } + ] +} ``` -Guidelines: -- Limit to **4 cards** for the first grid. Offer more on request. -- Each card must have `id` and `title`; keep `description` under 120 characters. -- Put the full information the user may need in `details` (modal view). -- `image` must be a direct URL, not a local path. -- After calling the component, still provide a short text summary and offer next steps. +### Rules for card_grid +1. Use `component_name = "card_grid"` only. +2. `payload.cards` must be a non-empty array. +3. Every card must have `"id"` (string) and `"title"` (string). +4. Show **maximum 4 cards** in one call. If there are more, say so and offer the next batch. +5. `"image"` must be a direct URL, never a local file path. +6. Keep `"description"` under 120 characters. +7. Put everything the user might want to see after clicking a card into `"details"` (it opens in a modal). +8. After calling the component, still write a short text summary and ask what the user wants next. ## Workflow summary 1. Understand the user's request. Ask clarifying questions if criteria are vague (budget, district, rooms).