Bayline Golf

Developer guide

Connect your own app or AI shopping assistant to the Bayline Golf catalog. Create an account first, then follow the steps below.

Bayline Golf MCP — client guide

Connect a third-party app or AI assistant to the Bayline Golf storefront tools. This guide is published at https://baylinegolf.com/developers. Need an account first? Sign up at https://baylinegolf.com/signup.

  • Endpoint: https://baylinegolf.com/mcp (available once the site is published)
  • Transport: MCP Streamable HTTP (JSON-RPC 2.0 over POST)
  • Auth: OAuth 2.1 with PKCE — every request needs a bearer token
  • Access model: each tool requires a scope granted to the signed-in account
  • Rate limits: 60 requests/minute and 1000/day per account (defaults)
  • Live data: prices, per-variant stock and sold-out status are read straight from Shopify on every call — nothing is cached. Product results include price, priceRange, inStock, availability (in_stock / partially_in_stock / sold_out), variantsInStock, variantsTotal, soldOutOptions and stockCheckedAt. get_product returns live price and availability for every size/color variant.

1. Authentication

The MCP server is a resource server. Tokens are issued by the Bayline Golf identity service:

issuer: https://oexuwaqfinegjagdynlu.supabase.co/auth/v1
audience: authenticated

Automatic (recommended)

Any MCP-compliant client (Claude, ChatGPT, Cursor, the AI SDK MCP client) discovers all of this on its own. Point it at the endpoint and it will:

  1. POST /mcp without a token → 401 plus a WWW-Authenticate header pointing at https://baylinegolf.com/.well-known/oauth-protected-resource
  2. Read that document to find the authorization server
  3. Self-register via dynamic client registration (no client ID needed up front)
  4. Send the user to the Bayline Golf sign-in + consent screen
  5. Exchange the code (PKCE) for an access token and refresh token

Claude / ChatGPT / Cursor: add a custom connector with the URL https://baylinegolf.com/mcp and complete the browser sign-in when prompted.

Manual OAuth flow

For a custom client, use the standard endpoints:

PurposeURL
Protected-resource metadatahttps://baylinegolf.com/.well-known/oauth-protected-resource
Authorization-server metadatahttps://oexuwaqfinegjagdynlu.supabase.co/auth/v1/.well-known/oauth-authorization-server
Dynamic client registrationPOST .../auth/v1/oauth/register
AuthorizeGET .../auth/v1/oauth/authorize
TokenPOST .../auth/v1/oauth/token
  1. Register your client (once), sending your redirect_uris.
  2. Redirect the user to the authorize URL with response_type=code, client_id, redirect_uri, code_challenge, code_challenge_method=S256, state. The user signs in and approves your app on the consent screen.
  3. Exchange the returned code at the token endpoint with your code_verifier to receive access_token and refresh_token.
  4. Send Authorization: Bearer <access_token> on every MCP request, and refresh before expiry.

Notes:

  • No scope parameter is used at the OAuth layer — permissions are granted per account inside Bayline Golf (see section 3).
  • A plain password-login session token from the website will not work; the server requires a token minted through the OAuth client flow.
  • Signing out everywhere or revoking agent access from /account invalidates issued tokens immediately; the next call returns an error telling the user to reconnect.

2. Talking to the server

Every call is a JSON-RPC POST with both accept types set:

POST /mcp HTTP/1.1
Host: baylinegolf.com
Authorization: Bearer <access_token>
Content-Type: application/json
Accept: application/json, text/event-stream

Omitting Accept: application/json, text/event-stream returns 406 Not Acceptable.

Initialize

{
  "jsonrpc": "2.0", "id": 1, "method": "initialize",
  "params": {
    "protocolVersion": "2025-06-18",
    "capabilities": {},
    "clientInfo": { "name": "my-app", "version": "1.0.0" }
  }
}

The response includes an Mcp-Session-Id header — echo it back on later requests. Then send the notifications/initialized notification.

List tools

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

Call a tool

{
  "jsonrpc": "2.0", "id": 3, "method": "tools/call",
  "params": { "name": "browse_collection", "arguments": { "slug": "polos" } }
}

Each result carries a content array (JSON as text) and a structuredContent object with the same data already parsed.


3. Scopes

ScopeUnlocks
catalog:browselist_collections, browse_collection
catalog:searchlist_products
catalog:detailget_product
catalog:checkoutcreate_checkout

New accounts get the default grants (catalog:browse, catalog:checkout); an admin can grant more. my_access needs no scope — call it first to see what the connected account may do. A call without the required scope fails with a message naming the missing scope.


4. Tool reference

my_access

No scope required. Returns the account's scopes and rate limits.

Arguments: none.

{
  "user_id": "…", "email": "you@example.com",
  "scopes": [{ "scope": "catalog:browse", "description": "…" }],
  "rate_limits": { "per_minute": 60, "per_day": 1000 }
}

list_collections — scope catalog:browse

