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

# SQLite Setup

> Use SQLite for local session, elicitation, and event storage without external infrastructure

`@frontmcp/storage-sqlite` provides a local storage backend for FrontMCP using SQLite. It replaces Redis for single-process deployments where you need persistence without managing external infrastructure.

<CardGroup cols={2}>
  <Card title="Zero Infrastructure" icon="server">
    No Redis or external database needed — just a local file
  </Card>

  <Card title="Persistent Sessions" icon="floppy-disk">
    Sessions, elicitation state, and SSE events survive restarts
  </Card>

  <Card title="Optional Encryption" icon="lock">
    AES-256-GCM at-rest encryption via HKDF-SHA256
  </Card>

  <Card title="WAL Mode" icon="bolt">
    Write-Ahead Logging enabled by default for better read concurrency
  </Card>
</CardGroup>

## When to Use SQLite

| Use Case | SQLite | Redis |
| - | - | - |
| Local-only / single-process | **Recommended** | Overkill |
| Unix socket deployments | **Recommended** | Not needed |
| Background daemons | **Recommended** | Not needed |
| Multi-instance production | Not supported | **Required** |
| Pub/Sub (resource subscriptions) | EventEmitter (single process) | **Required** for distributed |
| Edge / serverless | Not supported | **Required** |

<Info>
  SQLite is ideal for local-only deployments — Unix socket servers, CLI tools, and background daemons. For multi-instance production or serverless, use [Redis](/frontmcp/deployment/redis-setup) or [Vercel KV](/frontmcp/deployment/vercel-kv) instead.
</Info>

## Installation

```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
npm install @frontmcp/storage-sqlite better-sqlite3
# or
yarn add @frontmcp/storage-sqlite better-sqlite3
```

