{"_id":"@dortort/ai-tool-guard","name":"@dortort/ai-tool-guard","dist-tags":{"latest":"0.1.0"},"versions":{"0.1.0":{"name":"@dortort/ai-tool-guard","version":"0.1.0","description":"Policy enforcement middleware for Vercel AI SDK tool calls — guards, approvals, rate limiting, output filtering, and observability.","type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","exports":{".":{"import":"./dist/index.js","types":"./dist/index.d.ts"},"./policy":{"import":"./dist/policy/index.js","types":"./dist/policy/index.d.ts"},"./approval":{"import":"./dist/approval/index.js","types":"./dist/approval/index.d.ts"},"./guards":{"import":"./dist/guards/index.js","types":"./dist/guards/index.d.ts"},"./otel":{"import":"./dist/otel/index.js","types":"./dist/otel/index.d.ts"},"./mcp":{"import":"./dist/mcp/index.js","types":"./dist/mcp/index.d.ts"}},"scripts":{"build":"tsc","test":"vitest run","test:e2e":"vitest run --config vitest.config.e2e.ts","test:all":"vitest run && vitest run --config vitest.config.e2e.ts","test:watch":"vitest","lint":"tsc --noEmit","docs:build":"mkdocs build --strict","docs:serve":"mkdocs serve","prepublishOnly":"npm run build"},"repository":{"type":"git","url":"git+https://github.com/dortort/ai-tool-guard.git"},"keywords":["ai","vercel-ai-sdk","guardrails","tool-calling","policy-engine","mcp","opentelemetry","rate-limiting","approval-flow"],"author":{"name":"Francis Eytan Dortort"},"license":"MIT","peerDependencies":{"ai":">=4.0.0","zod":">=3.0.0","@opentelemetry/api":">=1.0.0"},"peerDependenciesMeta":{"@opentelemetry/api":{"optional":true}},"devDependencies":{"@ai-sdk/provider":"^1.1.0","@opentelemetry/api":"^1.9.0","@types/node":"^25.2.3","ai":"^4.3.0","typescript":"^5.7.0","vitest":"^3.0.0","zod":"^3.24.0"},"_id":"@dortort/ai-tool-guard@0.1.0","gitHead":"7936d63c21210e77d9932a5dc94d48b145355e7e","bugs":{"url":"https://github.com/dortort/ai-tool-guard/issues"},"homepage":"https://github.com/dortort/ai-tool-guard#readme","_nodeVersion":"20.20.1","_npmVersion":"10.8.2","dist":{"integrity":"sha512-IwKiMLPe9Kpw1fNKlmDuFGW/ivgkNul+QIyyD5dPuvKY1PQ5fvd2OVqfQAkJOQmT7cduWSA4p7rv0uaDO7eLqQ==","shasum":"72255100bf96891e0fd8076a665a3a68ce160db0","tarball":"https://registry.npmjs.org/@dortort/ai-tool-guard/-/ai-tool-guard-0.1.0.tgz","fileCount":95,"unpackedSize":171121,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@dortort%2fai-tool-guard@0.1.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIAs7N96av+Xg/NZbwiAXeAymkY9DcYI4DQ6WOQBIj3sYAiEAwIt3GdM4TEqL6dcTijXz85T293HjndrJZTuKmbjx6kw="}]},"_npmUser":{"name":"dortort","email":"francis@dortort.com"},"directories":{},"maintainers":[{"name":"dortort","email":"francis@dortort.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/ai-tool-guard_0.1.0_1774809021519_0.6849600511762015"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-29T18:30:21.458Z","0.1.0":"2026-03-29T18:30:21.729Z","modified":"2026-03-29T18:30:22.159Z"},"maintainers":[{"name":"dortort","email":"francis@dortort.com"}],"description":"Policy enforcement middleware for Vercel AI SDK tool calls — guards, approvals, rate limiting, output filtering, and observability.","homepage":"https://github.com/dortort/ai-tool-guard#readme","keywords":["ai","vercel-ai-sdk","guardrails","tool-calling","policy-engine","mcp","opentelemetry","rate-limiting","approval-flow"],"repository":{"type":"git","url":"git+https://github.com/dortort/ai-tool-guard.git"},"author":{"name":"Francis Eytan Dortort"},"bugs":{"url":"https://github.com/dortort/ai-tool-guard/issues"},"license":"MIT","readme":"<p align=\"center\">\n  <img src=\"hero.png\" alt=\"AI Tool Guard\" width=\"600\" />\n</p>\n\n[![CI](https://github.com/dortort/ai-tool-guard/actions/workflows/ci.yml/badge.svg)](https://github.com/dortort/ai-tool-guard/actions/workflows/ci.yml)\n[![npm version](https://img.shields.io/npm/v/ai-tool-guard)](https://www.npmjs.com/package/ai-tool-guard)\n[![TypeScript](https://img.shields.io/badge/TypeScript-5.7-blue?logo=typescript&logoColor=white)](https://www.typescriptlang.org/)\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)\n[![Node.js](https://img.shields.io/badge/Node.js-%E2%89%A520-green?logo=node.js&logoColor=white)](https://nodejs.org/)\n[![Vercel AI SDK](https://img.shields.io/badge/Vercel_AI_SDK-%E2%89%A54.0-black?logo=vercel&logoColor=white)](https://sdk.vercel.ai)\n[![Docs](https://readthedocs.org/projects/ai-tool-guard/badge/?version=latest)](https://ai-tool-guard.readthedocs.io/)\n\nPolicy enforcement middleware for [Vercel AI SDK](https://sdk.vercel.ai) tool calls.\n\nGuards, approvals, argument validation, rate limiting, output filtering, prompt-injection detection, MCP drift detection, and OpenTelemetry observability — as a composable middleware layer around your AI SDK tools.\n\n**[Read the full documentation](https://ai-tool-guard.readthedocs.io/)**\n\n```\nnpm install ai-tool-guard\n```\n\n## Quick start\n\n```ts\nimport { createToolGuard, deny, requireApproval, defaultPolicy } from \"ai-tool-guard\";\nimport { generateText } from \"ai\";\nimport { openai } from \"@ai-sdk/openai\";\nimport { tool } from \"ai\";\nimport { z } from \"zod\";\n\n// 1. Define your tools as usual.\nconst getWeather = tool({\n  description: \"Get the weather for a city\",\n  parameters: z.object({ city: z.string() }),\n  execute: async ({ city }) => `Weather in ${city}: sunny, 72°F`,\n});\n\nconst deleteUser = tool({\n  description: \"Delete a user account\",\n  parameters: z.object({ userId: z.string() }),\n  execute: async ({ userId }) => `User ${userId} deleted`,\n});\n\n// 2. Create a guard with policy rules.\nconst guard = createToolGuard({\n  rules: defaultPolicy(),\n  onApprovalRequired: async (token) => {\n    console.log(`Approval needed for ${token.toolName}:`, token.originalArgs);\n    return { approved: true, approvedBy: \"admin\" };\n  },\n  onDecision: (record) => {\n    console.log(`[${record.verdict}] ${record.toolName}: ${record.reason}`);\n  },\n});\n\n// 3. Wrap tools with per-tool risk levels.\nconst tools = guard.guardTools({\n  getWeather: { tool: getWeather, riskLevel: \"low\" },\n  deleteUser: { tool: deleteUser, riskLevel: \"high\" },\n});\n\n// 4. Use with AI SDK as normal.\nconst result = await generateText({\n  model: openai(\"gpt-4o\"),\n  tools,\n  prompt: \"What's the weather in Tokyo?\",\n});\n```\n\n## Features\n\n| Feature | Description |\n|---------|-------------|\n| **Policy engine** | Rule-based allow/deny/require-approval with glob patterns, risk levels, priorities, and async conditions |\n| **External policy backends** | Adapter interface for OPA/Rego, Cedar, or custom ABAC engines |\n| **Decision records** | Structured audit output for every evaluation (matched rules, risk category, attributes, redactions) |\n| **Dry-run / simulation** | Evaluate policies across recorded traces without executing tools |\n| **Conversation-aware policies** | Policies can incorporate session risk score, prior failures, recent approvals |\n| **Approve with edits** | Approval handler can patch arguments before execution |\n| **Approval correlation** | Payload-hash tokens with TTL prevent mismatch between request and resolution |\n| **Argument guards** | Zod schemas, allowlists, denylists, regex, PII scanning per field |\n| **Injection detection** | Heuristic prompt-injection detector that can deny or downgrade to approval |\n| **Output filtering** | Secrets stripping, PII redaction, custom filters on tool results |\n| **Rate limiting** | Sliding-window rate limits + concurrency caps with reject or queue backpressure |\n| **OpenTelemetry** | Opinionated spans for policy eval, approval wait, tool execution, redaction |\n| **MCP drift detection** | SHA-256 schema fingerprinting, drift detection, actionable remediation |\n\n## Architecture\n\nEvery guarded tool call passes through a 7-stage execution pipeline: injection detection, argument validation, policy evaluation, approval flow, rate limiting, tool execution, and output filtering. Each stage emits an OpenTelemetry span.\n\nSee the **[architecture overview](https://ai-tool-guard.readthedocs.io/#architecture)** for the full pipeline diagram.\n\n## API reference\n\n### `createToolGuard(options)`\n\nCreates a `ToolGuard` instance. All options are optional.\n\n```ts\ninterface GuardOptions {\n  rules?: PolicyRule[];           // Built-in policy rules\n  backend?: PolicyBackend;        // External policy backend\n  defaultRiskLevel?: RiskLevel;   // Default risk for unconfigured tools (\"low\")\n  onApprovalRequired?: ApprovalHandler;  // Approval callback\n  injectionDetection?: InjectionDetectorConfig;\n  defaultRateLimit?: RateLimitConfig;\n  defaultMaxConcurrency?: number;\n  otel?: OtelConfig;\n  dryRun?: boolean;               // Simulation mode\n  onDecision?: (record: DecisionRecord) => void | Promise<void>;\n  resolveUserAttributes?: () => Record<string, unknown> | Promise<Record<string, unknown>>;\n  resolveConversationContext?: () => ConversationContext | Promise<ConversationContext>;\n}\n```\n\n### `guard.guardTool(name, tool, config?)`\n\nWrap a single AI SDK tool.\n\n```ts\nconst guarded = guard.guardTool(\"sendEmail\", sendEmailTool, {\n  riskLevel: \"medium\",\n  riskCategories: [\"network\", \"pii\"],\n  argGuards: [piiGuard(\"body\")],\n  outputFilters: [secretsFilter()],\n  rateLimit: { maxCalls: 10, windowMs: 60_000 },\n  maxConcurrency: 2,\n});\n```\n\n### `guard.guardTools(map)`\n\nWrap multiple tools at once. Returns a flat tools map compatible with `generateText({ tools })`.\n\n```ts\nconst tools = guard.guardTools({\n  readFile:  { tool: readFileTool,  riskLevel: \"low\" },\n  writeFile: { tool: writeFileTool, riskLevel: \"high\", requireApproval: true },\n  search:    { tool: searchTool },\n});\n```\n\n---\n\n## Policy rules\n\n### Built-in rule builders\n\n```ts\nimport { allow, deny, requireApproval } from \"ai-tool-guard\";\n\nconst rules = [\n  allow({ tools: \"read*\", description: \"Allow all read tools\" }),\n  requireApproval({ tools: \"write*\", riskLevels: [\"medium\", \"high\"] }),\n  deny({\n    tools: \"delete*\",\n    condition: (ctx) => ctx.userAttributes.role !== \"admin\",\n    description: \"Only admins can delete\",\n    priority: 10,\n  }),\n];\n```\n\n### Preset policies\n\n```ts\nimport { defaultPolicy, readOnlyPolicy } from \"ai-tool-guard\";\n\n// low → allow, medium → require-approval, high/critical → deny\nconst rules = defaultPolicy();\n\n// Allow specific tools, deny everything else\nconst rules = readOnlyPolicy([\"getUser\", \"listItems\", \"search*\"]);\n```\n\n### External policy backend (OPA, Cedar, custom)\n\n```ts\nimport type { PolicyBackend } from \"ai-tool-guard\";\n\nconst opaBackend: PolicyBackend = {\n  name: \"opa\",\n  async evaluate(ctx) {\n    const res = await fetch(\"http://opa:8181/v1/data/tool_policy\", {\n      method: \"POST\",\n      body: JSON.stringify({ input: ctx }),\n    });\n    const data = await res.json();\n    return {\n      verdict: data.result.allow ? \"allow\" : \"deny\",\n      reason: data.result.reason,\n      matchedRules: data.result.matched_rules ?? [],\n    };\n  },\n};\n\nconst guard = createToolGuard({ backend: opaBackend });\n```\n\n---\n\n## Approval flow\n\nThe approval handler receives an `ApprovalToken` and returns an `ApprovalResolution`.\n\n### Basic approval\n\n```ts\nconst guard = createToolGuard({\n  rules: [requireApproval({ tools: \"payment*\" })],\n  onApprovalRequired: async (token) => {\n    const answer = await askUser(\n      `Allow ${token.toolName} with args ${JSON.stringify(token.originalArgs)}?`\n    );\n    return { approved: answer === \"yes\" };\n  },\n});\n```\n\n### Approve with edits (parameter patching)\n\n```ts\nonApprovalRequired: async (token) => {\n  // User can modify the amount before approving\n  const editedAmount = await showEditableForm(token.originalArgs);\n  return {\n    approved: true,\n    patchedArgs: { amount: editedAmount },\n    approvedBy: \"finance-team\",\n  };\n},\n```\n\nThe `ApprovalToken` includes a `payloadHash` for correlation — the SHA-256 of the canonical `{ toolName, args }` object. This prevents mismatch bugs when message history is reshaped.\n\n---\n\n## Argument guards\n\nValidate tool arguments before policy evaluation.\n\n```ts\nimport {\n  zodGuard, allowlist, denylist, regexGuard, piiGuard\n} from \"ai-tool-guard\";\nimport { z } from \"zod\";\n\nconst guarded = guard.guardTool(\"queryDb\", queryTool, {\n  argGuards: [\n    // Zod schema validation\n    zodGuard({ field: \"limit\", schema: z.number().int().min(1).max(100) }),\n\n    // Allowlist\n    allowlist(\"table\", [\"users\", \"orders\", \"products\"]),\n\n    // Denylist\n    denylist(\"operation\", [\"DROP\", \"TRUNCATE\"]),\n\n    // Regex: must match allowed domain\n    regexGuard(\"url\", /^https:\\/\\/.*\\.example\\.com/, {\n      message: \"Only example.com URLs are allowed\",\n    }),\n\n    // Regex: must NOT match forbidden pattern\n    regexGuard(\"query\", /DROP\\s+TABLE/i, {\n      mustMatch: false,\n      message: \"SQL injection detected\",\n    }),\n\n    // PII scanning\n    piiGuard(\"userInput\", { allowedTypes: [\"email\"] }),\n  ],\n});\n```\n\nGuards support dot-path field access for nested arguments:\n\n```ts\nallowlist(\"config.region\", [\"us-east-1\", \"eu-west-1\"])\n```\n\n---\n\n## Output filtering\n\nControl what comes back from tool execution.\n\n```ts\nimport { secretsFilter, piiOutputFilter, customFilter } from \"ai-tool-guard\";\n\nconst guarded = guard.guardTool(\"fetchData\", fetchTool, {\n  outputFilters: [\n    // Strip AWS keys, GitHub tokens, JWTs, API keys, bearer tokens, private keys\n    secretsFilter(),\n\n    // Redact emails, SSNs, phone numbers, credit card numbers\n    piiOutputFilter({ allowedTypes: [\"email\"] }),\n\n    // Custom filter\n    customFilter(\"size-limit\", async (result) => {\n      const str = JSON.stringify(result);\n      if (str.length > 100_000) {\n        return { verdict: \"block\", output: null };\n      }\n      return { verdict: \"pass\", output: result };\n    }),\n  ],\n});\n```\n\nFilters run in order after tool execution. If any filter returns `\"block\"`, the filter chain stops, the tool result is discarded, and a `ToolGuardError` is thrown.\n\n---\n\n## Injection detection\n\nHeuristic prompt-injection detection at the tool boundary.\n\n```ts\nconst guard = createToolGuard({\n  injectionDetection: {\n    threshold: 0.5,    // Suspicion score 0-1\n    action: \"deny\",    // \"deny\" | \"downgrade\" | \"log\"\n  },\n});\n```\n\n- **`deny`** — Block the tool call entirely.\n- **`downgrade`** — Convert the call to require approval.\n- **`log`** — Allow but flag in the decision record.\n\nCustom detectors (e.g., LLM-as-judge):\n\n```ts\ninjectionDetection: {\n  threshold: 0.7,\n  action: \"downgrade\",\n  detect: async (args) => {\n    const score = await myLlmJudge(JSON.stringify(args));\n    return score; // 0-1\n  },\n},\n```\n\n---\n\n## Rate limiting and concurrency\n\n```ts\nconst guard = createToolGuard({\n  // Global defaults\n  defaultRateLimit: { maxCalls: 100, windowMs: 60_000, strategy: \"reject\" },\n  defaultMaxConcurrency: 5,\n});\n\n// Per-tool overrides\nguard.guardTool(\"expensiveApi\", tool, {\n  rateLimit: { maxCalls: 5, windowMs: 60_000, strategy: \"queue\" },\n  maxConcurrency: 1,\n});\n```\n\n- **`reject`** — Immediately throw `ToolGuardError` with code `\"rate-limited\"`.\n- **`queue`** — Wait for a slot to become available (backpressure).\n\n---\n\n## Dry-run / simulation mode\n\nEvaluate policies without executing tools.\n\n### Global dry-run\n\n```ts\nconst guard = createToolGuard({ dryRun: true, rules: [...] });\n// All tool calls return { dryRun: true, toolName, args } instead of executing.\n```\n\n### Trace simulation\n\n```ts\nimport { simulate } from \"ai-tool-guard\";\n\nconst result = await simulate(\n  [\n    { toolName: \"readFile\", args: { path: \"/etc/passwd\" } },\n    { toolName: \"deleteUser\", args: { id: \"123\" } },\n    { toolName: \"getWeather\", args: { city: \"NYC\" } },\n  ],\n  { rules: defaultPolicy() },\n  {\n    readFile: { riskLevel: \"medium\" },\n    deleteUser: { riskLevel: \"critical\" },\n    getWeather: { riskLevel: \"low\" },\n  },\n);\n\nconsole.log(result.summary);\n// { total: 3, allowed: 1, denied: 1, requireApproval: 1 }\n\nconsole.log(result.blocked);\n// [{ toolCall: { toolName: \"deleteUser\", ... }, decision: { verdict: \"deny\", ... } }, ...]\n```\n\n---\n\n## Decision records\n\nEvery policy evaluation produces a structured `DecisionRecord`:\n\n```ts\ninterface DecisionRecord {\n  id: string;                    // Unique correlation id\n  timestamp: string;             // ISO-8601\n  verdict: \"allow\" | \"deny\" | \"require-approval\";\n  toolName: string;\n  matchedRules: string[];        // Rule ids that matched\n  riskLevel: RiskLevel;\n  riskCategories: RiskCategory[];\n  attributes: Record<string, unknown>;  // User attributes consumed\n  reason: string;                // Human-readable explanation\n  redactions?: string[];         // Fields redacted in output\n  evalDurationMs: number;        // Policy eval time\n  dryRun: boolean;\n}\n```\n\nSubscribe via `onDecision`:\n\n```ts\nconst guard = createToolGuard({\n  onDecision: (record) => {\n    auditLog.write(record);\n    if (record.verdict === \"deny\") {\n      alerting.fire(\"tool-denied\", record);\n    }\n  },\n});\n```\n\n---\n\n## Conversation-aware policies\n\nPolicies can incorporate conversation metadata for contextual decisions.\n\n```ts\nconst guard = createToolGuard({\n  resolveConversationContext: () => ({\n    sessionId: currentSession.id,\n    riskScore: currentSession.riskScore,\n    priorFailures: currentSession.failureCount,\n    recentApprovals: currentSession.approvedTools,\n  }),\n  rules: [\n    deny({\n      tools: \"*\",\n      condition: (ctx) => (ctx.conversation?.riskScore ?? 0) > 0.8,\n      description: \"Block all tools when conversation risk is high\",\n    }),\n    requireApproval({\n      tools: \"*\",\n      condition: (ctx) => (ctx.conversation?.priorFailures ?? 0) > 3,\n      description: \"Require approval after repeated failures\",\n    }),\n  ],\n});\n```\n\n---\n\n## MCP drift detection\n\nPin tool schemas and detect when MCP servers change.\n\n```ts\nimport {\n  pinFingerprint, detectDrift, FingerprintStore\n} from \"ai-tool-guard/mcp\";\n\n// Pin fingerprints for your MCP tools\nconst store = new FingerprintStore();\nstore.set(await pinFingerprint(\"readFile\", \"fs-server\", readFileSchema, \"production\"));\nstore.set(await pinFingerprint(\"queryDb\", \"db-server\", queryDbSchema, \"production\"));\n\n// Before using tools, check for drift\nconst result = await detectDrift(store.getAll(), [\n  { toolName: \"readFile\", serverId: \"fs-server\", schema: currentReadFileSchema },\n  { toolName: \"queryDb\",  serverId: \"db-server\",  schema: currentQueryDbSchema },\n]);\n\nif (result.drifted) {\n  for (const change of result.changes) {\n    console.error(change.remediation);\n    // \"Tool \"queryDb\" from server \"db-server\" has changed since it was pinned\n    //  at 2025-01-15T... Expected hash: a1b2c3..., got: d4e5f6...\n    //  Re-pin with pinFingerprint() after reviewing the schema change.\"\n  }\n  throw new Error(\"MCP schema drift detected. Aborting.\");\n}\n```\n\nPersist fingerprints:\n\n```ts\n// Export to file\nfs.writeFileSync(\"fingerprints.json\", store.export());\n\n// Import from file\nstore.import(fs.readFileSync(\"fingerprints.json\", \"utf-8\"));\n```\n\n---\n\n## OpenTelemetry\n\nAutomatic spans when `@opentelemetry/api` is installed.\n\n```ts\nconst guard = createToolGuard({\n  otel: {\n    enabled: true,\n    tracerName: \"my-app\",\n    defaultAttributes: { \"service.name\": \"ai-agent\" },\n  },\n});\n```\n\nSpans emitted:\n\n| Span name | When | Key attributes |\n|-----------|------|---------------|\n| `ai_tool_guard.policy_eval` | Every policy evaluation | `tool.name`, `tool.risk_level`, `decision.verdict`, `decision.reason` |\n| `ai_tool_guard.tool_execute` | Tool execution | `tool.name` |\n| `ai_tool_guard.approval_wait` | Waiting for approval | `tool.name`, `approval.token_id` |\n| `ai_tool_guard.injection_check` | Injection suspected | `injection.score`, `injection.suspected` |\n| `ai_tool_guard.rate_limit` | Rate limit hit | `rate_limit.allowed` |\n| `ai_tool_guard.output_filter` | Output redacted/blocked | `output.redacted`, `output.blocked` |\n\nAll attribute keys are exported as `ATTR` for custom span creation.\n\n---\n\n## Error handling\n\nAll guard failures throw `ToolGuardError` with a machine-readable `code`:\n\n```ts\nimport { ToolGuardError } from \"ai-tool-guard\";\n\ntry {\n  await generateText({ model, tools, prompt: \"...\" });\n} catch (err) {\n  // AI SDK wraps tool errors in ToolExecutionError — unwrap with .cause\n  const cause = err instanceof Error ? (err as { cause?: unknown }).cause : err;\n  if (cause instanceof ToolGuardError) {\n    switch (cause.code) {\n      case \"policy-denied\":         // Policy rule blocked the call\n      case \"approval-denied\":       // Human denied approval\n      case \"no-approval-handler\":   // Approval required but no handler set\n      case \"arg-validation-failed\": // Argument guard failed\n      case \"injection-detected\":    // Prompt injection suspected\n      case \"rate-limited\":          // Rate limit exceeded\n      case \"output-blocked\":        // Output filter blocked the result\n      case \"mcp-drift\":             // MCP schema drift detected\n    }\n    console.log(cause.toolName);   // Which tool\n    console.log(cause.decision);   // Full DecisionRecord (if available)\n  }\n}\n```\n\n---\n\n## TypeScript\n\nThe library is written in TypeScript and exports all types:\n\n```ts\nimport type {\n  // Core\n  RiskLevel, RiskCategory, DecisionVerdict, DecisionRecord,\n  PolicyContext, ConversationContext, GuardOptions,\n  // Policy\n  PolicyRule, PolicyBackend, PolicyBackendResult,\n  // Tools\n  ToolGuardConfig, AiSdkTool,\n  // Guards\n  ArgGuard, ZodArgGuard, OutputFilter, OutputFilterResult,\n  // Approval\n  ApprovalToken, ApprovalResolution, ApprovalHandler,\n  // Rate limiting\n  RateLimitConfig, RateLimitState,\n  // Injection\n  InjectionDetectorConfig,\n  // MCP\n  McpToolFingerprint, McpDriftResult, McpDriftChange,\n  // OTel\n  OtelConfig,\n} from \"ai-tool-guard\";\n```\n\n## Subpath exports\n\n```ts\nimport { evaluatePolicy, allow, deny } from \"ai-tool-guard/policy\";\nimport { ApprovalManager } from \"ai-tool-guard/approval\";\nimport { zodGuard, secretsFilter, RateLimiter } from \"ai-tool-guard/guards\";\nimport { createTracer, ATTR } from \"ai-tool-guard/otel\";\nimport { detectDrift, FingerprintStore } from \"ai-tool-guard/mcp\";\n```\n\n## Examples\n\nFull worked examples are available in the [documentation](https://ai-tool-guard.readthedocs.io/):\n\n- **[Next.js Integration](https://ai-tool-guard.readthedocs.io/examples/nextjs-integration/)** — App Router setup with per-tool config, approval flow, and error mapping\n- **[Chatbot Safety](https://ai-tool-guard.readthedocs.io/examples/chatbot-safety/)** — Multi-layered defense for a customer support chatbot (5 risk levels, injection detection, PII redaction)\n- **[Multi-Tenant Policies](https://ai-tool-guard.readthedocs.io/examples/multi-tenant/)** — SaaS platform with plan/role-based access and per-tenant audit logs\n- **[Audit Logging](https://ai-tool-guard.readthedocs.io/examples/audit-logging/)** — Structured audit system with denial alerting and OpenTelemetry correlation\n- **[MCP Drift Detection](https://ai-tool-guard.readthedocs.io/examples/mcp-drift-detection/)** — Schema fingerprinting, drift detection, and environment-scoped pinning\n- **[Simulation & Testing](https://ai-tool-guard.readthedocs.io/examples/simulation-testing/)** — Policy validation with recorded traces and CI/CD integration\n\n## License\n\nMIT\n","readmeFilename":"README.md","_rev":"1-c2449b87dcaab6196cfee06fa56a12c5"}