PHS Pallets MCP Gateway

Documentation

Everything you need to connect an AI assistant to the PHS Pallets catalog: endpoint details, authentication, and a complete reference for all 10 tools.

Introduction

The PHS Pallets MCP Gateway exposes live PHS production data — 100+ plastic pallet SKUs, real quotes with freight estimates, order history and shipment tracking — to any client that speaks the Model Context Protocol (ChatGPT, Claude, and any MCP-compatible agent).

PropertyValue
Endpointhttps://mcp.phsinc.com/api/mcp
TransportStreamable HTTP (stateless, JSON responses)
Wire formatJSON-RPC 2.0 over HTTP POST
Tools10 (6 read-only, 4 non-destructive writes)
Server authOptional — anonymous OAuth 2.1 available for clients that require it
Data storageNone — every call proxies to the PHS production API over HTTPS

Quick start

ChatGPT

Settings → Apps & Connectors → Create. Set Name to PHS Pallets, Server URL to the endpoint below, and Authentication to No Auth.

MCP Server URL
https://mcp.phsinc.com/api/mcp

Claude

Settings → Connectors → Add custom connector, then paste the same URL. Claude discovers the gateway's OAuth server automatically and connects in one click — no client ID or secret required.

cURL

List all tools
curl -X POST https://mcp.phsinc.com/api/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
Call a tool
curl -X POST https://mcp.phsinc.com/api/mcp \
  -H "Content-Type: application/json" \
  -H "Accept: application/json, text/event-stream" \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/call",
       "params":{"name":"popular_pallets","arguments":{"max_results":3}}}'

Authentication

The gateway has two independent layers. Neither ever handles passwords in chat.

LayerWho it's forHow it works
Server accessMCP clients (ChatGPT, Claude)Public — no credentials required. Clients that mandate OAuth (Claude) use the anonymous auto-approving OAuth 2.1 flow below. Issued Bearer tokens are accepted on every request.
Customer accountsReturning PHS customers, inside the conversationPasswordless one-time codes: request_login_code emails a 6-digit code, login_with_code exchanges it for a bearer token passed as a tool argument.

OAuth 2.1 endpoints

A spec-compliant, auto-approving authorization server for MCP clients: dynamic client registration (RFC 7591), PKCE S256 (RFC 7636), authorization-server metadata (RFC 8414) and protected-resource metadata (RFC 9728). Codes and tokens are stateless HMAC-signed values.

EndpointMethodPurpose
/.well-known/oauth-authorization-serverGETAuthorization-server discovery metadata.
/.well-known/oauth-protected-resource/api/mcpGETProtected-resource metadata for the MCP endpoint.
/api/oauth/registerPOST (JSON)Dynamic client registration — returns a public client_id (no secret).
/api/oauth/authorizeGETAuto-approves and 302-redirects to redirect_uri with ?code=…&state=….
/api/oauth/tokenPOST (form-encoded)authorization_code (PKCE S256 verified) and refresh_token grants.
Token exchange
curl -X POST https://mcp.phsinc.com/api/oauth/token \
  -H "Content-Type: application/x-www-form-urlencoded" \
  -d "grant_type=authorization_code" \
  -d "code=<code from redirect>" \
  -d "code_verifier=<PKCE verifier>" \
  -d "redirect_uri=<your callback>" \
  -d "client_id=<from /api/oauth/register>"

In-chat customer sign-in

The three-step passwordless flow assistants should follow for returning customers:

  1. Call request_login_code with the customer's account email. A single-use 6-digit code (10-minute expiry) is emailed.
  2. Ask the customer for the code from their inbox — never ask for a password.
  3. Call login_with_code to exchange it for a bearer token, then pass that token to my_orders, track_order, my_quotes and reorder.

Tools reference

Every tool publishes a typed outputSchema and MCP ToolAnnotations. Results arrive as structuredContent in the JSON-RPC response. Writes are strictly additive — nothing can be modified or deleted through this API.

search_pallets

READ-ONLY · IDEMPOTENT

Search the catalog by keyword and/or category. The gateway fetches the full product list from the PHS production API and filters in memory on name, description, pallet type and material.

Parameters

NameTypeRequiredDescription
querystringnoKeyword matched against name, description, pallet_type and material. Empty = match all.
categorystringnoCategory filter, e.g. "rackable", "nestable", "stackable", "export", "hygienic".
max_resultsintegernoMaximum results to return. Default 8.