<Warning>
  `better-sqlite3` is a native module and requires a C++ compiler. On most systems this is already available. If you run into build issues, see the [better-sqlite3 troubleshooting guide](https://github.com/WiseLibs/better-sqlite3/blob/master/docs/troubleshooting.md).
</Warning>

## Quick Start

### With Unix Socket (Recommended)

The simplest way to use SQLite is via the `sqlite` option on `FrontMcpInstance.runUnixSocket()`:

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import { FrontMcpInstance } from '@frontmcp/sdk';
import { serverConfig } from './config';

const handle = await FrontMcpInstance.runUnixSocket({
  ...serverConfig,
  socketPath: '/tmp/my-app.sock',
  sqlite: {
    path: '~/.frontmcp/data/my-app.sqlite',
  },
});
```

Or via the CLI:

```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
frontmcp socket ./src/main.ts --db ~/.frontmcp/data/my-app.sqlite
```

### With @FrontMcp Decorator

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
import 'reflect-metadata';
import { FrontMcp } from '@frontmcp/sdk';

@FrontMcp({
  info: { name: 'My Server', version: '1.0.0' },
  http: { port: 3001 },
  sqlite: {
    path: './data/my-app.sqlite',
  },
})
export default class Server {}
```

## Configuration Options

| Option | Type | Default | Description |
| - | - | - | - |
| `path` | `string` | *required* | Path to the `.sqlite` database file |
| `encryption.secret` | `string` | — | Secret key for AES-256-GCM encryption via HKDF-SHA256 |
| `walMode` | `boolean` | `true` | Enable WAL mode for better read concurrency |
| `busyTimeoutMs` | `number` | `5000` | How long a connection waits for a lock held by another process before failing with `SQLITE_BUSY` |
| `ttlCleanupIntervalMs` | `number` | `60000` | Interval in ms for purging expired keys |

<Note>
  The database location can also be supplied through the `FRONTMCP_SQLITE_PATH` environment variable (used by `frontmcp start --db`, `frontmcp socket --db` and the generated installer). It overrides `sqlite.path`; other `sqlite` options are kept.
</Note>

## Encryption

Enable at-rest encryption to protect stored session data. Keys are stored in plaintext (needed for lookups); only values are encrypted.

```typescript theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
sqlite: {
  path: './data/my-app.sqlite',
  encryption: {
    secret: process.env.FRONTMCP_ENCRYPTION_SECRET!,
  },
}
```

The encryption pipeline:

1. Your secret is run through **HKDF-SHA256** to derive a 256-bit key
2. Each value is encrypted with **AES-256-GCM** using a random 96-bit IV
3. Stored format: `base64url(iv):base64url(tag):base64url(ciphertext)`

<Warning>
  If you lose the encryption secret, stored data becomes unrecoverable. Store the secret securely (environment variable, secrets manager) and keep a backup.
</Warning>

## Store Types

`@frontmcp/storage-sqlite` provides three specialized stores, all built on a common `SqliteKvStore`:

### Session Store

Stores MCP session data with TTL support.

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

const sessions = new SqliteSessionStore({
  path: './data/app.sqlite',
  keyPrefix: 'mcp:session:',   // default
  defaultTtlMs: 3600000,       // 1 hour (default)
});

await sessions.set('session-123', { user: { sub: 'user-1' } });
const data = await sessions.get('session-123');
```

### Event Store

Stores SSE events for resumability with max event limits and TTL-based eviction.

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

const events = new SqliteEventStore({
  path: './data/app.sqlite',
  maxEvents: 10000,    // default
  ttlMs: 300000,       // 5 minutes (default)
});

const eventId = await events.storeEvent('stream-1', { type: 'update' });
```

### Elicitation Store

Stores pending elicitation requests with EventEmitter-based single-process pub/sub.

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

const elicitation = new SqliteElicitationStore({
  path: './data/app.sqlite',
  keyPrefix: 'mcp:elicit:',   // default
});
```

## Comparison: SQLite vs Redis vs Vercel KV

| Feature | SQLite | Redis | Vercel KV |
| - | - | - | - |
| **Infrastructure** | Local file | Server or managed service | Vercel-managed |
| **Setup** | `npm install` | Docker / cloud setup | Dashboard toggle |
| **Multi-instance** | Single process only | Full support | Full support |
| **Pub/Sub** | EventEmitter (local) | Native Redis pub/sub | Not supported |
| **Encryption** | Built-in AES-256-GCM | TLS in transit | TLS in transit |
| **Edge Compatible** | No | No | Yes |
| **Latency** | \~0ms (local file I/O) | \~1-5ms (TCP) | \~5-15ms (REST) |
| **Best For** | Local daemons, CLI tools | Production servers | Vercel deployments |

## Troubleshooting

<AccordionGroup>
  <Accordion title="Error: Cannot find module 'better-sqlite3'">
    **Cause**: The `better-sqlite3` native module is not installed.

    **Solution**:

    ```bash theme={"theme":{"light":"snazzy-light","dark":"dark-plus"}}
    npm install better-sqlite3
    ```

    If you're using yarn with PnP, you may need to add it as a dependency (not devDependency).
  </Accordion>

  <Accordion title="SQLITE_CANTOPEN: unable to open database file">
    **Cause**: The parent directory is created for you, so this means the path itself cannot be opened: no permission to create the directory, a path that points at a directory or a file that is not a database, or a read-only file system.

    **Solution**: Check the permissions and that `path` names a file. Serverless targets with a read-only file system need a writable path such as `/tmp`, or a different store.
  </Accordion>

  <Accordion title="Database is locked">
    **Cause**: Multiple processes are trying to write to the same SQLite file simultaneously.

    **Solution**: The store sets `busy_timeout` (5 s by default) before it enables WAL and creates tables, and retries `SQLITE_BUSY` during startup, so two processes starting on the same file wait for each other. If a writer holds the lock longer than that, raise `busyTimeoutMs`. SQLite still allows one writer at a time: for many writer processes, use Redis.
  </Accordion>

  <Accordion title="Encryption errors after changing the secret">
    **Cause**: Existing data was encrypted with a different secret.

    **Solution**: Reading a value written under another secret throws `SqliteDecryptionError` (code `SQLITE_DECRYPTION_FAILED`) with a message that names the secret as the likely cause. Restore the original secret or delete the database file and let the server create a fresh one. There is no migration path between encryption keys.
  </Accordion>
</AccordionGroup>

## Related Documentation

<CardGroup cols={2}>
  <Card title="Unix Socket" icon="plug-circle-bolt" href="/frontmcp/deployment/unix-socket">
    Run FrontMCP as a persistent local server over Unix sockets
  </Card>

  <Card title="Redis Setup" icon="database" href="/frontmcp/deployment/redis-setup">
    Configure Redis for multi-instance production deployments
  </Card>

  <Card title="Vercel KV" icon="bolt" href="/frontmcp/deployment/vercel-kv">
    Edge-compatible storage for Vercel deployments
  </Card>

  <Card title="Runtime Modes" icon="layer-group" href="/frontmcp/deployment/runtime-modes">
    Compare SDK, Server, and Handler deployment modes
  </Card>
</CardGroup>


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