integrate-arcjet-guard-claude-agent-sdk
Integrate Arcjet security into a Claude Agent SDK agent using @arcjet/guard — wrap tool() handlers, screen inbound prompts with UserPromptSubmit, and deny unwrapped built-in/MCP tools with PreToolUse. Use when asked to add Arcjet to a Claude Agent SDK or Claude Code agent, rate limit its tools, screen inbound messages, or block prompt injection / PII.
git clone --depth 1 https://github.com/arcjet/arcjet-js /tmp/integrate-arcjet-guard-claude-agent-sdk && cp -r /tmp/integrate-arcjet-guard-claude-agent-sdk/arcjet-guard/skills/integrate-arcjet-guard-claude-agent-sdk ~/.claude/skills/integrate-arcjet-guard-claude-agent-sdkSKILL.md
# Integrate Arcjet Guard into a Claude Agent SDK agent
`@arcjet/guard`'s Claude Agent SDK v0 namespace wraps the agent's existing
Arcjet client. It never talks to the Arcjet API itself. Three surfaces, one
decision rule:
- **An authored tool** (`tool()` + `createSdkMcpServer()`) → `guardTool()`.
DENY is a `CallToolResult` with `isError: true`. Do not throw.
- **Inbound text** → `guardHooks()` `UserPromptSubmit`. DENY is
`{ decision: "block" }`. Timeout already fail-closes the prompt
(Claude Code v2.1.208+).
- **Built-ins / unwrapped MCP** → `guardHooks()` `PreToolUse` with
`permissionDecision: "deny"`. Timeout already fail-closes (the tool does
not run). `PostToolUse` is capture only.
- **Correlation** → `claudeAgentContext()` reads `session_id` from hook
input or `options.sessionId`. Subagents have `agent_id` (metadata only).
It never mints a new id. **`options.sessionId` must be a UUID and can only
be created once** — see Step 5.
## Screen inbound with UserPromptSubmit
This is the only place a turn can be declined before the model sees the
prompt. There is no `guardInbound`. Put `detectPromptInjection` on
`guardHooks({ inbound })`. A DENY returns `{ decision: "block", reason }`
and Claude Code erases the prompt.
## canUseTool is not a policy gate
Claude's docs say `canUseTool` is skipped by `allowedTools`, allow rules,
and `bypassPermissions` / `acceptEdits`. There is no `guardCanUseTool`.
Do not put Arcjet policy on `canUseTool`.
## PreToolUse is the only deny for unwrapped tools
Built-ins (Bash, Write, …) and MCP tools you did not pass through
`guardTool` are gated here. Annotations (`readOnlyHint`, …) and sandbox
settings are not enforcement. `PostToolUse` cannot undo a tool that already
ran.
## Questions to ask the human first
Ask only what you cannot infer from the code; suggest defaults.
1. Which tools are **risky** (external side effects, irreversible, spends
money, sends messages)? Those get `guardTool`. Built-ins and unwrapped
MCP get `guardHooks` PreToolUse.
2. What **limits**? (e.g. "10 lookups/min per order" → `tokenBucket`.)
3. Who is the **user** for metadata — an opaque user/tenant ID (never PII)?
Default: none. Pass it via `metadata` on the policy. Session id is the
correlation id, not the user.
4. Is an Arcjet outage unacceptable? Every helper defaults to
`onGuardError: "deny"`. Ask explicitly about inbound
`UserPromptSubmit`: failing closed there means the agent stops answering
for the duration of the outage, so `"allow"` is a routine and legitimate
choice at that one call site.
## The seven things readers get wrong
1. **There is no `guardInbound`.** Screen prompt injection on
`guardHooks({ inbound })` via `UserPromptSubmit`.
2. **`canUseTool` is not a policy gate.** It is skipped by `allowedTools`,
allow rules, and `bypassPermissions` / `acceptEdits`. Use `guardTool` or
`PreToolUse`.
3. **The import path is versioned and there is no alias.**
`@arcjet/guard/claude-agent-sdk/v0`. `@arcjet/guard/claude-agent-sdk`
does not resolve.
4. **`options.sessionId` is a UUID, and only once.** A non-UUID exits with
"Invalid session ID"; reusing one exits with "already in use". Mint a
UUID for the conversation and `resume` it on later turns.
5. **Correlation is read, never minted.** Do not call `createAgentContext`
inside a Claude callback — that generates a second id and splits the
Sequence. `claudeAgentContext` reads `session_id` / `options.sessionId`
and omits `correlationId` when neither is a valid id. Subagent
`agent_id` is metadata, not the correlation id.
6. **Do not double-wrap with `@arcjet/guard/vercel-ai/v7` or
`@arcjet/guard/agents`.** Claude tools are `tool()`, not AI SDK
`tool()`. `guardTool` throws if the tool already carries the Arcjet
protection brand. Applying `guardTool` and `guardHooks` PreToolUse to
the same authored tool double-calls the guard.
7. **A denial from `guardTool` is a `CallToolResult` with `isError: true`**,
not a throw. If `onDeny` throws, the handler still does not run and the
model still receives the default denial result.
## Step 1: Install and find the guard client
Install `@arcjet/guard` (required), plus `@anthropic-ai/claude-agent-sdk`
(optional peer, needed for `@arcjet/guard/claude-agent-sdk/v0`). Always
use the versioned path: `@arcjet/guard/claude-agent-sdk/v0` resolves;
`@arcjet/guard/claude-agent-sdk` throws `ERR_PACKAGE_PATH_NOT_EXPORTED`.
```sh
npm install @arcjet/guard @anthropic-ai/claude-agent-sdk
```
If the agent has no guard client yet, launch one **once at module scope**:
```ts
import { launchArcjet } from "@arcjet/guard";
export const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
```
## Step 2: Gate authored tools
```ts
import { tool } from "@anthropic-ai/claude-agent-sdk";
import { z } from "zod";
import { guardTool } from "@arcjet/guard/claude-agent-sdk/v0";
import { tokenBucket, localDetectSensitiveInfo } from "@arcjet/guard";
import { arcjet } from "./arcjet.js";
const lookupLimit = tokenBucket({
bucket: "lookups",
refillRate: 10,
intervalSeconds: 60,
maxTokens: 10,
});
const detectPii = localDetectSensitiveInfo();
export const lookupOrder = guardTool(
arcjet,
tool(
"lookup_order",
"Look up an order by ID",
{
orderId: z.string(),
note: z.string(),
},
async ({ orderId, note }) => ({
content: [{ type: "text", text: `${orderId}: shipped (${note})` }],
}),
),
{
action: "order.looked-up",
rules: (input) => [lookupLimit({ key: input.orderId, requested: 1 }), detectPii(input.note)],
},
);
```
- Omit `rules` to submit none. The guard call still happens.
- On DENY the tool's handler never runs. The model receives
`{ content, structuredContent: { arcjetDenied, reason, message, retryable }, isError: true }`.
- Default `onGuardError: "deny"` blocks the tool if Arcjet is unreachable.
- Pass the same `sessionId` you give `query({ options.sessIntegrate Arcjet security into a Vercel AI SDK (v7) application using @arcjet/guard — wrap agent tools with guard checks, enforce rules on risky app actions, and emit audit events joined by one correlation ID. Use when asked to add Arcjet to an AI SDK app, protect or rate limit agent tool calls, guard AI agent actions, or audit what an agent did.
Integrate Arcjet security into a Vercel Eve agent using @arcjet/guard — add guard gates to tools and connections, screen inbound messages, and record agent lifecycle events correlated to the session. Use when asked to add Arcjet to an Eve agent, rate limit its tools, guard connection access, or screen inbound messages.
Integrate Arcjet security into a Genkit JS agent using @arcjet/guard — wrap ai.defineTool, put guardMiddleware on generate({ use }) for unwrapped / MCP / filesystem tools, and read a caller-owned id from generate({ context }). Use when asked to add Arcjet to genkit, rate limit its tools, screen inbound messages, or block prompt injection / PII.
Integrate Arcjet security into a LangChain JS createAgent using @arcjet/guard — wrap tool() / StructuredTool, put guardMiddleware on createAgent({ middleware }) for MCP / unwrapped tools, and read configurable.thread_id for correlation. Use when asked to add Arcjet to langchain createAgent, rate limit its tools, screen inbound messages, or block prompt injection / PII. This is LangChain JS, not the Python page.
Integrate Arcjet security into a LangGraph Graph API agent using @arcjet/guard — wrap tool() / StructuredTool, wrap ToolNode for unwrapped MCP tools, and read thread_id for correlation. Use when asked to add Arcjet to a LangGraph StateGraph / ToolNode agent, rate limit its tools, screen inbound messages, or block prompt injection / PII.
Integrate Arcjet security into a Mastra agent using @arcjet/guard — wrap createTool execute, screen input/output with a Processor tripwire, and gate unwrapped MCP/workspace tools with hooks. Use when asked to add Arcjet to a Mastra agent, rate limit its tools, screen inbound messages, or block prompt injection / PII.
Integrate Arcjet security into an OpenAI Agents text Agent using @arcjet/guard — wrap tool({ execute }), screen inbound before run(), and read a caller-owned id from runContext.context. Use when asked to add Arcjet to @openai/agents, rate limit its tools, screen inbound messages, or block prompt injection / PII.
Integrate Arcjet security into a Strands Agents JS app using @arcjet/guard — wrap tool({ callback }), put guardHooks on Agent({ plugins }) for unwrapped / MCP / vended tools, and read a caller-owned id from invocationState. Use when asked to add Arcjet to strands-agents, rate limit its tools, screen inbound messages, or block prompt injection / PII.