> ## Documentation Index
> Fetch the complete documentation index at: https://docs.agentfront.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Guard API Reference

> Complete API reference for @frontmcp/guard — rate limiting, concurrency, timeout, and IP filtering.

Full API reference for the `@frontmcp/guard` package. This library is SDK-agnostic and works with any `StorageAdapter` backend.

```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
npm install @frontmcp/guard
```

<Info>
  When using `@frontmcp/sdk`, guard features are integrated automatically via the `throttle` config and tool/agent decorators. You only need to import from `@frontmcp/guard` directly if building custom integrations.
</Info>

***

## Configuration Types

### `GuardConfig`

Top-level configuration for the guard system. Passed to `@FrontMcp({ throttle: ... })` or `createGuardManager()`.

| Field | Type | Default | Description |
| - | - | - | - |
| `enabled` | `boolean` | *required* | Enable or disable all guard features |
| `storage` | `StorageConfig` | in-memory | Storage backend configuration (`{ type: 'redis', redis: { config \| url }, fallback? }`, see below) |
| `keyPrefix` | `string` | `'mcp:guard:'` | Prefix for all storage keys; a trailing `:` is dropped, so keys read `mcp:guard:<entity>:…` |
| `global` | `RateLimitConfig` | — | Global rate limit for all requests |
| `globalConcurrency` | `ConcurrencyConfig` | — | Global concurrency limit |
| `defaultRateLimit` | `RateLimitConfig` | — | Default rate limit for entities without explicit config |
| `defaultConcurrency` | `ConcurrencyConfig` | — | Default concurrency for entities without explicit config |
| `defaultTimeout` | `TimeoutConfig` | — | Default timeout for entity execution |
| `ipFilter` | `IpFilterConfig` | — | IP filtering configuration |

### `RateLimitConfig`

Configuration for sliding window rate limiting.

| Field | Type | Default | Description |
| - | - | - | - |
| `maxRequests` | `number` | *required* | Maximum requests allowed in the window |
| `windowMs` | `number` | `60000` | Time window in milliseconds |
| `partitionBy` | `PartitionKey` | `'global'` | How to bucket rate limits |

### `ConcurrencyConfig`

Configuration for distributed semaphore concurrency control.

| Field | Type | Default | Description |
| - | - | - | - |
| `maxConcurrent` | `number` | *required* | Maximum simultaneous executions |
| `queueTimeoutMs` | `number` | `0` | Max time (ms) to wait for a slot. `0` = reject immediately |
| `partitionBy` | `PartitionKey` | `'global'` | How to bucket concurrency limits |

### `TimeoutConfig`

Configuration for execution timeout.

| Field | Type | Default | Description |
| - | - | - | - |
| `executeMs` | `number` | *required* | Maximum execution time in milliseconds |

### `IpFilterConfig`

Configuration for IP-based access control.

| Field | Type | Default | Description |
| - | - | - | - |
| `allowList` | `string[]` | `[]` | IPs or CIDR ranges to always allow |
| `denyList` | `string[]` | `[]` | IPs or CIDR ranges to always block |
| `defaultAction` | `'allow' \| 'deny'` | `'allow'` | Action when IP matches neither list, or no client IP is known |
| `trustProxy` | `boolean` | `false` | **Not read** (startup warning); set `FRONTMCP_TRUST_PROXY` |
| `trustedProxyDepth` | `number` | `1` | **Not read** (startup warning); set `FRONTMCP_TRUSTED_PROXY_DEPTH` |

### `PartitionKey`

Determines how limits are bucketed across requests.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
type PartitionKey = PartitionKeyStrategy | CustomPartitionKeyFn;

type PartitionKeyStrategy = 'ip' | 'session' | 'userId' | 'global';

type CustomPartitionKeyFn = (ctx: PartitionKeyContext) => string;
```

**`PartitionKeyContext`:**

| Field | Type | Description |
| - | - | - |
| `sessionId` | `string` | MCP session identifier |
| `clientIp` | `string \| undefined` | Client IP address |
| `userId` | `string \| undefined` | Authenticated user identifier |

***

## Classes

### `GuardManager`

Orchestrates all guard features. Created via `createGuardManager()` or automatically by the SDK when `throttle` is configured.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
class GuardManager {
  readonly config: GuardConfig;

  constructor(storage: NamespacedStorage, config: GuardConfig);
}
```

#### `checkRateLimit()`

