Skip to content

MCP Integration Guide

This guide is for developers who want to connect their e-commerce or business platform to Webnav.ai using the Model Context Protocol (MCP) — an open standard for exposing tools over JSON-RPC 2.0 over HTTP.

MCP mode is an Enterprise feature.


Why MCP?

MCP is the integration method for AI Actions: you run one MCP tool server and Webnav.ai discovers your tools automatically via tools/list, drives confirmation logic from their annotations (destructiveHint, readOnlyHint, etc.), and calls them over an open standard. The same confirmation flow, SKU validation, call logging, and SSRF protections apply to every tool.


Architecture

Visitor → Widget → Webnav.ai ──JSON-RPC 2.0──► Your MCP Server
                               ◄──────────────── (tool result)
  1. Webnav.ai calls initialize on your MCP endpoint when the domain first loads.
  2. It calls tools/list to discover your available tools (with annotations).
  3. When a visitor triggers an action, Webnav.ai calls tools/call with the tool name and arguments.
  4. Write tools (create_order, create_refund) go through a confirmation card step — the visitor must click Confirm before execution.

Canonical tools (5 standard tools)

These are the five tools Webnav.ai understands natively. You may implement a subset; all five are recommended for full e-commerce coverage.

Tool summary

ToolTypeConfirmationPurpose
list_productsreadnoReturn product catalogue for SKU validation
query_orderreadnoLook up order status / details
query_logisticsreadnoTrack shipment
create_orderwriteyesPlace a new order (items array, one call)
create_refundwriteyesStart a refund request

list_products is used internally by Webnav.ai to validate SKUs before showing a confirmation card. If a visitor asks for a SKU that does not appear in list_products, the assistant asks them to pick from the real catalogue — no confirmation card is shown for invalid SKUs.


Tool specifications

list_products

json
{
  "name": "list_products",
  "description": "Return the product catalogue available for purchase.",
  "inputSchema": { "type": "object", "properties": {} },
  "annotations": { "readOnlyHint": true }
}

Response data.products:

json
[
  { "sku": "PB-10000", "name": "Power Bank 10000 mAh", "price": "19.99" },
  { "sku": "CB-USBC",  "name": "USB-C Cable 2m",        "price": "7.99"  }
]

query_order

json
{
  "name": "query_order",
  "description": "Look up order status and details by order ID.",
  "inputSchema": {
    "type": "object",
    "properties": { "order_id": { "type": "string", "description": "Order identifier" } },
    "required": ["order_id"]
  },
  "annotations": { "readOnlyHint": true }
}

Response data:

json
{
  "order_id": "SO20260613",
  "status": "paid",
  "amount": "199.00",
  "currency": "USD",
  "items": [{ "sku": "PB-10000", "name": "Power Bank", "qty": 2 }]
}

query_logistics

json
{
  "name": "query_logistics",
  "description": "Track shipment status for an order.",
  "inputSchema": {
    "type": "object",
    "properties": { "order_id": { "type": "string" } },
    "required": ["order_id"]
  },
  "annotations": { "readOnlyHint": true }
}

Response data:

json
{
  "order_id": "SO20260613",
  "carrier": "SF Express",
  "tracking_no": "SF123456789",
  "status": "in_transit",
  "tracks": [
    { "time": "2026-06-12 14:30", "desc": "Arrived at Shenzhen hub" },
    { "time": "2026-06-13 09:10", "desc": "Out for delivery" }
  ]
}

create_order

json
{
  "name": "create_order",
  "description": "Place a new order. Each item must be a real product from list_products.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "items": {
        "type": "array",
        "description": "List of items to order",
        "items": {
          "type": "object",
          "properties": {
            "sku":      { "type": "string", "description": "Product SKU" },
            "quantity": { "type": "integer", "minimum": 1 },
            "name":     { "type": "string", "description": "Product name (optional)" }
          },
          "required": ["sku", "quantity"]
        }
      },
      "address": { "type": "string", "description": "Delivery address (optional)" }
    },
    "required": ["items"]
  },
  "annotations": { "destructiveHint": true, "idempotentHint": false, "title": "Place Order" }
}

