File size: 11,887 Bytes
ca002c4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 | """
DealerMax MCP demo — Gradio Space.
Vetrina interattiva del Model Context Protocol server pubblico di
DealerMax (registrato sull'Anthropic MCP Registry come
`app.dealermax/public-search`). Espone i 6 tool MCP via UI semplice
per chi non vuole / non puo' configurare un client MCP nativo
(Claude Desktop, ChatGPT, ecc.).
Endpoint upstream: https://mcp.dealermax.app/mcp/
Trasporto: streamable-http JSON-RPC 2.0
Tool: search_vehicles, search_nlt_offers, get_vehicle_details,
find_dealer, get_market_intel, get_vehicle_specs
Cosa fa la Space:
- radio button per selezionare il tool
- form per input parametri (cambia in base al tool)
- bottoni "esempi pronti" che pre-popolano i campi
- chiamata HTTP POST diretta al MCP server (no SDK, no auth)
- rendering risultato JSON pretty-print + tabella highlights
Niente API key richiesta. Rate limit equo lato server (60 req/min/IP).
"""
from __future__ import annotations
import json
from typing import Any
import gradio as gr
import requests
MCP_ENDPOINT = "https://mcp.dealermax.app/mcp/"
HEADERS = {
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
}
def call_mcp_tool(tool_name: str, arguments: dict[str, Any]) -> dict[str, Any]:
"""Chiama un tool MCP via JSON-RPC 2.0. Restituisce il payload risultato
parsato come dict (gli MCP tool DealerMax tornano sempre JSON serializzato
in `content[0].text`).
"""
arguments = {k: v for k, v in arguments.items() if v not in (None, "", 0)}
payload = {
"jsonrpc": "2.0",
"id": 1,
"method": "tools/call",
"params": {"name": tool_name, "arguments": arguments},
}
try:
r = requests.post(MCP_ENDPOINT, json=payload, headers=HEADERS, timeout=15)
r.raise_for_status()
data = r.json()
except requests.exceptions.RequestException as e:
return {"error": f"Network error: {e}"}
except json.JSONDecodeError as e:
return {"error": f"Invalid JSON response: {e}"}
if "error" in data:
return {"error": data["error"]}
result = data.get("result", {})
content = result.get("content", [])
if content and isinstance(content, list):
first = content[0]
if first.get("type") == "text":
try:
return json.loads(first["text"])
except json.JSONDecodeError:
return {"raw_text": first["text"]}
return result
def format_output(payload: dict[str, Any]) -> str:
"""Pretty-print JSON con indent."""
return json.dumps(payload, indent=2, ensure_ascii=False)
# --------------------------------------------------------------------------- #
# Tool wrappers (1 funzione per tool, parametri allineati allo schema MCP)
# --------------------------------------------------------------------------- #
def tool_search_vehicles(query: str, region: str, brand: str, fuel_type: str, budget_max: int, limit: int) -> str:
return format_output(call_mcp_tool("search_vehicles", {
"query": query, "region": region or None, "brand": brand or None,
"fuel_type": fuel_type or None, "budget_max": budget_max or None,
"limit": limit or 5,
}))
def tool_search_nlt_offers(query: str, durata_max_mesi: int, canone_max: int, region: str, limit: int) -> str:
return format_output(call_mcp_tool("search_nlt_offers", {
"query": query, "durata_max_mesi": durata_max_mesi or None,
"canone_max": canone_max or None, "region": region or None,
"limit": limit or 5,
}))
def tool_get_vehicle_details(vehicle_slug: str) -> str:
return format_output(call_mcp_tool("get_vehicle_details", {"vehicle_slug": vehicle_slug}))
def tool_find_dealer(region: str, brand: str) -> str:
return format_output(call_mcp_tool("find_dealer", {
"region": region or None, "brand": brand or None,
}))
def tool_get_market_intel(query: str, types: str, limit: int) -> str:
types_list = [t.strip() for t in types.split(",") if t.strip()] if types else None
return format_output(call_mcp_tool("get_market_intel", {
"query": query, "types": types_list, "limit": limit or 5,
}))
def tool_get_vehicle_specs(query: str, brand: str, model: str, fuel_type: str, limit: int) -> str:
return format_output(call_mcp_tool("get_vehicle_specs", {
"query": query or None, "brand": brand or None, "model": model or None,
"fuel_type": fuel_type or None, "limit": limit or 5,
}))
# --------------------------------------------------------------------------- #
# UI Gradio
# --------------------------------------------------------------------------- #
INTRO_MD = """
# DealerMax MCP — vetrina interattiva
Demo pubblica del **Model Context Protocol server di DealerMax**, registrato sul
[registro ufficiale Anthropic](https://registry.modelcontextprotocol.io/v0.1/servers?search=app.dealermax)
come `app.dealermax/public-search` v1.0.0.
Sei strumenti su un solo endpoint pubblico, no-auth, fair-use rate-limited:
| Tool | Cosa restituisce |
|---|---|
| `search_vehicles` | Veicoli usati cross-dealer del network DealerMax |
| `search_nlt_offers` | Offerte di noleggio lungo termine (NLT) cross-dealer |
| `get_vehicle_details` | Dettaglio singolo veicolo (specs, prezzo, dealer, podcast) |
| `find_dealer` | Directory concessionari per regione / brand |
| `get_market_intel` | Knowledge base automotive italiana (guide, glossario, FAQ, news) |
| `get_vehicle_specs` | Specifiche tecniche pubbliche di veicoli (dimensioni, consumi, motore...) |
Endpoint: `https://mcp.dealermax.app/mcp/` · Rate limit: 60 req/min · License: CC-BY-4.0
"""
with gr.Blocks(title="DealerMax MCP demo", theme=gr.themes.Soft()) as demo:
gr.Markdown(INTRO_MD)
with gr.Tabs():
# --- search_vehicles ---
with gr.Tab("🚗 search_vehicles"):
gr.Markdown("Ricerca cross-dealer di veicoli usati italiani. Filtri opzionali su regione, brand, alimentazione, budget.")
with gr.Row():
sv_query = gr.Textbox(label="Query semantica", placeholder="es. SUV ibrido familiare", value="SUV ibrido familiare")
sv_region = gr.Textbox(label="Regione/provincia (opzionale)", placeholder="Lombardia, MI, Milano…")
with gr.Row():
sv_brand = gr.Textbox(label="Brand (opzionale)", placeholder="BMW, Toyota…")
sv_fuel = gr.Textbox(label="Alimentazione (opzionale)", placeholder="benzina, ibrido, elettrico…")
sv_budget = gr.Number(label="Budget max € (opzionale)", value=None, precision=0)
sv_limit = gr.Number(label="Limit", value=5, precision=0, minimum=1, maximum=30)
sv_btn = gr.Button("Cerca veicoli", variant="primary")
sv_out = gr.Code(language="json", label="Risultato JSON")
sv_btn.click(tool_search_vehicles, [sv_query, sv_region, sv_brand, sv_fuel, sv_budget, sv_limit], sv_out)
# --- search_nlt_offers ---
with gr.Tab("📅 search_nlt_offers"):
gr.Markdown("Offerte di noleggio lungo termine cross-dealer. Filtri su durata massima, canone massimo, regione.")
with gr.Row():
nlt_query = gr.Textbox(label="Query", placeholder="es. elettrica city car under 300/mese", value="elettrica city car")
nlt_durata = gr.Number(label="Durata max mesi (36/48/60)", value=None, precision=0)
with gr.Row():
nlt_canone = gr.Number(label="Canone max €/mese", value=None, precision=0)
nlt_region = gr.Textbox(label="Regione/provincia", placeholder="Lazio, Roma…")
nlt_limit = gr.Number(label="Limit", value=5, precision=0, minimum=1, maximum=30)
nlt_btn = gr.Button("Cerca offerte NLT", variant="primary")
nlt_out = gr.Code(language="json", label="Risultato JSON")
nlt_btn.click(tool_search_nlt_offers, [nlt_query, nlt_durata, nlt_canone, nlt_region, nlt_limit], nlt_out)
# --- get_vehicle_specs ---
with gr.Tab("📐 get_vehicle_specs"):
gr.Markdown(
"Specifiche tecniche pubbliche dei veicoli del mercato italiano. Risponde a query come "
"*Quanto è lunga la Mazda 3?*, *Quanto consuma una Peugeot 2008 ibrida?*"
)
with gr.Row():
vs_query = gr.Textbox(label="Query free-text (opzionale)", placeholder="Mazda 3 2024", value="")
vs_brand = gr.Textbox(label="Marca", placeholder="Peugeot, Mazda, Tesla…", value="Peugeot")
with gr.Row():
vs_model = gr.Textbox(label="Modello", placeholder="2008, Model 3, 3…", value="2008")
vs_fuel = gr.Textbox(label="Alimentazione (opzionale)", placeholder="ibrido, elettrico…", value="ibrido")
vs_limit = gr.Number(label="Limit", value=3, precision=0, minimum=1, maximum=30)
vs_btn = gr.Button("Cerca specifiche", variant="primary")
vs_out = gr.Code(language="json", label="Risultato JSON")
vs_btn.click(tool_get_vehicle_specs, [vs_query, vs_brand, vs_model, vs_fuel, vs_limit], vs_out)
# --- get_vehicle_details ---
with gr.Tab("🔍 get_vehicle_details"):
gr.Markdown(
"Dettaglio completo di un singolo veicolo (specs, prezzo, dealer, podcast). "
"Accetta UUID o slug `marca-modello-id_auto`."
)
vd_slug = gr.Textbox(label="Vehicle slug / UUID", placeholder="UUID o slug del veicolo")
vd_btn = gr.Button("Recupera dettaglio", variant="primary")
vd_out = gr.Code(language="json", label="Risultato JSON")
vd_btn.click(tool_get_vehicle_details, [vd_slug], vd_out)
# --- find_dealer ---
with gr.Tab("🏬 find_dealer"):
gr.Markdown("Directory dei dealer attivi nel network DealerMax. Filtra per regione/provincia/citta + brand venduto.")
with gr.Row():
fd_region = gr.Textbox(label="Regione/provincia (opzionale)", placeholder="Toscana, FI, Firenze…", value="Lombardia")
fd_brand = gr.Textbox(label="Brand (opzionale)", placeholder="BMW, Tesla…")
fd_btn = gr.Button("Cerca dealer", variant="primary")
fd_out = gr.Code(language="json", label="Risultato JSON")
fd_btn.click(tool_find_dealer, [fd_region, fd_brand], fd_out)
# --- get_market_intel ---
with gr.Tab("📚 get_market_intel"):
gr.Markdown(
"Knowledge base automotive italiana: guide editoriali, glossario (212 termini), FAQ globali (139), news. "
"Filtra per tipi `guide,glossary,faq,news` (default tutti)."
)
with gr.Row():
mi_query = gr.Textbox(label="Query semantica", placeholder="es. incentivi auto elettriche 2026", value="incentivi auto elettriche 2026")
mi_types = gr.Textbox(label="Tipi (comma-separated, opzionale)", placeholder="guide,glossary,faq,news")
mi_limit = gr.Number(label="Limit", value=5, precision=0, minimum=1, maximum=30)
mi_btn = gr.Button("Cerca knowledge", variant="primary")
mi_out = gr.Code(language="json", label="Risultato JSON")
mi_btn.click(tool_get_market_intel, [mi_query, mi_types, mi_limit], mi_out)
gr.Markdown(
"## Configura il tuo client AI nativo\n\n"
"Per usare il server MCP direttamente da Claude Desktop, ChatGPT, Cursor, Gemini-CLI, "
"vai sulla pagina onboarding ufficiale: [dealermax.app/mcp](https://dealermax.app/mcp).\n\n"
"Repository: [VMAzure/dealermax-mcp](https://github.com/VMAzure/dealermax-mcp) · "
"Operatore: AZURE Srl (P.IVA IT13005450963) · Contatto: support@dealermax.app"
)
if __name__ == "__main__":
demo.launch()
|