Free shipping on orders over $85AI-personalized in 60 seconds80% less plastic than bottlesFree shipping on orders over $85Doctor-formulated, third-party testedCancel or pause anytimeFree shipping on orders over $85AI-personalized in 60 seconds80% less plastic than bottlesFree shipping on orders over $85Doctor-formulated, third-party testedCancel or pause anytime
PerfectPackspowered byOK Capsule
Build my pack

Updated documentation is now at okcapsule.com/mcp

View docs
OK CapsuleShowcase · Built with Lovable

One public MCP.
A full supplement storefront.

This entire Perfect Packs demo — concierge AI, lab parsing, pack builder, Shopify checkout, customer memory — was built on Lovable using nothing but OK Capsule's public MCP server. Here's everything it does, and why MCP + Lovable made it fast.

MCP tools used

3

Auth required

None

Hand-written API glue

~150 lines

Routes built

10+

What this storefront ships

Every capability below is live in this demo and powered by the same public MCP endpoint. Click into the AI Pack Builder on the home page to try most of them right now.

AI supplement concierge

A chat-first pack builder that asks one or two questions, then proposes a doctor-curated 4–8 pill pack. The model is constrained to OK Capsule's live catalog — it can never recommend a SKU that won't check out.

src/components/Chatbot.tsx · src/lib/chat.functions.ts

Lab-result parsing

Drop a PDF or photo of a blood panel. A vision model extracts biomarkers (Vit D, ferritin, B12…), tags them low/high, and feeds them into the recommendation as evidence.

src/lib/biomarkers.functions.ts

Dosing & allergen detail

Every recommended supplement opens a detail panel with serving size, AM/PM timing, capsule form, contains-flags (fish, soy, dairy) and free-from claims — inferred from the MCP catalog.

src/components/SupplementDetailDialog.tsx

Manual Pack Builder

Browse the full catalog, mix AM + PM servings, see live pricing with the OK Capsule volume discount and the subscribe-and-save 10% applied automatically.

src/routes/pack-builder.tsx

Returning-customer memory

Signed-in shoppers see their last pack remembered. The concierge opens with a warm "welcome back" line and uses the prior order as the anchor for the new recommendation.

src/lib/shopify-customer-browser.ts

Real Shopify checkout

Recommendations mint a real Shopify Storefront cart — same line-item attributes, same discount codes, same checkout URL as a hand-built pack. One source of truth for both flows.

src/lib/shopify-cart.ts

Public Developer Kit

A /developers page ships a zero-dependency TypeScript MCP client, three runnable example scripts, and a copy-paste Lovable prompt so anyone can rebuild this site in minutes.

mcp-demo-kit/ · src/routes/developers.tsx

Curated health-goal packs

Sleep, Energy, Stress, PCOS, Liver Detox and more. The concierge maps a user's goal to a full curated pack and pulls the matching MCP product IDs automatically.

src/lib/packs-catalog.ts

Aggregate Supplement Facts Panel

Every personalized pack renders a draft FDA-style Supplement Facts label — ingredients merged, per-serving amounts summed, %DV calculated against the 2020 FDA table. Built entirely from the MCP's per-product strength field.

src/lib/sfp.ts · src/components/SupplementFactsPanel.tsx

Why MCP + Lovable

The benefits stack: a model-native protocol on OK Capsule's side, a prompt-native builder on yours.

No SDK, no auth dance

The MCP server is public. A single fetch + JSON-RPC handshake gets you the entire OK Capsule catalog, product intelligence, and a pre-filled checkout URL.

Lovable wires it for you

Paste the demo-kit prompt and Lovable generates the server function, the chat UI, the cart bridge and the routes — typed end-to-end against the same TanStack Start stack used here.

Single source of truth

Catalog, dosing, pricing and checkout all flow from the MCP. The site never duplicates SKU data — when OK Capsule adds a product, it appears in every surface here automatically.

Built for AI

MCP is the protocol LLMs already speak. A model can call okc_get_catalog and okc_pack_builder_url the same way it calls any tool — no glue code, no bespoke schemas.

The build, end to end

