← Blog
GuideJuly 15, 2026Updated September 7, 20268 min read

Naïve inside your agent framework: skill.md, MCP tools, policies

Keep the coding agent or orchestrator you already use. Point it at usenaive.ai/skill.md, reach every Naïve primitive as an MCP tool, and gate each one with allow, ask, or deny.

Dennis Zax· CTO, Naïve

TL;DR

  • Your framework or coding agent keeps running the loop. Naïve is the primitives layer underneath it: computers, identities, vaults, connections, deployments, all reached as tools.
  • Onboarding is one prompt. Any coding agent (Claude Code, Cursor, Codex) reads https://usenaive.ai/skill.md, logs in, declares the company in naive.config.ts, and runs naive up.
  • Every primitive is an MCP tool. POST /v1/mcp publishes the API as a tool catalog generated from the route table, so a tool call is an ordinary, scoped API call.
  • Governance is per tool: allow, ask, or deny. MCP tools default to ask, an ask pauses the session until a person confirms, and the approver is recorded.
  • Credentials live in the vault and are injected server-side when the agent connects to an MCP server. No route ever returns a secret value.

Keep the loop, change what it reaches for

Naïve inside your agent framework means keeping the loop you already have and putting real primitives underneath it. A coding agent on your machine, an orchestrator you wrote, or Vetta, our managed agent, decides what to do next. Naïve is what it reaches for when the next step is to provision a computer, connect to a third-party app, use a credential, deploy a schedule, or send something into the world.

The integration surface is deliberately small. There is one onboarding manifest, https://usenaive.ai/skill.md, that any coding agent can read. There is one protocol, MCP, that any framework's tool layer already speaks. And there is one place governance happens: the tool call, where each tool resolves to allow, ask, or deny before it runs.

The quickstart and the MCP server reference hold the exact commands.

What each layer owns

LayerOwnsDoes not own
Your framework or coding agentThe model, the prompt, the planning loop, state, streamingIdentity, credentials, spend enforcement, approvals
NaïveOrganization, agents, sessions, computers, identities and personas, vaults, connections, deployments, budgets, policies, auditYour graph topology, your model provider, your harness

The split matters because the usual failure is bolting credentials and approvals onto the framework, which puts policy in a prompt where the model can argue with it. Naïve keeps orchestration in your stack and moves every real-world action behind a server that checks identity, budget, and policy first, whatever the model decides to attempt.

Step one: your coding agent reads skill.md

The onboarding path is a prompt, not an SDK install. The home page hands you this string to paste into Claude Code, Cursor, Codex, or whatever agent you use:

The onboarding prompt
Read https://usenaive.ai/skill.md and follow it to set up naïve in my project: register or log in, declare the company in naive.config.ts, then naive up. Check you are in the project directory first — if not, ask me where it is.

skill.md is a skill in the same sense as any other: YAML frontmatter plus a Markdown body of instructions the agent follows. It tells the agent how to authenticate, that a plan and prepaid credits come before anything else, that every agent needs a budget it must ask the operator for rather than pick, and that tools which send, move money, or delete should get an ask or deny policy. It also tells the agent not to rebuild server-side guarantees: no client-side budget math, no approval queue of its own.

If you prefer to drive it by hand, the CLI is one install away:

Install the CLI
npm install -g @usenaive-sdk/vetta-cli

The package installs both the vetta and naive binaries; every command works under either name. naive claim --key sk_live_... binds the clone to your organization, and naive up reconciles naive.config.ts against the platform. See the naive CLI reference.

Step two: primitives arrive as MCP tools

Once the company exists, your framework calls it through POST /v1/mcp: the Vetta API published as a Model Context Protocol tool catalog. It is a JSON-RPC endpoint that speaks initialize, tools/list, and tools/call, and it authenticates with the same organization-scoped API key as every other endpoint.

Three properties make it safe to hand to a model:

  • The catalog is generated from the route table. One tool per served route, named resource_verb: agents_create, sessions_list_events, computers_exec_command. A tool cannot exist without a route, and the argument schema is the same schema the server validates with.
  • A tool call is an ordinary API call. It is re-dispatched through the full middleware chain with the caller's own bearer, so authentication, scopes, the plan gate, and rate limits apply once and identically. A key scoped agents:read calling agents_create gets a 403 back as a tool error. A tool can never reach further than its key.
  • Every tool carries annotations.readOnlyHint. true for every read, false for every write, so a client can allow reads and ask on writes without maintaining a list.

Nine routes are deliberately withheld from the catalog: the endpoint itself, the never-ending SSE stream, the multipart upload, the four API key routes that mint or return live credentials, the one vault route that accepts a secret value, and the model proxy. They answer normally over HTTP; they are simply never handed to a model.

An agent that runs on Vetta declares the server like any other MCP server:

Naïve as an MCP server on an agent
{
  "mcp_servers": [
    { "type": "url", "name": "vetta", "url": "https://api.vetta.sh/v1/mcp" }
  ]
}

A framework with its own MCP client points at the same URL with the same header. There is deliberately no vetta mcp command: every operation behind the endpoint already has one, and this endpoint exists for MCP clients.

Step three: allow, ask, or deny, per tool

Governance is the reason to route tool calls through Naïve rather than call vendors from the loop. Every tool resolves to one of three permissions:

PermissionBehavior
allowThe tool runs without confirmation.
askThe runtime emits a tool.confirm event and pauses the tool until you approve or reject it.
denyThe tool is not offered to the model at all.

Tools are configured as one object: a default_config that sets the baseline, and a configs map of per-tool overrides keyed by tool name. MCP tools are keyed <server>.<tool>, so the catalog above lands in the same map as bash:

