> ## 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.

# Cache Plugin

> Transparently cache tool responses to reduce redundant computation and improve response times.

The Cache Plugin provides transparent response caching for tools based on their input payloads, reducing redundant computation and improving response time.

## Why Use Caching?

<CardGroup cols={2}>
  <Card title="Faster Responses" icon="gauge-high">
    Return cached results instantly without re-executing expensive operations
  </Card>

  <Card title="Reduce Load" icon="server">
    Minimize API calls, database queries, and computational overhead
  </Card>

  <Card title="Cost Savings" icon="dollar-sign">
    Lower infrastructure costs by reducing redundant processing
  </Card>

  <Card title="Better UX" icon="user-check">
    Improve perceived performance with instant responses for repeated queries
  </Card>
</CardGroup>

## Installation

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

## How It Works

<Steps>
  <Step title="Before Execution">
    The plugin hashes the tool's validated input and checks the cache store
  </Step>

  <Step title="Cache Hit">If a cached result exists, it's returned immediately, bypassing tool execution entirely</Step>

  <Step title="Cache Miss">The tool executes normally, and the result is stored with the configured TTL</Step>

  <Step title="Sliding Window (Optional)">
    When enabled, each cache read refreshes the TTL to keep hot entries alive longer
  </Step>
</Steps>

<Info>
  Cache entries are keyed using a **deterministic hash** of the tool's validated input. The same input always produces
  the same cache key.
</Info>

***

## Quick Start

### Basic Setup (In-Memory)

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { App } from '@frontmcp/sdk';
import { CachePlugin } from '@frontmcp/plugins';

@App({
  id: 'my-app',
  name: 'My App',
  plugins: [CachePlugin], // Default: memory store, 1-day TTL
  tools: [
    /* your tools */
  ],
})
export default class MyApp {}
```

### Enable Caching on Tools

Caching is **opt-in** per tool. Add the `cache` field to your tool metadata:

<CodeGroup>
  ```ts Class-based tool theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  import { Tool, ToolContext } from '@frontmcp/sdk';
  import { z } from 'zod';

  @Tool({
    name: 'get-user',
    description: 'Get user by ID',
    inputSchema: { id: z.string() },
    cache: true, // Enable caching with defaults
  })
  export default class GetUserTool extends ToolContext {
    async execute(input: { id: string }) {
      // Expensive operation (e.g., database query)
      return await this.database.getUser(input.id);
    }
  }
  ```

  ```ts Function-based tool theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  import { tool } from '@frontmcp/sdk';
  import { z } from 'zod';

  export const GetUser = tool({
    name: 'get-user',
    description: 'Get user by ID',
    inputSchema: { id: z.string() },
    cache: true, // Enable caching with defaults
  })(async (input) => {
    // Expensive operation
    return await database.getUser(input.id);
  });
  ```
</CodeGroup>

***

## Storage Options

### In-Memory (Default)

Best for: Single-instance deployments, development, non-critical caching

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
CachePlugin.init({
  type: 'memory',
  defaultTTL: 300, // 5 minutes (default: 86400 = 1 day)
});
```

<Warning>Memory cache resets when the process restarts. Not shared across multiple instances.</Warning>

### Redis (Recommended for Production)

Best for: Multi-instance deployments, persistent caching, production environments

<CodeGroup>
  ```ts Redis connection config theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  CachePlugin.init({
    type: 'redis',
    defaultTTL: 600, // 10 minutes
    config: {
      host: '127.0.0.1',
      port: 6379,
      password: process.env.REDIS_PASSWORD, // optional
      db: 0, // optional
    },
  });
  ```

  ```ts Reuse existing Redis client theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  import { Redis } from 'ioredis';

  const redis = new Redis({
    host: 'redis.example.com',
    port: 6379,
  });

  CachePlugin.init({
    type: 'redis-client',
    defaultTTL: 900, // 15 minutes
    client: redis,
  });
  ```
</CodeGroup>

<Check>Redis enables cache sharing across multiple server instances and persists cache across restarts.</Check>

***

## Configuration Options

### Plugin-Level Configuration

Configure default behavior when registering the plugin:

<ParamField path="type" type="'memory' | 'redis' | 'redis-client'" default="'memory'">
  Cache store backend to use