Response data:

json
{ "order_id": "SO20260614", "pay_url": "https://pay.example.com/SO20260614", "amount": "39.98" }

items is an array — the visitor can add multiple products in one conversation turn, and Webnav.ai calls create_order once with all items. Only one confirmation card is shown.


create_refund

json
{
  "name": "create_refund",
  "description": "Start a refund request for an order.",
  "inputSchema": {
    "type": "object",
    "properties": {
      "order_id": { "type": "string" },
      "reason":   { "type": "string", "description": "Reason for refund (optional)" }
    },
    "required": ["order_id"]
  },
  "annotations": { "destructiveHint": true, "title": "Request Refund" }
}

Response data:

json
{ "refund_id": "RF20260613", "status": "pending", "amount": "199.00" }

Tool annotations

Annotations are metadata in tools/list that tell Webnav.ai how to handle each tool:

AnnotationTypeEffect
destructiveHintbooltrue → Webnav.ai always shows a confirmation card before calling this tool
readOnlyHintbooltrue → no confirmation needed; call directly
idempotentHintboolfalse → extra caution; may enforce stricter single-use pending
openWorldHintbooltrue → tool may have unpredictable side effects
titlestringHuman-readable label shown on the confirmation card

Trust model: destructiveHint is self-reported by your server. Webnav.ai does not trust annotations alone — it also applies name-based rules (create_*, refund_*, cancel_*, pay_* → always confirms) and your mcpConfirmTools list configured in the dashboard. All three layers are OR-ed together, so a write tool is never accidentally skipped even if it forgets to set destructiveHint.


Wire format

Handshake (initialize)

Webnav.ai sends this when the domain first connects:

json
{
  "jsonrpc": "2.0",
  "id": "1",
  "method": "initialize",
  "params": {
    "protocolVersion": "2024-11-05",
    "clientInfo": { "name": "webnav-ai", "version": "1.0" },
    "capabilities": {}
  }
}

Your server responds:

json
{
  "jsonrpc": "2.0",
  "id": "1",
  "result": {
    "protocolVersion": "2024-11-05",
    "serverInfo": { "name": "my-shop-mcp", "version": "0.1.0" },
    "capabilities": {}
  }
}

If the protocol versions differ, Webnav.ai logs a versionMismatch warning (visible in Dashboard → AI Actions → Test Connection) but continues to operate.


Tool discovery (tools/list)

json
// request
{ "jsonrpc": "2.0", "id": "2", "method": "tools/list" }

// response
{
  "jsonrpc": "2.0",
  "id": "2",
  "result": {
    "tools": [
      {
        "name": "list_products",
        "description": "Return product catalogue.",
        "inputSchema": { "type": "object", "properties": {} },
        "annotations": { "readOnlyHint": true }
      },
      {
        "name": "create_order",
        "description": "Place a new order.",
        "inputSchema": { "type": "object", "properties": { "items": { "type": "array" } } },
        "annotations": { "destructiveHint": true, "title": "Place Order" }
      }
    ]
  }
}

Tool call (tools/call)

json
// request
{
  "jsonrpc": "2.0",
  "id": "3",
  "method": "tools/call",
  "params": {
    "name": "query_order",
    "arguments": { "order_id": "SO20260613" }
  }
}

// response — canonical shape (preferred)
{
  "jsonrpc": "2.0",
  "id": "3",
  "result": {
    "ok": true,
    "data": { "order_id": "SO20260613", "status": "paid", "amount": "199.00" },
    "display": {
      "title": "Order SO20260613",
      "fields": [{ "label": "Status", "value": "Paid" }]
    }
  }
}

