@arcjet/skills 1.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/docs/guard.md ADDED
@@ -0,0 +1,142 @@
1
+ # Guard non-HTTP JavaScript with Arcjet
2
+
3
+ Source documentation for the `guard` Agent Skill. JavaScript and TypeScript
4
+ only. For HTTP routes, see `docs/protect.md`.
5
+
6
+ ## When to use Guard
7
+
8
+ Use `@arcjet/guard` when there is no HTTP request object: agent tool calls,
9
+ MCP server handlers, queue workers, and background jobs.
10
+
11
+ The word "server" on MCP is misleading. MCP tools are invoked over stdio or
12
+ SSE and do not receive an HTTP request.
13
+
14
+ ## Client
15
+
16
+ ```ts
17
+ import { launchArcjet } from "@arcjet/guard";
18
+
19
+ export const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
20
+ ```
21
+
22
+ Create the client once at module scope. Prefer passing the client explicitly.
23
+ `registerArcjet` enables free `guard()` / `capture()` / `flush()` when you
24
+ cannot thread a client. Free `guard()` fail-opens if nothing is registered —
25
+ check `hasFailedOpen()` and do not treat that ALLOW as a pass.
26
+
27
+ Declare rules at module scope so `.deniedResult(decision)` and
28
+ `.errorResult(decision)` have a stable reference.
29
+
30
+ ## One `guard()` per operation
31
+
32
+ Call `guard()` where you already know the operation. Hardcode the `label`.
33
+ Do not interpolate names in a generic dispatcher.
34
+
35
+ Labels are slugs: lowercase letters, digits, dash, and dot only; must start
36
+ and end with a letter or digit; max 256 bytes. Use `tools.get-weather`, not
37
+ `tools.get_weather`. Metadata keys may contain underscores; labels and
38
+ rate-limit `bucket` names may not.
39
+
40
+ ```ts
41
+ const decision = await arcjet.guard("tools.get-weather", {
42
+ rules: [/* ... */],
43
+ metadata: { user: { id: userId } },
44
+ });
45
+
46
+ if (decision.conclusion === "DENY") {
47
+ const rateLimited = toolCallLimit.deniedResult(decision);
48
+ if (rateLimited) {
49
+ throw new Error(`rate limited – retry after unix ${rateLimited.resetAtUnixSeconds}`);
50
+ }
51
+ if (decision.reason === "PROMPT_INJECTION") {
52
+ throw new Error("input flagged as prompt injection");
53
+ }
54
+ return { error: decision.reason };
55
+ }
56
+ ```
57
+
58
+ `decision.reason` is a flat string on DENY (`"RATE_LIMIT"`,
59
+ `"PROMPT_INJECTION"`, `"SENSITIVE_INFO"`, `"MODERATE_CONTENT"`, …) and
60
+ `undefined` on ALLOW. Branch on which rule denied, not only on `DENY`.
61
+
62
+ `metadata` is nested JSON for the Console. It does not affect the decision.
63
+ Do not put secrets or PII in it.
64
+
65
+ Every rate-limit rule needs a `key` and a `bucket`. Use a user or session id
66
+ when you have one; otherwise a stable identifier you control (`"default"`,
67
+ deployment name). Do not pass an empty string.
68
+
69
+ A denial by one rule still spends the others' budget in the same `guard()`
70
+ call. Split rules across two calls if a PII false positive must not drain a
71
+ rate-limit bucket.
72
+
73
+ `guard()` never throws for runtime degradation. `hasFailedOpen()` is true
74
+ when ALLOW means a rule or the decision could not be processed — that is
75
+ the fail-closed gate. `warnings` are request-validation diagnostics; they
76
+ never change the conclusion. Prefer `hasFailedOpen()` over deprecated
77
+ `hasError()`.
78
+
79
+ Pass `correlationId` to join a guard decision with a request or agent
80
+ trace. It is a dedicated field, not metadata.
81
+
82
+ `localDetectSensitiveInfo()` defaults to WASM and matches four types:
83
+ card, email, phone, IP. Names, addresses, and government / financial
84
+ identifiers need `@arcjet/sensitive-info-rampart` (`backend: rampart()`).
85
+ Listing a Rampart-only entity without that backend never matches.
86
+ `moderateContent()` is Guard-only.
87
+
88
+ ## Framework wrappers
89
+
90
+ Prefer the versioned Guard namespaces over hand-wrapping every tool:
91
+
92
+ | SDK | Import |
93
+ | --- | ------ |
94
+ | Vercel AI SDK v7 | `@arcjet/guard/vercel-ai/v7` |
95
+ | Vercel Eve | `@arcjet/guard/vercel-eve/v0` |
96
+ | Mastra | `@arcjet/guard/mastra/v1` |
97
+ | Claude Agent SDK | `@arcjet/guard/claude-agent-sdk/v0` |
98
+ | Claude Managed Agents | `@arcjet/guard/claude-managed-agents/v0` |
99
+ | LangGraph JS | `@arcjet/guard/langgraph/v1` |
100
+ | LangChain JS `createAgent` | `@arcjet/guard/langchain/v1` |
101
+ | OpenAI Agents | `@arcjet/guard/openai-agents/v0` |
102
+ | Genkit JS | `@arcjet/guard/genkit/v1` |
103
+ | Google ADK JS | `@arcjet/guard/google-adk/v2` |
104
+ | Strands Agents JS | `@arcjet/guard/strands-agents/v1` |
105
+ | TanStack AI | `@arcjet/guard/tanstack-ai/v0` |
106
+
107
+ Unversioned aliases do not resolve. Load the matching
108
+ `@arcjet/guard#integrate-arcjet-guard-*` skill for the integration you are
109
+ implementing. Those skills ship in the `@arcjet/guard` package.
110
+
111
+ Framework wrappers fail closed by default when Guard is unavailable. Core
112
+ `guard()` fails open — check `hasFailedOpen()`.
113
+
114
+ JS LangChain `createAgent` is `@arcjet/guard/langchain/v1`, not LangGraph.
115
+ JS Google ADK is `guardPlugin` on the Runner — there is no `guardTool`.
116
+ TanStack AI is `guardMiddleware` — do not use `guardTool` (an `execute`
117
+ throw is swallowed). JS Strands Agents is official `@strands-agents/sdk`,
118
+ not Python `strands`.
119
+
120
+ Every adapter uses one `ArcjetDenialResult` payload
121
+ (`{ arcjetDenied: true, reason, message, retryable }`). Delivery is
122
+ per-framework: return the object (AI SDK, Mastra, OpenAI Agents, Genkit
123
+ `toolResponse.output`, LangGraph, LangChain `guardTool`, Google ADK plugin
124
+ dict, Strands `guardTool`); wrap it (Claude Agent SDK `isError: true`,
125
+ LangChain `guardMiddleware` `ToolMessage`, Strands `guardHooks` cancel
126
+ string, TanStack `onBeforeToolCall` skip); or throw `ArcjetDeniedError`
127
+ (Eve, unless `onDeny: "result"`). A throw from a return-style adapter
128
+ drops the fields.
129
+
130
+ Human-in-the-loop APIs (`needsApproval`, `interrupt()`, `requireApproval`,
131
+ `requireConfirmation`, `canUseTool`) are not policy gates. After a human
132
+ yes, Guard still runs.
133
+
134
+ ## Capture and flush
135
+
136
+ `capture()` records that something happened. It never denies. Call `flush()`
137
+ on shutdown. On serverless, pass a platform `waitUntil`.
138
+
139
+ ## Verify
140
+
141
+ Invoke the protected function directly. Do not `curl` an HTTP route to test
142
+ Guard. Confirm with `npx @arcjet/cli@latest guards list --site-id <id>`.
package/docs/mcp.md ADDED
@@ -0,0 +1,74 @@
1
+ # Arcjet MCP server
2
+
3
+ Connect an AI coding client to the Arcjet API over HTTP with OAuth. Use this
4
+ when the CLI is not available or the client has built-in MCP support.
5
+
6
+ **Endpoint:** `https://api.arcjet.com/mcp`
7
+
8
+ Verify that URL before connecting. Only connect from trusted clients. Enable
9
+ confirmation prompts for write operations.
10
+
11
+ ## Client setup
12
+
13
+ ### VS Code
14
+
15
+ `.vscode/mcp.json`:
16
+
17
+ ```json
18
+ {
19
+ "servers": {
20
+ "arcjet": {
21
+ "type": "http",
22
+ "url": "https://api.arcjet.com/mcp"
23
+ }
24
+ }
25
+ }
26
+ ```
27
+
28
+ ### Claude Code
29
+
30
+ ```bash
31
+ claude mcp add arcjet --transport http https://api.arcjet.com/mcp
32
+ ```
33
+
34
+ ### Cursor
35
+
36
+ `.cursor/mcp.json`:
37
+
38
+ ```json
39
+ {
40
+ "mcpServers": {
41
+ "arcjet": {
42
+ "type": "streamable-http",
43
+ "url": "https://api.arcjet.com/mcp"
44
+ }
45
+ }
46
+ }
47
+ ```
48
+
49
+ ### Windsurf
50
+
51
+ ```json
52
+ {
53
+ "mcpServers": {
54
+ "arcjet": {
55
+ "serverUrl": "https://api.arcjet.com/mcp"
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
61
+ ### Claude Desktop / ChatGPT
62
+
63
+ Add a custom connector named `Arcjet` with URL `https://api.arcjet.com/mcp`.
64
+
65
+ ## Authentication
66
+
67
+ OAuth. The first connection redirects to sign in with an Arcjet account.
68
+
69
+ ## Common workflows
70
+
71
+ - Bootstrap: `list-teams` → `list-sites` → `get-site-key` → write `ARCJET_KEY`
72
+ - Investigate: `analyze-traffic` → `list-requests` → `investigate-ip`
73
+ - Remote rules: `create-rule` (DRY_RUN) → `get-dry-run-impact` → `promote-rule`
74
+ - Briefing: `get-security-briefing`
@@ -0,0 +1,186 @@
1
+ # Protect HTTP routes with Arcjet
2
+
3
+ Source documentation for the `protect` Agent Skill. JavaScript and TypeScript
4
+ only. For non-HTTP tool calls, jobs, and MCP handlers, see `docs/guard.md`.
5
+
6
+ ## When to use request protection
7
+
8
+ Use a framework adapter when the code has an HTTP request object (Next.js
9
+ `Request`, Express `req`, Fastify request, SvelteKit `RequestEvent`).
10
+
11
+ | Runtime / framework | Package |
12
+ | ------------------- | ------- |
13
+ | Next.js | `@arcjet/next` |
14
+ | Node.js (`http`, Express) | `@arcjet/node` |
15
+ | Bun | `@arcjet/bun` |
16
+ | Deno | `@arcjet/deno` |
17
+ | Fastify | `@arcjet/fastify` |
18
+ | NestJS | `@arcjet/nest` |
19
+ | Nuxt | `@arcjet/nuxt` |
20
+ | React Router | `@arcjet/react-router` |
21
+ | Remix | `@arcjet/remix` |
22
+ | SvelteKit | `@arcjet/sveltekit` |
23
+ | Astro | `@arcjet/astro` |
24
+
25
+ Hono on Node uses `@arcjet/node`. Hono on Bun uses `@arcjet/bun`.
26
+
27
+ MCP servers, queue workers, and agent tool calls do **not** receive an HTTP
28
+ request. Use `@arcjet/guard` instead.
29
+
30
+ Server actions, tRPC, and other RPC-over-HTTP still have an HTTP request —
31
+ use request protection.
32
+
33
+ Runtime baseline: Node.js `>=22.21.0 <23 || >=24.5.0`, Bun ≥ 1.3.0, Deno
34
+ `stable` / `lts`. Bump the runtime before installing if the project is below
35
+ those floors.
36
+
37
+ ## Client setup
38
+
39
+ Create **one** `arcjet()` client at module scope in a shared file. Use
40
+ `withRule()` for route-specific extras so clones share the decision cache.
41
+ Sibling `arcjet()` constructors do not share cache.
42
+
43
+ Always include `shield({ mode: "LIVE" })` as a base rule.
44
+
45
+ ```ts
46
+ import arcjet, { shield, detectBot } from "@arcjet/next";
47
+
48
+ export const aj = arcjet({
49
+ key: process.env.ARCJET_KEY!,
50
+ rules: [
51
+ shield({ mode: "LIVE" }),
52
+ detectBot({ mode: "LIVE", allow: ["CATEGORY:SEARCH_ENGINE"] }),
53
+ ],
54
+ });
55
+ ```
56
+
57
+ Rules that omit `mode` default to `DRY_RUN` in the JavaScript HTTP SDKs. Pass
58
+ `mode: "LIVE"` to enforce.
59
+
60
+ Never hardcode `ARCJET_KEY`. Write it to the project's env file. Prefer
61
+ `npx @arcjet/cli@latest` to create a site and fetch a key (see `docs/cli.md`).
62
+
63
+ Install the adapter with the project's package manager. Do not hand-edit
64
+ `package.json` and guess a version.
65
+
66
+ ### Frameworks that are not a shared client file
67
+
68
+ - **Astro** — register the integration in `astro.config.mjs` and import
69
+ `arcjet:client`. There is no `lib/arcjet.ts` and no `withRule()`.
70
+ `@arcjet/astro` validates with Zod `.strict()`, so leftover
71
+ `detectPromptInjection({ threshold })` throws at startup.
72
+ - **Nuxt** — register the `@arcjet/nuxt` module. The key lives in
73
+ `nuxt.config.ts`. Import from `#arcjet`. Each `arcjet()` call is its own
74
+ cache — share one server module.
75
+ - **NestJS** — `ArcjetModule.forRoot()` plus `@InjectArcjet()`.
76
+ - **Bun / Deno** — wrap the fetch handler with `aj.handler()` so Arcjet can
77
+ see the socket for client IP. Still call `protect()` inside. Deno imports
78
+ use the `npm:` prefix.
79
+ - **Hono on Node** — type the app with `HttpBindings` from
80
+ `@hono/node-server` and pass `c.env.incoming` to `protect()`. Hono on Bun
81
+ passes `c.req.raw`.
82
+
83
+ ### What to pass to `protect()`
84
+
85
+ | Framework | Argument |
86
+ | --------- | -------- |
87
+ | Express / Node.js | `req` |
88
+ | Next.js App Router | `req` |
89
+ | Next.js Server Components / actions | `await request()` from `@arcjet/next` |
90
+ | Fastify | `request` (Fastify request, not raw Node) |
91
+ | NestJS | `req` |
92
+ | SvelteKit / Nuxt | `event` |
93
+ | Remix / React Router | `args` |
94
+ | Astro / Bun / Deno | `request` |
95
+ | Hono on Node | `c.env.incoming` |
96
+ | Hono on Bun | `c.req.raw` |
97
+
98
+ ## Call `protect()` in the handler
99
+
100
+ Call `protect()` inside each route handler, once per request. Do not call it
101
+ from Express middleware or Next.js middleware.
102
+
103
+ ```ts
104
+ export async function GET(request: Request) {
105
+ const decision = await aj.protect(request);
106
+
107
+ if (decision.isDenied()) {
108
+ return Response.json({ error: "Forbidden" }, { status: 403 });
109
+ }
110
+
111
+ return Response.json({ ok: true });
112
+ }
113
+ ```
114
+
115
+ Map denial reasons to HTTP responses only when the status or body should
116
+ differ:
117
+
118
+ - `decision.reason.isRateLimit()` → 429
119
+ - `decision.reason.isEmail()` / `isSensitiveInfo()` / `isPromptInjection()` → 400
120
+ - everything else (bot, shield, filter) → 403
121
+
122
+ `decision.isErrored()` means the SDK failed open. Log it and allow.
123
+
124
+ `detectBot` requires exactly one of `allow` or `deny`. Neither or both throws.
125
+
126
+ `detectPromptInjection` takes `mode` only. Do not pass `threshold`. Do not
127
+ read `score`; branch on `isPromptInjection()` / `injectionDetected`.
128
+
129
+ Rules that need extra input at `protect()` time: `tokenBucket` needs
130
+ `{ requested }`, `validateEmail` / `protectSignup` need `{ email }`,
131
+ `sensitiveInfo` needs `{ sensitiveInfoValue }`, `detectPromptInjection`
132
+ needs `{ detectPromptInjectionMessage }`.
133
+
134
+ Pass `correlationId` when the decision must join another request, guard
135
+ call, or agent trace. It does not affect fingerprinting.
136
+
137
+ ## Characteristics and client IP
138
+
139
+ Put a `userId` characteristic on the rule that needs it, then pass a trusted,
140
+ authenticated user ID at protection time. Never rate limit by a client-controlled
141
+ header unless a trusted proxy strips and rewrites it.
142
+
143
+ Treat client-IP provenance as security configuration. When no usable public
144
+ address is available, adapters may fall back to forwarding headers and log one
145
+ `client_ip_provenance="unverified-header"` warning for the lifetime of each
146
+ SDK client. Configure every trusted proxy (`proxies`, or a helper such as
147
+ `cloudflare()`). The application must be reachable only through infrastructure
148
+ that overwrites or safely appends those headers. Malformed proxy entries are
149
+ rejected. `0.0.0.0/0` and `::/0` warn because they trust every peer.
150
+
151
+ If the application already has an independently trusted client IP, pass it as
152
+ `ipSrc` to both `protect()` and the diagnostics API. A non-empty value wins
153
+ over automatic detection. An empty string is treated as omitted. Malformed
154
+ values are rejected. Syntax validation does not prove provenance.
155
+
156
+ Never silence an `unverified-header` warning by copying `X-Forwarded-For` (or
157
+ another client-controlled header) into `ipSrc`. That relabels attacker input
158
+ as trusted. Inspect representative requests with
159
+ `client.clientIpDetails(request)` in `@arcjet/node`, or `findIpDetails()` /
160
+ `resolveClientIp()` from `@arcjet/ip`. Check `ip`, `provenance`, `verified`,
161
+ and `header`. These diagnostics do not consume the once-per-client warning.
162
+
163
+ `protect()` accepts nested-JSON `metadata`. It does not affect fingerprinting.
164
+ Do not put secrets or PII in it. When present, request decisions also expose
165
+ optional IP threat intelligence (`decision.ip.threat`).
166
+
167
+ When present, `decision.ip.threat` is optional IP threat intelligence. Check
168
+ before reading.
169
+
170
+ ## Common mistakes
171
+
172
+ - Using `@arcjet/guard` on an HTTP route, or a request adapter on a tool call
173
+ - Calling `protect()` in middleware instead of the route handler
174
+ - Calling `protect()` twice for the same request (double-counts rate limits)
175
+ - Leaving rules in `DRY_RUN` and expecting them to block
176
+ - Passing both `allow` and `deny` to `detectBot`
177
+ - Passing `threshold` to `detectPromptInjection`
178
+ - Copying `X-Forwarded-For` into `ipSrc` to hide an unverified-header warning
179
+ - Hardcoding `ARCJET_KEY`
180
+
181
+ ## Verify
182
+
183
+ 1. Type-check or build the project.
184
+ 2. Hit the protected route (`curl`). To trip a rate limit, loop the request.
185
+ 3. Confirm the decision with `npx @arcjet/cli@latest requests list --site-id <id>`
186
+ or the [Console](https://console.arcjet.com).
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "@arcjet/skills",
3
+ "version": "1.12.0",
4
+ "description": "Versioned Agent Skills for the Arcjet JavaScript SDK, shipped with TanStack Intent",
5
+ "keywords": [
6
+ "agent-skills",
7
+ "ai",
8
+ "arcjet",
9
+ "security",
10
+ "tanstack-intent"
11
+ ],
12
+ "homepage": "https://arcjet.com",
13
+ "bugs": {
14
+ "url": "https://github.com/arcjet/arcjet-js/issues",
15
+ "email": "support@arcjet.com"
16
+ },
17
+ "license": "Apache-2.0",
18
+ "author": {
19
+ "name": "Arcjet",
20
+ "email": "support@arcjet.com",
21
+ "url": "https://arcjet.com"
22
+ },
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/arcjet/arcjet-js.git",
26
+ "directory": "arcjet-skills"
27
+ },
28
+ "files": [
29
+ "dist",
30
+ "docs",
31
+ "skills"
32
+ ],
33
+ "type": "module",
34
+ "main": "./dist/index.js",
35
+ "types": "./dist/index.d.ts",
36
+ "exports": {
37
+ ".": {
38
+ "types": "./dist/index.d.ts",
39
+ "default": "./dist/index.js"
40
+ },
41
+ "./package.json": "./package.json"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "scripts": {
47
+ "build": "tsdown",
48
+ "typecheck": "tsc --noEmit",
49
+ "lint": "oxlint --tsconfig=tsconfig.json",
50
+ "format": "oxfmt",
51
+ "format:check": "oxfmt --check",
52
+ "validate-skills": "node ../.github/scripts/intent-cli.mjs validate",
53
+ "stale-skills": "node ../.github/scripts/intent-cli.mjs stale --json",
54
+ "test-api": "node --test -- test/*.test.ts",
55
+ "test-coverage": "node --experimental-test-coverage --test -- test/*.test.ts",
56
+ "test": "npm run build && npm run test-coverage && npm run validate-skills && node ../.github/scripts/check-skill-source-stale.mjs"
57
+ },
58
+ "devDependencies": {
59
+ "@tanstack/intent": "0.3.8",
60
+ "@types/node": "22.20.1",
61
+ "tsdown": "0.22.14",
62
+ "typescript": "7.0.2"
63
+ },
64
+ "engines": {
65
+ "node": ">=22.21.0 <23 || >=24.5.0"
66
+ }
67
+ }
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: choose-protections
3
+ description: Choose which Arcjet rules address a security problem. Use when deciding between detectBot, shield, rate limits, detectPromptInjection, sensitiveInfo, validateEmail, filter, or Guard-only content moderation.
4
+ license: Apache-2.0
5
+ compatibility: JavaScript and TypeScript Arcjet SDK.
6
+ metadata:
7
+ author: arcjet
8
+ type: core
9
+ library: "@arcjet/skills"
10
+ library_version: "1.12.0" # x-release-please-version
11
+ sources:
12
+ - docs/choose-protections.md
13
+ ---
14
+
15
+ # Choose Arcjet protections
16
+
17
+ Load this skill when you need to pick rules. Then load `@arcjet/skills#protect`
18
+ or `@arcjet/skills#guard` to implement them.
19
+
20
+ | Problem | Rule | Where |
21
+ | --- | --- | --- |
22
+ | Bots, scrapers, automated clients | `detectBot` (`allow` **or** `deny`, not both) | Request only |
23
+ | SQLi / XSS / common probes | `shield` (`mode: "LIVE"`) | Request only |
24
+ | Cost / abuse quotas | `tokenBucket`, `fixedWindow`, `slidingWindow` | Request and Guard |
25
+ | Jailbreaks, instruction overrides | `detectPromptInjection` (binary; no `threshold`) | Request and Guard |
26
+ | Card / email / phone / IP in text | `sensitiveInfo` / `localDetectSensitiveInfo` | Request and Guard |
27
+ | Names, addresses, gov IDs | same rules + `@arcjet/sensitive-info-rampart` | Server runtime |
28
+ | Unsafe / abusive text | `moderateContent` | Guard only |
29
+ | Signup spam, disposable email | `validateEmail` + `protectSignup` | Request only |
30
+ | VPN / Tor / country / IP lists | `filter` | Request only |
31
+ | Dangerous tool calls | Guard per-tool `label` + rate limits | Guard only |
32
+
33
+ Token bucket: variable-cost AI (`requested` per call). Fixed window: hard cap.
34
+ Sliding window: no boundary burst. Request rate limits default to IP; set
35
+ `characteristics: ["userId"]` for a user key. Guard rate limits always need
36
+ `key` and `bucket`.
37
+
38
+ Default sensitive-info backend matches four structured types. Rampart-only
39
+ entities never match without `backend: rampart()`.
40
+
41
+ `capture()` records that an action happened. It is not a protection rule.
42
+
43
+ Remote bot and filter rules can be managed with the CLI or MCP without a
44
+ redeploy — load `@arcjet/skills#cli` or `@arcjet/skills#mcp`.
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: cli
3
+ description: "Connect a project to Arcjet with the CLI: sign in, pick a team and site, and write ARCJET_KEY. Use when bootstrapping Arcjet, listing requests or guards, or managing remote rules from a terminal."
4
+ license: Apache-2.0
5
+ compatibility: Any project that can run npx or the arcjet binary.
6
+ metadata:
7
+ author: arcjet
8
+ type: core
9
+ library: "@arcjet/skills"
10
+ library_version: "1.12.0" # x-release-please-version
11
+ sources:
12
+ - docs/cli.md
13
+ ---
14
+
15
+ # Arcjet CLI
16
+
17
+ Preferred way to get a real `ARCJET_KEY` before writing Arcjet code. Do not
18
+ leave the key as a TODO.
19
+
20
+ ```bash
21
+ npx -y @arcjet/cli@latest auth status
22
+ # if not signed in:
23
+ npx -y @arcjet/cli@latest auth login
24
+
25
+ npx -y @arcjet/cli@latest teams list --output json --fields id,name
26
+ npx -y @arcjet/cli@latest sites list --team-id <team_id> --output json --fields id,name
27
+ npx -y @arcjet/cli@latest sites get-key --site-id <site_id> --output json --fields key
28
+ ```
29
+
30
+ Write `key` to the project's env file as `ARCJET_KEY=ajkey_...`. Match
31
+ existing env-file conventions and `.gitignore`. Never hardcode the key.
32
+
33
+ Frequent use: `npm install -g @arcjet/cli`, `brew install arcjet/tap/arcjet`,
34
+ or `curl -sSfL https://arcjet.com/cli/install.sh | bash`.
35
+
36
+ `--output json` and `--fields` keep agent context small. Confirm with the
37
+ user before write or delete operations (`--confirm`).
38
+
39
+ Investigate: `requests list` / `guards list` / `requests explain` /
40
+ `guards explain` / `briefing`. Remote rules: `rules create` (DRY_RUN) →
41
+ `analyze dry-run-impact` → `rules promote`.
42
+
43
+ If the CLI cannot run, load `@arcjet/skills#mcp` or send the user to
44
+ https://console.arcjet.com.
@@ -0,0 +1,85 @@
1
+ ---
2
+ name: guard
3
+ description: "Add Arcjet Guard to non-HTTP JavaScript: agent tool calls, MCP handlers, queue workers, and background jobs. Use when there is no HTTP request object, or when the user asks to guard tools, rate-limit agent actions, or block prompt injection on tool arguments."
4
+ license: Apache-2.0
5
+ compatibility: JavaScript and TypeScript apps using @arcjet/guard on Node.js >= 22.
6
+ metadata:
7
+ author: arcjet
8
+ type: core
9
+ library: "@arcjet/skills"
10
+ library_version: "1.12.0" # x-release-please-version
11
+ sources:
12
+ - docs/guard.md
13
+ ---
14
+
15
+ # Guard non-HTTP JavaScript
16
+
17
+ Use `@arcjet/guard` when there is no HTTP request. MCP tools, queue workers,
18
+ and agent tool calls are Guard. HTTP routes are `@arcjet/skills#protect`.
19
+
20
+ For a specific vendor SDK, load the matching skill from `@arcjet/guard`
21
+ (`@arcjet/guard#integrate-arcjet-guard-agents`, `-eve`, `-mastra`,
22
+ `-langgraph`, `-langchain`, `-openai-agents`, `-genkit`, `-google-adk`,
23
+ `-strands-agents`, `-tanstack-ai`, `-claude-agent-sdk`,
24
+ `-claude-managed-agents`).
25
+
26
+ ## Client
27
+
28
+ ```ts
29
+ import { launchArcjet } from "@arcjet/guard";
30
+
31
+ export const arcjet = launchArcjet({ key: process.env.ARCJET_KEY! });
32
+ ```
33
+
34
+ One client at module scope. Get the key with `@arcjet/skills#cli` first.
35
+ Declare rules at module scope so `.deniedResult(decision)` works.
36
+
37
+ ## One `guard()` per operation
38
+
39
+ Hardcode the `label`. Do not interpolate in a generic dispatcher.
40
+
41
+ Labels: lowercase letters, digits, `-`, `.` only; start and end with a letter
42
+ or digit. `tools.get-weather`, not `tools.get_weather`.
43
+
44
+ ```ts
45
+ const decision = await arcjet.guard("tools.get-weather", {
46
+ rules: [/* ... */],
47
+ metadata: { user: { id: userId } },
48
+ });
49
+
50
+ if (decision.conclusion === "DENY") {
51
+ const rateLimited = toolCallLimit.deniedResult(decision);
52
+ if (rateLimited) {
53
+ return { error: `rate limited, retry after ${rateLimited.resetAtUnixSeconds}` };
54
+ }
55
+ return { error: decision.reason };
56
+ }
57
+ if (decision.hasFailedOpen()) {
58
+ // ALLOW only because a rule could not run — deny here if the site is sensitive
59
+ }
60
+ ```
61
+
62
+ Core `guard()` fails open — check `hasFailedOpen()`. `warnings` never change
63
+ the conclusion. Rate-limit rules need `key` and `bucket`. Nested `metadata`
64
+ is Console-only; no secrets or PII. `decision.reason` is a flat string on
65
+ DENY and `undefined` on ALLOW.
66
+
67
+ `localDetectSensitiveInfo()` default backend matches card / email / phone /
68
+ IP only. Names and government IDs need `backend: rampart()`.
69
+ `moderateContent()` is Guard-only.
70
+
71
+ `capture()` is visibility, never a deny. `flush()` on shutdown.
72
+
73
+ ## Versioned wrappers
74
+
75
+ Import `@arcjet/guard/<vendor>/v<major>` — unversioned paths do not resolve.
76
+ Wrappers fail closed by default. HITL (`needsApproval`, `interrupt()`,
77
+ `requireConfirmation`, `canUseTool`) is not a policy gate; Guard still runs
78
+ after a human yes.
79
+
80
+ Google ADK is `guardPlugin` (no `guardTool`). TanStack AI is
81
+ `guardMiddleware` (do not wrap `execute`). LangChain `createAgent` is
82
+ `/langchain/v1`, not LangGraph.
83
+
84
+ Load `@arcjet/skills#choose-protections` to pick rules. Exact signatures live
85
+ in the installed `@arcjet/guard` types.
@@ -0,0 +1,44 @@
1
+ ---
2
+ name: mcp
3
+ description: Connect an AI coding client to the Arcjet MCP server at https://api.arcjet.com/mcp. Use when the client has built-in MCP support or the CLI is not available.
4
+ license: Apache-2.0
5
+ compatibility: AI clients that support remote MCP over HTTP / OAuth.
6
+ metadata:
7
+ author: arcjet
8
+ type: core
9
+ library: "@arcjet/skills"
10
+ library_version: "1.12.0" # x-release-please-version
11
+ sources:
12
+ - docs/mcp.md
13
+ ---
14
+
15
+ # Arcjet MCP server
16
+
17
+ Endpoint: `https://api.arcjet.com/mcp` only. OAuth on first connect. Enable
18
+ write confirmations. Prefer `@arcjet/skills#cli` when a terminal is available.
19
+
20
+ **Cursor** — `.cursor/mcp.json`:
21
+
22
+ ```json
23
+ {
24
+ "mcpServers": {
25
+ "arcjet": {
26
+ "type": "streamable-http",
27
+ "url": "https://api.arcjet.com/mcp"
28
+ }
29
+ }
30
+ }
31
+ ```
32
+
33
+ **VS Code** — `.vscode/mcp.json` with `"type": "http"` and the same URL.
34
+
35
+ **Claude Code:**
36
+
37
+ ```bash
38
+ claude mcp add arcjet --transport http https://api.arcjet.com/mcp
39
+ ```
40
+
41
+ Bootstrap: `list-teams` → `list-sites` → `get-site-key` → write `ARCJET_KEY`.
42
+ Investigate: `analyze-traffic`, `list-requests`, `list-guards`,
43
+ `investigate-ip`. Remote rules: `create-rule` (DRY_RUN) →
44
+ `get-dry-run-impact` → `promote-rule`.