Check per-entity rate limit.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
async checkRateLimit(
  entityName: string,
  entityConfig?: RateLimitConfig,
  context?: PartitionKeyContext,
): Promise<RateLimitResult>
```

| Parameter | Description |
| - | - |
| `entityName` | Tool or agent name |
| `entityConfig` | Per-entity rate limit config. Falls back to `config.defaultRateLimit` if not provided |
| `context` | Partition key context for key resolution |

Returns `{ allowed: true, remaining: Infinity, resetMs: 0 }` if no config applies.

#### `checkGlobalRateLimit()`

Check global (server-wide) rate limit.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
async checkGlobalRateLimit(
  context?: PartitionKeyContext,
): Promise<RateLimitResult>
```

Uses `config.global` configuration. Returns allowed result if no global config.

#### `acquireSemaphore()`

Acquire a concurrency slot for an entity.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
async acquireSemaphore(
  entityName: string,
  entityConfig?: ConcurrencyConfig,
  context?: PartitionKeyContext,
): Promise<SemaphoreTicket | null>
```

| Parameter | Description |
| - | - |
| `entityName` | Tool or agent name |
| `entityConfig` | Per-entity concurrency config. Falls back to `config.defaultConcurrency` |
| `context` | Partition key context for key resolution |

Returns `SemaphoreTicket` on success, `null` if no config applies. May throw `QueueTimeoutError` if queue timeout expires.

#### `acquireGlobalSemaphore()`

Acquire a global concurrency slot.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
async acquireGlobalSemaphore(
  context?: PartitionKeyContext,
): Promise<SemaphoreTicket | null>
```

Uses `config.globalConcurrency` configuration.

#### `checkIpFilter()`

Check if a client IP is allowed.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
checkIpFilter(clientIp?: string): IpFilterResult | undefined
```

Returns `undefined` only when no IP filter is configured. A missing or unparseable `clientIp` matches no rule and gets the configured `defaultAction`, so with `defaultAction: 'deny'` it is rejected.

#### `isIpAllowListed()`

Check if a client IP is explicitly on the allow list.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
isIpAllowListed(clientIp?: string): boolean
```

Returns `true` only if an IP filter is configured, `clientIp` is provided, and the IP matches the allow list.

#### `destroy()`

Disconnect storage backend and clean up resources.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
async destroy(): Promise<void>
```

***

### `SlidingWindowRateLimiter`

Implements sliding window rate limiting with O(1) storage per key.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
class SlidingWindowRateLimiter {
  constructor(storage: StorageAdapter);
}
```

#### `check()`

Check and consume a rate limit token.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
async check(
  key: string,
  maxRequests: number,
  windowMs: number,
): Promise<RateLimitResult>
```

**Algorithm:** Uses two adjacent fixed-window counters with weighted interpolation. The estimated count is:

```
estimatedCount = previousWindowCount * (1 - elapsedRatio) + currentWindowCount
```

If `estimatedCount < maxRequests`, the request is allowed and the current window counter is atomically incremented.

**`RateLimitResult`:**

| Field | Type | Description |
| - | - | - |
| `allowed` | `boolean` | Whether the request is allowed |
| `remaining` | `number` | Requests remaining in the window |
| `resetMs` | `number` | Milliseconds until window resets |
| `retryAfterMs` | `number \| undefined` | Recommended retry time (only when denied) |

#### `reset()`

Reset rate limit counters for a key.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
async reset(key: string, windowMs: number): Promise<void>
```

***

### `DistributedSemaphore`

Implements a distributed counting semaphore with optional queuing.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
class DistributedSemaphore {
  constructor(storage: StorageAdapter, ticketTtlSeconds?: number);
}
```

Default `ticketTtlSeconds`: `300` (5 minutes).

#### `acquire()`

Acquire a semaphore slot.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
async acquire(
  key: string,
  maxConcurrent: number,
  queueTimeoutMs: number,
  entityName: string,
): Promise<SemaphoreTicket | null>
```

* Returns `SemaphoreTicket` if a slot is acquired.
* Returns `null` if `queueTimeoutMs <= 0` and no slot is available.
* Throws `QueueTimeoutError` if queued and timeout expires.

Uses pub/sub when available for efficient slot release detection, falls back to polling with exponential backoff (100ms to 1000ms).

**`SemaphoreTicket`:**

| Field | Type | Description |
| - | - | - |
| `ticket` | `string` | Unique ticket identifier (UUID) |
| `release()` | `() => Promise<void>` | Release the slot back to the pool |

#### `getActiveCount()`

Get current number of active tickets for a key.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
async getActiveCount(key: string): Promise<number>
```

#### `forceReset()`

Force-clear all tickets and reset the counter for a key.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
async forceReset(key: string): Promise<void>
```

