We built Tonita to make shopping search feel natural for humans: describe what you want, refine it in conversation, search by image, and get results grounded in real apparel inventory. Now we are opening the same deep search engine to AI agents.
Tonita MCP lets LLM clients tap into Tonita's apparel search through the Model Context Protocol. Instead of relying on brittle web browsing or generic product guesses, agents can call a purpose-built shopping search stack and receive structured, inventory-grounded JSON results. This guide covers the Generic MCP server: tools, requests, responses, and JSON. Our ChatGPT app uses a separate server with interactive product card widgets.
Tonita MCP is a Model Context Protocol server that exposes Tonita's apparel search to LLM clients. This guide covers the Generic MCP server (tools + JSON). Our ChatGPT app uses a separate server with interactive product card widgets.
service: tonita-generic-mcp protocol: MCP 2025-03-26 over Streamable HTTP endpoint: https://mcp-generic.tonita.co/mcp auth: header: Authorization format: "Bearer <your Tonita API key>" example: "tnk_live_kp_abc12345_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" tools: - tonita-search - tonita-get-listing-details rate_limit: default_per_day: 100 reset: UTC midnight contact: hello+mcp@tonita.co privacy: https://tonita.co/privacy
Every POST /mcp request requires one Tonita API key. There is no separate key per environment — create one key in Settings and use it everywhere below.
Keys look like tnk_live_kp_abc12345_…. One active key per account. Default limit: 100 requests per UTC day.
Higher limits or partner access: hello+mcp@tonita.co
The fastest way to verify your key is our Python sample client. It mimics what an LLM client (like Claude) does: initialize → tools/list → tonita-search → tonita-get-listing-details.
pip install requests export MCP_API_KEY="tnk_live_kp_your_key_id_your_secret" export MCP_URL="https://mcp-generic.tonita.co" # Save the sample_client.py block below, then: python sample_client.py
#!/usr/bin/env python3
"""Minimal Tonita MCP client — get started without Claude or Cursor.
Mimics what an LLM client does on connect:
initialize → tools/list → tonita-search → tonita-get-listing-details
Usage:
pip install requests
export MCP_API_KEY='tnk_live_kp_...'
python3 sandbox/tonita/api_server/tonita_mcp/sample_client.py
Optional env vars:
MCP_URL Base URL (default: generic MCP on Cloud Run)
MCP_QUERY Override the sample search query
Requires: pip install requests
"""
from __future__ import annotations
import json
import os
import sys
import uuid
import requests
DEFAULT_MCP_URL = (
"https://mcp-generic.tonita.co"
)
def parse_sse_body(text: str) -> dict:
for line in text.splitlines():
if not line.startswith("data: "):
continue
msg = json.loads(line[len("data: ") :])
if "result" in msg or "error" in msg:
return msg
raise ValueError(f"No JSON-RPC message in SSE body:\n{text[:500]}")
def mcp_call(
base_url: str,
api_key: str,
method: str,
params: dict | None = None,
timeout: int = 120,
) -> dict:
payload: dict = {
"jsonrpc": "2.0",
"id": str(uuid.uuid4()),
"method": method,
}
if params is not None:
payload["params"] = params
resp = requests.post(
f"{base_url.rstrip('/')}/mcp",
json=payload,
headers={
"Content-Type": "application/json",
"Accept": "application/json, text/event-stream",
"Authorization": f"Bearer {api_key}",
},
timeout=timeout,
)
if resp.status_code == 401:
raise SystemExit("401 unauthorized — check MCP_API_KEY")
if resp.status_code == 429:
body = resp.json()
raise SystemExit(
"429 rate limit — "
f"used={body.get('used')} limit={body.get('limit')} "
f"retry_after_seconds={body.get('retry_after_seconds')}"
)
resp.raise_for_status()
content_type = resp.headers.get("Content-Type", "")
if "text/event-stream" in content_type:
return parse_sse_body(resp.text)
return resp.json()
def call_tool(base_url: str, api_key: str, name: str, arguments: dict) -> dict:
envelope = mcp_call(
base_url,
api_key,
"tools/call",
{"name": name, "arguments": arguments},
)
if "error" in envelope:
raise RuntimeError(f"JSON-RPC error: {envelope['error']}")
result = envelope["result"]
if result.get("isError"):
raise RuntimeError(f"Tool {name} failed: {result.get('content')}")
return result
def main() -> int:
base_url = os.environ.get("MCP_URL", DEFAULT_MCP_URL).rstrip("/")
api_key = os.environ.get("MCP_API_KEY", "").strip()
if not api_key:
print(
"Set MCP_API_KEY to your Tonita API key (tnk_live_kp_...).",
file=sys.stderr,
)
return 2
query = os.environ.get("MCP_QUERY", "white leather sneakers")
print(f"MCP URL: {base_url}/mcp\n")
print("1) initialize")
init = mcp_call(
base_url,
api_key,
"initialize",
{
"protocolVersion": "2025-03-26",
"capabilities": {},
"clientInfo": {"name": "sample-client", "version": "1.0"},
},
)
server = init["result"]["serverInfo"]
print(f" server: {server.get('name')} {server.get('version', '')}")
print("2) tools/list")
tools_msg = mcp_call(base_url, api_key, "tools/list")
tools = tools_msg["result"]["tools"]
print(f" tools: {[t['name'] for t in tools]}")
print("3) tonita-search")
search = call_tool(
base_url,
api_key,
"tonita-search",
{
"product_type": "sneakers",
"gender": "Mens",
"retrieval_queries": [query],
},
)
sc = search["structuredContent"]
cards = sc.get("listingCards") or []
print(f" title: {sc.get('title')!r}")
print(f" results: {len(cards)}")
for card in cards[:2]:
print(
f" - {card.get('listingId')}: "
f"{(card.get('title') or '')[:60]}"
)
if len(cards) > 2:
print(f" ... and {len(cards) - 2} more")
if cards:
listing_id = cards[0]["listingId"]
print("4) tonita-get-listing-details")
details = call_tool(
base_url,
api_key,
"tonita-get-listing-details",
{"listing_ids": [listing_id]},
)
entry = details["structuredContent"]["listing_details"][listing_id]
thumb = entry.get("thumbnail_image") or {}
print(f" url: {entry.get('url', '')[:80]}")
print(f" thumbnail image_id: {thumb.get('image_id')}")
print("\nDone.")
return 0
if __name__ == "__main__":
raise SystemExit(main())
Illustrative only — your listings will differ.
MCP URL: https://mcp-generic.tonita.co/mcp 1) initialize server: tonita-mcp 1.25.0 2) tools/list tools: ['tonita-search', 'tonita-get-listing-details'] 3) tonita-search title: 'white leather sneakers' results: 20 - seavees.com!360921c9ade74097: Mens - Diamond Cup - White - greats.com!34b88f1fab7ee769: The Kingston - White White ... and 18 more 4) tonita-get-listing-details url: https://seavees.com/products/mens-diamond-cup-white?variant=42288352067658 thumbnail image_id: 9143b224a1934df1 Done.
When your MCP client connects, Tonita sends server instructions that tell the model how to use our tools. Much of this text is shared with the interactive tonita.co chat, so some lines assume a shopper is browsing product carousels on our website.
| Guidance | MCP? | Notes |
|---|---|---|
| One product type per search | Yes | Required for good results |
| Literal, self-contained queries | Yes | |
| Call listing details before follow-ups | Yes | Model must track listing IDs from prior tool results — no on-screen carousel |
| "Previously shown listing" flows | Partially | Works if the model saved IDs from earlier tool calls |
| Widget / carousel hints | No | Generic MCP returns JSON; your client presents results |
For v1, treat the instructions as search discipline (how to call the tools well), not as UI behavior.
Disclaimer: Catalog contents change constantly. Examples below are illustrative — your actual titles, prices, IDs, and counts will differ.
tonita-search (first 2 listings){
"retrieval_queries": ["white leather low-top sneakers"],
"total_results": 20,
"listings": [
{
"listingId": "greats.com!548716d2c2d39029",
"title": "The Royale 2.0 - Blanco",
"currentPrice": "$118.97",
"brandName": "GREATS",
"storeName": "greats.com",
"url": "https://greats.com/products/the-royale-2-0-blanco-mens?...",
"availableSizes": ["7", "8", "8.5", "9", "10"]
},
{
"listingId": "seavees.com!360921c9ade74097",
"title": "Mens - Diamond Cup - White",
"currentPrice": "$85.00",
"brandName": "SeaVees",
"storeName": "seavees.com",
"url": "https://seavees.com/products/mens-diamond-cup-white?...",
"availableSizes": ["8", "9", "10", "11"]
}
]
}tonita-get-listing-details{
"listing_details": {
"greats.com!548716d2c2d39029": {
"url": "https://greats.com/products/the-royale-2-0-blanco-mens?...",
"title": "The Royale 2.0 - Blanco",
"one_line_description": "Minimal white leather low-top sneaker with embossed branding...",
"current_price": {"value": 118.97, "currency": "USD"},
"available_sizes": ["7", "8", "8.5", "9", "10", "11"],
"thumbnail_image": {
"image_id": "89e667d28336d70f",
"image_url": "https://cdn.shopify.com/..."
}
}
}
}The tool's text content for listing details is a short summary (Fetched details for 1 listings). The JSON above lives in structuredContent.listing_details.
| Setting | Value |
|---|---|
| Default daily cap | 100 requests / key / UTC day |
| Counter reset | UTC midnight |
| When exceeded | HTTP 429 with Retry-After |
https://mcp-generic.tonita.co/mcp
{
"mcpServers": {
"tonita": {
"url": "https://mcp-generic.tonita.co/mcp",
"headers": {
"Authorization": "Bearer tnk_live_kp_your_key_id_your_secret"
}
}
}
}export TONITA_MCP_KEY="tnk_live_kp_your_key_id_your_secret"
curl -sS -X POST \
"https://mcp-generic.tonita.co/mcp" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
# Expected: {"error": "unauthorized"}curl -sS -X POST \
"https://mcp-generic.tonita.co/mcp" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer $TONITA_MCP_KEY" \
-d '{
"jsonrpc": "2.0",
"id": "search-1",
"method": "tools/call",
"params": {
"name": "tonita-search",
"arguments": {
"product_type": "sneakers",
"gender": "Mens",
"retrieval_queries": ["white leather low-top sneakers"]
}
}
}'curl -sS -X POST \
"https://mcp-generic.tonita.co/mcp" \
-H "Content-Type: application/json" \
-H "Accept: application/json, text/event-stream" \
-H "Authorization: Bearer $TONITA_MCP_KEY" \
-d '{
"jsonrpc": "2.0",
"id": "details-1",
"method": "tools/call",
"params": {
"name": "tonita-get-listing-details",
"arguments": {
"listing_ids": ["LISTING_ID_FROM_SEARCH"]
}
}
}'