Returns

FieldTypeDescription
resultsPalletSummary[]Matching pallets. Each includes: product_id, name, sku, pallet_type, material, length_in, width_in, height_in, weight_lb, capacity_static_lb, capacity_dynamic_lb, price_usd, quote_only, stock, availability, rackable, image, permalink.
countintegerNumber of results returned.

Example

tools/call · search_pallets
{"jsonrpc": "2.0", "id": 1, "method": "tools/call",
 "params": {"name": "search_pallets",
            "arguments": {"query": "rackable", "max_results": 3}}}

get_pallet_details

READ-ONLY · IDEMPOTENT

Full engineering spec sheet for a single SKU — dimensions, static/dynamic load, deck type, material, color, weight, price, availability and description.

Parameters

NameTypeRequiredDescription
product_idstringyesPHS product id, e.g. "phs_15735".

Returns

FieldTypeDescription
…PalletSummaryobjectAll summary fields (see search_pallets).
colorstringPallet color.
deck_typestringDeck construction, e.g. "Solid" or "Grid".
descriptionstringFull marketing/engineering description.

Example

tools/call · get_pallet_details
{"jsonrpc": "2.0", "id": 2, "method": "tools/call",
 "params": {"name": "get_pallet_details",
            "arguments": {"product_id": "phs_15735"}}}

request_quote

WRITE · NON-DESTRUCTIVE

Files a real quote request with the PHS sales team (same flow as the mobile app). PHS emails the customer a confirmation with a payment link — no payment ever happens in chat.

Parameters

NameTypeRequiredDescription
contact_namestringyesCustomer's full name.
contact_emailstringyesWhere the quote and payment link are emailed.
destination_zipstringyesUS ZIP code for the freight estimate.
itemsarrayyes[{product_id, quantity}] — one entry per pallet SKU.
companystringnoCompany name.
notesstringnoFree-form notes for the sales team.

Returns

FieldTypeDescription
quote_numberstringPHS quote reference.
statusstringQuote status, e.g. "pending".
subtotal_usd / freight_usd / total_usdnumberItemized pricing with live freight estimate.
lead_time_daysintegerEstimated production/shipping lead time.
view_urlstringBrowser link to review the quote.
messagestringHuman-readable confirmation.

Example

tools/call · request_quote
{"jsonrpc": "2.0", "id": 4, "method": "tools/call",
 "params": {"name": "request_quote",
            "arguments": {"contact_name": "Jane Doe",
                          "contact_email": "jane@acme.com",
                          "destination_zip": "75201",
                          "items": [{"product_id": "phs_15735", "quantity": 200}],
                          "company": "Acme Logistics"}}}

request_login_code

WRITE · NON-DESTRUCTIVE

Starts passwordless sign-in for a returning customer: emails a single-use 6-digit code (10-minute expiry). The response is identical whether or not the account exists, preventing enumeration. Assistants must never ask for passwords.

Parameters

NameTypeRequiredDescription
emailstringyesThe customer's PHS Pallets account email.

Returns

FieldTypeDescription
okbooleanAlways true for accepted requests.
messagestringGeneric confirmation that a code was emailed if the account exists.

Example

tools/call · request_login_code
{"jsonrpc": "2.0", "id": 5, "method": "tools/call",
 "params": {"name": "request_login_code",
            "arguments": {"email": "customer@example.com"}}}

login_with_code

WRITE · NON-DESTRUCTIVE

Completes passwordless sign-in: exchanges the emailed 6-digit code for a bearer token used by my_orders, track_order, my_quotes and reorder. Codes are single-use with attempt limits.

Parameters

NameTypeRequiredDescription
emailstringyesThe account email the code was sent to.
codestringyesThe 6-digit code from the email.

Returns

FieldTypeDescription
tokenstringBearer token — pass as the token argument to authenticated tools.
namestringThe customer's display name.

Example

tools/call · login_with_code
{"jsonrpc": "2.0", "id": 6, "method": "tools/call",
 "params": {"name": "login_with_code",
            "arguments": {"email": "customer@example.com", "code": "847291"}}}

my_orders

READ-ONLY · IDEMPOTENT

Lists the signed-in customer's orders — status, totals, dates and line items.

Parameters

NameTypeRequiredDescription
tokenstringyesBearer token from login_with_code.

Returns

FieldTypeDescription
ordersOrderSummary[]order_id, order_number, status, total_usd, created_at, items.
countintegerNumber of orders.

