Retrieve Custom Data with Medusa MCP
In this guide, you'll learn how to expose custom data from your store to AI tools connected to the Medusa MCP server, using abandoned carts as an example.
Why Add Custom API Routes?#
The Medusa MCP server retrieves data by calling your store's GET API routes. Medusa's Admin and Store API routes cover common requests, such as listing orders or products. However, some questions need data that no existing API route returns, such as:
- Carts that customers abandoned in the last day.
- Products that haven't sold in the last month.
- Data from your own custom modules, such as brands or suppliers.
To answer these questions, create a custom GET API route under the /admin or /store prefix that returns the data. After you deploy the API route to Cloud, the AI tool finds and calls it when you ask a related question.
/custom. It also can't call POST or DELETE API routes, since the Medusa MCP server is read-only.Choose the API Route Prefix#
The prefix of your API route determines how the AI tool calls it:
/admin: The AI tool calls the API route as your admin user. Use this prefix for data that only admin users should access, such as abandoned carts or customer details./store: The AI tool calls the API route as a guest customer with a publishable API key. Use this prefix for data that shoppers can access, such as products in a sales channel.
/store API routes that require a logged-in customer. If the AI tool needs that data, expose it in an /admin API route instead.Example: Retrieve Abandoned Carts#
In this section, you'll create an API route that returns abandoned carts. You can then ask your AI tool questions like "Which carts were abandoned in the last 24 hours?".
Step 1: Add the Validation Middleware#
First, define a Zod schema that validates the API route's query parameters, and apply it to the API route in src/api/middlewares.ts:
1import {2 defineMiddlewares,3 validateAndTransformQuery,4} from "@medusajs/framework/http"5import { z } from "@medusajs/framework/zod"6 7export const AdminGetAbandonedCartsParams = z.object({8 /**9 * Return carts not updated in this number of hours.10 */11 hours: z.coerce.number().min(1).default(24),12 /**13 * The maximum number of carts to return.14 */15 limit: z.coerce.number().max(100).default(20),16 /**17 * The number of carts to skip.18 */19 offset: z.coerce.number().default(0),20})21 22export type AdminGetAbandonedCartsParamsType = z.infer<23 typeof AdminGetAbandonedCartsParams24>25 26export default defineMiddlewares({27 routes: [28 {29 matcher: "/admin/abandoned-carts",30 methods: ["GET"],31 middlewares: [32 validateAndTransformQuery(33 AdminGetAbandonedCartsParams,34 {}35 ),36 ],37 },38 ],39})
The schema accepts the following query parameters:
hours: Carts that weren't updated within this number of hours are considered abandoned.limitandoffset: Paginate the returned carts, similar to Medusa's list API routes.
The comments on each property help the AI tool understand what the parameter does.
The middleware validates the query parameters and sets them in the req.validatedQuery property. Learn more in the Request Validation chapter.
Step 2: Create the API Route#
Next, create the API route that retrieves the abandoned carts. Create the file src/api/admin/abandoned-carts/route.ts with the following content:
1import {2 AuthenticatedMedusaRequest,3 MedusaResponse,4} from "@medusajs/framework/http"5import {6 ContainerRegistrationKeys,7} from "@medusajs/framework/utils"8import {9 AdminGetAbandonedCartsParamsType,10} from "../../middlewares"11 12type AbandonedCart = {13 id: string14 email: string | null15 currency_code: string16 updated_at: Date17 items: {18 title: string19 quantity: number20 unit_price: number21 }[]22}23 24type AdminAbandonedCartsResponse = {25 carts: AbandonedCart[]26 count: number27 limit: number28 offset: number29}30 31/**32 * List abandoned carts33 *34 * Retrieve carts with an email that customers didn't35 * complete or update within the specified number of hours.36 */37export const GET = async (38 req: AuthenticatedMedusaRequest<39 unknown,40 AdminGetAbandonedCartsParamsType41 >,42 res: MedusaResponse<AdminAbandonedCartsResponse>43) => {44 const query = req.scope.resolve(45 ContainerRegistrationKeys.QUERY46 )47 const { hours, limit, offset } = req.validatedQuery48 49 const abandonedBefore = new Date(50 Date.now() - hours * 60 * 60 * 100051 )52 53 const { data: carts, metadata } = await query.graph({54 entity: "cart",55 fields: [56 "id",57 "email",58 "currency_code",59 "updated_at",60 "items.title",61 "items.quantity",62 "items.unit_price",63 ],64 filters: {65 updated_at: {66 $lt: abandonedBefore,67 },68 email: {69 $ne: null,70 },71 completed_at: null,72 },73 pagination: {74 skip: offset,75 take: limit,76 },77 })78 79 res.json({80 carts: carts as AbandonedCart[],81 count: metadata?.count ?? 0,82 limit,83 offset,84 })85}
Keep the following in mind when you create API routes for the AI tool:
- Add a comment above the
GETfunction: Medusa uses the comment's first line as the API route's summary and the rest as its description. The AI tool searches these to find the API route that matches your question, so use words you'd use when asking, such as "abandoned carts". - Type the request and response: Medusa reads the query parameters from the request's type, such as the second type argument of
AuthenticatedMedusaRequest, and the response's shape fromMedusaResponse. Without these types, the AI tool doesn't know which parameters the API route accepts. - Paginate list responses: Accept
limitandoffsetquery parameters, and returncount,limit, andoffsetin the response, like Medusa's list API routes. The AI tool uses them to page through results, and smaller responses are faster to process. - Keep the response shallow: Medusa truncates the documented response shape of deeply nested or very large types. Return only the fields the AI tool needs.
Deploy and Ask Your AI Tool#
Medusa indexes your store's API routes, including custom ones, each time the environment builds. So, the AI tool can only find your new API route after you deploy it.
To deploy the API route, push your changes to the branch of the environment, as explained in the Deployments guide. Once the deployment is live, ask your AI tool a question that the API route answers. For example:
- "Which carts were abandoned in the last 24 hours in my store?"
- "List the emails of customers who abandoned their carts in the last 3 days, with their cart items."
The AI tool searches your store's API routes, finds the /admin/abandoned-carts API route, and calls it with the hours query parameter based on your question.
Troubleshooting#
AI Tool Doesn't Find the Custom API Route#
If the AI tool doesn't find your custom API route, make sure that:
- The API route's path starts with
/adminor/store, and its folder names don't start with an underscore (_). - The API route's file exports a
GETfunction. - The environment built and deployed after you added the API route. Redeploying an existing deployment reuses its previous build, so Medusa doesn't index the new API route.
- The comment above the
GETfunction describes the data with the words you use in your question. You can also mention the API route's path in your question, such as "Use the/admin/abandoned-cartsAPI route".
Store API Route Returns an Authentication Error#
The AI tool calls /store API routes as a guest customer. If your /store API route requires a logged-in customer, the AI tool can't call it. Expose the data in an /admin API route instead.
Admin API Route Returns a Permission Error#
The AI tool calls /admin API routes as your admin user. If your API route applies custom permission checks, make sure your admin user is allowed to access it.