MainSDK
Search⌘K
How-to

Expose an action to agents

Write an action once and expose it as an endpoint and an MCP tool with the same checks.

Preview

Status Status is where the work is: Planned (in a milestone, not started), Building (tickets in progress), Preview (usable, may change), then the stability it ships at.

Stability The promise an interface ships with.

  • Stable Follows semantic versioning; breaking changes need a new major, 12 months of overlap and a codemod.
  • Beta Usable in production; may change before it becomes stable.
  • Experimental May change or go away; needs an explicit opt-in in main.yaml.
Stability levels
Updated 2026-10-28

You will write one action, expose it over MCP and require a person's yes for a destructive one. It takes about 15 minutes.

You need

  • A route or job that already does the work you want to expose.

1. Write the action

app/actions/refund-order.ts
import { action, models, pay } from "mainsdk";
import { s } from "mainsdk/schema";

const { orders } = models({ orders: s.object({ paymentId: s.string() }) });

export const refundOrder = action({
  name: "orders.refund",
  intent: "Refund an order the signed-in user owns",
  input: s.object({ orderId: s.string(), reason: s.string() }),
  risk: "destructive",
  mcp: true,
  run: async ({ input, user }) => {
    const order = await orders.for(user!).get(input.orderId);
    return pay.refund(order!.paymentId, { reason: input.reason });
  },
});

2. Expose it

main.yaml
mcp:
  expose: [orders.refund]
  confirm: { write: true, destructive: true }
app/api/mcp/route.ts
import { mcpServer, type Action } from "mainsdk";

declare const refundOrder: Action<{ orderId: string; reason: string }, unknown>;   // from app/actions/refund-order.ts

export const POST = mcpServer({ actions: [refundOrder] });

Check it worked

An MCP client lists orders.refund. Calling it as an agent waits for a person's yes, because it is destructive.

Next

Was this page helpful?

↑↓ moveEnter openEsc close

Not decided yet