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.

Note: The AI tool can't find or call custom API routes under other prefixes, such as /custom, or API routes defined in plugins. 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.
Warning: The AI tool can't call /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:

src/api/middlewares.ts
import {
  defineMiddlewares,
  validateAndTransformQuery,
} from "@medusajs/framework/http"
import { z } from "@medusajs/framework/zod"

export const AdminGetAbandonedCartsParams = z.object({
  /**
   * Return carts not updated in this number of hours.
   */
  hours: z.coerce.number().min(1).default(24),
  /**
   * The maximum number of carts to return.
   */
  limit: z.coerce.number().max(100).default(20),
  /**
   * The number of carts to skip.
   */
  offset: z.coerce.number().default(0),
})

export type AdminGetAbandonedCartsParamsType = z.infer<
  typeof AdminGetAbandonedCartsParams
>

export default defineMiddlewares({
  routes: [
    {
      matcher: "/admin/abandoned-carts",
      methods: ["GET"],
      middlewares: [
        validateAndTransformQuery(
          AdminGetAbandonedCartsParams,
          {}
        ),
      ],
    },
  ],
})

The schema accepts the following query parameters:

  • hours: Carts that weren't updated within this number of hours are considered abandoned.
  • limit and offset: 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:

src/api/admin/abandoned-carts/route.ts
import {
  AuthenticatedMedusaRequest,
  MedusaResponse,
} from "@medusajs/framework/http"
import {
  ContainerRegistrationKeys,
} from "@medusajs/framework/utils"
import {
  AdminGetAbandonedCartsParamsType,
} from "../../middlewares"

type AbandonedCart = {
  id: string
  email: string | null
  currency_code: string
  updated_at: Date
  items: {
    title: string
    quantity: number
    unit_price: number
  }[]
}

type AdminAbandonedCartsResponse = {
  carts: AbandonedCart[]
  count: number
  limit: number
  offset: number
}

/**
 * List abandoned carts
 *
 * Retrieve carts with an email that customers didn't
 * complete or update within the specified number of hours.
 */
export const GET = async (
  req: AuthenticatedMedusaRequest<
    unknown,
    AdminGetAbandonedCartsParamsType
  >,
  res: MedusaResponse<AdminAbandonedCartsResponse>
) => {
  const query = req.scope.resolve(
    ContainerRegistrationKeys.QUERY
  )
  const { hours, limit, offset } = req.validatedQuery

  const abandonedBefore = new Date(
    Date.now() - hours * 60 * 60 * 1000
  )

  const { data: carts, metadata } = await query.graph({
    entity: "cart",
    fields: [
      "id",
      "email",
      "currency_code",
      "updated_at",
      "items.title",
      "items.quantity",
      "items.unit_price",
    ],
    filters: {
      updated_at: {
        $lt: abandonedBefore,
      },
      email: {
        $ne: null,
      },
      completed_at: null,
    },
    pagination: {
      skip: offset,
      take: limit,
    },
  })

  res.json({
    carts: carts as AbandonedCart[],
    count: metadata?.count ?? 0,
    limit,
    offset,
  })
}

The API route uses the hours, limit, and offset query parameters to retrieve the abandoned carts with Query. It also follows the best practices that help the AI tool find and call it:

  • The comment above the GET function describes the API route.
  • The second type argument of AuthenticatedMedusaRequest declares the query parameters.
  • The API route paginates its response.

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.

Note: When you update an API route's code, comments, or types, deploy the changes again so the AI tool uses the updated API route.

Best Practices for Custom API Routes#

Medusa doesn't run your API routes to index them. Instead, it reads each API route's file during the build to describe the API route to the AI tool:

  • The API route's path, from the file's location under src/api.
  • The API route's summary and description, from the comment above the exported GET function.
  • The API route's parameters, from the TypeScript types of the request and the comments on their properties.

The AI tool searches the summaries, descriptions, paths, and parameter names to find an API route for your question. Then, it reads the description and parameters to decide how to call it. So, the more your API route's comments and types describe it, the better the AI tool's answers.

Follow these best practices when you create API routes for the AI tool.

Describe the API Route in a Comment#

Add a comment above the exported GET function. Medusa uses the comment's first line as the API route's summary, and the rest of the comment as its description:

src/api/admin/abandoned-carts/route.ts
/**
 * List abandoned carts
 *
 * Retrieve carts with an email that customers didn't
 * complete or update within the specified number of hours.
 */
