Skip to content

drandrewlaw/agentgate

Repository files navigation

agentgate

The missing safety layer for AI agents.

npm version License: MIT TypeScript

Your agent can call tools. But should it?

agentgate gives you declarative permission rules, lifecycle hooks, and tool execution control for any AI agent — extracted from battle-tested patterns in production AI systems serving millions of users.

Your Agent (LangChain / CrewAI / OpenAI / Custom)
         │
    ┌────▼─────────────────┐
    │      agentgate        │
    │  permissions + hooks  │
    └────┬─────────────────┘
         │
    Actual Tools (filesystem, bash, APIs, databases)

Why agentgate?

Every AI agent framework gives you tools. None gives you governance.

Feature agentgate LangChain CrewAI OpenAI Agents ShipAny
Permission DSL (allow/deny/ask) Yes No No No No
Lifecycle hooks (pre/post/error) Yes No No No No
Provider-agnostic Yes Yes Yes No No
MCP support Yes No No No Yes
Type-safe tools (Zod) Yes Partial No Partial Yes
Zero runtime deps Yes No No No No

Install

npm install agentgate zod

Quick Start — 5 lines to safety

import { agentgate, buildTool, matchPattern } from 'agentgate'
import { z } from 'zod'

const Bash = buildTool({
  name: 'Bash',
  inputSchema: z.object({ command: z.string() }),
  call: async (input) => ({ data: `ran: ${input.command}` }),
  preparePermissionMatcher: (input) => (pattern) => matchPattern(pattern, input.command),
})

const gate = agentgate({
  permissions: {
    mode: 'default',
    rules: [
      { allow: 'Bash(git *)' },   // ✅ Allow git commands
      { deny:  'Bash(rm *)' },    // ❌ Block rm commands
    ],
  },
  tools: [Bash],
})

await gate.execute('Bash', { command: 'git status' })  // ✅ allowed
await gate.execute('Bash', { command: 'rm -rf /' })    // ❌ denied

Core Concepts

1. Permission Engine

Declarative rules control what your agent can do. Rules use a compact DSL:

import { createPermissionEngine } from 'agentgate'

const permissions = createPermissionEngine({
  mode: 'default',  // 'strict' | 'default' | 'permissive' | 'bypass'
  rules: [
    { allow: 'Read' },                    // Allow entire tool
    { allow: 'Bash(git *)' },             // Allow by input pattern
    { deny:  'Bash(rm *)' },              // Deny by input pattern
    { ask:   'Write' },                   // Require approval
    { allow: 'mcp__github__*' },          // Allow all MCP server tools
    { deny:  'mcp__filesystem__write_*' }, // Deny MCP write tools
  ],
})

Permission modes:

  • strict — Deny by default. Only explicitly allowed tools run.
  • default — Allow reads, ask for writes/destructive actions.
  • permissive — Allow unless explicitly denied.
  • bypass — Allow everything (testing only).

2. Lifecycle Hooks

Hooks fire before/after tool execution for logging, validation, and control:

import { agentgate } from 'agentgate'

const gate = agentgate({
  hooks: {
    preToolUse: [{
      matcher: 'Write',  // Only for Write tool
      hooks: [{
        type: 'callback',
        fn: async (input) => {
          const { path } = input.toolInput as { path: string }
          if (path.startsWith('/etc/')) {
            return { continue: false, decision: 'block', reason: 'Cannot write to /etc/' }
          }
          return { continue: true, decision: 'approve' }
        },
      }],
    }],
    postToolUse: [{
      hooks: [{
        type: 'http',
        url: 'https://audit.company.com/log',  // Webhook for audit trail
      }],
    }],
  },
})

Hook types:

  • callback — In-process async function (fastest)
  • command — Shell command (stdin: JSON input, exit code 0=approve, 2=block)
  • http — HTTP POST webhook (JSON request/response)

Hook events: preToolUse, postToolUse, toolError, permissionDenied, sessionStart, sessionEnd, beforeCompact, afterCompact, configChange, custom

3. Type-Safe Tools

Build tools with Zod schemas and built-in safety classification:

import { buildTool } from 'agentgate'
import { z } from 'zod'

const DatabaseQuery = buildTool({
  name: 'DatabaseQuery',
  inputSchema: z.object({
    query: z.string(),
    database: z.enum(['production', 'staging', 'dev']),
  }),

  // Safety classification
  isReadOnly: (input) => /^\s*SELECT\b/i.test(input.query),
  isDestructive: (input) => /\b(DROP|TRUNCATE)\b/i.test(input.query),

  // Tool-specific permission check
  checkPermissions: async (input) => {
    if (input.database === 'production' && !/^\s*SELECT\b/i.test(input.query)) {
      return { behavior: 'deny', message: 'Only SELECT on production' }
    }
    return { behavior: 'allow', updatedInput: input }
  },

  call: async (input) => {
    const result = await db.query(input.query)
    return { data: result }
  },
})