***

### `IpFilter`

IP-based access control with CIDR support for IPv4 and IPv6.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
class IpFilter {
  constructor(config: IpFilterConfig);
}
```

Parses all CIDR rules at construction time using BigInt bitmask representation.

#### `check()`

Check if a client IP is allowed.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
check(clientIp: string | undefined): IpFilterResult
```

**Evaluation order:**

1. Deny list (takes precedence)
2. Allow list
3. Default action — also for a missing or unparseable IP

An IPv4-mapped address (`::ffff:203.0.113.7`, `::ffff:cb00:7107`) is matched as the IPv4 address it maps, and a rule written in mapped form (`::ffff:10.0.0.0/104`) as the IPv4 rule (`10.0.0.0/8`). A `%zone` suffix is ignored.

**`IpFilterResult`:**

| Field | Type | Description |
| - | - | - |
| `allowed` | `boolean` | Whether the IP is allowed |
| `reason` | `'allowlisted' \| 'denylisted' \| 'default' \| undefined` | Reason for the decision |
| `matchedRule` | `string \| undefined` | Specific IP or CIDR that matched |

#### `isAllowListed()`

Check if an IP is explicitly on the allow list.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
isAllowListed(clientIp: string): boolean
```

***

## Functions

### `createGuardManager()`

Factory function to create a fully initialized `GuardManager`.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
async function createGuardManager(args: CreateGuardManagerArgs): Promise<GuardManager>
```

**`CreateGuardManagerArgs`:**

| Field | Type | Description |
| - | - | - |
| `config` | `GuardConfig` | Full guard configuration |
| `logger` | `GuardLogger \| undefined` | Optional logger for diagnostic output |

**Behavior:**

1. Creates storage backend from `config.storage` (or in-memory if not set)
2. Connects the storage backend. A configured backend that cannot be created or reached rejects with `GuardStorageUnavailableError` — rate limits fail closed — unless `config.storage.fallback` is `'memory'`. The returned manager applies the same policy if the backend stops answering later: limit checks and slot acquisitions reject with `GuardStorageUnavailableError`, or, with `fallback: 'memory'`, run on per-instance counters and retry the configured backend every 30 seconds (`GuardManagerOptions.retryPrimaryAfterMs`). Releasing a concurrency slot never throws. `createStorage` defaults `fallback` to `'error'` in production and `'memory'` otherwise.
3. Creates namespaced storage with `config.keyPrefix`, minus any trailing `:` (the namespace adds its own separator)
4. Returns initialized `GuardManager`

`config.storage` is a `StorageConfig` from `@frontmcp/utils`: `type` selects the backend and its options go under the key of that name — `{ type: 'redis', redis: { config: { host, port, password?, tls? } } }`, `{ type: 'redis', redis: { url } }`, `{ type: 'vercel-kv', vercelKv: { url, token } }` or `{ type: 'upstash', upstash: { url, token } }`. A block without `type` is auto-detected from the environment.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { createGuardManager } from '@frontmcp/guard';

const manager = await createGuardManager({
  config: {
    enabled: true,
    storage: { type: 'redis', redis: { config: { host: 'localhost', port: 6379 } } },
    global: { maxRequests: 1000, windowMs: 60_000, partitionBy: 'ip' },
  },
  logger: console,
});
```

<Note>
  Keys are `mcp:guard:<entity>:<partition>:<kind>:…`. Before 1.8.6 the default prefix wrote `mcp:guard::<entity>:…`; the two versions do not read each other's counters, so limits briefly split during a rolling deploy.
</Note>

***

### `withTimeout()`

Wraps an async function with an execution deadline.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
async function withTimeout<T>(
  fn: (signal: AbortSignal) => Promise<T>,
  timeoutMs: number,
  entityName: string,
): Promise<T>
```

| Parameter | Type | Description |
| - | - | - |
| `fn` | `(signal: AbortSignal) => Promise<T>` | Async function to execute |
| `timeoutMs` | `number` | Maximum execution time in milliseconds |
| `entityName` | `string` | Name included in error message |

Throws `ExecutionTimeoutError` if the deadline is exceeded, and aborts the `signal` passed to `fn` so the work can stop. Uses `AbortController` + `Promise.race` internally.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { withTimeout } from '@frontmcp/guard';

const result = await withTimeout(
  (signal) => fetch(url, { signal }),
  5000,
  'data-fetcher',
);
```

***

### `resolvePartitionKey()`

Resolve a partition key strategy to a concrete string value.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
function resolvePartitionKey(
  partitionBy?: PartitionKey,
  context?: PartitionKeyContext,
): string
```