The exact workflow used to ship this site. Repeatable for any OK Capsule partner.

  1. 01

    Drop the MCP into Lovable

    Open the /developers page, copy the bootstrap prompt, paste it into a fresh Lovable project. Lovable scaffolds the server function that speaks JSON-RPC + SSE to storefront.okcapsule.app.

  2. 02

    Generate the storefront

    Ask Lovable for a pack-builder, a chat concierge, or a full landing page. It pulls real product names, images and prices from the MCP — no mock data.

  3. 03

    Iterate in plain English

    "Add allergen flags." "Show a subscribe-and-save toggle." "Remember the last pack for signed-in customers." Each request becomes a typed change against the same MCP-backed source of truth.

  4. 04

    Ship a real checkout

    The MCP's pack_builder_url tool returns a live OK Capsule checkout the moment the user clicks buy. No payment integration, no fulfillment glue.

Build your own client

A zero-dependency TypeScript MCP client and four runnable examples. Pure fetch — runs under Bun, Deno, or Node 22+. Copy the files straight from this page.

The MCP client

Handles the JSON-RPC handshake, session ID caching, and SSE frame parsing.

mcp-client.ts
/**
 * Minimal MCP client for OK Capsule's public storefront MCP.
 *
 * Endpoint:  https://storefront.okcapsule.app/mcp/perfect-packs
 * Auth:      none — public, read-only-ish (pack-builder URL generation
 *            mutates nothing on OK Capsule's side until checkout).
 *
 * Speaks MCP Streamable HTTP (POST + JSON-RPC, SSE-framed responses).
 * Zero dependencies — runs under bun, deno, or node >= 20.
 */

const MCP_URL = "https://storefront.okcapsule.app/mcp/perfect-packs";

let sessionId: string | null = null;
let requestId = 0;
let initPromise: Promise<void> | null = null;

async function rpc(method: string, params?: unknown, isNotification = false) {
  const headers: Record<string, string> = {
    "Content-Type": "application/json",
    // REQUIRED by the MCP Streamable HTTP spec — servers reject calls
    // missing either media type with HTTP 406.
    Accept: "application/json, text/event-stream",
  };
  if (sessionId) headers["mcp-session-id"] = sessionId;

  const body: Record<string, unknown> = { jsonrpc: "2.0", method };
  if (!isNotification) body.id = ++requestId;
  if (params !== undefined) body.params = params;

  const res = await fetch(MCP_URL, {
    method: "POST",
    headers,
    body: JSON.stringify(body),
  });

  // The server assigns a session ID on the first response; reuse it.
  const sid = res.headers.get("mcp-session-id");
  if (sid && !sessionId) sessionId = sid;

  if (isNotification) return null;
  if (!res.ok) {
    throw new Error(`MCP ${method} failed (${res.status}): ${(await res.text()).slice(0, 300)}`);
  }

  // Responses come back as either a single JSON object or as an SSE
  // stream with one "data: {...}" frame. Handle both.
  const text = await res.text();
  let payload: { result?: unknown; error?: { message: string } } | null = null;
  for (const line of text.split("\n")) {
    if (line.startsWith("data:")) {
      payload = JSON.parse(line.slice(5).trim());
      break;
    }
  }
  if (!payload) payload = JSON.parse(text);
  if (payload.error) throw new Error(`MCP ${method} error: ${payload.error.message}`);
  return payload.result;
}

async function ensureInit() {
  if (initPromise) return initPromise;
  initPromise = (async () => {
    await rpc("initialize", {
      protocolVersion: "2025-06-18",
      capabilities: {},
      clientInfo: { name: "okc-mcp-demo-kit", version: "1.0.0" },
    });
    // The "initialized" notification has no id and expects no response.
    await rpc("notifications/initialized", undefined, true);
  })();
  return initPromise;
}

/** List every tool the MCP server exposes. */
export async function listTools() {
  await ensureInit();
  return rpc("tools/list");
}

/**
 * Invoke a tool by name. Tool results come back as `content: [{type:"text",text}]`;
 * the text is usually a JSON string, so we try to parse it for you.
 */
export async function callTool(name: string, args: Record<string, unknown> = {}) {
  await ensureInit();
  const result = (await rpc("tools/call", { name, arguments: args })) as {
    content?: Array<{ text?: string }>;
  };
  const text = result?.content?.[0]?.text;
  if (!text) return result;
  try {
    return JSON.parse(text);
  } catch {
    return text;
  }
}

Example: list the catalog

examples/list-catalog.ts
/**
 * List the full OK Capsule supplement catalog.
 *
 * Run:  bun run examples/list-catalog.ts
 *       (or)  node --experimental-strip-types examples/list-catalog.ts
 */
