ChatCompletionAgent,
wraps that into a first-class agent with instructions and memory.
- What Semantic Kernel ships — a
Kernel, native/OpenAPI/MCP plugins, automatic function calling (FunctionChoiceBehavior.Auto),ChatCompletionAgent, structured outputs, filters/telemetry, and first-class MCP client support viaMCPSsePlugin/MCPStreamableHttpPlugin/MCPStdioPlugin. - 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
- Semantic Kernel turns any MCP server into a plugin: point an
MCPSsePluginat the server URL,add_pluginit to the kernel or agent, and each MCP tool becomes akernel_functionthe model can call — no manual schema wiring. - 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.
- Each session is one URL + one bearer scoped to one user. Build an
MCPSsePluginfrom it, hand it to aChatCompletionAgent, and every tool call runs as that user — gated server-side.
Tested against:
semantic-kernel 1.43.x with the [mcp] extra
(semantic_kernel.connectors.mcp.MCPSsePlugin with headers, ChatCompletionAgent,
OpenAIChatCompletion, OpenAIChatPromptExecutionSettings, FunctionChoiceBehavior.Auto),
the mcp Python package 1.x (SSE transport), and Naive API v2 (hosted MCP server
over SSE + per-user sessions), on Python ≥ 3.10.Naive’s session URL contains /mcp/sse/…, so pair it with Semantic Kernel’s MCPSsePlugin
(not MCPStreamableHttpPlugin). The scoped bearer rides in the plugin’s headers argument,
never the URL. MCP support isn’t in the base install — add the mcp extra
(semantic-kernel[mcp]). There is no language-specific Naive SDK for Python or .NET yet — provision the control plane
over the REST API, the dashboard, the CLI, or the Node SDK
(@usenaive-sdk/server). Pin your versions and
set the model to one you have access to. The MCPSsePlugin connectors are marked
experimental upstream.Prerequisites
- A Naive API key (
nv_sk_...) — get one from the dashboard. - An
OPENAI_API_KEYfor the model that runs the agent (or swap in any other Semantic Kernel chat service, e.g.AzureChatCompletion). - Python ≥ 3.10.
Minimal viable integration
The shortest path to a Semantic Kernel 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 aChatCompletionAgent.
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
At runtime, mint a short-lived session for the user. It returns the
scoped SSE endpoint and a bearer that lives in the headers — never in the URL — and expires
(default 15 min, max 24h):
Build the agent — and let it transact
Point Semantic Kernel’s The model discovers GitHub (
MCPSsePlugin at the session’s scoped SSE endpoint, add_plugin it
to a ChatCompletionAgent, and call get_response. Semantic Kernel discovers Naive’s tools,
exposes them as kernel functions, and runs the automatic function-calling loop for you (call
tool → feed result back → continue). The plugin is an async context manager — keep it open
for the duration of the run: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 agent loop is Semantic Kernel’s; the real-world actions
are Naive’s.The session’s tool list is already filtered by Alice’s kit — the agent never sees a tool the
policy forbids. Each MCP tool is registered as a kernel function under the plugin name (e.g.
Naive-naive_cards_create). To narrow further per agent, scope the model with
FunctionChoiceBehavior.Auto(filters={"included_functions": ["Naive-naive_connections_connect"]}),
or load a subset with MCPSsePlugin(..., load_tools=True) and a dedicated session whose kit
is tighter.Extension: human-in-the-loop spend
Because the kit setcards.requiresApproval: true, the agent cannot silently spend. When
the model 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:
202 response uses action; approval records from the approvals API use
action_type.
The agent 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:
pending → executed / failed / denied) and the deny endpoint.
Want a UX gate inside the run too? Add a Semantic Kernel
auto-function-invocation filter
that inspects each function call and pauses for confirmation. That shapes the conversation;
the real enforcement is Naive’s server-side approval gate above, which applies regardless of
prompt or kernel configuration.
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 definition, every tenant
A Naive session is scoped to one user, so to serve every tenant from the same agent definition, mint a fresh session per request and build theMCPSsePlugin from it. The
instructions, model, and settings stay identical — only which user’s session backs the
plugin changes, and every call is Account-Kit-gated server-side:
DELETE /v1/users/{user_id}/sessions/{id}.
Because it’s Semantic Kernel, you can also pin a typed result: set
settings.response_format = MySchema (a Pydantic model) so the run returns a validated
object — e.g. { github_connect_url, card_status } — instead of free text, while the
transacting still happens through Naive’s gated tools.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 kernel 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/:idto revoke it early.
Next steps
- 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
- AutoGen · Pydantic AI — the same MCP-session pairing for other Python stacks