Skip to main content
Letta

Letta remembers the agent · Naive lets it transact

Letta is a trademark of its respective owner, used here for identification and integration guidance only. No endorsement, partnership, or affiliation is implied.
Letta (the team behind MemGPT) is an open-source platform for stateful agents — agents that persist their own memory across sessions instead of starting cold every request. You give an agent memory blocks and tools; the Letta server runs the loop, manages the context window, and keeps the agent alive between calls. It has first-class MCP support: register a remote MCP server once, and every tool it exposes becomes attachable to any agent — with tool-call approval rules for human-in-the-loop.
  • What Letta ships — a persistent, memory-native agent runtime (Letta Cloud or self-hosted), the agent loop and context management, MCP server registration over streamable-http, sse, and stdio (with auth headers on remote servers), per-tool approval rules, agent-scoped variables, and a Python/TypeScript SDK.
  • 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.
That’s the half Naive adds. You keep Letta’s stateful orchestration; Naive gives each agent a tenant identity, a virtual card, 1,000+ third-party connections, and an Account Kit that bounds exactly what the agent can do — enforced server-side, with human approval on the sensitive actions. A Letta agent that remembers a user forever now also has that user’s real, governed account to act on.

How the pairing works

  • Letta registers an MCP server once on the Letta server, discovers its tools, and lets you attach those tools to any agent by id. Tool execution is forwarded server-side to the MCP server — the agent never holds the credentials.
  • 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.
  • Register a Naive session as an MCP server, attach its tools to a Letta agent, and every tool call runs as that one user — gated server-side. Because Letta agents are persistent and per-user, one Letta agent maps cleanly to one Naive user.
Tested against: letta-client 1.12.1 (Python — Letta, client.mcp_servers.create, client.mcp_servers.tools.list, client.agents.create, client.agents.messages.create, RequiresApprovalToolRule), against Letta Cloud or a current self-hosted Letta server, and Naive API v2 (hosted MCP server over SSE + per-user sessions), on Python ≥ 3.10. A matching TypeScript SDK (@letta-ai/letta-client) mirrors this surface (client.mcpServers.create, client.agents.create).Naive’s session URL contains /mcp/sse/…, so register the MCP server with mcp_server_type: "sse". Letta marks SSE as a legacy transport in favor of streamable-http, but it’s still fully supported — use it to match Naive’s endpoint. The scoped bearer rides in the auth_header / auth_token fields (the Authorization header), never in the URL. 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. A Naive session is short-lived (default 15 min, max 24h) while a Letta MCP registration is persistent — see rotation below. Pin your versions and set the model to a handle you have access to.

Prerequisites

  • A Naive API key (nv_sk_...) — get one from the dashboard.
  • A Letta server: a Letta Cloud API key (LETTA_API_KEY) or a self-hosted server (docker run ... letta/letta, reachable at http://localhost:8283).
  • Model + embedding access configured on that server (OpenAI, Anthropic, …).
  • Python ≥ 3.10.

Minimal viable integration

The shortest path to a Letta agent that can actually transact: define a policy and provision a user (Naive control plane, once), then mint a per-user MCP session, register it on Letta, and attach its tools to a stateful 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. A Letta MCP registration is persistent, so mint with a longer ttl_ms (up to 24h) and plan to rotate it:
3

Register Naive, attach its tools, and build the agent

Register the session’s scoped SSE endpoint as an MCP server on Letta, list the tools it exposes, and attach them to a new stateful agent. The Authorization header is split into Letta’s auth_header + auth_token fields:
The agent discovers GitHub (naive_connections_connect), returns a connect link for Alice to authorize, and attempts to issue the card (naive_cards_create) — a real card on Alice’s account, capped by her kit. The loop and the memory are Letta’s; the real-world actions are Naive’s — and because the agent is persistent, the next message already remembers the card it set up.
That’s the moat: the same Letta agent that would otherwise just remember wanting to spend money now issues a policy-bounded card on a specific user’s account — and keeps that context for next time.

Extension: human-in-the-loop spend

Because the kit set cards.requiresApproval: true, the agent cannot silently spend. Letta lets you add a client-side checkpoint too, and Naive enforces the server-side one — so you get defense in depth: Letta can pause before the tool runs, but even if a call gets through, the spend gate still lives on Naive’s servers.
  • In Letta — add a requires_approval tool rule so the agent pauses before a sensitive tool runs and emits an approval request instead of calling it.
  • On the server — even if a call gets through, Naive freezes the sensitive action and returns a pending approval (HTTP 202) instead of a live card. This holds no matter what runtime calls it.
When the agent reaches naive_cards_create, the run stops with an approval request. Your app shows it to a human, then resumes the loop by approving (or denying) the specific tool call:
Independently, Naive’s server-side gate returns a pending approval rather than a live card — regardless of what the client did:
Your app resolves it out of band — and on approval, Naive replays the frozen action server-side:
See Approvals for the full lifecycle (pending → executed / failed / denied) and the deny endpoint.
Letta’s requires_approval tool rule is a convenience for your own UX — the real enforcement is Naive’s server-side approval gate above, which can’t be bypassed from the agent config, the tool list, or the tool rule.
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: one agent per tenant

Letta agents are already per-user and persistent — so serving every tenant is natural: give each end-user their own agent, each backed by their own Naive session. Register the Naive server once with an agent-scoped variable in the token, then set each agent’s variable to that user’s freshly minted session bearer:
Nothing about the agent widens what a user may do: the toolset is the intersection of the session and that user’s Account Kit, enforced on Naive’s servers. Because sessions expire (default 15 min, max 24h) and a Letta registration is persistent, refresh the agent’s variables with a new session on expiry — or update a single-user registration in place with client.mcp_servers.update(...). Revoke a session early with DELETE /v1/users/{user_id}/sessions/{id}.
Prefer TypeScript? Letta also ships @letta-ai/letta-client (client.mcpServers.create, client.agents.create), and Naive plugs into TS agent stacks. On TypeScript you can provision the control plane with the @usenaive-sdk/server SDK (naive.accountKits.create, naive.users.create, client.session(...)) and hand the scoped MCP session to any MCP-aware runtime — see the Vercel AI SDK and Mastra guides.

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 (default 15 min) and you can DELETE /v1/users/:user_id/sessions/:id to revoke it early.

Next steps