Allow reads, ask on writes
{
  "tools": {
    "default_config": { "permission": "ask" },
    "configs": {
      "vetta.agents_list": { "enabled": true, "permission": "allow" },
      "vetta.sessions_list_events": { "enabled": true, "permission": "allow" },
      "vetta.agents_create": { "enabled": true, "permission": "ask" },
      "tracker.delete_project": { "enabled": false }
    }
  }
}

The default permission for MCP tools is ask, so a newly exposed server tool never auto-runs. To trust one, give it an explicit allow by name; there is no server-wide trust switch. When an ask fires, the session goes idle with stop_reason: "awaiting_approval", and you resolve it from the CLI:

Answer an ask
vetta session confirm --session $SID --tool-call $CALL_ID --allow
vetta session confirm --session $SID --tool-call $CALL_ID --deny --reason "not this account"

The decision records who answered, on the event and in the audit trail. A held tool consumes no budget while it waits, and because the loop is durable, an agent can sit on a confirmation for hours at storage cost only. ask_timeout_seconds and on_timeout bound the wait; the default on timeout is deny. See Approvals for the primitive and how to add human approval to an AI agent for the driving code.

Step four: credentials live in the vault, not in the agent

Configuration is split so secrets never live on the agent definition. The agent declares MCP servers by name and URL. The vault holds the credential, as a static_bearer or mcp_oauth credential keyed by the server URL, and the platform injects it server-side when the agent connects:

A bearer for an MCP server, sealed into a vault
vetta vault create --name ava-secrets --identity idn_...
printf '%s' "$TOKEN" | vetta vault set --vault vlt_... --mcp-bearer https://mcp.example.com/mcp
vetta vault credentials --vault vlt_...   # metadata only; no value is ever returned

The value travels on stdin, never in argv, and the sandbox never receives the token. The model, the transcript, and your logs see that the connection worked and nothing else. Secret fields are write-only on the API, and the one route that accepts a secret value is withheld from the MCP catalog so a model never types one into a tool.started event. An auth failure does not stop the session; it surfaces as a session.error event naming the server and is retried on the next wake.

Third-party apps follow the same rule through connections: an identity authorizes an app over OAuth or an API key through a hosted link, and the agent reaches the app's tools as <connector>.<tool> under the same allow, ask, deny filter.

Declare it once in naive.config.ts, then naive up

Everything above is config, and a coding agent following skill.md writes it for you. A naive.config.ts declares skills, identities, vaults, apps, and agents by name. naive up reconciles them against the platform in that order, so agents can reference the rest, and reports every resource as created, updated, unchanged, deleted, or refused with the reason. A vault credential's value is from_env only, read from your shell at apply time, so a secret never sits in the file. Re-running is always safe: nothing is deleted by omission, and naive up --dry-run says what an apply would do without writing anything.

The config is the source of truth: a field edited by hand in the dashboard is drift, and the next naive up converges it back. Your orchestrator code changes as often as you like; the company does not.

Where the loop runs

Framework choice and where the loop runs are separate decisions. Hosted vs bring-your-own runtime compares both. If your loop runs on your own machine or in your own service, it reaches Naïve over MCP as above. If you want the loop hosted too, a Vetta agent bundles a model, a prompt, tools, skills, a mandatory USD budget, and the harness you pick, and runs in metered sessions on our runtime, which pauses between turns. Either way the tool boundary is the same.

What stays enforced

No matter which framework or transport you pick:

  • Identity. Every call carries an organization-scoped API key, and identity-scoped primitives run as a named identity or persona.
  • Budget. Every agent has a USD budget from birth, and a call that would breach it is refused before it runs.
  • Policy. Every tool resolves to allow, ask, or deny before it runs, with ask as the default for anything reached over MCP.
  • Audit. Approvals record the approver, and control-plane actions are principal-attributed in the audit log.
  • Revoke. Deny the tool, scope the key, or delete the key. See how to revoke AI agent access instantly.

Where to start

  1. Paste the onboarding prompt into your coding agent. It will ask for your API key and your budget; it must not invent either.
  2. Point your framework's MCP client at https://api.vetta.sh/v1/mcp with the same bearer, or declare it in mcp_servers on a Vetta agent. See MCP sessions.
  3. Set the policy before the first write: allow reads, ask on writes, deny anything the agent should never have.
  4. Put the first MCP credential in a vault and confirm the token never appears in a transcript.
  5. Commit naive.config.ts and make naive up the only way the company changes.

FAQ

Does Naïve replace my agent framework or coding agent?
No. The loop stays where it is: a coding agent on your laptop, your own orchestrator, or Vetta, our managed agent. Naïve supplies the primitives that loop reaches for, exposed as MCP tools, and enforces identity, budget, and per-tool policy on the server side of every call.
What does a coding agent do when it reads skill.md?
It follows the onboarding manifest: register or log in, confirm the organization, declare skills, identities, vaults, apps, and agents in naive.config.ts, then run naive up to reconcile them against the platform. It asks the operator for anything it must not invent, such as an API key or a budget.
How do primitives show up as tools?
POST /v1/mcp is a JSON-RPC endpoint that speaks initialize, tools/list, and tools/call. The catalog is generated from the API's route table, one tool per served route, named resource_verb. Any MCP client can point at it with the same bearer key it uses for the REST API.
How do I stop an agent from doing something irreversible?
Set that tool to ask or deny in the agent's tools config. An ask pauses the session with a tool.confirm event until someone approves or rejects it; a deny means the tool is never offered to the model. MCP tools default to ask, so a newly exposed tool never auto-runs.
Where do the credentials for an MCP server live?
In a vault, as a static_bearer or mcp_oauth credential keyed by the server URL. The token is injected server-side when the agent connects. The sandbox, the transcript, and the model only ever see that the connection worked.