Updated documentation is now at okcapsule.com/mcp
View docs
Showcase · Built with LovableOne 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.
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.
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.
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.
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.
/**
* 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
/**
* 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
/**
* 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
/**
* 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.
/**
* 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 — 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.
# 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.