| Strategy | Resolved Value |
| - | - |
| `undefined` | `'global'` |
| `'global'` | `'global'` |
| `'ip'` | `context.clientIp`, else `user:<userId>`, else `'ip:unresolved'` |
| `'session'` | `context.sessionId` |
| `'userId'` | `context.userId`, else `context.sessionId` |
| Custom function | `fn(context)` |

***

### `buildStorageKey()`

Build a namespaced storage key from components.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
function buildStorageKey(
  entityName: string,
  partitionKey: string,
  suffix?: string,
): string
```

**Examples:**

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
buildStorageKey('search', 'user-123', 'rl');
// → 'search:user-123:rl'

buildStorageKey('search', 'global');
// → 'search:global'
```

***

## Error Classes

All guard errors extend `GuardError`, which has `code` (string) and `statusCode` (number) properties.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
class GuardError extends Error {
  readonly code: string;
  readonly statusCode: number;
}
```

### `ExecutionTimeoutError`

Thrown when execution exceeds the configured timeout.

| Property | Type | Value |
| - | - | - |
| `code` | `string` | `'EXECUTION_TIMEOUT'` |
| `statusCode` | `number` | `408` |
| `entityName` | `string` | Name of the tool/agent |
| `timeoutMs` | `number` | Configured timeout value |

### `ConcurrencyLimitError`

Thrown when no concurrency slot is available and `queueTimeoutMs` is 0.

| Property | Type | Value |
| - | - | - |
| `code` | `string` | `'CONCURRENCY_LIMIT'` |
| `statusCode` | `number` | `429` |
| `entityName` | `string` | Name of the tool/agent |
| `maxConcurrent` | `number` | Configured concurrency limit |

### `QueueTimeoutError`

Thrown when a queued request exceeds its wait time.

| Property | Type | Value |
| - | - | - |
| `code` | `string` | `'QUEUE_TIMEOUT'` |
| `statusCode` | `number` | `429` |
| `entityName` | `string` | Name of the tool/agent |
| `queueTimeoutMs` | `number` | Configured queue timeout |

### `IpBlockedError`

For your own code to throw when a client IP matches a deny list. The SDK's built-in `throttle.ipFilter` does not throw it; it answers the HTTP request with 403 from each flow's `checkIpFilter` stage.

| Property | Type | Value |
| - | - | - |
| `code` | `string` | `'IP_BLOCKED'` |
| `statusCode` | `number` | `403` |
| `clientIp` | `string` | The blocked IP address |

### `IpNotAllowedError`

For your own code to throw when a client IP is not on an allow list and `defaultAction` is `'deny'`. Not thrown by the built-in filter.

| Property | Type | Value |
| - | - | - |
| `code` | `string` | `'IP_NOT_ALLOWED'` |
| `statusCode` | `number` | `403` |
| `clientIp` | `string` | The rejected IP address |

### `GuardStorageUnavailableError`

Thrown when the configured `storage` backend cannot be created or reached: by `createGuardManager()` at SDK startup, and by `GuardManager` limit checks (`checkRateLimit`, `checkGlobalRateLimit`, `acquireSemaphore`, `acquireGlobalSemaphore`) if the backend goes away while the server runs. The message names `throttle.storage` and says to set `throttle.storage.fallback: 'memory'` to use per-instance counters instead.

| Property | Type | Value |
| - | - | - |
| `code` | `string` | `'GUARD_STORAGE_UNAVAILABLE'` |
| `statusCode` | `number` | `503` |
| `storageType` | `string` | The configured `storage.type` (or `'auto'`) |
| `cause` | `unknown` | The error the storage backend failed with |

***

## Zod Schemas

Validation schemas for all configuration types. Useful for validating user-provided configuration.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import {
  guardConfigSchema,
  rateLimitConfigSchema,
  concurrencyConfigSchema,
  timeoutConfigSchema,
  ipFilterConfigSchema,
  partitionKeySchema,
} from '@frontmcp/guard';
```

| Schema | Validates |
| - | - |
| `guardConfigSchema` | `GuardConfig` |
| `rateLimitConfigSchema` | `RateLimitConfig` |
| `concurrencyConfigSchema` | `ConcurrencyConfig` |
| `timeoutConfigSchema` | `TimeoutConfig` |
| `ipFilterConfigSchema` | `IpFilterConfig` |
| `partitionKeySchema` | `PartitionKey` (string strategy or function) |


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.