</ParamField>

<ParamField path="defaultTTL" type="number" default="86400">
  Default time-to-live in seconds (applies to all cached tools unless overridden)
</ParamField>

<ParamField path="config" type="object">
  Redis connection configuration (required when `type: 'redis'`)

  <Expandable title="properties">
    <ResponseField name="host" type="string" required>
      Redis server hostname
    </ResponseField>

    <ResponseField name="port" type="number" required>
      Redis server port
    </ResponseField>

    <ResponseField name="password" type="string">
      Redis authentication password (optional)
    </ResponseField>

    <ResponseField name="db" type="number">
      Redis database number (optional, default: 0)
    </ResponseField>
  </Expandable>
</ParamField>

<ParamField path="client" type="Redis">
  Existing ioredis client instance (required when `type: 'redis-client'`)
</ParamField>

<ParamField path="toolPatterns" type="string[]">
  Tool names or glob patterns to cache. Tools matching these patterns use `defaultTTL` unless they have custom cache metadata.

  ```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  toolPatterns: ['mintlify:*', 'local:ping', 'api:get-*']
  ```
</ParamField>

<ParamField path="bypassHeader" type="string" default="'x-frontmcp-disable-cache'">
  HTTP header name that clients can send to bypass cache for a specific request. When present with value `'true'` or `'1'`, cache read/write is skipped.
</ParamField>

### Tool-Level Configuration

Configure caching behavior per tool in the `@Tool` or `tool()` metadata:

<CodeGroup>
  ```ts Simple (use defaults) theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  @Tool({
    name: 'get-report',
    cache: true, // Uses plugin's defaultTTL
  })
  ```

  ```ts Custom TTL theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  @Tool({
    name: 'get-report',
    cache: {
      ttl: 60, // Cache for 1 minute
    },
  })
  ```

  ```ts With sliding window theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  @Tool({
    name: 'get-popular-items',
    cache: {
      ttl: 300, // 5 minutes
      slideWindow: true, // Refresh TTL on each read
    },
  })
  ```
</CodeGroup>

<ParamField path="cache" type="boolean | object">
  Enable caching for this tool

  * `true` - Use plugin defaults
  * `object` - Custom configuration
</ParamField>

<ParamField path="cache.ttl" type="number">
  Time-to-live in seconds for this tool's cache entries (overrides plugin default)
</ParamField>

<ParamField path="cache.slideWindow" type="boolean" default="false">
  When `true`, reading from cache refreshes the TTL, keeping frequently accessed entries alive longer
</ParamField>

***

## Caching Remote Tools

For remote MCP tools that you don't control (connected via URL), use the `toolPatterns` option to enable caching by name or pattern:

<CodeGroup>
  ```ts Cache All Tools from Namespace theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  CachePlugin.init({
    type: 'memory',
    defaultTTL: 300, // 5 minutes
    toolPatterns: ['mintlify:*'], // Cache all Mintlify tools
  })
  ```

  ```ts Cache Specific Tools theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  CachePlugin.init({
    type: 'redis',
    config: { host: 'localhost', port: 6379 },
    toolPatterns: [
      'external:expensive-query',
      'external:get-user-*', // Glob pattern
    ],
  })
  ```

  ```ts Combined with Remote Apps theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
  @FrontMcp({
    apps: [
      {
        name: 'mintlify-docs',
        urlType: 'url',
        url: 'https://mintlify.com/docs/mcp',
        namespace: 'mintlify',
      },
    ],
    plugins: [
      CachePlugin.init({
        type: 'memory',
        defaultTTL: 300,
        toolPatterns: ['mintlify:*'], // Cache all remote tools
      }),
    ],
  })
  ```
</CodeGroup>

### Pattern Syntax

| Pattern       | Matches                    |
| ------------- | -------------------------- |
| `tool-name`   | Exact match only           |
| `namespace:*` | All tools in namespace     |
| `prefix-*`    | Tools starting with prefix |
| `*-suffix`    | Tools ending with suffix   |
| `api:*:list`  | Middle wildcard pattern    |

### Priority Rules

