One route() is four surfaces.
June is the opinionated, agent-ready React framework. A single route() serves a page to people and .md, .json, and /mcp to agents — from one definition. Nothing drifts, because nothing is duplicated.
The opinionated, agent-ready React framework.
HTML to people; .md, .json, and /mcp to agents — derived, never maintained by hand.
Auth, data, and agents are one model. run(input, ctx) is the only gate.
Writes auto-invalidate the cache; reads auto-batch into one query.
01 — One definition
Write it once. Serve humans and agents alike.
Every route() projects four surfaces, and every defineAction() is a UI action and an MCP tool. The projections are derived — never maintained by hand.
Human view
Server-streamed RSC, zero client JS by default.
Agent reads
The same route(), as clean markdown — append .md to any page.
Machine API
The same route() as structured data.
Agent acts
Every defineAction() is a callable tool, one auth gate.
02 — No glue layer
Auth, data, and agents are one model.
There is no "expose to agents" step and no second permission system. A defineAction() is a server action and an MCP tool behind a single authorization gate — run(input, ctx) is the only gate.
- one gaterun(input, ctx) — the same ctx whether the caller is your React UI or an agent at /mcp.
- no adapteryou wire no adapter matrix. The route graph derives the API catalog, sitemap, and llms.txt.
- authBetter Auth is the blessed default (a first-class integration is coming soon) — or bring your own. Either way the gate is the same.
import { db } from "@junejs/db"; // ambient + scoped — never on ctx
export const createUser = defineAction({
id: "createUser",
description: "Create a user", // → an MCP tool
input: { name: "string" },
run: (input, ctx) => {
// ctx is the principal (user/session) — UI or agent.
return db.users.insert(input);
},
});03 — Agents as scoped users
Watch an agent use your app.
No SDK, no tool re-declaration. Your routes and actions are the surface — an agent connects as a scoped principal and works the same authorization the UI does.
- /mcpevery action, as a callable tool
- /llms.txtthe site map an agent reads first
- *.mdyour authored bytes, verbatim
04 — Data
Declare it once. The layer does the rest.
Declare db / kv / blob in your config; reach them ambiently with import { db } — scoped per request, plain SQL, no ORM ceremony. It maps to D1, Turso, or local SQLite per target — or bring your own.
- ambient dbimport { db }, scoped per request — ctx is identity-only (user/session), never the database.
- plain SQLthe SQL you read is the SQL that runs — no DSL for a human or an agent to misread.
- writesauto-invalidate the cache — no revalidatePath, no tags to remember.
- reads8.8× fewer queries on D1 vs concurrent per-component reads, auto-batched.
async function Page() { // N reads in N components → 1 batched query const user = await db.users.find(id); const posts = await db.posts.byAuthor(id); return <Profile user={user} posts={posts} />; } // a write anywhere auto-invalidates the reads await db.posts.insert({ ... }); // cache: stale ✓
05 — Start
Ship in an afternoon.
Four commands from empty directory to an app deployed at the edge — already speaking to humans and agents.
06 — Measured, not marketing
Every number traces to a named run.
Your app is the agent's API.
One React framework for both audiences. 0.0.x preview — APIs will change, and we'll tell you when.