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,soldOutOptionsandstockCheckedAt.get_productreturns 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:
POST /mcpwithout a token →401plus aWWW-Authenticateheader pointing athttps://baylinegolf.com/.well-known/oauth-protected-resource- Read that document to find the authorization server
- Self-register via dynamic client registration (no client ID needed up front)
- Send the user to the Bayline Golf sign-in + consent screen
- 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:
| Purpose | URL |
|---|---|
| Protected-resource metadata | https://baylinegolf.com/.well-known/oauth-protected-resource |
| Authorization-server metadata | https://oexuwaqfinegjagdynlu.supabase.co/auth/v1/.well-known/oauth-authorization-server |
| Dynamic client registration | POST .../auth/v1/oauth/register |
| Authorize | GET .../auth/v1/oauth/authorize |
| Token | POST .../auth/v1/oauth/token |
- Register your client (once), sending your
redirect_uris. - 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. - Exchange the returned
codeat the token endpoint with yourcode_verifierto receiveaccess_tokenandrefresh_token. - 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
| Scope | Unlocks |
|---|---|
catalog:browse | list_collections, browse_collection |
catalog:search | list_products |
catalog:detail | get_product |
catalog:checkout | create_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.
| Argument | Type | Required | Notes |
|---|---|---|---|
slug | string | yes | polos, 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.
| Argument | Type | Required | Notes |
|---|---|---|---|
search | string | no | Shopify query, e.g. polo or title:hat |
limit | integer | no | 1–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.
| Argument | Type | Required | Notes |
|---|---|---|---|
handle | string | yes | e.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.
| Argument | Type | Required | Notes |
|---|---|---|---|
lines | array | no | [{ "variantId": "gid://shopify/ProductVariant/…", "quantity": 1 }], 1–20 lines |
handle | string | no | Shortcut: product handle; the first available variant is used |
quantity | integer | no | Quantity 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 see | Meaning | Fix |
|---|---|---|
401 Unauthorized | Missing, expired, or non-OAuth token | Refresh the token or run the OAuth flow again |
406 Not Acceptable | Missing Accept header | Send application/json, text/event-stream |
| Tool error: missing scope | Account lacks the scope | Ask an admin to grant it; check with my_access |
| Tool error: access revoked | User revoked agent access or signed out everywhere | Reconnect and sign in again |
| Tool error: rate limit exceeded | Over 60/min or 1000/day | Back 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.
