{"serverInfo":{"name":"pai-managed-mcp-server","title":"Payments AI","version":"2.0.0","description":"Merchant of Record for AI-built apps. Provision merchants, create products, and return hosted checkout links."},"transport":{"type":"streamable-http","endpoint":"https://mor.payments.ai/api/mcp"},"capabilities":{"tools":true,"resources":false,"prompts":false},"authentication":{"types":["oauth2","api_key"],"oauth2":{"authorization_server":"https://mor.payments.ai/.well-known/oauth-authorization-server","resource_metadata":"https://mor.payments.ai/.well-known/oauth-protected-resource/api/mcp","required_scopes":["merchant:create","merchant:read","merchant:go_live","product:create","product:read","docs:read","checkout:read","checkout:write"]},"api_key":{"header":"Authorization","scheme":"Bearer","obtain_url":"https://mor.payments.ai/developer-tools"}},"websiteUrl":"https://mor.payments.ai","documentation_url":"https://doc.mor.payments.ai/mcp","skill_md_url":"https://doc.mor.payments.ai/SKILL.md","llms_txt_url":"https://doc.mor.payments.ai/llms.txt","vendor":{"name":"Payments AI","url":"https://payments.ai"},"protocol_version":"2025-06-18","name":"pai-managed-mcp-server","version":"2.0.0","tools":[{"name":"get_my_merchant","description":"List the merchant accounts this connection already owns. Call this BEFORE create_merchant_account: a new agent session cannot remember a merchant created in an earlier one, and creating again produces a second merchant with its own products and its own checkout links. Takes no arguments — the merchants come from the authorization on the connection itself. Returns { merchants: [{ merchantId, name, deployment }] }. \"deployment\" is \"sandbox\" before go_live and \"live\" after it has succeeded, and it matches where create_product and list_products will route for that merchant right now. An empty merchants array is a normal result, not an error: this connection owns no merchant yet, so call create_merchant_account. When more than one merchant comes back, ask which one to use — do not pick for the user. Requires the \"merchant:read\" scope."},{"name":"create_merchant_account","description":"Create a merchant account on PAI Managed. Call get_my_merchant first: if it returns a merchant, reuse that merchantId instead of creating another, because a duplicate merchant gets its own products and checkout links. Requires only name and email. The email must be the user's own real address, asked from the user or taken from the conversation: example or placeholder addresses (such as you@example.com) and addresses that cannot receive mail are rejected. Phone is optional. If you pass one it must be the user's own mobile number, taken from the conversation or asked from the user; never invent a placeholder or a fictional number (such as a 555 number), because after go_live the identity-verification link is sent to it by SMS and a number that cannot be dialled is rejected. Use E.164 with country code (format only: +14155552671). If the user does not share a phone, omit it and the link goes by email. The business name is a display label and is not globally unique — every downstream tool (create_product, list_products, checkout) keys on merchantId, so if two merchants happen to share a name, disambiguate them by merchantId (do not pick a distinct name here to work around it). No identity verification (KYC/KYB) is needed to use sandbox — products, checkout links and test transactions all work without it; verification is required on live. Before identity verification (KYC) completes, a live merchant can process up to $5,000 in card transactions; payouts (adding a payout method and withdrawing funds) require completed identity verification. Dual-provisions live and sandbox with a shared merchantId. On partial dual-provision failure (one backend created it, the other did not), retry with the same merchantId, name, and email to finish provisioning. When both backends fail, the returned error names the appropriate recovery — follow it as-is rather than looping with the same merchantId, and treat repeated identical failures as needing escalation to support. Returns that merchant ID. Without a merchantId, a merchant this account already owns is returned with existing: true instead of a new one being created, and whichever of live or sandbox is missing it gets a copy with the details the existing copy holds. Next: create_product (and checkout tools) on sandbox, then go_live when ready."},{"name":"get_merchant_activation_status","description":"Get the production activation status of a merchant account on PAI Managed. Returns { status, identityVerification }. status \"pending\": the merchant has not been promoted to live yet; this is the only status where go_live is the next step, once the user confirms the merchant is ready, and polling this tool will not change it on its own. status \"verification_pending\": the merchant is promoted to live and identity verification (KYC) is not complete yet; do not call go_live again. status \"active\": live and identity verified. status \"not_provisioned\": no live merchant record exists (dual provision never finished on live); recovery: re-run create_merchant_account with the same merchantId, which reuses that id idempotently — go_live cannot fix this and fails while the live twin is missing. Before identity verification (KYC) completes, a live merchant can process up to $5,000 in card transactions; payouts (adding a payout method and withdrawing funds) require completed identity verification. identityVerification is the KYC state: \"not_started\", \"pending\", \"in_review\", \"verified\" or \"rejected\"."},{"name":"go_live","description":"IRREVERSIBLE. Promote a sandbox merchant to live by setting liveMerchantId (Merchant Go-Live). This is one-way: it assigns merchant-of-record liability and notifies KYC on the live backend, and there is no tool that undoes it. Confirm with the user before calling it. Validates deployment readiness (sandbox merchant exists, not linked to a different live id, live twin reachable), marks the sandbox merchant live with the dual-provision twin id, then best-effort notifies KYC on the live backend. Pass dryRun: true to run exactly those readiness checks and stop before anything is written — the response then carries dryRun: true, and its absence is your proof that a real promotion happened. Unknown arguments are rejected rather than ignored, so a guessed safety flag fails loudly instead of being silently dropped. Idempotent: a call for a merchant that is already live changes nothing and returns alreadyLive: true, so there is never a reason to call it twice. The response carries activationStatus, the same shape get_merchant_activation_status returns (\"verification_pending\" right after promotion until identity verification completes; omitted only when the live status cannot be read), and a real call also returns nextSteps to relay to the user. The live catalog starts empty: sandbox products, plans and checkout links are not copied to live. Create the products and plans again with create_product (it now creates them on live) and share the new live checkout URLs; the old sandbox links keep taking test payments only. Before identity verification (KYC) completes, a live merchant can process up to $5,000 in card transactions; payouts (adding a payout method and withdrawing funds) require completed identity verification. The identity-verification link goes by SMS to the merchant phone, or by email when there is no dialable phone; kycNotify.phoneIssue \"invalid\" means the phone number cannot be dialled, so tell the user it is invalid. Distinct from get_merchant_activation_status (activation and identity-verification status) and from ApiKeyEnvironment."},{"name":"search_payments_ai_docs","description":"Search the PAI Managed documentation knowledge base to find guides, API references, UI walkthroughs, and feature explanations. Returns matching sections with titles and direct documentation links. Use this tool to understand how PAI Managed works, find step-by-step instructions for dashboard tasks, or locate specific features such as creating a merchant account (phone / E.164), go-live, creating products, configuring plans, or generating checkout links."},{"name":"create_product","description":"Create a product with one or more pricing plans for a merchant on the PAI Managed platform. Product and checkout mutations target sandbox until go_live. Products represent digital offerings (software, subscriptions, services) that customers can purchase via a checkout link. Each plan defines the price (a decimal dollar amount in the plan currency (major units, e.g. 9.99 for $9.99 — NOT cents, so 1999 means $1,999.00; currency is one of usd, eur, gbp, pln, chf, sek, dkk, nok, cad, aud)), billing type (one-time, recurring, or free-access), and billing period. A plan `amount` is at most 100000, `periodLength` at most one year of the chosen billingPeriod (52 week / 12 month / 1 year), and `freeTrial` at most 730 days and only on recurring plans. A plan accepts only its documented keys; an unknown key (such as freeTrialDays) is rejected, not ignored. Optional `status` ('draft' | 'active' | 'archived') defaults to 'active' — the product is created live and purchasable immediately via its checkout link, regardless of status. Pass `status: 'draft'` only when the end user explicitly asks for a draft or temporary/not-yet-ready product. Product `name` (<=80 chars), product `description` (<=80 chars) and plan `description` (<=80 chars) are all short one-line copy — if yours is longer, shorten it before calling and put the full version in that plan's `richDescription` (<=65535 chars), which the hosted checkout page renders as plain text beneath the short description (newlines preserved, markup not interpreted). `richDescription` is per plan, so product-level copy that overflows must still be edited down. Returns the product ID, the plan IDs, and ready-to-open hosted-checkout URLs: `checkoutUrl` for the first created plan and `planCheckoutUrls` (one `{planId, checkoutUrl}` entry per created plan) — do not construct them yourself. The URLs already reflect the merchant's go-live status, not your credentials: sandbox (`?isSandbox=true`) before go_live, live (no query param) once go_live has succeeded — nothing to switch on your side. `plans` repeats what was stored for each plan, in `planIds` order: amount, currency, `price` formatted as the checkout shows it, and for recurring plans billingPeriod, periodLength and freeTrial — check `price` against what the user asked for. `warnings`, when present, flags an amount that looks like cents; relay it and, if the user meant the smaller price, fix it with update_plan before sharing the link. Product images are not supported on create — after success, call get_product_image_upload_url with the returned id as productId, POST the file bytes (multipart/form-data) to the returned URL, then call confirm_product_image with the returned key."},{"name":"create_order","description":"Create a payable order for a merchant on PAI Managed. Use create_order when the buyer pays for two or more items, or when you set the price yourself. Use the checkoutUrl from create_product or list_products for a single catalogue plan. Provide merchantId, currency (3 letters, for example usd), and items. A catalogue item is { planId, quantity }. A caller-priced item is { label, unitPrice, quantity, type } where type is one_time or recurring; a recurring caller-priced item also needs billingPeriod (day, week, month, or year) and periodLength. unitPrice is decimal dollars, not cents (10 means $10.00). currency is one of usd, eur, gbp, pln, chf, sek, dkk, nok, cad, aud. unitPrice is at most 100000, and so is each line (unitPrice x quantity) and the order total; quantity is at most 10000; periodLength is at most one year of the billingPeriod (365 day / 52 week / 12 month / 1 year). When billingPeriod is \"day\", periodLength must be at least 2 (the payment provider rejects true daily subscriptions). trialPeriodDays is at most 730, only on recurring custom lines, and must match the trial length of every other recurring line in the order (catalogue plan or custom). Optional idempotencyKey: repeating the same key returns the same orderId; omitting it creates a new order. Returns orderId and checkoutUrl — use the URL verbatim, do not construct it yourself. The URL already reflects the merchant's go-live status: sandbox (`?isSandbox=true`) before go_live, live (no query param) once go_live has succeeded. Requires the \"checkout:write\" scope."},{"name":"update_product","description":"Update an existing product for a merchant on PAI Managed. Fields are MERGED: send only the fields you want to change — any field you omit is left unchanged. Editable: `name`, `description`, `status` ('draft' or 'active'). NOT editable via this tool: the product's `discountCode` (managed by a separate discount-codes surface, and this update does not propagate it to the payment provider), the product image (use get_product_image_upload_url + confirm_product_image), and the product `status: 'archived'` (use the dashboard archive flow — archiving has extra checks this tool does not run). Send at least one field to change; an empty payload is rejected. Returns `{ id }`."},{"name":"update_plan","description":"Update an existing plan on a product for a merchant on PAI Managed. Fields are MERGED: send only the fields you want to change — any field you omit is left unchanged. Editable: `name`, `description`, `billingPeriod`, `periodLength`, `amount`, `freeTrial`. NOT editable: `type` (immutable after creation — a plan's billing shape is fixed; create a new plan and archive the old one if you need a different type), `currency` (would flip only the local row while the payment provider keeps charging in the original currency — create a new plan in the target currency instead), and `richDescription` (edit via the dashboard). `amount` and per-period `periodLength` caps are the same as create_product. If the plan already has active subscriptions, changing `amount` is rejected server-side (409): tell the user the change is blocked and, if they need a different price for new customers, create a new plan on the same product. Send at least one field to change; an empty payload is rejected. Returns `{ id, productId }`."},{"name":"list_products","description":"List products and pricing plans for a merchant on PAI Managed. Returns paginated products; each product carries a ready-to-open hosted-checkout `checkoutUrl`, and each plan summary (planId, name, amount, currency, `price` formatted as the checkout shows it, type, and for recurring plans billingPeriod, periodLength and freeTrial) carries its own `checkoutUrl` — use those directly, do not construct them yourself. The URLs already reflect the merchant's go-live status, not your credentials: sandbox (`?isSandbox=true`) before go_live, live (no query param) once go_live has succeeded — nothing to switch on your side. Optional filters: q (name search), status (draft|active|archived), type (one-time|recurring|free-access), dateRangeStart/dateRangeEnd, page, limit. Use before create_product when you need existing productId/planId values, or to share checkout links for plans that already exist. Targets sandbox until go_live, same as other product tools."},{"name":"get_product_image_upload_url","description":"Get a presigned POST policy to upload a product image directly to storage — the image bytes never pass through the tool call, and S3 enforces the 5 MB cap at upload time via `content-length-range` (oversized bodies get a 403 from S3, not the backend). Use this on any surface that can issue an HTTP multipart/form-data POST (CLI, agent, dashboard). The product must already exist (call create_product first). Provide merchantId, productId, fileName, and mimeType (image/png, image/jpeg, image/jpg, or image/webp). Returns { url, fields, key, expiresIn }: issue an HTTP POST to `url` with Content-Type: multipart/form-data. Add every entry from `fields` as a form field FIRST, then add the `file` field last (S3 requires the file field last), e.g. `curl -X POST <all -F fields...> -F \"file=@./logo.png\" \"<url>\"`. Then call confirm_product_image with the returned key to validate and attach the image."},{"name":"confirm_product_image","description":"Confirm a product image uploaded via a presigned POST policy from get_product_image_upload_url. Call this after the multipart/form-data POST succeeds. Provide merchantId, productId, and the key returned by get_product_image_upload_url. The backend validates the stored object (format, size, magic bytes) and attaches it to the product. Returns { imageUrl } for verification. Replaces any existing product image."},{"name":"get_checkout_customization","description":"Get a merchant's checkout customization on PAI Managed — theme mode, colors (background, button, button text, font), font family, input style, and the current logo URL (read-only). Use this to inspect current branding before changing it."},{"name":"update_checkout_customization","description":"Update a merchant's checkout customization on PAI Managed. Fields are MERGED: send only the fields you want to change — any field you omit is left unchanged. To remove/reset a field, use the dashboard (clearing is not supported over MCP). Colors are 6-digit hex like '#1A2B3C'; fontFamily is 'Inter' or 'Roboto'; themeMode is 'light' or 'dark'; inputStyle is 'rounded' or 'square'. The logo is set with set_checkout_logo (mint → PUT → confirm), not this tool. Returns the full customization after the merge."},{"name":"set_checkout_logo","description":"Set a merchant checkout logo via a two-step presigned PUT flow (image bytes never pass through the tool). Step 1 — action \"mint\" with merchantId, fileName, and contentType (image/png, image/jpeg, image/jpg, or image/webp): returns { uploadUrl, path, expiresAt }. HTTP PUT the file bytes to uploadUrl with the same Content-Type (≤ 5 MB). Step 2 — action \"confirm\" with merchantId and the exact path from mint: returns { logoUrl }. Does not accept an imageUrl to fetch server-side (SSRF-safe). Replaces any existing checkout logo."}],"authorizationUrl":"https://mor.payments.ai/api/v1/oauth/authorize","tokenUrl":"https://mor.payments.ai/api/v1/oauth/token","registrationUrl":"https://mor.payments.ai/api/v1/oauth/register"}