import { callTool } from "../mcp-client.ts";

const catalog = await callTool("okc_get_catalog");

const products: Array<{ id: string; product_name?: string; serving_size?: string }> =
  Array.isArray(catalog) ? catalog : (catalog?.products ?? []);

console.log(`Found ${products.length} products`);
for (const p of products.slice(0, 25)) {
  console.log(`  ${p.id.padEnd(8)}  ${p.product_name ?? "—"}`);
}
if (products.length > 25) console.log(`  …and ${products.length - 25} more`);

Example: product detail

examples/product-detail.ts
/**
 * Fetch deep "product intelligence" for a single supplement:
 * benefits, mechanism of action, suggested stacks, contraindications, etc.
 *
 * Run:  bun run examples/product-detail.ts <product_id>
 */
import { callTool } from "../mcp-client.ts";

const productId = process.argv[2];
if (!productId) {
  console.error("Usage: product-detail.ts <product_id>");
  console.error("Tip: run list-catalog.ts first to find IDs.");
  process.exit(1);
}

const detail = await callTool("okc_get_product_intelligence", { product_id: productId });
console.log(JSON.stringify(detail, null, 2));

Example: build a pack

examples/build-pack.ts
/**
 * Build a personalized 28-day pack and get a pre-filled checkout URL.
 *
 * Run:  bun run examples/build-pack.ts
 */
import { callTool } from "../mcp-client.ts";

const result = await callTool("okc_pack_builder_url", {
  pack_name: "Demo Pack",
  items: [
    { product_id: "MAG-001", serving_size: 1, toa: "pm", cycle: "daily", duration: 28 },
    { product_id: "OMEGA-3", serving_size: 1, toa: "am", cycle: "daily", duration: 28 },
    { product_id: "VIT-D",   serving_size: 1, toa: "am", cycle: "daily", duration: 28 },
  ],
});

const url =
  (typeof result === "string" && result) ||
  (result as { url?: string; checkout_url?: string; pack_builder_url?: string })?.url ||
  (result as { checkout_url?: string })?.checkout_url ||
  (result as { pack_builder_url?: string })?.pack_builder_url;

console.log("Pack checkout URL:");
console.log(url ?? result);

Example: aggregate Supplement Facts Panel

Render a draft FDA-style panel for a personalized pack from catalog data alone — the same logic that powers the SFP preview in the AI Pack Builder.

examples/aggregate-sfp.ts
/**
 * Build an aggregate Supplement Facts Panel for a personalized pack using
 * only the OK Capsule MCP catalog. Demonstrates that the per-product
 * `strength` and `serving_size` fields are enough to render a draft FDA-style
 * panel client-side — the final regulatory label is produced at fulfillment.
 *
 * Run:  bun run examples/aggregate-sfp.ts
 */
import { callTool } from "../mcp-client.ts";

type CatalogProduct = {
  id: string;
  product_name?: string;
  description?: string;
  serving_size?: number | string;
  pill_type?: string;
};

const STRENGTH_RE = /(\d+(?:[.,]\d+)?)\s*(mg|mcg|g|iu)\b/i;
const CFU_RE = /(\d+(?:[.,]\d+)?)\s*billion\s*cfu/i;

function extractStrength(desc = ""): string | undefined {
  const cfu = desc.match(CFU_RE);
  if (cfu) return `${cfu[1]} billion CFU`;
  const m = desc.match(STRENGTH_RE);
  if (!m) return undefined;
  const unit = m[2].toLowerCase() === "iu" ? "IU" : m[2].toLowerCase();
  return `${m[1]}${unit === "IU" ? " " : ""}${unit}`;
}

const catalog = (await callTool("okc_get_catalog")) as
  | CatalogProduct[]
  | { products?: CatalogProduct[] };
const products: CatalogProduct[] = Array.isArray(catalog) ? catalog : (catalog?.products ?? []);

// Demo pack: pick the first 4 products with a parseable strength.
const pack = products
  .map((p) => ({ ...p, strength: extractStrength(p.description) }))
  .filter((p) => p.strength)
  .slice(0, 4);