1. **Tool metadata takes precedence** - If a tool has `cache: { ttl: 60 }` metadata, that TTL is used
2. **Pattern list uses `defaultTTL`** - Matched tools without metadata use the plugin's default TTL
3. **Union behavior** - A tool is cached if it matches `toolPatterns` OR has `cache` metadata

<Info>
  The `toolPatterns` option is especially useful for remote MCP servers where you can't add `cache: true` to tool metadata directly.
</Info>

***

## Bypassing Cache

Clients can bypass caching for specific requests by sending a header:

```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
# Bypass cache for this request
curl -H "x-frontmcp-disable-cache: true" http://localhost:3000/mcp
```

Configure a custom header name:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
CachePlugin.init({
  type: 'memory',
  bypassHeader: 'x-no-cache', // Custom header name
})
```

<Tip>
  Cache bypass is useful for debugging, forcing fresh data, or when the client knows the cached data is stale.
</Tip>

***

## Advanced Usage

### Multi-Tenant Caching

Include tenant or user identifiers in your tool inputs to ensure cache isolation:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'get-tenant-data',
  inputSchema: {
    tenantId: z.string(),
    dataType: z.string(),
  },
  cache: { ttl: 600 },
})
export default class GetTenantDataTool extends ToolContext {
  async execute(input: { tenantId: string; dataType: string }) {
    // Cache key includes tenantId automatically via input hash
    return await this.fetchTenantData(input.tenantId, input.dataType);
  }
}
```

<Tip>
  The cache key is derived from the **entire** input object, so including tenant/user IDs ensures proper isolation.
</Tip>

### Session-Scoped Caching

For user-specific data, include session or user identifiers:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
export const GetUserDashboard = tool({
  name: 'get-user-dashboard',
  inputSchema: {
    userId: z.string(),
    dateRange: z.object({
      start: z.string(),
      end: z.string(),
    }),
  },
  cache: {
    ttl: 120, // 2 minutes
    slideWindow: true,
  },
})(async (input, ctx) => {
  // Cache key includes userId and dateRange
  return await generateDashboard(input.userId, input.dateRange);
});
```

### Time-Based Invalidation

Use short TTLs for frequently changing data:

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
@Tool({
  name: 'get-live-stock-price',
  inputSchema: { symbol: z.string() },
  cache: {
    ttl: 5, // Only cache for 5 seconds
  },
})
```

***

## Best Practices

<AccordionGroup>
  <Accordion title="1. Only Cache Deterministic Tools">
    Cache tools whose outputs depend **solely** on their inputs. Don't cache tools that:

    * Return random data
    * Depend on external time-sensitive state
    * Have side effects (mutations, API calls that change state)
  </Accordion>

  <Accordion title="2. Choose Appropriate TTLs">
    * **Short TTLs (5-60s)**: Real-time data, frequently changing content - **Medium TTLs (5-30min)**: User dashboards,
      reports, analytics - **Long TTLs (hours-days)**: Static content, configuration, reference data
  </Accordion>

  <Accordion title="3. Use Redis for Production">
    Redis provides: - Cache persistence across restarts - Sharing across multiple server instances - Better memory
    management with eviction policies
  </Accordion>

  <Accordion title="4. Include Scoping in Inputs">
    Always include tenant IDs, user IDs, or other scoping fields in your tool inputs:

    ```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    // Good: includes tenantId for isolation
    { tenantId: "t-123", reportId: "r-456" }

    // Bad: no scoping, shared across tenants
    { reportId: "r-456" }
    ```
  </Accordion>

  <Accordion title="5. Use Sliding Windows for Hot Data">
    Enable `slideWindow` for frequently accessed data to keep it cached longer:

    ```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    cache: {
      ttl: 300,
      slideWindow: true, // Popular items stay cached
    }
    ```
  </Accordion>
</AccordionGroup>

***

## Cache Behavior Reference

