> ## Documentation Index
> Fetch the complete documentation index at: https://docs.metabind.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Oak & Ivory: a sample retail MCP App

> A complete retail MCP App example covering Interactive Tools, Data Tools, and a connected shopping flow

Oak & Ivory is the sample retail MCP App that ships with Metabind. It demonstrates the patterns most product MCP Apps will share — a Data Tool that searches a catalog, an Interactive Tool that renders product details, and a layout component reused across multiple Tools. This page walks through what's in it and why each piece exists.

<video controls autoPlay muted loop playsInline className="w-full rounded-2xl" src="https://cdn.metabind.ai/IgJH0BzIn4LlfnCbcDc7/DOfvQE8ss2yWQnIIvudn/assets/3Ae5slShEZdxZfBXI0Yc/vuXJfYmuCx9jhOUt43n9__demo_draft1-1-.mp4" />

## What Oak & Ivory does

The template defines 12 tools — 8 Interactive Tools and 4 Data Tools. This page walks through two of them:

| Tool | Type | Purpose |
| - | - | - |
| `product_search` | Data Tool | Searches the catalog by keyword and returns structured product data |
| `product_detail` | Interactive Tool | Renders a single product as an interactive card, with image, title, price, and call-to-action |

Together they support flows like:

<Frame>
  <img src="https://mintcdn.com/yapstudios/ZJLavl8Q7LnCwqCq/images/diagrams/oak-and-ivory-flow.svg?fit=max&auto=format&n=ZJLavl8Q7LnCwqCq&q=85&s=a29918fe3f702801e282f6c7c6aa7f28" alt="A user asks for running shoes; the AI calls product_search, then product_detail, and the user sees an interactive product card rendered inline." noZoom width="960" height="540" data-path="images/diagrams/oak-and-ivory-flow.svg" />
</Frame>

The AI decides when to chain — `product_search` returns JSON, `product_detail` returns interactive UI.

## Get the sample

Two ways:

1. **In Metabind Studio onboarding.** When you sign up, onboarding offers the **MCP Starter Project** (the Oak & Ivory retail demo). Metabind Studio provisions the components, Types, and sample data.
2. **From a template.** In an existing organization, click **New Project → Create MCP Server**, then choose **MCP Starter Project**.

You get a fully working MCP App with no further setup. Open the project to see the components, test the tools, and connect a host.

<Frame>
  <img src="https://mintcdn.com/yapstudios/ZJLavl8Q7LnCwqCq/images/getting-started/your-first-mcp-app/onboarding-sample-picker.png?fit=max&auto=format&n=ZJLavl8Q7LnCwqCq&q=85&s=6904bfcfd74be46dc67afc7297bfb9f3" alt="The new-project sample picker in Metabind Studio with the Oak & Ivory retail template available alongside a blank starter" width="3680" height="2264" data-path="images/getting-started/your-first-mcp-app/onboarding-sample-picker.png" />
</Frame>

## What's in the project

The sample API runs on Metabind's infrastructure — no external dependencies to set up. For a production deployment, the `product_search` handler would point at your real catalog.

## How `product_search` works

The Data Tool's handler queries the catalog through the Metabind Content API and maps each result to a product:

```ts theme={null}
// Simplified from the template's ProductSearch component
const API_BASE = "https://api.metabind.ai";
const ORG_ID = "<catalog-org-id>";
const PROJ_ID = "<catalog-project-id>";

export default defineDataSource({
  metadata: {
    title: "Product Search",
    description: "Semantic product search via Metabind Content API"
  },
  properties: {
    searchTerm: { type: "string", description: "Term to search for" },
    needsImage: { type: "boolean", description: "Only return products that have a non-empty image" },
    needsVideo: { type: "boolean", description: "Only return products that have a non-empty video" },
    compact: { type: "boolean", defaultValue: true, description: "Return only { id, description } per product" }
  },
  output: {
    products: {
      type: "array",
      valueType: {
        type: "group",
        properties: { id: { type: "string" }, description: { type: "string" } }
      }
    },
    errors: { type: "array", valueType: { type: "string" } }
  },
  handler: async (props, env) => {
    const url = `${API_BASE}/api/v1/organizations/${ORG_ID}/projects/${PROJ_ID}/content/search?query=${props.searchTerm}`;
    const res = await fetch(url, { headers: { "x-api-key": env.secrets.METABIND_API_KEY } });
    const { data } = await res.json();
    // The template also paginates, applies needsImage / needsVideo,
    // and returns full product fields when compact is false.
    return {
      products: data.map((item) => ({ id: item.id, description: item.content.description })),
      errors: []
    };
  }
});
```

