A small, self-contained Mastra project used to demo the Agent Editor in Studio:
non-technical agent authoring, versioning, RBAC, dynamic behavior via
requestContext, and programmatic control.
src/
├── mastra/
│ ├── index.ts # Mastra entry — registers editor, MCP, Studio auth
│ ├── auth.ts # SimpleAuth tokens + MastraRBACStudio role mapping
│ ├── agents/
│ │ └── customer-support.ts # The code-defined agent everyone overrides
│ ├── tools/
│ │ ├── lookup-order.ts # Local `createTool` — code-defined tool
│ │ └── issue-credit.ts # Local tool intentionally gated via the editor
│ └── mcp/
│ └── server.ts # In-process MCP server with a search-knowledge-base tool
└── scripts/
├── seed-prompt-blocks.ts # Pre-creates the demo prompt blocks
└── programmatic-block.ts # "Studio is just a UI" — creates a block via code
The customer-support agent has:
- A
requestContextSchemawithcustomerName,tier(free | premium), anduserRole(user | admin) — the workshop demos how this drives prompt blocks and conditional tools. - Two local tools:
lookup-orderandissue-credit. - One MCP-served tool:
search-knowledge-base. - An integration provider (Arcade) registered with the editor — so during the demo Alex can add e.g. Gmail or Slack tools from Studio without code.
pnpm install
cp .env.example .env
# Then edit .env and fill in OPENAI_API_KEY (required) plus optionally
# ARCADE_API_KEY.pnpm seed creates the Tone of Voice and Premium-tier perks prompt
blocks. Re-running it is safe — it skips ones that already exist.
pnpm dev # mastra dev — local Studio at http://localhost:4111
pnpm build # bundles the project for production-style Studio
pnpm start # runs the bundled output
pnpm seed # seeds the demo prompt blocks
pnpm programmatic # demos creating a block from code
pnpm typecheck
pnpm clean # nukes mastra.db and .mastraThe dev server runs SimpleAuth (from @mastra/core/server) so the workshop
demos auth + RBAC locally with no WorkOS / shared-API setup. Three pre-baked
tokens map to three roles:
| Token | Role | What they can do |
|---|---|---|
admin-token |
admin |
Everything (the developer hat) |
editor-token |
editor |
Read + execute everything; create, edit, and publish drafts |
viewer-token |
viewer |
Read-only across the app; can chat with agents but cannot save |
Try it from the terminal:
# Viewer can list, but cannot delete:
curl -H "Authorization: Bearer viewer-token" http://localhost:4111/api/stored/agents
curl -X DELETE -H "Authorization: Bearer viewer-token" \
http://localhost:4111/api/stored/agents/customer-support-agent
# → {"error":"Forbidden"} (HTTP 403)
# Editor can:
curl -X DELETE -H "Authorization: Bearer editor-token" \
http://localhost:4111/api/stored/agents/customer-support-agentTo use the tokens in Studio, set the mastra-token cookie via DevTools, or
hit /api/auth/sign-in with the token as the password field. See
src/mastra/auth.ts for the role → permission mapping.
Permissions come from @mastra/core — stored-agents:write, stored:*, etc.
Today the framework does not split publish from write: both save draft
and activate version resolve to stored-agents:write, so the editor role
can publish too. That's a teachable moment, not a bug.
For production, swap SimpleAuth for MastraAuthStudio (with WorkOS) — the
MastraRBACStudio role mapping stays exactly the same.
The demo is split between two presenters:
- Daniel — developer host. Drives the IDE, runs the deploy, narrates code.
- Alex — non-technical user persona. Drives Studio, edits the agent.
pnpm install && pnpm seedpnpm dev(or deploy to Mastra Cloud and share the link)- Studio loads at
http://localhost:4111— confirm:- The agent Customer Support Agent is visible
- Under Prompts you see "Tone of Voice — Professional Customer Service" and "Premium-tier perks reminder"
- The agent's Editor tab is reachable
- Two test accounts ready in your Mastra Cloud org:
alex+editor@…mapped to roleeditoralex+viewer@…mapped to roleviewer
Two audiences, two outcomes:
- Non-technical users — control agent behavior, edit, publish safely.
- Developers — set the project up and understand how to extend it.
Definition: the Agent Editor is a UI to edit, test, and deploy agents without code changes.
Daniel:
pnpm dev # or `mastra deploy`- Open Studio. Show the agent. Send a quick message ("Where's my order A-1001?"). Note the response is fine but generic.
- Hand the link to Alex.
Alex:
- Authenticate via Google.
- Open the agent → Editor tab.
- Edit the inline prompt. Add the Tone of Voice — Professional Customer Service block from the prompt picker.
- Click Save version — note "Draft" badge appears.
- Chat against the draft. Same question. Tone is now warmer and personalized.
- Click Publish.
Daniel: prove the new version is live for production traffic:
curl -s http://localhost:4111/api/agents/customer-support-agent/chat \
-H 'Content-Type: application/json' \
-d '{
"messages": [{"role":"user","content":"Where is my order A-1001?"}],
"requestContext": {"customerName":"Sam","tier":"free","userRole":"user"}
}' | jqEmphasis: versioning = no risk. Publishing = controlled release.
Demonstrate, in Studio's Editor tab → Tools:
- Add the local
issue-credittool. Save version. - Remove
lookup-order, save, chat — agent now refuses to look up orders. Re-add it, save again. - Override
lookup-order's description to: "Look up an order. Always confirm the order ID with the customer before answering." Save, chat, notice the new behavior. - Add an MCP tool — pick
search-knowledge-basefrom thesupport-knowledge-baseserver. - Add an Arcade tool (e.g. Gmail
send_emailor Slacksend_message). RequiresARCADE_API_KEYin.env.
Slide intro: explain requestContext — values that flow in with every
request and are visible to instructions, prompt blocks, tools, and rules.
In Studio:
-
Make
issue-creditconditional ontier == 'premium'. Save, publish. -
Add the Premium-tier perks reminder prompt block. It's already conditional on
tier == 'premium'(seeseed-prompt-blocks.ts). -
Test from Studio's chat with two
requestContextvalues:{ "customerName": "Sam", "tier": "free", "userRole": "user" }{ "customerName": "Sam", "tier": "premium", "userRole": "user" }Premium gets perks mentioned and
issue-creditavailable; free does not.
Open Studio → Prompts. Show the same blocks live there as standalone,
reusable, versioned content. Show {{customerName}} resolving from the
agent's request context.
Walk through:
package.json→@mastra/editor,@mastra/auth-studio, Arcade depsrc/mastra/index.ts:new MastraEditor({ toolProviders: { arcade: ... } })- The same
LibSQLStorepowers the agent's memory and the editor — no extra storage to wire up.
src/mastra/agents/customer-support.ts:requestContextSchema(drives Studio forms and rule operators)- Tools object — both inline
createTooland MCP-served tools
- Frame: the editor is a stateful layer on top of source code, not a
replacement.
applyStoredOverridesforks the agent and overlays instructions and tools. Code-only fields (id,name,model,requestContextSchema,memory) are never overridden.
Open src/mastra/auth.ts. Walk through SimpleAuth (three pre-baked tokens)
and the MastraRBACStudio role mapping table.
Run the viewer-can't-edit demo from the terminal:
# Viewer can read…
curl -s -o /dev/null -w "%{http_code}\n" \
-H 'Authorization: Bearer viewer-token' \
http://localhost:4111/api/stored/agents
# → 200
# …but cannot delete:
curl -s -o /dev/null -w "%{http_code}\n" \
-X DELETE -H 'Authorization: Bearer viewer-token' \
http://localhost:4111/api/stored/agents/customer-support-agent
# → 403
# Editor can:
curl -s -o /dev/null -w "%{http_code}\n" \
-X DELETE -H 'Authorization: Bearer editor-token' \
http://localhost:4111/api/stored/agents/customer-support-agent
# → 404 (record didn't exist — but RBAC let us through)Frame: in production you'd swap SimpleAuth for MastraAuthStudio (Google
SSO via WorkOS); the role mapping stays the same.
pnpm programmaticRefresh Studio → the new block is in Prompts. The point: Studio is
the UI on top of mastra.getEditor(). The same API is available to
scripts, jobs, and CI.
Three takeaways:
- Non-technical control — Alex never opened a terminal and shipped a prompt change to production.
- Dynamic via request context — the same agent, instructions, and tool list adapt per request. No new code paths.
- Code/UI parity — anything in Studio is a method call. Anything you build in code shows up in Studio.
- Studio shows no prompt blocks → run
pnpm seed. MASTRA_EDITOR_REQUIRED_FOR_VERSIONED_AGENT_LOOKUP→ editor is not registered. Checksrc/mastra/index.tsand that the editor is being passed intonew Mastra({ ... }).- Arcade tool provider missing in Studio →
ARCADE_API_KEYnot set. The provider only registers when the env var is present. - Agent overrides don't apply → call
mastra.getAgentById('customer-support-agent')(notmastra.agents['customer-support-agent']). The editor only applies overrides viagetAgentById.