Install
$ agentstack add mcp-arcjet-arcjet-py ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ● Network access Used
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ● Environment & secrets Used
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
arcjet
Arcjet is the runtime security platform that ships in your AI code. Detect prompt injection, authorize agent tool calls, redact sensitive data, and block bots and abuse. Real-time security building blocks you call inside your app, before an action happens.
This is the Python SDK for Arcjet — use arcjet / arcjet_sync for request protection (FastAPI, Flask, Django route handlers) and arcjet.guard for guard protection (AI agent tool calls, MCP servers, background jobs).
Why Arcjet?
Your app's AI features and agents take real actions, calling tools, reading data, hitting APIs. Arcjet runs inside that code and lets you enforce security on each action in real time, then audit what happened.
Getting started
Install the Arcjet CLI
The CLI is used to log in, manage site keys, and install protection skills.
Homebrew (macOS and Linux):
brew install arcjet/tap/arcjet
npx (Node.js) — run any command without installing:
npx @arcjet/cli
Or download a binary for macOS (Apple Silicon, Intel), Linux (x8664, arm64), and Windows (x86_64, arm64).
> Examples below use the arcjet binary. If you installed via npx, replace > arcjet with npx @arcjet/cli.
Quick setup with an AI agent
- Log in with the CLI:
``sh arcjet auth login ``
- Install the Arcjet skill:
``sh npx skills add arcjet/skills ``
- Tell your agent what to protect — it handles the rest.
Manual setup
- Log in with the CLI (or at
app.arcjet.com):
``sh arcjet auth login ``
pip install arcjet(oruv add arcjet)- Get your site key:
``sh arcjet sites get-key `` Or copy it from the Arcjet dashboard.
- Set
ARCJET_KEY=ajkey_yourkeyin.env - Protect a route — see the [AI protection example](#quick-start) or
individual [feature examples](#features) below.
Get help
Join our Discord server or reach out for support.
- Documentation — full reference and guides
- Examples — FastAPI
and Flask example apps, including LangChain integration
- Blueprints — recipes for common security
patterns
Quick start
> Note: Examples below use FastAPI (async). For Flask and other sync > frameworks, use arcjet_sync instead of arcjet. The API is identical — see > [Async vs. sync client](#async-vs-sync-client).
Protect an AI chat endpoint with prompt injection detection, token budget rate limiting, and bot protection:
# main.py
import os
from fastapi import FastAPI, Request
from fastapi.responses import JSONResponse
from pydantic import BaseModel
from arcjet import (
arcjet, # async client — use arcjet_sync for Flask and other sync frameworks
detect_bot,
detect_prompt_injection,
detect_sensitive_info,
shield,
token_bucket,
Mode,
SensitiveInfoEntityType,
)
app = FastAPI()
arcjet_key = os.getenv("ARCJET_KEY")
if not arcjet_key:
raise RuntimeError(
"ARCJET_KEY is required. Get one with: arcjet sites get-key"
" or from https://app.arcjet.com"
)
# Create a single Arcjet instance and reuse it across requests.
# Use arcjet_sync instead if you are using Flask or another sync framework.
aj = arcjet(
key=arcjet_key,
rules=[
# Detect and block prompt injection attacks in user messages
detect_prompt_injection(mode=Mode.LIVE),
# Block sensitive data (e.g. credit cards, PII) from reaching your LLM
detect_sensitive_info(
mode=Mode.LIVE,
deny=[
SensitiveInfoEntityType.CREDIT_CARD_NUMBER,
SensitiveInfoEntityType.EMAIL,
SensitiveInfoEntityType.PHONE_NUMBER,
],
),
# Rate limit by token budget — refill 100 tokens every 60 seconds
token_bucket(
characteristics=["userId"],
mode=Mode.LIVE,
refill_rate=100,
interval=60,
capacity=1000,
),
# Block automated clients and scrapers from your AI endpoints
detect_bot(
mode=Mode.LIVE,
allow=[], # empty = block all bots
),
# Protect against common web attacks (SQLi, XSS, etc.)
shield(mode=Mode.LIVE),
],
)
class ChatRequest(BaseModel):
message: str
@app.post("/chat")
async def chat(request: Request, body: ChatRequest):
userId = "user_123" # replace with real user ID from session
decision = await aj.protect(
request,
requested=5, # tokens consumed per request
characteristics={"userId": userId},
detect_prompt_injection_message=body.message, # scan for prompt injection
sensitive_info_value=body.message, # scan for PII
)
if decision.is_denied():
status = 429 if decision.reason_v2.type == "RATE_LIMIT" else 403
return JSONResponse({"error": "Denied"}, status_code=status)
# Safe to pass body.message to your LLM
return {"reply": "..."}
Features
| Feature | Request (arcjet) | Guard (arcjet.guard) | | --- | :---: | :---: | | Rate Limiting | ✅ | ✅ | | Prompt Injection Detection | ✅ | ✅ | | Sensitive Information Detection | ✅ | ✅ | | Bot Protection | ✅ | — | | Shield WAF | ✅ | — | | Email Validation | ✅ | — | | Request Filters | ✅ | — | | IP Analysis | ✅ | — | | Custom Rules | — | ✅ |
- 🔒 [Prompt Injection Detection](#prompt-injection-detection) — detect and block
prompt injection attacks before they reach your LLM.
- 🤖 [Bot Protection](#bot-protection) — stop scrapers, credential stuffers, and
AI crawlers from abusing your endpoints.
- 🛑 [Rate Limiting](#rate-limiting) — token bucket, fixed window, and sliding
window algorithms; model AI token budgets per user.
- 🕵️ [Sensitive Information Detection](#sensitive-information-detection) — block
PII, credit cards, and custom patterns from entering your AI pipeline.
- 🛡️ [Shield WAF](#shield-waf) — protect against SQL injection, XSS, and other
common web attacks.
- 📧 [Email Validation](#email-validation) — block disposable, invalid, and
undeliverable addresses at signup.
combines bot protection, email validation, and rate limiting to protect your signup forms.
- 🎯 [Request Filters](#request-filters) — expression-based rules on IP, path,
headers, and custom fields.
- 🌐 [IP Analysis](#ip-analysis) — geolocation, ASN, VPN, proxy, Tor, and hosting
detection included with every request.
- 🧩 [Arcjet Guard](#arcjet-guard) — lower-level API for AI agent tool calls and
background tasks where there is no HTTP request.
Which features do I need?
| If your app has... | Recommended features | | ----------------------------- | ----------------------------------------------------------------------------- | | LLM / AI chat endpoints | Prompt injection + sensitive info + token bucket rate limit + bot protection + shield | | AI agent tool calls | [Arcjet Guard](#arcjet-guard) — rate limiting + prompt injection + sensitive info + custom rules | | Public API | Rate limiting + bot protection + shield | | Signup / login forms | Email validation + bot protection + rate limiting (or signup protection) | | Internal / admin routes | Shield + request filters (country, VPN/proxy blocking) | | Any web application | Shield + bot protection (good baseline for all apps) |
All features can be combined in a single Arcjet instance. Rules are evaluated together — if any rule denies the request, decision.is_denied() returns True. Use Mode.DRY_RUN on individual rules to test them before enforcing.
Installation
# With a uv project
uv add arcjet
# With an existing pip managed project
uv pip install arcjet
Or with pip:
pip install arcjet
Prompt injection detection
Detect and block prompt injection attacks — attempts by users to hijack your LLM's behavior through crafted input — before they reach your model.
FastAPI
from arcjet import arcjet, detect_prompt_injection, Mode
aj = arcjet(
key=arcjet_key,
rules=[
detect_prompt_injection(mode=Mode.LIVE),
],
)
@app.post("/chat")
async def chat(request: Request, body: ChatRequest):
decision = await aj.protect(
request,
detect_prompt_injection_message=body.message,
)
if decision.is_denied():
return JSONResponse({"error": "Prompt injection detected"}, status_code=403)
# safe to pass body.message to your LLM
Flask
from arcjet import arcjet_sync, detect_prompt_injection, Mode
aj = arcjet_sync(
key=arcjet_key,
rules=[
detect_prompt_injection(mode=Mode.LIVE),
],
)
@app.route("/chat", methods=["POST"])
def chat():
body = request.get_json()
decision = aj.protect(request, detect_prompt_injection_message=body["message"])
if decision.is_denied():
return jsonify(error="Prompt injection detected"), 403
# safe to pass body["message"] to your LLM
You can tune the detection sensitivity with the threshold parameter (0.0–1.0, default 0.5). Higher values require stronger signals to trigger a denial, reducing false positives but potentially missing subtle attacks:
detect_prompt_injection(mode=Mode.LIVE, threshold=0.8)
See the Prompt Injection docs for more details.
Bot protection
Manage traffic from automated clients. Block scrapers, credential stuffers, and AI crawlers, while allowing legitimate bots like search engines and monitors.
FastAPI
from arcjet import arcjet, detect_bot, Mode, BotCategory
aj = arcjet(
key=arcjet_key,
rules=[
detect_bot(
mode=Mode.LIVE,
allow=[
BotCategory.SEARCH_ENGINE, # Google, Bing, etc.
# BotCategory.MONITOR, # Uptime monitoring
# BotCategory.PREVIEW, # Link previews (Slack, Discord)
# "OPENAI_CRAWLER_SEARCH", # Allow OpenAI crawler
],
),
],
)
@app.get("/")
async def index(request: Request):
decision = await aj.protect(request)
if decision.is_denied():
return JSONResponse({"error": "Bot detected"}, status_code=403)
return {"message": "Hello world"}
Flask
from arcjet import arcjet_sync, detect_bot, is_spoofed_bot, Mode, BotCategory
aj = arcjet_sync(
key=arcjet_key,
rules=[
detect_bot(mode=Mode.LIVE, allow=[BotCategory.SEARCH_ENGINE]),
],
)
@app.route("/")
def index():
decision = aj.protect(request)
if decision.is_denied():
return jsonify(error="Bot detected"), 403
if any(is_spoofed_bot(r) for r in decision.results):
return jsonify(error="Spoofed bot"), 403
return jsonify(message="Hello world")
Bot categories
Configure rules using categories or specific bot identifiers:
detect_bot(
mode=Mode.LIVE,
allow=[
BotCategory.SEARCH_ENGINE,
"OPENAI_CRAWLER_SEARCH",
],
)
Available categories: ACADEMIC, ADVERTISING, AI, AMAZON, ARCHIVE, BOTNET, FEEDFETCHER, GOOGLE, META, MICROSOFT, MONITOR, OPTIMIZER, PREVIEW, PROGRAMMATIC, SEARCH_ENGINE, SLACK, SOCIAL, TOOL, UNKNOWN, VERCEL, YAHOO. Use BotCategory. in Python or pass the string directly. You can also allow or deny specific bots by name.
If you specify an allow list, all other bots are denied. An empty allow list blocks all bots. The reverse applies for deny lists.
Verified vs. spoofed bots
Bots claiming to be well-known crawlers (e.g. Googlebot) are verified against their known IP ranges. Use is_spoofed_bot() to check:
from arcjet import is_spoofed_bot
if any(is_spoofed_bot(r) for r in decision.results):
return jsonify(error="Spoofed bot"), 403
See the Bot Protection docs for more details.
Rate limiting
Limit request rates per IP, user, or any custom characteristic. Arcjet supports token bucket, fixed window, and sliding window algorithms. Token buckets are ideal for controlling AI token budgets — set capacity to the max tokens a user can spend, refill_rate to how many tokens are restored per interval, and deduct tokens per request via requested in protect(). The interval accepts seconds as a number. Use characteristics to track limits per user instead of per IP.
Token bucket (recommended for AI)
Rate limits track by IP address by default. To track per user, declare the key name in characteristics on the rule, then pass the actual value in protect():
from arcjet import arcjet, token_bucket, Mode
aj = arcjet(
key=arcjet_key,
rules=[
token_bucket(
characteristics=["userId"], # or ["ip.src"] for IP-based
mode=Mode.LIVE,
refill_rate=100, # tokens added per interval
interval=60, # interval in seconds
capacity=1000, # maximum tokens per bucket
),
],
)
@app.post("/chat")
async def chat(request: Request):
decision = await aj.protect(
request,
requested=5, # tokens consumed by this request
characteristics={"userId": "user_123"},
)
if decision.is_denied():
return JSONResponse({"error": "Rate limited"}, status_code=429)
Fixed window
from arcjet import arcjet, fixed_window, Mode
aj = arcjet(
key=arcjet_key,
rules=[
fixed_window(mode=Mode.LIVE, window=60, max=100),
],
)
Sliding window
from arcjet import arcjet, sliding_window, Mode
aj = arcjet(
key=arcjet_key,
rules=[
sliding_window(mode=Mode.LIVE, interval=60, max=100),
],
)
See the Rate Limiting docs for more details.
Sensitive information detection
Detect and block PII in request content before it reaches your LLM or data store. The default (local WebAssembly) backend detects EMAIL, PHONE_NUMBER, IP_ADDRESS, and CREDIT_CARD_NUMBER. You can provide a custom detect callback for additional patterns, or the optional on-device Rampart backend (see below) for names, addresses, and government/financial identifiers.
from arcjet import arcjet, detect_sensitive_info, SensitiveInfoEntityType, Mode
aj = arcjet(
key=arcjet_key,
rules=[
detect_sensitive_info(
mode=Mode.LIVE,
deny=[
SensitiveInfoEntityType.EMAIL,
SensitiveInfoEntityType.CREDIT_CARD_NUMBER,
],
),
],
)
# Pass the content to scan with each protect() call
decision = await aj.protect(request, sensitive_info_value="User input to scan")
You can supplement built-in detectors with a custom detect callback:
def my_detect(tokens: list[str]) -> list[str | None]:
return ["CUSTOM_PII" if "secret" in t.lower() else None for t in tokens]
rules = [
detect_sensitive_info(
mode=Mode.LIVE,
deny=["CUSTOM_PII"],
detect=my_detect,
),
]
On-device Rampart backend (more entity types)
The d
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: arcjet
- Source: arcjet/arcjet-py
- License: Apache-2.0
- Homepage: https://arcjet.com
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.