---
title: "Expose an action to agents"
description: "Write an action once and expose it as an endpoint and an MCP tool with the same checks."
section: "guides"
status: "Preview"
spec: "mcp-actions"
canonical: "https://mainsdk.dev/docs/guides/expose-an-action-to-agents"
updated: "2026-10-28"
---

# Expose an action to agents

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

Status: Preview · Canonical: https://mainsdk.dev/docs/guides/expose-an-action-to-agents · 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

```ts title="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

```yaml title="main.yaml"
mcp:
  expose: [orders.refund]
  confirm: { write: true, destructive: true }
```

```ts title="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

- [Actions and agent tools](https://mainsdk.dev/docs/interfaces/action)
- [Govern app agents](https://mainsdk.dev/docs/agents/governance)
