Agent a model,
instructions, tools, and memory, and it runs the tool-calling loop — plus workflows, RAG,
and evals — until the job is done.
- What Mastra ships — agents, the model-calling loop, tools, workflows, memory, RAG, streaming, and first-class MCP support.
- What it doesn’t ship — a way for those agents to act as a real account: sign up for a SaaS tool, hold a funded card, or spend within a budget you control, per end-user.
How the pairing works
- Mastra has first-class MCP support via
MCPClient— point it at any MCP server and its tools are discovered and converted into Mastra tools automatically (no manual schema wiring). - Naive ships a hosted MCP server and mints per-user sessions — short-lived, revocable endpoints whose tool list is the fused native + third-party toolset, already filtered by that user’s Account Kit.
MCPClient.listToolsets()is built for exactly this: per-request, per-user tool config (Mastra’s documented multi-tenant pattern). Mint a session for one user, hand it to the agent for one run, every tool call runs as that user — gated server-side.
Tested against:
@usenaive-sdk/server 0.12.x (Naive API v2, hosted MCP server
over SSE + per-user sessions), @mastra/core 1.46.x (Agent, generate), and
@mastra/mcp 1.12.x (MCPClient, listToolsets, requireToolApproval), on
Node ≥ 20.Naive’s MCP server uses SSE transport. Mastra’s MCPClient tries Streamable HTTP first
and falls back to SSE, and SSE with custom headers requires both requestInit and
eventSourceInit (a known MCP-SDK quirk) — both are wired below. On older Mastra (@mastra/mcp
0.x), listTools() / listToolsets() were named getTools() / getToolsets(). Pin your
versions and adjust the model id to a provider you have access to.Prerequisites
- A Naive API key (
nv_sk_...) — get one from the dashboard. - A model provider key for the model that runs the agent. This guide uses the Mastra model
router (
"anthropic/claude-sonnet-4-5"), which readsANTHROPIC_API_KEY. - Node ≥ 20.
Minimal viable integration
The shortest path to a Mastra agent that can actually transact: define a policy and provision a user (control plane, once), then at runtime mint a per-user MCP session and hand it to the agent.Define the policy, then provision a user
An Account Kit is the spend/capability policy. Here a tenant
user gets a card (capped at $500, approval required), the vault, and an allowlist of apps.
Everything the agent does is bounded by this kit — server-side. These are one-time
control-plane calls:
Mint a per-user MCP session and connect it
At runtime, mint a short-lived session for the user and point Mastra’s
MCPClient at its scoped SSE endpoint. The bearer lives in the session headers, never in
the URL, and expires (default 15 min, max 24h):Build the agent — and let it transact
Define an The model discovers GitHub, returns a connect link for Alice to authorize, and attempts to
issue the card — a real card on Alice’s account, capped by her kit. The whole agent
loop is Mastra’s; the real-world actions are Naive’s.
Agent and pass the session’s tools per call via listToolsets(). Because the
toolset is minted per request, the same agent definition can serve every tenant — each run
is scoped to whichever user’s session you pass:Extension: human-in-the-loop spend (two gates)
Because the kit setcards.requiresApproval: true, the agent cannot silently spend.
You get two complementary layers — pair them for defense in depth:
- In the agent — Mastra’s
requireToolApprovalinterrupts before a tool from that server runs, so the run pauses and surfaces the call for review. - On the server — even if a call gets through, Naive freezes it and returns a pending
approval (HTTP
202) instead of a live card. This holds no matter what runtime calls it.
requireToolApproval on the Naive server so write actions pause for review. Pass a
function to keep read-only discovery (search/list) flowing while gating everything else:
Tool annotations are advisory hints, not a security boundary. Treat
requireToolApproval
as a UX/early-interrupt convenience — the real enforcement is Naive’s server-side approval
gate below, which applies regardless of prompt or agent configuration. You can also pass
requireToolApproval: true to gate every Naive tool.202 / isPendingApproval payload uses action; approval records from
approvals.list() or approvals.get() use action_type.
Your app then resolves it out of band — and on approval, Naive replays the frozen action
server-side:
isPendingApproval(res), or
poll a single approval to completion with client.approvals.wait(approvalId). See
Approvals for the full lifecycle (pending → executed / failed / denied).
Approvals are only enforced for agent (API-key / MCP) calls on real tenant users. A human
acting in your dashboard, and agent calls on the operator’s own default user, bypass the
gate — so end-user agents stay governed while your own automation isn’t slowed down.
Alternative: static tools for single-user automation
If the agent only ever acts as one account — your own back-office automation, a CLI, an internal bot — you don’t need per-request toolsets. Mint a session for the default user, load its tools once withlistTools(), and bake them into the agent at construction:
Prefer the loop to run entirely in your backend without MCP?
naive.forUser(id).agentTools()
returns the same capabilities as a discover-then-run toolset with a
handle(name, input) dispatcher you adapt into Mastra’s createTool (convert each tool’s
JSON Schema to a Standard Schema such as Zod). Same Account Kit, same approval gates — just
in-process instead of over MCP.What stays enforced
No matter which path you choose, the policy is enforced where it matters — on Naive’s servers, not in your prompt or your agent:- Identity — every action runs as a specific tenant user, fully isolated from your other users.
- Capability bounds — the Account Kit decides which
primitives and which apps the agent can touch (
allowlist/blocklist/ per-tool). - Scoped spend — virtual cards are capped per card and per user; the model can’t raise its own limit.
- Human-in-the-loop — sensitive actions (cards, domains, KYC, formation, connecting an app) freeze as approvals until a human says yes.
Next steps
- SDK overview — the full Naive client surface
- Sessions — per-user MCP sessions for any MCP-aware runtime
- Agent tools — what
agentTools()exposes and howhandle()works - Account Kits — author spend/capability policy
- Approvals — the human-in-the-loop lifecycle
- MCP server — how Naive’s hosted MCP server works