Govern a Vercel AI SDK App
You’re building with Vercel AI SDK. Your app calls tools — search, database queries, API actions. Now you need identity, permissions, and audit trails on every tool call without rewriting your app.
ACP integrates with Vercel AI SDK as an MCP tool provider. Your existing code stays the same — you just point tool discovery at your ACP endpoint, and every call gets governed automatically.
What you need
- A Vercel AI SDK application (Next.js, SvelteKit, or standalone)
- An ACP Cloud workspace (sign up)
- An identity provider configured in ACP (Auth0, Okta, or Entra ID)
Step 1: Install the MCP client
npm install @modelcontextprotocol/sdk
Vercel AI SDK supports MCP tool providers natively. ACP is a standard MCP server — no custom SDK needed.
Step 2: Connect to ACP
In your server-side route handler (e.g., app/api/chat/route.ts):
import { streamText } from "ai";
import { openai } from "@ai-sdk/openai";
import { experimental_createMCPClient } from "ai";
export async function POST(req: Request) {
const { messages } = await req.json();
const userJwt = req.headers.get("authorization")?.split(" ")[1];
// Connect to ACP as an MCP server
const mcpClient = await experimental_createMCPClient({
transport: {
type: "sse",
url: "https://api.makeagents.run/your-slug",
headers: {
Authorization: `Bearer ${userJwt}`,
},
},
});
const tools = await mcpClient.tools();
const result = streamText({
model: openai("gpt-4o"),
messages,
tools,
});
return result.toDataStreamResponse();
}
ACP returns only the tools the authenticated user has permission to use. A user with salesforce:read sees query tools. A user with salesforce:write also sees create/update tools.
Step 3: Pass user identity from the frontend
On the client side, include the user’s JWT in requests to your API route:
import { useChat } from "ai/react";
export default function Chat() {
const { messages, input, handleInputChange, handleSubmit } = useChat({
api: "/api/chat",
headers: {
Authorization: `Bearer ${session.accessToken}`,
},
});
return (
<form onSubmit={handleSubmit}>
{messages.map((m) => (
<div key={m.id}>{m.content}</div>
))}
<input value={input} onChange={handleInputChange} />
</form>
);
}
The JWT flows from your frontend → your API route → ACP. ACP verifies it on every tool call.
Step 4: Configure tool scopes in ACP
In your ACP dashboard under Policies → Tool Scopes:
{
"salesforce.query": ["salesforce:read"],
"salesforce.createRecord": ["salesforce:write"],
"github.listRepos": ["github:read"],
"slack.postMessage": ["slack:write"]
}
When the LLM tries to call a tool the user doesn’t have access to, ACP rejects it with 403 Insufficient scope. The LLM sees the rejection and adjusts its response.
What happens on every tool call
User message → Your API route → LLM → Tool call → ACP → Backend
↓
Identity verified
Scopes checked
PII scanned
Action logged
- JWT verification — RS256 signature check against your IdP’s JWKS endpoint
- Scope enforcement — user’s permissions checked against tool requirements
- Immutable rules — SSN, credit card, and SSRF patterns blocked (bypass-immune)
- Content scanning — configurable PII detection on inputs and outputs
- Rate limiting — per-user limits prevent abuse
- Execution — tool runs with the user’s own OAuth credentials
- Audit logging — full attribution: who, what tool, when, success/failure
Edge runtime compatibility
ACP uses standard HTTP (Streamable HTTP or SSE transport). It works with Vercel’s Edge runtime — no Node.js-specific dependencies required.
export const runtime = "edge";
export async function POST(req: Request) {
// Same code as above — works on Edge
}
Multiple users, isolated sessions
Each user’s JWT creates an isolated session. User A’s tool calls are scoped to their permissions and credentials. User B’s are completely separate. There’s no shared state.
This means you don’t need to manage per-user API keys, token storage, or permission checks in your application code. ACP handles all of it.
Back to guides · Add governance to LangChain → · PII detection →