Example

tools/call · my_orders
{"jsonrpc": "2.0", "id": 7, "method": "tools/call",
 "params": {"name": "my_orders", "arguments": {"token": "<bearer token>"}}}

track_order

READ-ONLY · IDEMPOTENT

Live shipment tracking for one of the customer's own orders: status, carrier, tracking number, ETA and the full event timeline (confirmed → processing → shipped → in transit → out for delivery → delivered).

Parameters

NameTypeRequiredDescription
order_idstringyesThe order to track (from my_orders).
tokenstringyesBearer token from login_with_code.

Returns

FieldTypeDescription
order_id / status / carrier / tracking_number / etastringCurrent shipment state.
timelineobject[]Chronological shipment events.

Example

tools/call · track_order
{"jsonrpc": "2.0", "id": 8, "method": "tools/call",
 "params": {"name": "track_order",
            "arguments": {"order_id": "<order id>", "token": "<bearer token>"}}}

my_quotes

READ-ONLY · IDEMPOTENT

Lists the signed-in customer's quote pipeline with statuses, totals and line items.

Parameters

NameTypeRequiredDescription
tokenstringyesBearer token from login_with_code.

Returns

FieldTypeDescription
quotesQuoteSummary[]quote_id, quote_number, status, total_usd, created_at, items.
countintegerNumber of quotes.

Example

tools/call · my_quotes
{"jsonrpc": "2.0", "id": 9, "method": "tools/call",
 "params": {"name": "my_quotes", "arguments": {"token": "<bearer token>"}}}

reorder

WRITE · NON-DESTRUCTIVE

One-step repeat purchase: creates a fresh quote from a past order (or the most recent order when order_id is omitted) at the customer's previous unit pricing with a fresh freight estimate, and emails a payment link. Strictly additive — the original order is never modified.

Parameters

NameTypeRequiredDescription
tokenstringyesBearer token from login_with_code.
order_idstringnoPast order to reorder. Empty = most recent order.

Returns

FieldTypeDescription
quote_number / status / itemsmixedThe new quote and its line items.
subtotal_usd / freight_usd / total_usdnumberRe-priced totals with live freight.
lead_time_days / view_urlmixedLead time and browser link.
messagestringConfirms a payment-link email was sent — payment never happens in chat.

Example

tools/call · reorder
{"jsonrpc": "2.0", "id": 10, "method": "tools/call",
 "params": {"name": "reorder", "arguments": {"token": "<bearer token>"}}}

Protocol notes

  • Stateless: no session or initialize handshake is required before tools/list or tools/call. Every request is self-contained and horizontally scalable.
  • Headers: send Content-Type: application/json and Accept: application/json, text/event-stream.
  • No redirects: /api/mcp and /api/mcp/ are equivalent — the gateway rewrites the path internally so clients never see a 307.
  • Structured output: tool results include both human-readable content and machine-readable structuredContent validated against each tool's outputSchema.
  • Annotations: read-only tools carry readOnlyHint=true, idempotentHint=true; write tools carry readOnlyHint=false, destructiveHint=false. All tools are openWorldHint=false — the gateway only ever talks to the PHS first-party API.
  • Payments: never happen in chat. Quotes are paid via Stripe checkout through the emailed link or the PHS Pallets mobile app.

Error handling

Tool failures return a normal JSON-RPC result with isError: true and a plain-text explanation — upstream API problems are surfaced as PHS API error <status>: <detail>. Invalid or expired sign-in codes, bad tokens and PKCE failures all produce descriptive errors rather than 5xx responses.

Error result shape
{"jsonrpc": "2.0", "id": 5,
 "result": {"isError": true,
            "content": [{"type": "text",
                         "text": "PHS API error 401: Invalid or expired code"}]}}

Resources

ResourceURL
MCP endpointhttps://mcp.phsinc.com/api/mcp
ChatGPT app submission JSON (download)https://mcp.phsinc.com/api/chatgpt-app-submission.json
App icon (256×256 PNG)https://mcp.phsinc.com/icon-256.png
Plastic pallet cataloghttps://www.phsinc.com/pallet/plastic-pallets/
PHS Pallets on Google Playhttps://play.google.com/store/apps/details?id=com.emergent.pallethub.wqcb7t
PHS Pallets on the App Storehttps://apps.apple.com/us/app/phs-pallet/id6788104215
Support(800) 404-2311 · phsinc.com