Built-in MCP
The feature
Turn on agent.mcp (it's on by default) and your app serves MCP at /mcp —
no separate server, no adapter, no tool re-declaration:
import { db } from "@junejs/db";
export const createUser = defineAction({
id: "createUser",
description: "Create a user", // description → listed as an MCP tool
input: { type: "object", properties: { name: { type: "string" } }, required: ["name"] },
run: ({ name }, ctx) => {
// ctx carries the principal (identity) — the SAME ctx whether the caller
// is your React UI or an agent calling /mcp. Authorize here, once; the db
// is ambient (import { db }), not threaded through ctx.
return db.run("insert into users (name) values (?)", [name]);
},
});
One defineAction() is simultaneously a server action (pass it to client
components as a prop), an MCP tool (auto-listed with its schema), and a
manifest entry agents discover. There is no "expose to agents" step and
no second permission system: run(input, ctx) is the only gate, so an agent
can never do anything your UI's authorization wouldn't allow.
Try it on this site
This site's search is an action. Call it the way an agent would:
curl -X POST https://june.build/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":1,"method":"tools/list","params":{}}'
curl -X POST https://june.build/mcp \
-H 'content-type: application/json' \
-d '{"jsonrpc":"2.0","id":2,"method":"tools/call","params":{"name":"search_site","arguments":{"query":"cold start"}}}'
Why it matters
Tools are intent-shaped and policy-checked — never auto-generated CRUD. The agent surface is exactly as capable as you declared, and exactly as authorized as your UI.