4. Tool Executor

The executor wires permissions + hooks + tools into a single lifecycle:

import { createToolExecutor, createPermissionEngine, createHookEngine } from 'agentgate'

const executor = createToolExecutor({
  permissions: createPermissionEngine({ mode: 'default', rules: [...] }),
  hooks: createHookEngine({ preToolUse: [...] }),
  tools: [ReadTool, WriteTool, BashTool],
  maxConcurrency: 10,

  // Handle 'ask' permission decisions interactively
  onPermissionRequest: async (toolName, message, input) => {
    const approved = await promptUser(`Allow ${toolName}? ${message}`)
    return approved
      ? { behavior: 'allow', updatedInput: input }
      : { behavior: 'deny', message: 'User rejected' }
  },
})

const result = await executor.execute('Bash', { command: 'git push' })
// result.status: 'allowed' | 'denied' | 'error' | 'blocked_by_hook'

5. MCP Integration (Optional)

Connect to MCP servers and govern their tools with the same permission system:

import { createPermissionEngine } from 'agentgate'
import { createMCPClient } from 'agentgate/mcp'

const mcp = createMCPClient({
  servers: {
    filesystem: {
      type: 'stdio',
      command: 'npx',
      args: ['-y', '@modelcontextprotocol/server-filesystem', '/tmp'],
    },
  },
})

await mcp.connect()
const tools = mcp.discoverTools()
// tools: [{ name: 'mcp__filesystem__read_file', ... }, ...]

// Same permission rules work for MCP tools
const perms = createPermissionEngine({
  rules: [
    { allow: 'mcp__filesystem__read_file' },
    { deny: 'mcp__filesystem__write_file' },
  ],
})

API Reference

Main Exports

Export Description
agentgate(config) One-liner: creates a configured ToolExecutor
buildTool(def) Create a type-safe tool with defaults
createPermissionEngine(config) Create a permission engine
createHookEngine(config) Create a hook engine
createToolExecutor(config) Create an executor (permissions + hooks + tools)
defineConfig(config) Type-safe config helper
createMCPClient(config) Create an MCP client (from agentgate/mcp)

Permission Rule DSL

"ToolName"              → matches entire tool
"ToolName(pattern)"     → matches tool + input pattern
"mcp__server__tool"     → matches specific MCP tool
"mcp__server__*"        → matches all tools from MCP server
"mcp__server"           → matches all tools from MCP server

Patterns support wildcards:
"git *"                 → prefix match
"*.ts"                  → suffix match
"src/*.ts"              → prefix + suffix
"*"                     → matches everything

Architecture

agentgate is designed as middleware, not a framework. It sits between your agent and its tools:

  1. Agent requests tool call → agentgate receives (toolName, input)
  2. Permission engine evaluates rules → allow, deny, or ask
  3. PreToolUse hooks fire → can approve, block, or modify input
  4. Tool executes with validated input
  5. PostToolUse hooks fire → logging, audit, side effects
  6. Result returns to agent

This works with any LLM provider and any agent framework because agentgate only cares about the tool layer, not the model layer.

Dependencies

  • Runtime: Zero. No dependencies.
  • Peer: zod >= 3.23 (you likely already have it)
  • Optional peer: @modelcontextprotocol/sdk >= 1.0 (only for MCP module)

Examples

See the examples/ directory — all runnable with npx tsx examples/<file>:

# Example Use Case
01 Basic Permissions Quick start with allow/deny/ask rules
02 Hooks and Lifecycle Pre/post hooks for logging and validation
03 Custom Tools Type-safe tools with Zod schemas
04 MCP Integration Govern MCP server tools
05 Infrastructure Safety Prevent agents from destroying your infra
06 Database Safety Tiered DB access (prod read-only, staging read-write)
07 Audit Trail SOC2/HIPAA/GDPR compliance logging with PII detection
08 Human-in-the-Loop Interactive approval for risky actions
09 Input Validation SQL injection blocking, PII redaction, email safety
10 Rate Limiting Per-tool limits, budget caps, cost tracking

Contributing

Contributions welcome! Please open an issue first to discuss what you'd like to change.

git clone https://github.com/agentgate/agentgate
cd agentgate
npm install
npm test

License

MIT

About

Production-grade permissions, hooks, and tool safety for AI agents. Works with any LLM provider.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors