# Stashq MCP server Stashq is a platform for selling digital products in India. This MCP server lets an AI assistant run one seller's own store: read their catalogue, sales, buyers, earnings and refund cases, and — depending on the token — draft changes, publish products, activate offers, publish the storefront, and approve or decline refunds. The seller chooses one of three permission levels when they create the token, so what you can actually call depends on the key you were given. Call tools/list and work from what it returns; do not assume a tool exists because it is documented here. Human documentation: https://www.stashq.in/docs/mcp ## Connection Endpoint: https://www.stashq.in/api/mcp Transport: Streamable HTTP (POST). Not SSE, not stdio. Auth: Authorization: Bearer The server implements MCP revision 2026-07-28 and also accepts the earlier initialize-based revisions (2025-11-25, 2025-06-18, 2025-03-26), so a client of either era works without special handling. ## Getting a token — do this BEFORE writing any config The user must create the token; you cannot. Until you have it there is nothing to configure: a request with no token, or with placeholder text still in it, returns 401 and there is nothing to debug. So ask them first, and tell them to: 1. Open https://www.stashq.in/settings/tokens 2. Click "New token" and name it after you. 3. Choose a permission level. Three radio options, least to most: - "Read only" — looks at everything, changes nothing. - "Read and draft" — also prepares changes; nothing goes live. - "Full access" — also publishes, activates offers, replies to buyers, and approves or declines refunds. This one spends real money. Tell them which level the task you were asked to do actually needs, and say why. Do not ask for full access to read their sales figures. 4. Copy it immediately. Stashq stores only a hash, so the token is shown once and can never be shown again. How to take it from them, whichever fits your client: - If you can read a secret store or an environment variable, tell them exactly where to put it and let them do that themselves. - If it can only live in a config file you write, ask them for it directly, write it into the file, and do not repeat it back to them afterwards. Copy it exactly. Clients use the Authorization header as written and most do not trim it, so a trailing space or newline makes every request fail with 401 — which looks identical to a bad token. ## Config, per client Only once you hold a real token. Find your own client below and use ITS shape: the field names differ and a wrong one fails silently — the client starts normally and simply has no Stashq tools. If your client is not listed, it needs the endpoint, Streamable HTTP, and the Authorization header above, in whatever form it accepts. Every block below writes sq_live_YOUR_TOKEN_HERE where the real token goes. Substitute it. Do not send it as-is. ### Claude Code File: .mcp.json (also reads ~/.claude.json) Format: json { "mcpServers": { "stashq": { "type": "http", "url": "https://www.stashq.in/api/mcp", "headers": { "Authorization": "Bearer sq_live_YOUR_TOKEN_HERE" } } } } Or from a terminal: claude mcp add --transport http stashq https://www.stashq.in/api/mcp \ --header "Authorization: Bearer sq_live_YOUR_TOKEN_HERE" Note: Add --scope user to the command to use Stashq in every project instead of just this one. If you write the JSON by hand, "type" is not optional: an entry with a url and no type is read as a stdio server and skipped, and the only sign is a line in the startup log. "streamable-http" works as an alias. ### Codex / ChatGPT File: ~/.codex/config.toml (also reads .codex/config.toml) Format: toml [mcp_servers.stashq] url = "https://www.stashq.in/api/mcp" bearer_token_env_var = "STASHQ_MCP_TOKEN" Then export the token in your shell profile: export STASHQ_MCP_TOKEN="sq_live_YOUR_TOKEN_HERE" Note: Codex reads the token from the environment, not from the file — bearer_token_env_var names the variable and Codex sends it as Authorization: Bearer. On an older Codex that picks up only stdio servers, add a [features] block with experimental_use_rmcp_client = true above the server entry, or upgrade. ### Antigravity File: ~/.gemini/config/mcp_config.json (also reads .agents/mcp_config.json) Format: json { "mcpServers": { "stashq": { "serverUrl": "https://www.stashq.in/api/mcp", "headers": { "Authorization": "Bearer sq_live_YOUR_TOKEN_HERE" } } } } Note: The field is serverUrl, not url — Antigravity does not accept url or httpUrl for a remote server, so a config copied from any other client here fails with nothing to read. In the IDE: ⋯ at the top of the agent panel → MCP Servers → Manage MCP Servers → View raw config. ### opencode File: ~/.config/opencode/opencode.json (also reads opencode.json) Format: json { "$schema": "https://opencode.ai/config.json", "mcp": { "stashq": { "type": "remote", "url": "https://www.stashq.in/api/mcp", "enabled": true, "headers": { "Authorization": "Bearer sq_live_YOUR_TOKEN_HERE" } } } } Note: The key is mcp, not mcpServers, and the type is remote rather than http. A header value may reference an environment variable as {env:STASHQ_MCP_TOKEN} if you would rather not keep the token in the file. ### openclaw File: ~/.openclaw/openclaw.json Format: json { "mcp": { "servers": { "stashq": { "url": "https://www.stashq.in/api/mcp", "transport": "streamable-http", "headers": { "Authorization": "Bearer sq_live_YOUR_TOKEN_HERE" } } } } } Note: Two levels — mcp.servers — and the field is transport, not type. ${STASHQ_MCP_TOKEN} works in place of the literal token. openclaw mcp set writes the same block if you would rather not edit the file. ### Hermes File: ~/.hermes/config.yaml Format: yaml mcp_servers: stashq: url: "https://www.stashq.in/api/mcp" headers: Authorization: "Bearer sq_live_YOUR_TOKEN_HERE" enabled: true Note: YAML, so indentation is load-bearing — paste it at the top level of the file, not inside another key. Streamable HTTP is the default transport, so there is no transport line to write. ${STASHQ_MCP_TOKEN} resolves from ~/.hermes/.env, which is the better place for the token. ## Permissions The user picks one of three levels when they create the token. Each includes everything the one above it has: - "Read only" — Look at everything, change nothing. Your store, products, offers and store design Sales, buyers, reviews and refund conversations Earnings, payouts and storage usage - "Read and draft" — Prepare changes for you to review. Nothing goes live. Everything Read only can do Create and edit products as drafts Create and edit offers as drafts Edit your store design as a draft — you still press Publish - "Full access" — Everything you can do in the dashboard, including refunding money. Everything Read and draft can do Publish, unpublish and archive products Activate and pause offers Publish your store design Reply to buyers, and approve or decline refunds — this moves real money Permissions are fixed when a token is created and cannot be edited afterwards. To change them the user creates a new token and revokes the old one. The levels expand to these individual permissions. Every tool checks its own before it touches anything: - store.read (Read only): Read your store profile, design, products and categories - sales.read (Read only): Read purchases, buyers, earnings, reviews and refund cases - payouts.read (Read only): Read the earnings ledger and payout state - products.write (Read and draft): Create and edit products as drafts - offers.write (Read and draft): Create and edit offers as drafts - store.write (Read and draft): Edit your store design as a draft - products.publish (Full access): Publish, unpublish and archive products - offers.publish (Full access): Activate and pause offers - store.publish (Full access): Publish your store design - refunds.reply (Full access): Reply to buyers in refund conversations - refunds.resolve (Full access): Approve and decline refunds ## Tools A tool whose permission the token does not carry is not listed at all, so tools/list reflects exactly what this token can do. - list_products (needs store.read, from "Read only"): Your catalogue, newest first. - get_product (needs store.read, from "Read only"): One product by its id — including a draft only you can see, with its description, categories, licence terms and file names. - get_storage_usage (needs store.read, from "Read only"): How much of your storage allowance is in use. - list_offers (needs store.read, from "Read only"): Your discounts and coupon codes. - get_store (needs store.read, from "Read only"): Your store profile and its full design document, plus whether you have an unpublished draft. - list_sales (needs sales.read, from "Read only"): What sold, what the buyer paid, and what you keep. - get_buyer (needs sales.read, from "Read only"): One buyer: what they bought, refunded, and whether they collected it. - list_reviews (needs sales.read, from "Read only"): The reviews on one of your products, with their status. - list_refund_requests (needs sales.read, from "Read only"): Refund cases buyers have opened, and what state each is in. - get_refund_request (needs sales.read, from "Read only"): One refund case in full: the conversation, and this buyer’s history with you. - get_earnings (needs payouts.read, from "Read only"): Available, pending, held and already paid out. - list_earnings_ledger (needs payouts.read, from "Read only"): Per-sale earnings: fees, when it clears, when it was paid out. - create_product (needs products.write, from "Read and draft"): Create a product as a draft. It cannot publish one. - update_product (needs products.write, from "Read and draft"): Edit a product. Only the fields you pass change, and this never publishes or unpublishes. - create_offer (needs offers.write, from "Read and draft"): Create a discount or coupon as a draft. It takes nothing off until it is activated. - update_offer (needs offers.write, from "Read and draft"): Change an offer’s usage limit or end date. - update_store_design (needs store.write, from "Read and draft"): Stage a store design change. Nothing shoppers see changes until you publish. - discard_store_draft (needs store.write, from "Read and draft"): Throw away staged design changes and return to the published design. - publish_product (needs products.publish, from "Full access"): Make a draft live. Needs a finished delivery file and a verified payout account; queues itself if the upload is still running. - unpublish_product (needs products.publish, from "Full access"): Take a live product off sale. Existing buyers keep their downloads. - archive_product (needs products.publish, from "Full access"): Archive a product. Not a deletion — restore_product brings it back as a draft. - restore_product (needs products.publish, from "Full access"): Bring an archived product back, as a draft rather than to your storefront. - set_offer_status (needs offers.publish, from "Full access"): Activate or pause an offer. Activating changes what buyers pay. - publish_store_design (needs store.publish, from "Full access"): Make your staged design live. The first time, this is also what opens your shop to the public. - reply_to_refund_request (needs refunds.reply, from "Full access"): Message a buyer in a refund conversation, under your name. It cannot be unsent. - resolve_refund_request (needs refunds.resolve, from "Full access"): Approve or decline a refund. Approving returns real money and revokes the buyer’s access. Money amounts are integers in the smallest currency unit (paise; 100 paise = 1 rupee) and every price is tax-inclusive. Product ids look like prd_xxxxxxxxxx and purchase ids like pur_xxxxxxxxxx. create_product always produces a draft, and no argument changes that. Tell the user their product was created as a draft and is not visible or purchasable yet. publish_product is a separate tool and needs a separate permission. It refuses unless the product already has a delivery file that finished uploading and the user's payout account is verified — and if the upload is still running it queues the product instead, so it publishes itself when the bytes land. Do not call it twice. No tool can attach a delivery file. That is the one step that has to happen in the Stashq app, so a draft an assistant created cannot be taken all the way to live without the user. Every change you make is recorded against the token that made it, before it happens, and the user reads that list in their settings. If Stashq cannot write that record it refuses the change rather than making an unattributable one. ## What a full-access token spends These need the "Full access" level, cannot be undone, and are the reason to check which level you were given before promising the user an outcome: - Approve a refund — real money leaves your balance and the buyer loses access - Message a buyer in your name, in a refund conversation, and it cannot be unsent - Publish, unpublish or archive a product, changing what shoppers can buy - Activate an offer, changing what buyers pay - Publish your store — the first time, this is what makes your shop public ## What this server cannot do At any level, with any token: - Change where your payouts go, or touch your bank details - Change your plan, buy storage, or cancel a subscription - Delete anything — a product, a file, a sale, or an offer - Read or change your account password, email or login - Open a refund case on a buyer’s behalf, or close one for them - Upload or replace a delivery file If the user asks for one of these, say it is not available through this connection and point them at the Stashq app. ## Errors 401 with "Invalid or missing access token." — the token is missing, malformed, revoked, or expired. The response is identical for all four on purpose, so the only useful action is to ask the user for a fresh token. 429 — rate limited. 600 calls per hour per token. Wait rather than retrying. A tool result with isError: true and a message about a missing permission means the token lacks that scope. Do not retry the call. Tell the user which permission to add and that a new token is needed to change it.