A real product would replace the Content API URL with their own API, keep its key in `env.secrets`, and likely transform the response shape. The patterns are what generalize: schema validation, V8 sandbox, output validation, sandbox limits.

See [Sandboxed execution](/guides/building/sandboxed-execution) for the runtime model.

## How `product_detail` works

The Interactive Tool is bound to the `ProductDetail` component. Its input schema mirrors the component's `properties`:

```ts theme={null}
properties: {
  productId: {
    type: "string",
    title: "Product ID",
    description: "Content id of the product to display. The view will call `product_lookup` to hydrate it.",
    required: true
  },
  features: {
    type: "array",
    title: "Features",
    valueType: { type: "string" },
    validation: { minItems: 0, maxItems: 8 }
  },
  rating: {
    type: "group",
    title: "Rating",
    properties: {
      value: { type: "string", title: "Rating Value" },
      count: { type: "string", title: "Review Count" },
      stars: { type: "number", title: "Star Count", validation: { min: 0, max: 5 } }
    }
  }
}
```

When the AI calls `product_detail` with a product, the platform:

1. Validates the input against the schema.
2. Compiles `ProductDetail`'s BindJS layout against the input.
3. Returns an MCP UI resource for the host to render.

The rendered view then calls the `product_lookup` tool to load the product's title, price, description, and image.

Inside your own app via the Assistant SDK, the card renders as SwiftUI on iOS and Jetpack Compose on Android. Web hosts like Claude and ChatGPT render it as React, via `@metabindai/bindjs-react` in a sandboxed iframe.

## Sub-component reuse

`ProductDetail` is composed from smaller pieces — `BrandBackground` and `EnvironmentSizeClass`. These are normal BindJS components in the project, available as building blocks for other layouts.

The template's other layouts already reuse them: `ProductCarousel` combines `BrandBackground`, `EnvironmentSizeClass`, and `ProductCard`. The sub-components carry the styling through.

## Component allowlist

The template declares its allowlists on layout slots: `ProductGroupings` allows `ProductSection`, and `ProductSection` allows `ProductCarousel`. If a new MCP App reused this package and added a `LandingHero` layout, that layout would declare its own slot allowlists.

See [Component allowlists](/guides/building/allowed-components).

## Connecting Oak & Ivory to Claude Desktop

Once the sample is in your account:

1. Open the **Server** tab in the project sidebar.
2. Copy the production URL.
3. Add it in Claude Desktop as a custom connector. See [Connect to Claude Desktop](/guides/connecting-to-mcp-hosts/claude-desktop).

In Claude Desktop, ask:

```
Show me some running shoes from Oak and Ivory.
```

Claude calls `product_search`, picks the top result, then calls `product_detail` to render it. You see an interactive product card inline in the conversation.

## Embedding Oak & Ivory in your own app

To embed the same tools in a custom app via the Assistant SDK, see [Assistant SDK overview](/guides/getting-started/embed-an-assistant). Point the SDK at the Oak & Ivory project, choose the Metabind Agent proxy or your own Anthropic key, and ship. The same `product_search` and `product_detail` calls work, and the rendered cards appear as SwiftUI on iOS, Jetpack Compose on Android, and React on the web.

## Customizing Oak & Ivory

Once Oak & Ivory is in your account, it's a real project — modify it freely:

* **Replace the sample API.** Point `product_search` at your real catalog and add an API key secret.
* **Restyle the components.** Edit `ProductCard`, change colors, change layout.
* **Add new tools.** A `cart_add`, a `wishlist_save`, a `recommendations` Data Tool. Each becomes another Type.
* **Publish.** When you're done, publish the package — it's now your own MCP App.

Starting from the sample is the recommended path. The shape is correct; the content is yours to evolve.

## Related

<CardGroup cols={2}>
  <Card title="Your first MCP App" icon="rocket" href="/guides/getting-started/your-first-mcp-app">
    The end-to-end tutorial that uses Oak & Ivory.
  </Card>

  <Card title="Build an Interactive Tool" icon="screwdriver-wrench" href="/guides/building/interactive-tools">
    The pattern `product_detail` follows.
  </Card>

  <Card title="Build a Data Tool" icon="plug" href="/guides/building/data-tools">
    The pattern `product_search` follows.
  </Card>

  <Card title="Connect to Claude Desktop" icon="desktop" href="/guides/connecting-to-mcp-hosts/claude-desktop">
    Try Oak & Ivory's tools in a real host.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.