// response — MCP content-block shape (also accepted)
{
  "jsonrpc": "2.0",
  "id": "3",
  "result": {
    "content": [{ "type": "text", "text": "{\"order_id\":\"SO20260613\",\"status\":\"paid\"}" }],
    "isError": false
  }
}

Both shapes are accepted. The canonical {ok, data, display} shape gives you richer widget card rendering.


Display rendering capabilities

The optional display object on a tool result controls how the widget renders a structured card. All fields are optional; mix them freely.

json
"display": {
  "title": "Order SO20260613",
  "subtitle": "Short helper line under the title.",
  "fields":  [{ "label": "Status", "value": "Paid" }],
  "items":   [{ "title": "Arrived at depot", "time": "2026-06-12 14:30" }],
  "buttons": [{ "label": "Open order", "url": "/orders/SO20260613", "style": "primary" }],
  "options": [{ "label": "AlipayPlus", "value": "alipay", "hint": "Local e-wallets" }],
  "submit":  { "label": "Generate order", "url": "/checkout?method={value}", "target": "_self" }
}
FieldTypeRenders as
titlestringCard heading
subtitlestringMuted helper line under the title
fields[{label, value}]Key–value rows
items[{title|desc, time?}]Timeline list (e.g. logistics tracks)
products[…]Horizontally scrollable product cards
buttons[{label, url, style?}]Action buttons. url opens in a new tab; a root-relative /path is prefixed with your data-base-url. style: primary (default) / secondary
options + submitsee belowA single-choice card (radio list + one submit button)

Single-choice card (options + submit)

Use this to let the visitor pick one option and act on it without typing. The widget renders options as radio rows; the submit button stays disabled until one is selected.

  • options: [{ label, value, hint? }]
  • submit: what happens on click (url takes precedence over prompt):
    • submit.url — navigate the host window to this URL. {value} / {label} are substituted from the chosen option. A root-relative /path resolves against the embedding page's origin (not data-base-url). submit.target: _self (default) or _blank.
    • submit.prompt — send this text as the visitor's next chat message (model-driven path; {value} / {label} substituted). Use when the next step should go back through the assistant.
    • submit.label — button text; if omitted the widget shows a localized default.

Example: a top-up tool returns the enabled payment methods as options and a submit.url like "/wallet?amount=5&method={value}&autopay=1". The visitor picks a method, taps the button, and the host page places the order and opens checkout — no extra chat turn or confirmation.

Authorization card (AUTH_REQUIRED)

When a user-scoped tool has no valid user token, return a business error with a display instead of failing silently:

json
{ "ok": false, "error_code": "AUTH_REQUIRED", "message": "Please sign in.",
  "display": { "title": "Connect your account",
               "subtitle": "Sign in to continue.",
               "buttons": [{ "label": "Sign in", "url": "/login", "style": "primary" }] } }

The widget renders this as an authorization card — both for read tools and when a write-tool confirmation fails — so the visitor gets a sign-in button rather than a bare error line.


Visitor identity forwarding (user token)