| Behavior           | Description                                                          |
| ------------------ | -------------------------------------------------------------------- |
| **Key Derivation** | Deterministic hash from validated input. Same input = same cache key |
| **Cache Hits**     | Bypasses tool execution entirely, returns cached result instantly    |
| **Default TTL**    | 86400 seconds (1 day) if not specified                               |
| **Sliding Window** | Extends TTL on reads when enabled                                    |
| **Store Choice**   | Memory is node-local; Redis enables multi-instance sharing           |
| **Invalidation**   | Automatic after TTL expires, or manually by restarting (memory)      |

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="No cache hits occurring">
    **Possible causes:**

    * Tool missing `cache: true` in metadata
    * Cache store offline or misconfigured
    * Input varies slightly (whitespace, order of fields)

    **Solutions:**

    * Verify `cache` field is set in tool metadata
    * Check Redis connection if using Redis backend
    * Ensure input structure is consistent
  </Accordion>

  <Accordion title="Stale data being returned">
    **Possible causes:**

    * TTL too long for data freshness requirements
    * Data changed but cache not invalidated

    **Solutions:**

    * Reduce TTL for the tool
    * Consider input-based cache busting (include timestamp or version in input)
    * Restart server to clear memory cache (or flush Redis)
  </Accordion>

  <Accordion title="Cache not shared across instances">
    **Possible cause:**

    * Using memory cache with multiple server instances

    **Solution:**

    * Switch to Redis backend for multi-instance deployments
  </Accordion>

  <Accordion title="Need to invalidate specific cache entries">
    **Solution:**

    * Currently, manual invalidation requires custom implementation
    * For memory: restart the server
    * For Redis: use Redis CLI to delete keys manually
    * Consider shorter TTLs or input-based versioning instead
  </Accordion>
</AccordionGroup>

***

## Complete Example

```ts theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { FrontMcp, App, Tool, ToolContext } from '@frontmcp/sdk';
import { CachePlugin } from '@frontmcp/plugins';
import { z } from 'zod';

// Configure Redis cache
const cachePlugin = CachePlugin.init({
  type: 'redis',
  defaultTTL: 600, // 10 minutes default
  config: {
    host: process.env.REDIS_HOST || 'localhost',
    port: parseInt(process.env.REDIS_PORT || '6379'),
    password: process.env.REDIS_PASSWORD,
  },
});

// Expensive report generation tool
@Tool({
  name: 'generate-monthly-report',
  description: 'Generate monthly sales report for a tenant',
  inputSchema: {
    tenantId: z.string(),
    month: z.string(), // "2025-01"
  },
  cache: {
    ttl: 1800, // 30 minutes (reports don't change often)
  },
})
class GenerateMonthlyReportTool extends ToolContext {
  async execute(input: { tenantId: string; month: string }) {
    this.logger.info('Generating report', input);

    // Expensive operation: aggregate data, generate charts, etc.
    const report = await this.database.generateReport(input.tenantId, input.month);

    return report;
  }
}

// Hot data with sliding window
@Tool({
  name: 'get-trending-products',
  description: 'Get current trending products',
  inputSchema: {
    category: z.string(),
    limit: z.number().default(10),
  },
  cache: {
    ttl: 120, // 2 minutes
    slideWindow: true, // Keep popular queries cached
  },
})
class GetTrendingProductsTool extends ToolContext {
  async execute(input: { category: string; limit: number }) {
    return await this.analytics.getTrendingProducts(input.category, input.limit);
  }
}

@App({
  id: 'analytics',
  name: 'Analytics App',
  plugins: [cachePlugin],
  tools: [GenerateMonthlyReportTool, GetTrendingProductsTool],
})
class AnalyticsApp {}

@FrontMcp({
  info: { name: 'Analytics Server', version: '1.0.0' },
  apps: [AnalyticsApp],
  http: { port: 3000 },
})
export default class Server {}
```

***

## Links & Resources

<CardGroup cols={2}>
  <Card title="Source Code" icon="github" href="https://github.com/agentfront/frontmcp/tree/main/plugins/plugin-cache">
    View the cache plugin source code
  </Card>

  <Card title="Demo Application" icon="code" href="https://github.com/agentfront/frontmcp/tree/main/apps/demo">
    See caching in action with real examples
  </Card>

  <Card title="Plugin Guide" icon="puzzle-piece" href="/frontmcp/extensibility/plugins">
    Learn more about FrontMCP plugins
  </Card>

  <Card title="Redis Documentation" icon="database" href="https://redis.io/docs/">
    Official Redis documentation
  </Card>
</CardGroup>