export const GET = async () => {}

When you write the comment:

  • Use the words that you'd use when asking about the data, such as "abandoned carts". The AI tool matches your question against these words.
  • Describe the data that the API route returns, and any path parameters, such as {id}. The AI tool reads the description, but it doesn't receive the response's shape or a description of path parameters.
  • Write the comment above the GET function, not above a function that it calls. Medusa only reads the comment of the exported function.

Without a comment, Medusa generates a generic summary from the API route's method and path, such as "List abandoned carts", and the AI tool has less information to find the API route.

Pass the Query Parameters as a Type Argument#

Pass the query parameters' type as the second type argument of MedusaRequest or AuthenticatedMedusaRequest:

src/api/admin/abandoned-carts/route.ts
export const GET = async (
  req: AuthenticatedMedusaRequest<
    unknown,
    AdminGetAbandonedCartsParamsType
  >,
  res: MedusaResponse<AdminAbandonedCartsResponse>
) => {
  // ...
}

Medusa reads the query parameters from this type argument, not from the validation middleware. So, you need both the middleware to validate the parameters, and the type argument for the AI tool to know about them. Without the type argument, the AI tool doesn't know which parameters the API route accepts.

You can infer the type from the Zod schema with z.infer, as in the example, so that you define the parameters once.

Pass the Request Body as a Type Argument#

For POST API routes, pass the request body's type as the first type argument of MedusaRequest or AuthenticatedMedusaRequest:

src/api/admin/brands/route.ts
type AdminCreateBrandBody = {
  /**
   * The brand's name.
   */
  name: string
}

/**
 * Create a brand
 */
export const POST = async (
  req: MedusaRequest<AdminCreateBrandBody>,
  res: MedusaResponse
) => {
  // ...
}

Medusa indexes the request body's type with the API route, similar to query parameters.

Note: The Medusa MCP server is read-only, so the AI tool currently only calls GET API routes.

Document Each Parameter#

Add a comment above each property of the query parameters' type or Zod schema. Medusa uses the comment as the parameter's description, which tells the AI tool which values to pass:

src/api/middlewares.ts
export const AdminGetAbandonedCartsParams = z.object({
  /**
   * Return carts not updated in this number of hours.
   */
  hours: z.coerce.number().min(1).default(24),
})

Medusa doesn't read descriptions that you set with Zod's describe method. Use comments instead.

Use Explicit Types#

Medusa can only describe the parameters that it can resolve from your types:

  • Don't use any or unknown for the query parameters or request body. Medusa can't describe their properties, so the AI tool doesn't know which parameters to pass.
  • Avoid index signatures and Record types, such as Record<string, string>, for parameters. Medusa describes them as a generic object without properties.
  • Keep types shallow. Medusa truncates deeply nested or very large types.

If Medusa can't resolve your project's types during the build, it still indexes your API routes, but without their parameters.

Paginate List Responses#

Accept limit and offset query parameters, and return count, limit, and offset in the response, like Medusa's list API routes. The AI tool uses them to page through results.

Return Only the Data the AI Tool Needs#

Smaller responses are faster for the AI tool to process, and leave room in its context for your question. Return only the fields that answer the questions that the API route is for, such as with the fields option of Query.


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 /admin or /store, and its folder names don't start with an underscore (_).
  • The API route's file exports a GET function.
  • 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 API route is defined in your Medusa application, not in a plugin.
  • The comment above the GET function 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-carts API route".

AI Tool Doesn't Pass the Right Query Parameters#

If the AI tool calls your API route without the query parameters that your question needs, make sure that:

  • You pass the query parameters' type as the second type argument of MedusaRequest or AuthenticatedMedusaRequest, as explained in the best practices.
  • Each property in the type or Zod schema has a comment that describes it.
  • The environment built and deployed after you changed the types.

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.

Was this guide helpful?
Ask Bloom
For assistance in your development, use Claude Code Plugins or Docs MCP server in Cursor, VSCode, etc...FAQ
What is Medusa?
How can I create a module?
How can I create a data model?
How do I create a workflow?
How can I extend a data model in the Product Module?
Recipes
How do I build a marketplace with Medusa?
How do I build digital products with Medusa?
How do I build subscription-based purchases with Medusa?
What other recipes are available in the Medusa documentation?
Chat is cleared on refresh
Line break
⇧↵