GreekNous
← All posts

Building Shopigent Returns

After shipping Shopigent — a general-purpose Shopify MCP app — I wanted to build something more focused. A product that solves one specific merchant pain point end-to-end, using the same agent-first approach but with a tighter scope.

Returns management was the obvious candidate. Every merchant I've talked to spends too much time on it. The decision criteria are simple enough for an agent but the volume is high enough to make automation valuable. Here's how I built it.

The architecture

Shopigent Returns is a standard Shopify embedded app with three runtime components:

1. The Embedded App (Remix + Polaris) — this is what the merchant installs and sees in their Shopify admin. It houses the dashboard, policy CRUD, analytics, and settings pages. Built on @shopify/shopify-app-remix with Prisma + PostgreSQL on Railway.

2. The MCP Server — a standalone Express server that speaks the Model Context Protocol over HTTP. It exposes 7 tools: analyze_return, approve_return, deny_return, check_fraud, execute_refund, list_returns, get_return_details, get_policy, and update_policy. The agent calls these to process returns autonomously.

3. The Docs Site — an Express + markdown zero-build site (same pattern as greeknous.com). Content is plain markdown with frontmatter, rendered on the fly. Docs, pricing, changelog — all editable as markdown files.

The agent loop

The core workflow looks like this:

  1. Customer submits via /return portal — email + order + items + reason
  2. Webhook firesORDERS_FULFILLED also triggers return eligibility setup
  3. Agent picks up — on a schedule or on-demand, the agent calls analyze_return with the return ID
  4. Policy evaluation — the agent checks the return against the merchant's configured policies (time window, amount cap, product exclusions, restocking fees)
  5. Fraud check — IP reputation, velocity (same customer returning multiple times in short window), amount anomalies
  6. Decision — approve (auto-refund + label) or deny (with reason) or flag (needs human review)
  7. Execution — on approval, the agent calls execute_refund which hits the Shopify Admin API and triggers email + label generation

The merchant only steps in for flagged edge cases. Everything else runs without intervention.

Design decisions

Policy engine first. Before the agent does anything useful, the merchant needs to define what "acceptable" means. I built the policy CRUD first — time windows, max amounts, restocking fees, auto-approve vs flag, product type exclusions. The agent is useless without good policies backing it.

Stateless confirmation tokens. Same pattern as Shopigent: HMAC-signed tokens for confirming destructive actions. Zero server-side state, horizontally scalable.

MCP over custom API. Using the Model Context Protocol means any MCP-compatible agent can drive the system — not just a custom frontend. This was the right call: it lets merchants use whatever AI tooling they already have.

Self-service portal as primary UX. Instead of building a complex admin-side return creation flow, I built a simple customer-facing portal. Merchants share the link, customers initiate themselves. Less work on both sides.

What I'd do differently

Start with fewer features. V1 has analytics, labels, fraud detection, email notifications. In retrospect I should have shipped just the core loop (submit → analyze → approve/deny → refund) and added the rest post-launch. The extra features delayed getting real merchant feedback.

Make the docs site from day one. I made this mistake with Shopigent too. The docs site is critical because agents read it (via /llms.txt and the content API) to learn how to use the MCP tools. I built it early this time, but not before I started coding the MCP server.

What's next

The app is live at returns-docs-production.up.railway.app. The Shopify App Store listing is being prepared. If you run a Shopify store and returns eat your time — this is the product I wish existed when I was processing them manually.