
LiveKit runs the voice pipeline · Naive lets it transact
LiveKit and LiveKit Agents are trademarks of their respective owners, used here for
identification and integration guidance only. No endorsement, partnership, or affiliation is
implied.
AgentSession wires up speech-to-text, an LLM,
text-to-speech, and turn detection into one low-latency loop, and a
worker dispatches a fresh agent per caller. Its
MCP support lets that agent call any MCP server’s
tools — spoken request in, real action out.
- What LiveKit ships — a realtime
AgentSessionpipeline (STT → LLM → TTS + turn detection), a worker/dispatch model that spins up one agent per participant, first-class tools andmcp.MCPServerHTTPfor remote MCP servers, and the rooms/telephony transport underneath. - What it doesn’t ship — a way for that voice agent 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
- Naive ships a hosted MCP server and mints per-user sessions — short-lived, revocable SSE endpoints whose tool list is the fused native + third-party toolset, already filtered by that user’s Account Kit. See the delegated MCP sessions explainer for the full lifecycle model.
- In the LiveKit entrypoint (one job per caller), you mint a session for that participant and
hand its URL + headers to
mcp.MCPServerHTTP(..., transport_type="sse", headers=...)— the scoped bearer rides in the request headers, never in the URL. Pass it to the agent viamcp_servers=[...]. - The
AgentSessionruns the loop: the caller speaks → STT → the LLM picks a Naive tool → the result is spoken back via TTS. Every call runs as that one user, gated on Naive’s servers. Sensitive actions resolve to apending_approvalpayload the agent can read aloud.
Tested against:
livekit-agents 1.6.5 (AgentServer, AgentSession, Agent, and
livekit.agents.llm.mcp.MCPServerHTTP), the MCP Python SDK 1.28.1 (the transport under
MCPServerHTTP), and Naive API v2 (hosted MCP server over SSE + per-user sessions), on
Python ≥ 3.9. Live-verified: passing Naive’s session headers to MCPServerHTTP(transport_type="sse", headers=...) discovers Naive’s tools (and allowed_tools=[...] narrows them); a wrong bearer fails
MCP session initialization.Version assumptions: MCPServerHTTP takes headers at construction and does not support
rotating them after connect (livekit/agents#5542),
so mint the Naive session inside the entrypoint (per job/caller) and give it a ttl_ms that
covers the whole call — up to 24h. Naive’s session URL contains /mcp/sse/…, so set
transport_type="sse" (LiveKit also supports "streamable_http" for …/mcp endpoints). There is no
Python Naive SDK yet, so the control plane (Account Kit, user, session) is shown over the REST API;
you can equally provision from the dashboard, the CLI, or the Node
SDK. Pin your versions and set the STT/LLM/TTS models to providers you have access to.Prerequisites
- A Naive API key (
nv_sk_...) — get one from the dashboard. - A LiveKit project (
LIVEKIT_URL,LIVEKIT_API_KEY,LIVEKIT_API_SECRET) plus model access for the STT/LLM/TTS you pick (this guide uses LiveKit Inference; provider plugins likeopenai,deepgram,cartesia,silerowork identically). - Python ≥ 3.9.
Minimal viable integration
The shortest path to a LiveKit voice agent that can actually transact: define a policy and provision a user (Naive control plane, once), then — inside the per-caller entrypoint — mint a session, pointmcp.MCPServerHTTP at its scoped SSE endpoint, and hand it to the agent.
1
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:
2
Mint a per-user MCP session
At runtime, mint a session for the user. It returns the scoped SSE
endpoint and a bearer that lives in the headers — never in the URL. Because
MCPServerHTTP headers
are fixed at construction, ask for a ttl_ms that comfortably covers the call:3
Wire Naive into the voice agent
In the LiveKit entrypoint (one job per caller), resolve which user this call belongs to, mint
their session, and build On a call, Alice can say “connect my GitHub, then issue a $50 card called ‘Ads budget’” — the
agent discovers GitHub (
mcp.MCPServerHTTP from mcp.url + mcp.headers. Pass it to the Agent
via mcp_servers=[...] — LiveKit discovers Naive’s tools and the LLM can call them mid-conversation:naive_connections_connect), returns a connect link for her to authorize,
and attempts to issue the card (naive_cards_create) — a real card on Alice’s account, capped
by her kit. The realtime pipeline is LiveKit’s; the real-world actions are Naive’s.mcp_servers also exists on AgentSession(...) if you’d rather scope the toolset to the whole
session than to a single Agent. Either way LiveKit initializes the MCP connection when the session
starts and closes it when the session ends.Extension: human-in-the-loop spend
Because the kit setcards.requiresApproval: true, the agent cannot silently spend. You get two
complementary layers — pair them for defense in depth:
- In the pipeline (scope + phrasing) — narrow the toolset with
allowed_tools=[...]so the agent can only touch the primitives you intend, and use atool_result_resolverto turn apending_approvalpayload into a clean sentence the TTS reads back. Treat this as UX. - On the server (the real boundary) — Naive freezes the action and returns a pending approval
(HTTP
202) instead of a live card. This holds no matter what runtime calls it, and can’t be bypassed from the prompt or the agent config.
LiveKit has no native pre-tool rejection hook —
allowed_tools decides what the agent may call,
and tool_result_resolver only shapes the result after Naive has already gated it. The real
enforcement is Naive’s server-side approval gate below, which can’t be bypassed from the agent
config, the tool list, or the resolver.pending → executed / failed / denied) and the deny endpoint.
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 account’s own default agent profile, bypass the gate — so
end-user agents stay governed while your own automation isn’t slowed down.
Alternative: one agent per tenant
A LiveKit worker already dispatches one job per caller, so multi-tenant is the natural shape: resolve the user from the job’s metadata, mint a fresh session for them, and build the MCP server bound to it — inside the entrypoint.allowed_tools narrows a caller’s toolset to exactly the
primitives you want that agent to touch — a second layer on top of the Account Kit:
allowed_tools filter — enforced on Naive’s servers. Because
MCPServerHTTP headers are fixed at connect, mint the session per job with a ttl_ms that covers
the call, and DELETE /v1/users/{user_id}/sessions/{id} to revoke it early when the call ends.
Long calls? A Naive session defaults to 15 minutes (max 24h) and its bearer can’t be rotated
mid-connection. Set
ttl_ms to cover the expected call length when you mint it in the entrypoint;
for very long-running sessions, end the call and start a fresh job (which mints a new session) rather
than trying to update the header in place.What stays enforced
No matter how the agent is wired, the policy is enforced where it matters — on Naive’s servers, not in your prompt or your agent config:- 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.
- Scoped, expiring access — the agent holds a per-user session bearer, not your API key; it
expires and you can
DELETE /v1/users/:user_id/sessions/:idto revoke it early.
Next steps
- The Governed Agent Profile, End to End — the developer pillar on scoped identity, spend caps, approvals, and revocation
- Delegated, Revocable MCP Sessions for AI Agents — the concept post behind per-user SSE sessions (this guide’s transport)
- How to Revoke an AI Agent’s Access Instantly — kill a session mid-call when a caller hangs up or policy changes
- MCP server — the hosted SSE server and its full tool list
- Sessions — per-user MCP sessions, TTL, and revocation
- Account Kits — author spend/capability policy
- Approvals — the human-in-the-loop lifecycle
- Pydantic AI · LlamaIndex · Agno · smolagents · DSPy · Letta — the same MCP-session pairing for other Python stacks