Auth & the scoped principal
One authorization gate for both audiences — run(input, ctx). ctx is the principal; an agent at /mcp is just another caller through the same gate.
One gate, both audiences
June has exactly one authorization gate, and both your UI and an agent pass
through it: a defineAction()'s run(input, ctx).
export const deletePost = defineAction({
id: "deletePost",
input: { id: "string" },
run: async (input, ctx) => {
const post = await db.posts.find(input.id);
if (post.authorId !== ctx.user?.id) throw new Error("forbidden");
return db.posts.delete(input.id);
},
});
There is no "expose to agents" step and no second permission system. The action is a server action and an MCP tool; the check you write once protects both.
ctx is the principal
ctx answers who is calling — ctx.user, ctx.session, and the request
(url, params). That is identity, and identity is all ctx carries: data
lives on the ambient db instead (see the data model), so
the only reason to reach for ctx is authorization.
An agent is just another caller
When an agent calls a tool at /mcp, it carries the caller's credential, so the
SAME run(input, ctx) sees the SAME ctx it would for a UI request — and runs
the SAME check. The agent is a scoped principal: it can do exactly what that
user can do, no more. Nothing is re-declared; no tool gets a parallel ACL.
UI button ─┐
├─ run(input, ctx) ← one gate, one ctx, one check
agent /mcp ─┘
Better Auth by default
Authentication itself — sessions, providers, the login flow — is Better
Auth, June's blessed default. A first-class integration (wiring a Better Auth
session straight into ctx as the principal, and into /mcp scoping) is
coming soon. Until then you can wire it by hand, and bring-your-own auth
works today — June only needs you to populate ctx.user / ctx.session; the
gate doesn't care who issued the session.
Why it matters
The expensive bug in an agent-ready app is an authorization gap between what the UI enforces and what the tool endpoint allows. June closes it by construction: there is only one gate, so there is nothing to keep in sync.
Status: Changing — the model (one gate,
ctx-as-principal) is settled; the Better Auth integration is in progress. See Stability.