For user-specific tools (an account's orders, balance, API keys, usage…), your site issues a short-lived user token for the logged-in visitor and hands it to the widget:

js
window.WebnavWidget.setUserToken('YOUR_SIGNED_USER_TOKEN')

Webnav.ai does not parse or store this token. On every tools/call it forwards it opaquely to your MCP endpoint as an HTTP header — by default:

X-Webnav-User-Token: <your user token>

You can override the header name in Dashboard → AI Actions (e.g. to Authorization). Your MCP server verifies the token itself (e.g. validates a JWT signature) and resolves the real user.

Write tools / confirmation: the token is also re-sent on the confirmation call and bound by hash to the pending action, so a confirmed create_* executes with the same identity that initiated it. A missing/altered token at confirm time is rejected (TOKEN_MISMATCH) without executing.

Read tools that need a user but receive no valid token should return a business error (e.g. {ok:false, error_code:"AUTH_REQUIRED"}) — the widget renders an authorization card prompting the visitor to sign in, rather than failing silently.


Error normalization

Webnav.ai maps all MCP transport and protocol errors to a unified error_code:

Scenarioerror_code
HTTP status non-2xxMCP_HTTP_<status> (e.g. MCP_HTTP_503)
JSON-RPC error fieldMCP_RPC_ERROR
isError: true in resultMCP_TOOL_ERROR
Connection timeoutMCP_TIMEOUT
Host unreachable / refusedMCP_UNREACHABLE

These codes appear in Dashboard → AI Actions → Call Logs and in the error message the assistant relays to the visitor.

Your own business errors should use descriptive codes in the data.error_code field (e.g. ORDER_NOT_FOUND, OUT_OF_STOCK).


Dashboard configuration

  1. Go to Dashboard → AI Actions.
  2. Switch Mode to MCP.
  3. Enter your MCP Endpoint (e.g. https://mcp.yourshop.com/mcp).
  4. Optionally add Auth Headers (key/value pairs — values are stored encrypted and never returned in plaintext).
  5. Click Test Connection — Webnav.ai probes your endpoint via initialize + tools/list and shows the discovered tool list with annotations.
  6. In the Confirm tools panel, review the pre-selected write tools. Add any additional tool names that should require a confirmation card.
  7. Click Save.

Note on Test Connection: The probe is routed through the Webnav.ai backend — your MCP endpoint is never contacted directly from the browser. The same SSRF protections apply (no private/loopback addresses unless ACTIONS_ALLOW_INSECURE is set locally).


Security

RequirementWhy
Serve over HTTPSWebnav.ai pins the resolved IP and verifies the certificate.
Use a public hostWebnav.ai refuses private/loopback/metadata addresses (SSRF protection).
Validate auth headersUse the mcp_headers field to pass a bearer token or API key that your server validates on every request.
Make write tools idempotentWebnav.ai sends a single confirmed call, but network retries can happen — design create_order/create_refund to be safe to receive twice.
Do not rely on destructiveHint aloneWebnav.ai also applies name-pattern rules and mcpConfirmTools as additional safety layers.

Demo MCP servers

The repository ships two demo MCP servers. Both implement the five canonical tools with proper annotations.

The demo shop (examples/shop/shop_server.py, port 4000) exposes a /mcp endpoint that shares the same order store as the storefront. Orders placed by the AI over MCP appear instantly in the shop's "My Orders" panel — the best way to see the full visitor → widget → MCP → order flow end to end.

bash
# starts the storefront + /mcp on http://localhost:4000
WEBNAV_SECRET=webnav-mock-secret-001 ./.venv/bin/python examples/shop/shop_server.py

Optional bearer auth: set MCP_AUTH_TOKEN=<token> when starting, then add Authorization: Bearer <token> under Auth Headers in the dashboard. Unset = no auth (demo default).

Then in Dashboard → AI Actions, set mode = MCP, endpoint = http://localhost:4000/mcp, and click Test Connection.

Option B — Standalone reference server

examples/mcp_server.py (port 4100) is a minimal standalone reference with its own in-memory order store (not connected to the storefront panel). Useful for protocol-only testing.

Warning: Both are reference/demo implementations. The standalone server has no authentication or idempotency. Do not deploy them to production as-is.

bash
./.venv/bin/python examples/mcp_server.py   # starts on http://localhost:4100/mcp

Test the wire protocol directly

bash
# Handshake (replace 4000 with 4100 for the standalone server)
curl -s -X POST http://localhost:4000/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":"1","method":"initialize","params":{"protocolVersion":"2024-11-05","clientInfo":{"name":"test","version":"1"}}}'

# Discover tools
curl -s -X POST http://localhost:4000/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":"2","method":"tools/list"}'

# Call a tool
curl -s -X POST http://localhost:4000/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"list_products","arguments":{}}}'

Webnav.ai — AI 智能客服