Skip to main content
CrewAI is a Python framework for multi-agent crews: you give each agent a role, a goal, and a backstory, hand the crew a set of tools, and it runs the tasks — sequentially or hierarchically — until the work is done.
  • What CrewAI ships — role-based agents, task graphs, delegation, planning, memory.
  • 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 CrewAI’s orchestration; Naive gives the crew a tenant identity, a virtual card, 1,000+ third-party connections, and an Account Kit that bounds exactly what the crew can do — enforced server-side, with human approval on the sensitive actions.

How the pairing works

  • CrewAI has first-class MCP support — point an agent’s mcps field at any MCP server and its tools are discovered automatically.
  • 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.
  • So the integration is: mint a session for one user → hand its SSE URL + scoped bearer to a CrewAI agent → every tool call runs as that user, gated server-side.
  CrewAI (Crew.kickoff)                 Naive
  ─────────────────────                 ─────
  agent ── task ──▶ tool call
        │  agent picks an MCP tool

  MCPServerSSE(url, headers)  ──────▶  /mcp/sse/sess_…   (per-user session)
                                          │  AccountKit-gated, scoped to one user

                                    connect GitHub · issue a $50 card · run a capability

                                    sensitive? → 202 pending_approval (human-in-the-loop)
Tested against: Naive API v2 (hosted MCP server over SSE, per-user sessions), crewai 1.x (crewai.mcp.MCPServerSSE, the agent mcps field), and the mcp Python package 1.x, on Python 3.10–3.13.Naive’s MCP server uses SSE transport, so this guide uses MCPServerSSE. There is no There is no Naive Python SDK today — provision the control plane over the REST API, the dashboard, the CLI, or the Node SDK (@usenaive-sdk/server). Pin your versions and adjust the model 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 crew (CrewAI reads OPENAI_API_KEY by default; set whichever provider you use).
  • Python 3.10–3.13.
pip install crewai mcp requests
export NAIVE_API_KEY=nv_sk_live_...
export OPENAI_API_KEY=sk-...

Minimal viable integration

The shortest path to a CrewAI crew that can actually transact: define a policy, provision a user (control plane, once), then at runtime mint a per-user MCP session and hand it to the crew.
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 crew does is bounded by this kit — server-side. These are one-time control-plane calls:
# provision.py — run once. No SDK needed; these are plain REST calls.
import os, requests

NAIVE = "https://api.usenaive.ai/v1"
HEAD = {
    "Authorization": f"Bearer {os.environ['NAIVE_API_KEY']}",
    "Content-Type": "application/json",
}

# Control plane: a reusable policy template.
kit = requests.post(f"{NAIVE}/account-kits", headers=HEAD, json={
    "name": "Pro",
    "primitives_config": {
        "cards": {"enabled": True, "requiresApproval": True,
                   "defaults": {"spending_limit_cents": 50000}},
        "vault": {"enabled": True},
    },
    "connections_config": {"mode": "allowlist",
                            "toolkits": ["github", "gmail", "stripe"]},
}).json()

# Provision one of your end-users and assign the kit.
alice = requests.post(f"{NAIVE}/users", headers=HEAD, json={
    "external_id": "user_123",
    "email": "alice@acme.com",
    "account_kit_id": kit["id"],
}).json()

print(alice["id"])  # → set as NAIVE_USER_ID for the crew
2

Mint a per-user MCP session

At runtime, mint a short-lived session scoped to one user. It returns an SSE URL plus a scoped bearer that lives only in the headers — never in the URL — and expires (default 15 min, max 24h):
# crew.py
import os, requests

NAIVE = "https://api.usenaive.ai/v1"
API_HEAD = {"Authorization": f"Bearer {os.environ['NAIVE_API_KEY']}"}
USER_ID = os.environ["NAIVE_USER_ID"]  # Alice, from provisioning

session = requests.post(
    f"{NAIVE}/users/{USER_ID}/sessions",
    headers=API_HEAD,
    json={"ttl_ms": 15 * 60 * 1000},
).json()

# session["mcp"] = {
#   "url": "https://api.usenaive.ai/mcp/sse/sess_...",
#   "headers": {"Authorization": "Bearer nv_sess_..."},
#   "expires_at": "..."
# }
3

Give the crew the session — and let it transact

Point a CrewAI agent’s mcps field at the session via MCPServerSSE. CrewAI discovers Naive’s tools automatically and runs the crew’s task loop for you:
from crewai import Agent, Task, Crew
from crewai.mcp import MCPServerSSE

ops = Agent(
    role="Operations Agent",
    goal="Act on Alice's real account to set up her marketing stack.",
    backstory="Runs real-world setup for one tenant, bounded by her Account Kit.",
    mcps=[
        MCPServerSSE(
            url=session["mcp"]["url"],
            headers=session["mcp"]["headers"],  # scoped bearer — never in the URL
            cache_tools_list=True,
        ),
    ],
)

task = Task(
    description=(
        "Connect Alice's GitHub, then issue a $50 virtual card called "
        "'Ads budget' for our marketing spend."
    ),
    expected_output="The GitHub connect link and the status of the card request.",
    agent=ops,
)

crew = Crew(agents=[ops], tasks=[task])
print(crew.kickoff())
The crew 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 whole crew is CrewAI’s; the real-world actions are Naive’s.
That’s the moat in ~40 lines: the same crew that would otherwise just describe spending money now issues a policy-bounded card on a specific user’s account.
The session’s tool list is already filtered by Alice’s kit — the crew never sees a tool the policy forbids. To narrow further per agent, pass a tool_filter to MCPServerSSE (e.g. create_static_tool_filter(allowed_tool_names=[...]) from crewai.mcp.filters).

Extension: human-in-the-loop spend

Because the kit set cards.requiresApproval: true, the crew cannot silently spend. When an agent calls the card-issuing tool, Naive freezes the request and the tool result comes back as a pending approval (HTTP 202) instead of a live card:
{
  "status": "pending_approval",
  "approval_id": "65589c8b-e033-4a65-b16c-379211c94429",
  "action": "cards.create",
  "primitive": "cards",
  "title": "Issue virtual card \"Ads budget\"",
  "message": "This action requires human approval before it executes."
}
The immediate 202 response uses action; approval records from the approvals API use action_type. The crew relays the pending status instead of claiming success. Your app then resolves it out of band — and on approval, Naive replays the frozen action server-side:
# resolve.py — operator side, out of band from the crew.
import os, requests

NAIVE = "https://api.usenaive.ai/v1"
HEAD = {"Authorization": f"Bearer {os.environ['NAIVE_API_KEY']}"}
USER_ID = os.environ["NAIVE_USER_ID"]

pending = requests.get(
    f"{NAIVE}/users/{USER_ID}/approvals",
    headers=HEAD, params={"status": "pending"},
).json()["approvals"]

for a in pending:
    # ...show a["title"] / a["action_type"] to a human in your UI...
    requests.post(f"{NAIVE}/users/{USER_ID}/approvals/{a['id']}/approve", headers=HEAD)
    # or: .../approvals/{a['id']}/deny  with {"reason": "over budget"}
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 crews stay governed while your own automation isn’t slowed down.

What stays enforced

No matter how the crew is wired, the policy is enforced where it matters — on Naive’s servers, not in your prompt or your crew 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 crew 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 crew 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