Fable Amazon MCP
README.md
# Fable Amazon / REYO Amazon MCP
Fable Amazon is a private Amazon India commerce intelligence and controlled-operations backend. It exposes 56 granular MCP tools over authenticated Streamable HTTP, reads current Amazon SP-API data, computes business metrics, stores private rules/history in Supabase, and gates Amazon mutations behind an exact preview → approval → execute workflow.
This repository is the backend, not a mock dashboard. Production paths never manufacture Amazon data. Tests use isolated fakes and never perform a real Amazon write.
## Architecture
```text
ChatGPT / Codex / Claude / Fable
│
HTTPS Streamable HTTP MCP
│
Next.js 16 App Router on Vercel
│
Fable application services
├── Amazon SP-API (India)
├── intelligence engines
├── mutation approval boundary
└── Supabase/Postgres
```
The application is split into Amazon adapters, intelligence engines, MCP tool registration, database repositories, mutation controls, and security. Amazon and Supabase credentials are read only in server modules. No buyer address, name, phone, or other restricted PII is requested.
Primary routes:
- `POST/GET/DELETE /mcp` — authenticated MCP Streamable HTTP endpoint
- `GET /api/health` — configuration readiness without revealing secrets
- `GET /.well-known/oauth-protected-resource` — OAuth protected-resource metadata
## Implemented capabilities
### Read intelligence
- Orders API `v2026-01-01`: current-day/range orders, normalized item rows, revenue, units, AOV, status mix, top SKUs, and explicit period comparisons. Current ranges are safely capped because Amazon requires `createdBefore` to be at least two minutes old.
- Finances API `v2024-06-19`: normalized transactions, released/deferred movement, contributing orders/SKUs, fee components, refunds, adjustments, settlement-related rows, and freshness caveats.
- Listings Items API `v2021-08-01`: listing search/get, attributes, offers, inventory, issues, and suppression signals.
- Reports API `v2021-06-30`: asynchronous request/status/download flow with JSON, CSV, TSV, and GZIP parsing. Serverless requests never wait for report generation.
- Product Pricing API `v2022-05-01`: competitive summaries, Featured Offer/reference-price evidence, and guarded price analysis.
- Sales and traffic: normalized sessions, page views, ordered sales, units, unit-session percentage, Featured Offer percentage, ASIN segmentation, and traffic-vs-conversion comparisons.
- Composite `amazon_review_account`: sales, comparable weekdays, finance, fee anomalies, estimated profit, listing issues, suppression, inventory, performance when a ready report is supplied, risks, opportunities, and timestamps.
### Financial intelligence
- Money movement explicitly separates posted/released and deferred amounts and does not claim pickup scans caused a posting.
- Supabase financial snapshots can be captured and compared. A change is never inferred unless at least two real snapshots exist.
- The seeded Easy Ship rule flags exactly ₹212.40 at high priority for matching `REYO%` SKUs, uses ₹64.90–₹88.50 as the expected range, and does not auto-flag ₹88.50.
- Profit combines Amazon-reported revenue/fees/logistics/refunds with private Fable COGS. SKU COGS takes priority; SQL-like product-family SKU patterns provide a fallback. Missing COGS produces `null` profit instead of a fabricated number.
### Controlled Amazon writes
- Price, inventory, supported listing-attribute/SEO patches, and listing creation.
- Exact bulk price, bulk inventory, bulk patch, and bulk-create previews for up to 1,000 selected SKUs.
- Direct Listings Items operations for up to 20 operations; larger sets use one asynchronous `JSON_LISTINGS_FEED`.
- Feed status inspection after bulk submission.
- PATCH-style changes are preferred. Listing deletion and offer closure are not exposed.
Every Amazon mutation follows this boundary:
```text
request → load current state → protection checks → Amazon validation preview
→ exact signed preview → explicit confirmation → protection re-check
→ single-use atomic consume → Amazon write/feed → immutable per-SKU audit
```
Approval tokens are HMAC-signed, expire in 60–3,600 seconds, are tied to the initiator and exact payload digest, and cannot be reused. Duplicate SKUs, modified values/SKU sets, expired tokens, validation issues, and protected listings are rejected. Stock-to-zero and large batches are marked high risk. Execution requires the literal confirmation `I APPROVE THE EXACT PREVIEW`.
## Amazon prerequisites
Use a private SP-API application in Amazon's Solution Provider Portal and self-authorize it for the seller account. Record the LWA client ID, LWA client secret, seller ID, and generated refresh token. Amazon documents that private apps can be self-authorized and that the LWA refresh token must be renewed annually.
Request only the non-restricted roles required by the enabled modules:
| Role | Used for |
| --- | --- |
| Inventory and Order Tracking | Orders and non-PII order tracking |
| Finance and Accounting | Financial transactions and settlement/report access |
| Product Listing | Listing reads, validation, updates, creation, inventory, and feeds |
| Pricing | Competitive pricing operations |
| Brand Analytics | `GET_SALES_AND_TRAFFIC_REPORT` and its traffic/conversion metrics |
Amazon can change operation-to-role mappings, so confirm the role shown for each operation in the current role mappings before production authorization. Do not add restricted PII roles for this V1.
Amazon India configuration is already the default:
```text
Marketplace ID: A21TJRUUN4KGV
SP-API region endpoint: https://sellingpartnerapi-eu.amazon.com
```
Current reference material:
- [Amazon SP-API onboarding](https://developer-docs.amazon.com/sp-api/docs/onboarding-overview)
- [Amazon marketplace IDs](https://developer-docs.amazon.com/sp-api/docs/marketplace-ids)
- [Amazon SP-API endpoints](https://developer-docs.amazon.com/sp-api/docs/sp-api-endpoints)
- [Connecting to SP-API](https://developer-docs.amazon.com/sp-api/docs/connecting-to-the-selling-partner-api)
- [Orders API](https://developer-docs.amazon.com/sp-api/docs/orders-api)
- [Finances `listTransactions`](https://developer-docs.amazon.com/sp-api/reference/listtransactions)
- [Manage product listings](https://developer-docs.amazon.com/sp-api/docs/manage-product-listings-guide)
- [SP-API roles](https://developer-docs.amazon.com/sp-api/docs/roles-in-the-selling-partner-api)
## Environment variables
Copy `.env.example` to `.env.local` for local development. Generate independent random values of at least 32 characters for both MCP and mutation secrets.
| Variable | Required | Purpose |
| --- | --- | --- |
| `AMAZON_LWA_CLIENT_ID` | yes | Private SP-API LWA application client ID |
| `AMAZON_LWA_CLIENT_SECRET` | yes | LWA client secret; server only |
| `AMAZON_REFRESH_TOKEN` | yes | Seller self-authorization refresh token; server only |
| `AMAZON_SELLER_ID` | yes | Amazon selling partner ID |
| `AMAZON_MARKETPLACE_ID` | yes | Use `A21TJRUUN4KGV` for India |
| `AMAZON_SP_API_ENDPOINT` | yes | Use the EU regional endpoint for India |
| `ALLOW_ENV_AMAZON_FALLBACK` | optional | Must be `true` to use legacy environment Amazon credentials |
| `SUPABASE_URL` | yes | Supabase project URL |
| `SUPABASE_SECRET_KEY` | yes | Server-only Supabase secret key |
| `SUPABASE_SERVICE_ROLE_KEY` | alternative | Legacy alias when a new secret key is unavailable |
| `NEXT_PUBLIC_SUPABASE_URL` | admin UI | Browser-safe Supabase Auth URL |
| `NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY` | admin UI | Browser-safe publishable key |
| `NEXT_PUBLIC_SUPABASE_ANON_KEY` | alternative | Legacy browser-safe key |
| `CREDENTIAL_ENCRYPTION_KEY` | yes | 32-byte server-only AES-256-GCM master key |
| `HEALTH_CHECK_SECRET` | yes | Internal callback secret stored in Supabase Vault too |
| `MCP_AUTH_MODE` | yes | `shared-secret` for private development or `oauth-jwt` for production clients |
| `MCP_AUTH_SECRET` | shared-secret | Bearer token, minimum 32 characters |
| `MCP_OAUTH_ISSUER` | oauth-jwt | Exact OAuth issuer URL |
| `MCP_OAUTH_AUDIENCE` | oauth-jwt | Expected JWT audience |
| `MCP_OAUTH_JWKS_URL` | oauth-jwt | HTTPS JWKS used to verify access-token signatures |
| `MCP_PUBLIC_URL` | yes | Public origin, for example `https://fable-amazon.vercel.app` |
| `MCP_ALLOWED_ORIGINS` | optional | Comma-separated additional browser origins |
| `MUTATION_APPROVAL_SECRET` | yes | Separate HMAC secret for exact approvals |
| `MUTATION_APPROVAL_TTL_SECONDS` | yes | Approval lifetime; default `600` |
Never prefix these with `NEXT_PUBLIC_`. Do not put `.env.local` in source control.
## Supabase setup
Create a Supabase project, then apply both migrations in `supabase/migrations`:
```powershell
npx supabase@latest login
npx supabase@latest link --project-ref YOUR_PROJECT_REF
npx supabase@latest db push
```
All application tables have RLS enabled, privileges are revoked from `anon` and `authenticated`, and only the server-side service role has access. `mutation_audit_logs` has a database trigger that prevents updates and deletes.
Add a protected listing in the Supabase SQL editor:
```sql
insert into public.protected_listings
(seller_sku, reason, protection_level)
values
('REYO-WINNER-001', 'Current winner', 'FULLY_PROTECTED');
```
Protect every selected SKU that sold during the last 30 days at mutation time:
```sql
insert into public.dynamic_protection_rules
(rule_name, rule_type, protection_level, parameters)
values
('protect recent sellers', 'SOLD_WITHIN_DAYS', 'FULLY_PROTECTED', '{"days":30}');
```
Add exact SKU COGS:
```sql
insert into public.sku_costs
(seller_sku, product_family, print_cost, paper_cost, packaging_cost, tube_cost, labour_cost, other_cost)
values
('REYO-A3-001', 'A3 poster', 18, 7, 4, 6, 5, 0)
on conflict (seller_sku) do update set
print_cost = excluded.print_cost,
paper_cost = excluded.paper_cost,
packaging_cost = excluded.packaging_cost,
tube_cost = excluded.tube_cost,
labour_cost = excluded.labour_cost,
other_cost = excluded.other_cost,
updated_at = now();
```
Add a fallback family cost. Patterns use SQL `LIKE` semantics: `%` matches many characters and `_` matches one.
```sql
insert into public.product_family_costs
(product_family, sku_pattern, unit_cost)
values
('A3 poster', 'REYO-A3-%', 40)
on conflict (product_family) do update set
sku_pattern = excluded.sku_pattern,
unit_cost = excluded.unit_cost,
updated_at = now();
```
The seeded fee rule is deliberately narrow. Add or change rules in `fee_rules`; a rule can target a SKU pattern and/or ASIN, specify an expected range, flag a specific amount, and exempt a legitimate amount. Rules only report evidence—they never file claims or change account settings.
## Local development and verification
Node.js 22 or newer is required.
```powershell
Copy-Item .env.example .env.local
npm install
npm run dev
```
Then inspect `http://localhost:3000/api/health`. A `503 configuration_required` response is expected until all required variables are populated.
Run the complete verification suite:
```powershell
npm run typecheck
npm run lint
npm test
npm run build
```
The MCP endpoint is `http://localhost:3000/mcp`. In shared-secret mode, clients must send:
```text
Authorization: Bearer <MCP_AUTH_SECRET>
```
The endpoint implements current MCP Streamable HTTP semantics on one URL and accepts `POST`, `GET`, and `DELETE`. Origin validation, bearer authentication, request throttling, no-store response headers, typed tool errors, Zod request/upstream boundaries, and secret redaction are enforced.
## Deploy to Vercel
1. Push this repository to a private Git provider repository and import it into Vercel, or link it with `npx vercel link`.
2. Add every production variable from the table above in Vercel Project Settings → Environment Variables. Use `oauth-jwt` for a ChatGPT production connection.
3. Set `MCP_PUBLIC_URL` to the final HTTPS origin with no `/mcp` suffix.
4. Set `MCP_ALLOWED_ORIGINS` only for browser origins that genuinely need access.
5. Deploy with `npx vercel deploy --prod` or the linked Git production branch.
6. Verify `https://<project>.vercel.app/api/health`, OAuth metadata, and an authenticated MCP tool scan.
Deployment does not apply the Supabase migrations automatically. Run `supabase db push` against the production project before enabling tools.
Production MCP URL:
```text
https://<project>.vercel.app/mcp
```
## MCP authentication and client connection
`shared-secret` verifies one constant-time bearer token and grants both `amazon:read` and `amazon:write`. Use it only with a private client that supports custom authorization headers. A generic remote-MCP connection needs the deployed URL plus the bearer header.
`oauth-jwt` is the production resource-server mode. It validates signature, issuer, audience, expiry, client identity, and space-delimited scopes against a remote JWKS. Use `amazon:read` for intelligence-only clients and add `amazon:write` only for clients allowed to create previews or execute approvals.
This repository intentionally does not act as an OAuth authorization server. Supply an OAuth 2.1/OpenID Connect provider that exposes authorization-server metadata, PKCE-compatible authorization, JWKS, and—where the client requires it—dynamic client registration and refresh/offline access. Configure that provider to issue JWT access tokens for `MCP_OAUTH_AUDIENCE`; then set the three OAuth variables. The MCP route publishes protected-resource metadata and returns a standards-compatible `WWW-Authenticate` challenge.
See the [MCP authorization specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/authorization) and [Streamable HTTP transport specification](https://modelcontextprotocol.io/specification/2025-03-26/basic/transports).
### ChatGPT custom app
Current ChatGPT setup for an eligible account/workspace:
1. Deploy the server over HTTPS and configure `oauth-jwt` plus a compatible OAuth provider. The provider should issue refresh tokens and advertise `offline_access` if continuous connectivity is required.
2. Enable Developer mode. Workspace admins/owners can use Workspace settings → Apps → Create; authorized users can use Settings → Apps → Create where their plan/workspace permits it.
3. Enter `https://<project>.vercel.app/mcp`, select OAuth, complete authorization, and choose **Scan Tools**.
4. Review the discovered read and write actions, create the draft app, and test it in a new chat.
5. Publish only after the Amazon role set, protected listings, COGS, fee rules, approvals, and audits have been verified. Refresh/re-review actions whenever tool definitions change.
Full write-capable MCP support is currently plan/workspace dependent and the UI is still evolving. Consult OpenAI's current [Developer mode and MCP apps in ChatGPT](https://help.openai.com/en/articles/12584461) before rollout.
For an application using OpenAI's Responses API, pass this server as a remote MCP tool with its `server_url`, an OAuth access token in `authorization`, a restricted `allowed_tools` list where appropriate, and approval behavior suitable for the selected tools. See [OpenAI MCP and Connectors](https://developers.openai.com/api/docs/guides/tools-connectors-mcp).
## Read versus write permissions
- Read tools are annotated `readOnlyHint: true`; they can call Amazon and compute facts but cannot change Amazon.
- Report requests and snapshot captures write only job/history metadata and are annotated as non-read-only.
- Preview tools are non-destructive but require `amazon:write` because they prepare a possible external mutation and call Amazon validation where supported.
- `amazon_execute_approved_mutation` is explicitly destructive, requires `amazon:write`, an unexpired exact approval token, and the literal confirmation.
- Tool metadata helps clients display confirmations, but the server-side approval boundary remains authoritative even if a client ignores annotations.
## Operational workflow
A safe first production check is:
1. Call `amazon_review_account` without a report ID to verify live orders, listings, finance, fees, and COGS coverage. Traffic is explicitly `UNAVAILABLE`, not guessed.
2. Call `amazon_business_report`; poll `amazon_report_status`; then use its ID with `amazon_sales_and_traffic`, `amazon_conversion_analysis`, or `amazon_review_account` after it is ready.
3. Call `amazon_money_added_yesterday` and `amazon_shipping_fee_audit` for financial reconciliation and the ₹212.40 check.
4. Capture scheduled financial history by invoking `amazon_capture_financial_snapshot` from a trusted scheduler, then query `amazon_financial_snapshots` between two timestamps.
5. Test a protected dummy SKU and a non-protected dummy SKU with a preview. Inspect the exact diff and validation output before executing.
## Admin panel and encrypted credential management
The private panel is protected by Supabase Auth plus an `admin_users` allowlist. Create the first Auth user in Supabase Dashboard → Authentication → Users, then authorize that user from the SQL editor:
```sql
insert into public.admin_users (user_id)
select id from auth.users where email = 'your-admin@example.com'
on conflict (user_id) do update set enabled = true;
```
Admin routes:
- `/admin/login` — Supabase Auth email/password login
- `/admin` — private control panel
- `/admin/amazon` — encrypted Amazon connection configuration and **Test connection**
- `/admin/system` — dependency state and 30-day connection history
- `/admin/audit` — administrator credential/connection events
`CREDENTIAL_ENCRYPTION_KEY` is a Vercel-only 32-byte base64/hex key. Amazon client ID, client secret, and refresh token are stored as AES-256-GCM ciphertext with per-value random IVs and authentication tags. The key is never placed in Supabase. Existing secrets are never rendered into HTML or API responses; blank secret fields retain their ciphertext. Updating credentials invalidates the in-process credential/access-token cache, so the next Amazon request authenticates with the new values without a redeploy.
The server uses this precedence: active encrypted `amazon_connections` row → environment credentials only when `ALLOW_ENV_AMAZON_FALLBACK=true` → `AMAZON_CONFIG_MISSING`. It never silently combines database and environment fields.
## Supabase scheduled health monitoring
The final migration enables Supabase `pg_cron`, `pg_net`, and Vault, creates the `fable-amazon-connection-health-30m` job, and creates a daily 90-day retention job. The cron job does not keep an Amazon session alive: it invokes `/api/internal/amazon-health`, which obtains an LWA token only for the read-only check, performs one bounded Orders API request, stores the result, and exits.
After the Vercel URL and `HEALTH_CHECK_SECRET` are configured, store the callback URL and callback secret in Supabase Vault (never in the migration or Git):
```sql
select vault.create_secret(
'https://YOUR_PROJECT.vercel.app/api/internal/amazon-health',
'fable_amazon_health_url'
);
select vault.create_secret(
'YOUR_HEALTH_CHECK_SECRET_VALUE',
'fable_amazon_health_secret'
);
```
The callback classifies `HEALTHY`, `AUTH_FAILED`, `RATE_LIMITED`, `AMAZON_UNAVAILABLE`, `CONFIG_MISSING`, and `UNKNOWN_ERROR`. A 429 never invalidates credentials; temporary 5xx responses are recorded as Amazon unavailable. Supabase Cron records job runs in its dashboard; inspect the two named jobs and the `amazon_connection_health` table after applying the migration. This follows Supabase’s current [scheduled Edge Function](https://supabase.com/docs/guides/functions/schedule-functions), [Cron](https://supabase.com/docs/guides/cron), and [Vault](https://supabase.com/docs/guides/database/vault) patterns.
## GitHub and Vercel Git integration
`vercel.json` pins Next.js framework detection, `npm ci`, and `npm run build`. `.github/workflows/ci.yml` runs typecheck, lint, tests, and build on pushes and pull requests. `.github/workflows/secret-scan.yml` runs Gitleaks. Connect the GitHub repository from Vercel Project Settings → Git; Vercel then creates previews for branches/PRs and production deployments for the selected production branch. Put all values from `.env.example` into Vercel’s encrypted Environment Variables UI. No secret belongs in `vercel.json` or GitHub workflow files.
Before the first push, inspect tracked files and run a secret scan. The repository ignores `.env`, `.env.local`, `.env.production`, `.vercel`, Supabase CLI state, and build output; only `.env.example` is intended to be committed.
## Known limitations and production gates
- No live Amazon call has been made because Amazon account credentials were not supplied. The Supabase connector is authenticated, but creation of the new project is pending confirmation of the `sellerboardv2` organization (quoted project cost: $0/month); migrations must be applied and verified there before production use. The deployed integration must then be verified with read-only tools and a controlled test SKU.
- A compatible external OAuth authorization server is required for production ChatGPT OAuth. JWT resource-server validation is included; authorization/login/consent pages are not.
- Sales/traffic requires Amazon's Brand Analytics role and an asynchronously generated ready report. `amazon_review_account` will not invent those metrics when absent.
- Amazon notes that Finances results may omit recent transactions for up to 48 hours. Movement output therefore reports freshness/confidence and can increase later.
- Snapshots exist only after a trusted caller or scheduler captures them. No historical balance is backfilled.
- Large listing feeds are asynchronous. Submission success does not mean every SKU was accepted; inspect `amazon_feed_status` and the Amazon result document in Seller Central. Result-document parsing for feeds is a future extension.
- Product Type Definitions schema discovery is not yet automated. Create/patch requests require the caller to supply the correct Amazon product type and attributes; Amazon validation preview runs for direct batches.
- Account review currently searches one Listings Items result stream with the API's pagination and accepts one ready traffic report. Very large catalogs should add persisted snapshot jobs and incremental report ingestion.
- Event-driven Amazon Notifications is intentionally deferred until core live reliability is proven.
## Security notes
- Keep the repository private and rotate any secret accidentally exposed outside Vercel/Supabase secret storage.
- Use separate dev and production Amazon authorizations, Supabase projects, OAuth clients, and approval secrets.
- Restrict OAuth scopes and tool access per client. A read-only AI should never receive `amazon:write`.
- Monitor `tool_run_logs`, immutable `mutation_audit_logs`, Amazon request IDs, feed results, and Vercel function errors.
- Treat MCP clients and report content as untrusted input. Review every bulk SKU list and diff; never bypass the approval service.
This server cannot be deployed
Maintenance
ActivityMaintained
ResponsivenessNo issues