console.log("Draft Supplement Facts");
console.log("Serving size: " + pack.length + " capsules daily");
console.log("Servings per container: 28");
console.log("---------------------------------------------");
console.log("Ingredient                Amount/serving");
console.log("---------------------------------------------");
for (const p of pack) {
  const name = (p.product_name ?? "").padEnd(26);
  console.log(`${name}${p.strength}`);
}
console.log("---------------------------------------------");
console.log("† Daily Value not established for some ingredients.");
console.log("Final regulatory label is generated at fulfillment by OK Capsule.");

Build a whole site in Lovable

Paste this prompt into a fresh Lovable project. It briefs the agent on the MCP endpoint, the tools, and the pages to build.

lovable-prompt.md
# Lovable prompt — build your own MCP-powered supplement site

Paste this into a fresh [Lovable](https://lovable.dev) project to bootstrap a
Perfect Packs–style demo on top of OK Capsule's public MCP. No API keys
needed — the MCP endpoint is open.

---

Build a marketing + commerce site for personalized daily supplement packs.
The product catalog and pack-checkout URL come from OK Capsule's public
MCP server at `https://storefront.okcapsule.app/mcp/perfect-packs`
(JSON-RPC over HTTP, SSE-framed responses, no auth).

**Tools the MCP exposes that you should wire up:**

- `okc_get_catalog` — returns every supplement in the brand. Use it to
  power a `/shop` browsing page and supplement detail dialogs.
- `okc_get_product_intelligence` (args: `{ product_id }`) — returns deep
  info on one supplement: benefits, dosage notes, suggested stacks.
- `okc_pack_builder_url` (args: `{ pack_name, items: [{ product_id,
  serving_size, toa: "am" | "pm", cycle: "daily", duration: 28 }] }`) —
  returns a pre-filled OK Capsule checkout URL for the selected pack.

**Required pages:**

1. `/` — hero with an AI concierge chat widget that asks 3–4 questions
   (goals, lifestyle, restrictions), then proposes a pack. The chat
   should call `okc_get_catalog` for grounding and `okc_pack_builder_url`
   to mint the final checkout link.
2. `/pack-builder` — manual UI: full catalog grid, add/remove items,
   AM/PM toggle, live count, "Get my pack" → `okc_pack_builder_url`.
3. `/shop` — browse-only catalog with category filters.
4. `/how-it-works`, `/about`, `/faq`, `/disclaimer` — static content.

**Implementation rules:**

- Put MCP calls in a server-only module (`src/lib/okcapsule.server.ts`).
  Never call the MCP directly from the browser — keep the session ID and
  SSE parsing on the server.
- Use the JSON-RPC handshake: `initialize` → `notifications/initialized`
  → `tools/call`. Cache the session ID returned in the `mcp-session-id`
  response header and reuse it for follow-up calls.
- Every outbound POST needs
  `Accept: application/json, text/event-stream` — the MCP server returns
  HTTP 406 without it.
- For the AI concierge, use Lovable AI with tool-calling. Expose the
  three MCP tools to the model and let it orchestrate.
- Treat the OK Capsule catalog as the source of truth; don't hardcode
  product lists.

**Tone:** clean, doctor-formulated, slightly editorial. Avoid medical
claims — describe ingredients with lifestyle/traditional-use language.

The reference implementation lives at
<https://okcapsule.ai> — feel free to study its
structure and copy patterns.

Full reference

The kit's README — protocol details, the catalog-vs-storefront gotcha, and an AI-agent wiring sketch.

README.md
# OK Capsule MCP — Demo Kit

A drop-in kit for building apps on top of [OK Capsule](https://okcapsule.com)'s
public storefront MCP server. Everything in this folder is what powers the
Perfect Packs reference site at <https://okcapsule.ai>.

- **Endpoint:** `https://storefront.okcapsule.app/mcp/perfect-packs`
- **Auth:** none — the endpoint is public.
- **Protocol:** MCP Streamable HTTP (JSON-RPC 2.0, SSE-framed responses).

## What's in here

| File | What it does |
|---|---|
| `mcp-client.ts` | Zero-dependency TypeScript MCP client. Handles session, SSE parsing, `tools/list`, `tools/call`. |
| `examples/list-catalog.ts` | Prints the full supplement catalog. |
| `examples/product-detail.ts` | Fetches deep info on one supplement. |
| `examples/build-pack.ts` | Builds a 28-day pack and prints a pre-filled OK Capsule checkout URL. |
| `examples/aggregate-sfp.ts` | Renders a draft FDA-style Supplement Facts Panel for a personalized pack from catalog data alone. |
| `lovable-prompt.md` | Paste this into [Lovable](https://lovable.dev) to bootstrap your own Perfect Packs–style site. |

## Try it in 60 seconds

Copy `mcp-client.ts` and any `examples/*.ts` file from this folder into a
local directory, then run:

```bash
bun run list-catalog.ts
```

(Or `node --experimental-strip-types list-catalog.ts` on Node 22+.
No `npm install` required — it's pure `fetch`.)

## The three tools used in the demo

- **`okc_get_catalog`** — every product in the brand. Returns an array of
  `{ id, product_name, serving_size, description, ... }`.
- **`okc_get_product_intelligence`** — `{ product_id }` → benefits,
  mechanism, suggested stacks, dosage guidance.
- **`okc_pack_builder_url`** — `{ pack_name, items: [{ product_id,
  serving_size, toa: "am" | "pm", cycle: "daily", duration: 28 }] }` →
  a pre-filled checkout URL on the OK Capsule storefront.

To see every tool the server actually exposes, call `tools/list`:

```ts
import { listTools } from "./mcp-client.ts";
console.log(await listTools());
```

## The JSON-RPC handshake

Every MCP client opens with the same dance, once per session:

```
POST  https://storefront.okcapsule.app/mcp/perfect-packs
      Content-Type: application/json
      Accept: application/json, text/event-stream
      { "jsonrpc":"2.0", "id":1, "method":"initialize",
        "params":{ "protocolVersion":"2025-06-18", "capabilities":{},
                   "clientInfo":{ "name":"my-app", "version":"1.0.0" } } }

← response includes `mcp-session-id: <uuid>` header
  → cache it and send it on every follow-up request

POST  …same endpoint
      mcp-session-id: <uuid>
      { "jsonrpc":"2.0", "method":"notifications/initialized" }
      (notification — no `id`, no response expected)

POST  …same endpoint
      { "jsonrpc":"2.0", "id":2, "method":"tools/call",
        "params":{ "name":"okc_get_catalog", "arguments":{} } }
```

Two things to get right:

1. **`Accept: application/json, text/event-stream`** — both media types,
   in one header. The server returns HTTP 406 without it.
2. **Responses are SSE-framed.** A reply usually comes back as
   `data: {...json...}\n\n`. Strip the `data: ` prefix before parsing.
   `mcp-client.ts` handles this — see the `rpc()` function.

## Gotcha: catalog vs. storefront-active subset

`okc_get_catalog` returns the full ingredient roster the brand has
*formulated*, but a given storefront only *sells* a curated subset of
those products. If you call `okc_pack_builder_url` with a product that's
in the catalog but not on the storefront, checkout will reject it with
"Product from url not found on this page".

For the Perfect Packs site we cross-check against the storefront's
widget endpoint and only ever recommend products that are actually
purchasable:

```
GET https://na1-prod.okcapsule.app/v2/pack-builders/<pack_builder_id>/widget/products
```

If you're building your own brand on OK Capsule, your storefront widget
URL is the source of truth for what's buyable today.

## Wiring it into an AI agent

The cleanest pattern: expose the MCP tools to your LLM via tool-calling,
and let the model orchestrate. A working system-prompt sketch:

```
You are a supplement concierge. You have these tools:

- okc_get_catalog(): list every available supplement.
- okc_get_product_intelligence(product_id): get deep info on one item.
- okc_pack_builder_url(pack_name, items[]): mint a checkout URL.

Ask the user about their goals, lifestyle, and any restrictions.
Propose a 4–6 item pack from the catalog. When they confirm,
call okc_pack_builder_url and return the link.

Use lifestyle/traditional-use language. Never make medical claims.
```

The reference implementation in `src/lib/okcapsule.server.ts` and
`src/lib/chat.functions.ts` of the demo site shows the full
TanStack Start + Lovable AI wiring.

## Reference site

<https://okcapsule.ai> — built end-to-end on this MCP.
Source: this repo.

---

© OK Capsule. The MCP server and supplement catalog are property of
OK Capsule. This demo kit is provided as a developer reference.

Reference implementation

This whole site is built on the MCP. Browse the surfaces that wire the tools into real product UI:

Build your own on top of OK Capsule

Grab the developer kit — a typed MCP client, runnable scripts, and a Lovable prompt that bootstraps a full storefront like this one. No API keys. No fulfillment glue.