Guides

MCP / AI Agent

Connect AI agents via the Streamable-HTTP MCP server and OAuth 2.0.

Use this guide to connect Cursor, Claude Desktop, or mcp-inspector to any Sendy MCP endpoint.

Available endpoints

URLTenant context
https://<host>/api/v1/store/mcpStore customer assistant
https://<host>/api/v1/admin/mcpPlatform admin
https://<host>/api/v1/delivery-company/mcpDelivery company

Transport: Streamable HTTP (MapMcp).

Authentication

MCP endpoints require OAuth 2.0 Bearer tokens — not API keys.

The flow is PKCE authorization code (code_challenge_method=S256). Steps:

  1. Start authorization

    GET /api/v1/oauth/authorize
      ?response_type=code
      &client_id=<registered_client_id>
      &redirect_uri=<registered_redirect_uri>
      &code_challenge=<S256_hash_of_verifier>
      &code_challenge_method=S256
      &state=<random>
      &scope=catalog:read orders:read orders:write
    

    If the user is not logged in, the server redirects to your login UI with ?request_id=<id>.

  2. User logs in (if not already authenticated)

    POST /api/v1/oauth/login
    { "requestId": "<id>", "phone": "...", "password": "..." }
    

    Accept: application/json to receive { "data": { "redirectUrl": "..." } } instead of a 302.

  3. Exchange code for token (form-encoded)

    POST /api/v1/oauth/token
    grant_type=authorization_code
    &code=<code>
    &code_verifier=<plain_verifier>
    &client_id=<client_id>
    &redirect_uri=<redirect_uri>
    

    Response:

    {
      "access_token": "...",
      "token_type": "Bearer",
      "expires_in": 3600,
      "refresh_token": "...",
      "scope": "catalog:read orders:read orders:write"
    }
    
  4. Call MCP endpoint

    Authorization: Bearer <access_token>
    

Required scopes per tool

ToolRequired scope
get_store_infocatalog:read
list_categoriescatalog:read
list_provincescatalog:read
list_areascatalog:read
list_productscatalog:read
search_productscatalog:read
get_productcatalog:read
send_order_otporders:write
create_storefront_orderorders:write
track_orderorders:read

Exposed tools

  • get_store_info — get store name, description, contact details (email, WhatsApp, social links), policies, and FAQs
  • list_categories — list the store's product categories for guided browsing
  • list_provinces — list provinces the store delivers to, with their codes
  • list_areas — list delivery areas/districts within a province (param: provinceCode); returns the area ID needed to place an order
  • list_products — list all active products in the store catalog
  • search_products — full-text search by SKU / name / description (max 50 results)
  • get_product — get one product by UUID
  • send_order_otp — send a verification code to the customer's phone via SMS/WhatsApp (param: phone); call before create_storefront_order
  • create_storefront_order — place an order the same way the storefront website does; requires an OTP code (customer name, phone, address, province code, area ID, items)
  • track_order — fetch order detail and status history by public ID (e.g. ORD-20260506-ABC123)

Ordering flow

To place an order the way a customer does on the website:

  1. list_provinces → customer picks a province (note its code)
  2. list_areas with that provinceCode → customer picks an area (note its id)
  3. send_order_otp with the customer's phone → a 6-digit code is sent to them
  4. create_storefront_order with the customer details, addressProvinceCode, addressAreaId, items (SKU + quantity), and the otpCode the customer received

Info endpoint

Each area exposes an info endpoint that returns the MCP server URL, tool names, and required scopes:

GET /api/v1/store/mcp/integration          # requires store.oauth_mcp.redirect_uris.view or store_owner
GET /api/v1/admin/mcp/integration          # requires admin.oauth_mcp.clients.view
GET /api/v1/delivery-company/mcp/integration  # requires dc.tickets.view

Cursor / Claude Desktop config example

{
  "mcpServers": {
    "sendy-store": {
      "url": "https://<host>/api/v1/store/mcp",
      "headers": {
        "Authorization": "Bearer <oauth_access_token>"
      }
    }
  }
}

Registering redirect URIs

Before the OAuth flow can complete, the redirect_uri must be registered. Stores register their own:

  • POST /api/v1/store/oauth-mcp/redirect-uris { "clientId": "...", "redirectUri": "..." } — requires store.oauth_mcp.redirect_uris.manage
  • Admin can manage all clients at /api/v1/admin/oauth-mcp/clients and /api/v1/admin/oauth-mcp/redirect-uris

Config keys required on server

OAuthMcp:SigningKey       # 32+ byte base-64 — required
OAuthMcp:Issuer
OAuthMcp:Audience
OAuthMcp:AuthorizationCodeLifetimeSeconds   (default 60)
OAuthMcp:AccessTokenLifetimeSeconds         (default 3600)
OAuthMcp:RefreshTokenLifetimeSeconds        (default 86400)

Notes

  • API keys do not work on MCP endpoints — only OAuth bearer tokens.
  • Tool responses use the same ApiResponse<T> envelope as REST controllers.
  • The same tool surface is exposed on all three MCP URLs; context is resolved from JWT claims.

Source: DOCS/03-integrations/store-mcp-server.md