Connect an AI assistant directly to your Product Maestro data.
The Model Context Protocol (MCP) is an open standard that lets an AI assistant call tools on a server instead of guessing from text: you connect the assistant once, it discovers which tools exist, and then it can answer "how many claims are still pending?" by actually querying your store rather than inventing a number. Claude Desktop, Claude Code and a growing list of other clients speak it.
Product Maestro exposes the same data as this API over MCP at https://api.sappo.xco.agency/product-maestro/mcp,
authenticated with the same mak_ token in the same
Authorization header. No separate signup, no second credential.
The tool list, the exact client configuration to paste, and the errors to
expect are on the MCP guide, with a
machine-readable copy at
https://api.sappo.xco.agency/product-maestro/docs/mcp.json.
| Endpoint | https://api.sappo.xco.agency/product-maestro/mcp |
|---|---|
| Transport | Streamable HTTP (POST) |
| Header | Authorization: Bearer mak_… |
| Token | Open the app in your Shopify admin, go to Settings → API Access, and create a token. The full token is shown once, at creation time; afterwards only its first 12 characters are stored in readable form. If that card is locked, API access is not included in your current plan and the app will not create a token until the plan changes. At most three tokens can be active at once, so revoke one you no longer use before creating another. |
| Protocol versions | 2025-11-25, 2025-06-18, 2025-03-26, 2024-11-05 |
Add this to the client's MCP configuration file — .mcp.json for
Claude Code — replacing the placeholder with the token you created in the app:
{
"mcpServers": {
"sappo-product-maestro": {
"type": "http",
"url": "https://api.sappo.xco.agency/product-maestro/mcp",
"headers": {
"Authorization": "Bearer mak_your_token_here"
}
}
}
}
claude mcp add --transport http sappo-product-maestro https://api.sappo.xco.agency/product-maestro/mcp --header "Authorization: Bearer mak_your_token_here"
Claude Desktop adds a remote server under Settings → Connectors → Add custom connector rather than from a configuration file, and it authenticates through the server's own flow instead of a header you type. This endpoint expects the Bearer token above, so use a client that lets you set the header: Claude Code, Cursor, or any client with a configuration file.
Point it at the endpoint above as a streamable-HTTP MCP server and have it send
the Authorization header on every request. The store is
identified from the token, so there is nothing else to configure.
The server sends these instructions to the client when it connects, so the assistant knows what it can do before it calls anything:
Product Maestro MCP server for accessing product schedule data from Shopify stores.
The store is identified automatically from your API token.
Available tools:
- `list_schedules`: List product schedules with optional status/archived filters and pagination
- `get_schedule`: Get a specific schedule by ID with its actions
- `list_actions`: List schedule actions with optional status/type filters and pagination
- `create_draft_schedule`: Create a draft schedule from structured actions (needs the schedules:write scope). Drafts never run on their own
- `execute_schedule`: Always refuses. Schedules are never run over the API
Drafting and running are deliberately separate. A draft runs only after the merchant reviews it in Product Maestro and clicks Save, so tell the merchant to do that rather than looking for a way to run it.
5 tools, read from the server itself. Every tool reads; none of them changes your data.
List product schedules for a Shopify store. Returns paginated schedules with their associated actions.
| Argument | Type | Description |
|---|---|---|
page
|
integer |
Page number (default: 1) |
page_size
|
integer |
Items per page, max 100 (default: 25) |
status
|
string |
Filter by status |
archived
|
boolean |
Filter by archived state |
Get a single product schedule by ID for a Shopify store, including its actions.
| Argument | Type | Description |
|---|---|---|
id
required |
integer |
Schedule ID |
List product schedule actions for a Shopify store using Product Maestro.
| Argument | Type | Description |
|---|---|---|
page
|
integer |
Page number (default: 1) |
page_size
|
integer |
Items per page, max 100 (default: 25) |
status
|
string |
Filter by action status |
type
|
string |
Filter by action type |
Create a draft product schedule for the store. Drafts never run on their own: the merchant must open the draft in Product Maestro, review it, and click Save. Actions that share a selection land on one draft; a different selection gets its own draft. Each action needs actionType + targetType from the list below, a selectionType valid for that target (PRODUCT: PRODUCTS, COLLECTION or ADVANCED; COLLECTION: COLLECTION; VARIANTS: VARIANTS; PRICE_LIST: PRICE_LIST), parameters matching the shape shown, and scheduledAtLocal as the store's local wall-clock time ("2026-10-03T09:00", no offset). filterConditions are only allowed with selectionType ADVANCED. Supported actions (actionType (targets) — parameters): - ADD_TAG (PRODUCT) — parameters: { tags: string[] } - REMOVE_TAG (PRODUCT) — parameters: { tags: string[] } - PUBLISH (PRODUCT) — parameters: {} — no fields - DRAFT (PRODUCT) — parameters: {} — no fields - ARCHIVE (PRODUCT) — parameters: {} — no fields - UPDATE_TEMPLATE_SUFFIX (PRODUCT/COLLECTION) — parameters: { templateSuffix: string } - JOIN_COLLECTION (PRODUCT) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - LEAVE_COLLECTION (PRODUCT) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - UPDATE_PRODUCT_METAFIELDS (PRODUCT/COLLECTION) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - UNPUBLISH_FROM_CHANNEL (PRODUCT/COLLECTION) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - PUBLISH_INTO_CHANNEL (PRODUCT/COLLECTION) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - INCREASE_PRICE (PRODUCT) — parameters: { amount: number, amount_type: "fixed"|"percentage", round?: "up"|"down"|"closest" } — round only applies when amount_type is "percentage" - DECREASE_PRICE (PRODUCT) — parameters: { amount: number, amount_type: "fixed"|"percentage", round?: "up"|"down"|"closest" } — round only applies when amount_type is "percentage"; the reverse of this action is INCREASE_PRICE with the same amount/amount_type - INCREASE_INVENTORY (PRODUCT) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - DECREASE_INVENTORY (PRODUCT) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - SET_INVENTORY (PRODUCT) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - SET_PRICE (PRODUCT) — parameters: { amount: number } - SET_PRICE_DECIMAL (PRODUCT) — parameters: { amount: number } - UPDATE_TITLE (PRODUCT/COLLECTION) — parameters: { title: string } - UPDATE_COLLECTION_SORT (COLLECTION) — parameters: { collectionSort: string } - ROUND_PRICE (PRODUCT) — parameters: { round: "up"|"down"|"closest" } - UPDATE_BODY_HTML (PRODUCT/COLLECTION) — parameters: { bodyHtml: string } — an empty string is a valid instruction: it clears the description - DELETE_PRODUCT (PRODUCT) — parameters: {} — no fields; only propose this if the merchant explicitly asked to delete, never as a guess - DELETE_COLLECTION (COLLECTION) — parameters: {} — no fields; only propose this if the merchant explicitly asked to delete, never as a guess - PRODUCT_SET (PRODUCT) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - REORDER_COLLECTION_PRODUCTS (COLLECTION) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - PRICE_LIST_UPDATE (PRICE_LIST) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - PRICE_LIST_FIXED_PRICES_ADD (PRICE_LIST) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - PRICE_LIST_FIXED_PRICES_BY_PRODUCT_UPDATE (PRICE_LIST) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - PRICE_LIST_FIXED_PRICES_DELETE (PRICE_LIST) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - PRICE_LIST_FIXED_PRICES_UPDATE (PRICE_LIST) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - UPDATE_FEATURED_IMAGE (PRODUCT/COLLECTION) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - UPDATE_VARIANT (VARIANTS) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - UNLIST (PRODUCT) — parameters: {} — no fields - UPDATE_COLLECTION_SEO (COLLECTION) — parameters: { title?: string, description?: string } — at least one is required - UPDATE_PRODUCT_SEO (PRODUCT) — parameters: { title?: string, description?: string } — at least one is required - PRODUCT_MEDIA_UPDATE (PRODUCT) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - ADD_VARIANT (VARIANTS) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - REMOVE_VARIANT (VARIANTS) — parameters: {} — shape not documented yet, ask for nothing beyond selection/date - PUBLISH_INTO_MARKET (PRODUCT) — parameters: { markets: array } — one or more market identifiers named by the merchant - UNPUBLISH_FROM_MARKET (PRODUCT) — parameters: { markets: array } — one or more market identifiers named by the merchant
| Argument | Type | Description |
|---|---|---|
summary
required |
string |
One line describing the intended change. The merchant sees it on the draft when reviewing. |
actions
required |
array |
Product Maestro does not run schedules over the API. This always refuses, and explains that the merchant must review the draft in the app and click Save; it then runs at its scheduled time.
| Argument | Type | Description |
|---|---|---|
id
|
integer |
Schedule ID |
Once the server is connected, you do not call these tools yourself — you ask in words and the assistant picks the tool. Each row below is a question that reaches the tool beside it, so you can check the connection works and see the shape of an answer.
| Ask | Tool it reaches |
|---|---|
| Show me the schedules. | list_schedules |
| Give me the details of a schedule. | get_schedule |
| Show me the actions. | list_actions |
| Use create_draft_schedule to tell me about the draft schedule. | create_draft_schedule |
| Use execute_schedule to tell me about the schedule. | execute_schedule |
The assistant reads the store from your token, so none of these needs a shop name in it. If it answers without calling a tool, it is guessing: say “use the Product Maestro tools” and ask again.
| Status | Code | What to do |
|---|---|---|
| 401 | MISSING_TOKEN |
Send an |
| 401 | INVALID_TOKEN |
Check the token was copied in full, is still listed as active in Settings → API Access, and has not passed its expiry date. Create a new token if in doubt. The same code is returned when the store is suspended. |
| 403 | PLAN_REQUIRED |
The token is valid, but this app sells API access on named plans only and this store is not on one of them. The plans that carry it are listed under Authentication above; change the plan in the app and the same token starts working — there is no need to create a new one. |
| 404 | APP_NOT_FOUND |
The app handle in the path is not one Sappo serves. Use the handle this documentation was generated for, and check for a typo or a stray path segment. |
| 429 | RATE_LIMITED |
Wait the number of seconds in the |
| 503 | API_NOT_AVAILABLE |
The app is served but its store of API tokens has not been created yet, so no token can be checked. Nothing is wrong with your request: retry later, and tell support if it lasts. |
The MCP endpoint also has its own rate limit, separate from the REST API's
per-token budget: 60 requests per
60 seconds per
client address. Over it, the endpoint answers 429
with Too Many Attempts. — Laravel's own throttle
response, not the API error envelope, so it carries no error.code —
and the headers
X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After.
An assistant that reports a tool "not found" is usually connected to the wrong app's endpoint. Compare the tool name it tried against the list above.