Lists the storefront sections. Arguments: none.

{ "name": "list_collections", "arguments": {} }

Returns { "collections": [{ "slug": "polos", "label": "…", "title": "…", "tagline": "…", "description": "…", "url": "…" }] }.

browse_collection — scope catalog:browse

Every product in one section.

ArgumentTypeRequiredNotes
slugstringyespolos, quarter-zips, hoodies, shirts, hats
{ "name": "browse_collection", "arguments": { "slug": "hats" } }

An unknown slug returns an error listing the valid ones.

list_products — scope catalog:search

Searches the whole catalog.

ArgumentTypeRequiredNotes
searchstringnoShopify query, e.g. polo or title:hat
limitintegerno1–50, default 20
{ "name": "list_products", "arguments": { "search": "quarter zip", "limit": 5 } }

Returns product summaries including handle, title, price and description.

get_product — scope catalog:detail

Full detail for one product, including every size/color variant.

ArgumentTypeRequiredNotes
handlestringyese.g. bayline-performance-polo (from list_products)
{ "name": "get_product", "arguments": { "handle": "bayline-pullover-hoodie" } }

Returns the summary plus images and variants (title, price, availableForSale, and the selected size/color options).

create_checkout — scope catalog:checkout

Creates a real Shopify cart through the Storefront API and returns a secure Shopify checkout URL. Nothing is charged until the shopper completes checkout.

ArgumentTypeRequiredNotes
linesarrayno[{ "variantId": "gid://shopify/ProductVariant/…", "quantity": 1 }], 1–20 lines
handlestringnoShortcut: product handle; the first available variant is used
quantityintegernoQuantity for the handle shortcut, default 1

Provide either lines (preferred — use get_product to pick the exact size and color) or handle.

{
  "name": "create_checkout",
  "arguments": {
    "lines": [{ "variantId": "gid://shopify/ProductVariant/123", "quantity": 2 }]
  }
}

Returns:

{
  "checkoutUrl": "https://bayline-golf-apparel-b1ylk.myshopify.com/cart/c/…?channel=online_store",
  "totalQuantity": 2,
  "subtotal": "$150.00",
  "lines": [{ "title": "Bayline Performance Polo — S / Navy", "quantity": 2, "price": "$75.00" }]
}

Open checkoutUrl in a browser to pay. Sold-out variants and unknown handles return an error instead of a link.


5. Example clients

curl

TOKEN="<access_token>"
curl -sS https://baylinegolf.com/mcp \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/call",
       "params":{"name":"list_collections","arguments":{}}}'

TypeScript — official MCP SDK

import { Client } from "@modelcontextprotocol/sdk/client/index.js";
import { StreamableHTTPClientTransport } from "@modelcontextprotocol/sdk/client/streamableHttp.js";

const transport = new StreamableHTTPClientTransport(
  new URL("https://baylinegolf.com/mcp"),
  { requestInit: { headers: { Authorization: `Bearer ${accessToken}` } } },
);

const client = new Client({ name: "my-app", version: "1.0.0" });
await client.connect(transport);

const { tools } = await client.listTools();
const result = await client.callTool({
  name: "get_product",
  arguments: { handle: "bayline-performance-polo" },
});
console.log(result.structuredContent);
await client.close();

TypeScript — Vercel AI SDK

import { createMCPClient } from "@ai-sdk/mcp";
import { streamText } from "ai";

const mcp = await createMCPClient({
  transport: {
    type: "http",
    url: "https://baylinegolf.com/mcp",
    headers: { Authorization: `Bearer ${accessToken}` },
    redirect: "error",
  },
});

try {
  const result = streamText({
    model: "openai/gpt-5-mini",
    tools: await mcp.tools(),
    prompt: "Which Bayline polos are in stock in sage?",
  });
  for await (const chunk of result.textStream) process.stdout.write(chunk);
} finally {
  await mcp.close();
}

Python

import requests

resp = requests.post(
    "https://baylinegolf.com/mcp",
    headers={
        "Authorization": f"Bearer {access_token}",
        "Content-Type": "application/json",
        "Accept": "application/json, text/event-stream",
    },
    json={
        "jsonrpc": "2.0", "id": 1, "method": "tools/call",
        "params": {"name": "list_products", "arguments": {"search": "polo"}},
    },
)
print(resp.json())

6. Errors

What you seeMeaningFix
401 UnauthorizedMissing, expired, or non-OAuth tokenRefresh the token or run the OAuth flow again
406 Not AcceptableMissing Accept headerSend application/json, text/event-stream
Tool error: missing scopeAccount lacks the scopeAsk an admin to grant it; check with my_access
Tool error: access revokedUser revoked agent access or signed out everywhereReconnect and sign in again
Tool error: rate limit exceededOver 60/min or 1000/dayBack off and retry; caps are visible on /account

Every call — success, denial, or error — is recorded in the account's activity log at /account with the tool name, time, outcome and duration.