loading…
Search for a command to run...
loading…
MCP server for multi-tenant DuckDB management with R2/S3 cloud storage, enabling AI agents to manage per-user databases with automatic cloud persistence.
MCP server for multi-tenant DuckDB management with R2/S3 cloud storage, enabling AI agents to manage per-user databases with automatic cloud persistence.
Model Context Protocol (MCP) server for multi-tenant DuckDB management with R2/S3 cloud storage.
Built on top of the duckpond library, this MCP server enables AI agents to manage per-user DuckDB databases with automatic cloud persistence.
# Global installation
npm install -g duckpond-mcp-server
# Or use directly with npx
npx duckpond-mcp-server
Add to your Claude Desktop config (~/Library/Application Support/Claude/claude_desktop_config.json on macOS):
{
"mcpServers": {
"duckpond": {
"command": "npx",
"args": ["-y", "duckpond-mcp-server"],
"env": {
"DUCKPOND_R2_ACCOUNT_ID": "your-account-id",
"DUCKPOND_R2_ACCESS_KEY_ID": "your-access-key",
"DUCKPOND_R2_SECRET_ACCESS_KEY": "your-secret-key",
"DUCKPOND_R2_BUCKET": "your-bucket"
}
}
}
}
# Start HTTP server on port 3000
npx duckpond-mcp-server --transport http
# With custom port
npx duckpond-mcp-server --transport http --port 8080
queryExecute a SQL query for a specific user and return results.
Input:
{
userId: string // User identifier
sql: string // SQL query to execute
}
Output:
{
rows: T[] // Query results
rowCount: number // Number of rows
executionTime: number // Execution time in ms
}
executeExecute DDL/DML statements (CREATE, INSERT, UPDATE, DELETE) without returning results.
Input:
{
userId: string // User identifier
sql: string // SQL statement to execute
}
Output:
{
success: boolean
message: string
executionTime: number
}
getUserStatsGet statistics about a user's database.
Input:
{
userId: string // User identifier
}
Output:
{
userId: string
attached: boolean // Is user currently cached?
lastAccess: string // ISO 8601 timestamp
memoryUsage: number // Bytes
storageUsage: number // Bytes
queryCount: number
}
isAttachedCheck if a user's database is currently cached in memory.
Input:
{
userId: string // User identifier
}
Output:
{
attached: boolean
userId: string
}
detachUserManually detach a user's database from the cache to free resources.
Input:
{
userId: string // User identifier
}
Output:
{
success: boolean
message: string
}
DUCKPOND_MEMORY_LIMIT - Memory limit (default: 4GB)DUCKPOND_THREADS - Number of threads (default: 4)DUCKPOND_CACHE_TYPE - Cache type: disk, memory, noop (default: disk)By default, DuckPond stores databases locally. For cloud deployments, configure R2 or S3.
Local Storage (Default)
# Databases stored in ~/.duckpond/data by default
# Customize with:
export DUCKPOND_DATA_DIR=/path/to/data
npx duckpond-mcp-server
Cloudflare R2
export DUCKPOND_R2_ACCOUNT_ID=your-account-id
export DUCKPOND_R2_ACCESS_KEY_ID=your-access-key
export DUCKPOND_R2_SECRET_ACCESS_KEY=your-secret-key
export DUCKPOND_R2_BUCKET=your-bucket
npx duckpond-mcp-server
AWS S3
export DUCKPOND_S3_REGION=us-east-1
export DUCKPOND_S3_ACCESS_KEY_ID=your-access-key
export DUCKPOND_S3_SECRET_ACCESS_KEY=your-secret-key
export DUCKPOND_S3_BUCKET=your-bucket
npx duckpond-mcp-server
S3-Compatible (MinIO, etc.)
export DUCKPOND_S3_REGION=us-east-1
export DUCKPOND_S3_ACCESS_KEY_ID=minioadmin
export DUCKPOND_S3_SECRET_ACCESS_KEY=minioadmin
export DUCKPOND_S3_BUCKET=duckpond
export DUCKPOND_S3_ENDPOINT=http://localhost:9000
npx duckpond-mcp-server
DUCKPOND_MAX_ACTIVE_USERS - LRU cache size (default: 10)DUCKPOND_EVICTION_TIMEOUT - Idle timeout in ms (default: 300000)DUCKPOND_STRATEGY - Storage strategy: parquet, duckdb, hybrid (default: duckdb)DUCKPOND_DATA_DIR - Local data directory (default: ~/.duckpond/data)DUCKPOND_R2_ACCOUNT_ID - R2 account IDDUCKPOND_R2_ACCESS_KEY_ID - R2 access keyDUCKPOND_R2_SECRET_ACCESS_KEY - R2 secret keyDUCKPOND_R2_BUCKET - R2 bucket nameDUCKPOND_S3_REGION - S3 region (e.g., us-east-1)DUCKPOND_S3_ACCESS_KEY_ID - S3 access keyDUCKPOND_S3_SECRET_ACCESS_KEY - S3 secret keyDUCKPOND_S3_BUCKET - S3 bucket nameDUCKPOND_S3_ENDPOINT - Custom S3 endpoint (for MinIO, etc.)export DUCKPOND_OAUTH_ENABLED=true
export DUCKPOND_OAUTH_USERNAME=admin
export DUCKPOND_OAUTH_PASSWORD=secret123
export DUCKPOND_OAUTH_USER_ID=admin-user
export [email protected]
npx duckpond-mcp-server --transport http
OAuth Endpoints:
/oauth/authorize - Authorization endpoint (login form)/oauth/token - Token endpoint (authorization_code & refresh_token)/oauth/jwks - JSON Web Key Set/oauth/register - Dynamic client registrationFeatures:
export DUCKPOND_BASIC_AUTH_USERNAME=admin
export DUCKPOND_BASIC_AUTH_PASSWORD=secret123
export DUCKPOND_BASIC_AUTH_USER_ID=admin-user
export [email protected]
npx duckpond-mcp-server --transport http
DUCKPOND_JWT_SECRET - Secret for signing JWTs (auto-generated if not set)DUCKPOND_JWT_EXPIRES_IN - Token expiration in seconds (default: 31536000 = 1 year)POST /mcp - MCP protocol endpoint (Server-Sent Events)Accept: application/json, text/event-streamGET / - Server info and capabilitiesGET /health - Health checkGET /oauth/authorize - Authorization endpointPOST /oauth/token - Token endpointGET /oauth/jwks - JSON Web Key SetPOST /oauth/register - Client registrationGET /ui - UI status and available usersGET /ui/:userId - Start UI for specific user (returns URL for direct access)The MCP server includes built-in support for DuckDB UI, allowing you to visually inspect and debug your database through a web browser.
With DUCKPOND_DEFAULT_USER set, the UI auto-starts when the server starts. Just open http://localhost:4213 in your browser.
The UI runs on port 4213 because DuckDB UI requires specific browser features (SharedArrayBuffer) that work best with direct access.
{
"mcpServers": {
"duckpond": {
"command": "npx",
"args": ["-y", "duckpond-mcp-server", "--ui"],
"env": {
"DUCKPOND_DEFAULT_USER": "claude",
"DUCKPOND_DATA_DIR": "${HOME}/.duckpond/data"
}
}
}
}
The UI automatically starts for the default user. Open http://localhost:4213 in your browser.
# Start server
npx duckpond-mcp-server --transport http --port 3000
# Start UI for user "claude"
curl http://localhost:3000/ui/claude
# Access UI directly
# Browser: http://localhost:4213
If no DUCKPOND_DEFAULT_USER is set, a management server starts for manual user selection:
# Start with UI management server
npx duckpond-mcp-server --ui --ui-port 4000
# Start UI for a user
curl http://localhost:4000/ui/claude
# Access UI directly
# Browser: http://localhost:4213
# Using docker-compose (recommended)
docker compose up -d
# Start UI for a user
curl http://localhost:3000/ui/claude
# Access UI directly
# Browser: http://localhost:4213
# Simple docker run
docker run -p 3000:3000 -p 4213:4213 duckpond-mcp-server
# Start UI for a user, then access directly
curl http://localhost:3000/ui/claude
# Browser: http://localhost:4213
Why direct port access? DuckDB UI uses SharedArrayBuffer which requires specific CORS headers. Direct access to port 4213 ensures full compatibility with the UI's WebAssembly requirements.
In HTTP mode, navigate to /ui/:differentUserId to switch between users. Only one user's UI is active at a time - switching automatically stops the previous UI and starts for the new user.
DUCKPOND_DEFAULT_USER - Default user ID; when set, UI auto-starts for this userDUCKPOND_UI_ENABLED - Enable UI (default: false, or use --ui flag)--ui - Enable DuckDB UI (auto-starts for DUCKPOND_DEFAULT_USER)--ui-port <port> - Management server port, only used when no default user (default: 4000)--ui-internal-port <port> - DuckDB UI port (default: 4213)# Clone repository
git clone https://github.com/jordanburke/duckpond-mcp-server.git
cd duckpond-mcp-server
# Install dependencies
pnpm install
# Development mode (watch)
pnpm dev
# Run tests
pnpm test
# Format and lint
pnpm validate
# Test stdio transport
pnpm serve:test
# Test HTTP transport
pnpm serve:test:http
# Test with OAuth
DUCKPOND_OAUTH_ENABLED=true \
DUCKPOND_OAUTH_USERNAME=admin \
DUCKPOND_OAUTH_PASSWORD=secret \
pnpm serve:test:http
# Test with Basic Auth
DUCKPOND_BASIC_AUTH_USERNAME=admin \
DUCKPOND_BASIC_AUTH_PASSWORD=secret \
pnpm serve:test:http
# Pre-checkin validation
pnpm validate # format + lint + test + build
# Individual commands
pnpm format # Format with Prettier
pnpm lint # Fix ESLint issues
pnpm test # Run tests
pnpm test:watch # Run tests in watch mode
pnpm test:coverage # Run tests with coverage
pnpm build # Production build
pnpm ts-types # Check TypeScript types
The MCP server is a thin transport layer over the duckpond library:
┌─────────────┐ ┌──────────────┐
│ stdio Mode │ │ HTTP Mode │
│ (index.ts) │ │(FastMCP/3000)│
└──────┬──────┘ └──────┬───────┘
│ │
└───────┬───────────┘
│
┌───────▼────────┐
│ MCP Tool Layer │ (server-core.ts)
│ - Error mapping│
│ - Result format│
└───────┬────────┘
│
┌───────▼────────┐
│ DuckPond │ npm: duckpond@^0.1.0
│ - Multi-tenant │
│ - LRU Cache │
│ - R2/S3 │
│ - Either<E,T> │
└───────┬────────┘
│
┌───────▼────────┐
│ DuckDB + Cloud │
└────────────────┘
src/index.ts - CLI entry point, transport selectionsrc/server-core.ts - DuckPond wrapper with MCP result typessrc/server-stdio.ts - stdio transport for Claude Desktopsrc/server-fastmcp.ts - HTTP transport with FastMCPsrc/tools/index.ts - MCP tool schemas and implementationsUses functype for functional error handling:
// DuckPond returns Either<Error, T>
const result = await pond.query(userId, sql)
// MCP server converts to MCPResult<T>
result.fold(
(error) => ({ success: false, error: formatError(error) }),
(data) => ({ success: true, data }),
)
Store per-user analytics data with automatic cloud backup:
// User creates their own tables
await execute({
userId: "user123",
sql: "CREATE TABLE orders (id INT, total DECIMAL, date DATE)",
})
// Query their data
const result = await query({
userId: "user123",
sql: "SELECT SUM(total) FROM orders WHERE date > '2024-01-01'",
})
Check DuckDB installation:
npm list duckdb
Verify environment variables:
printenv | grep DUCKPOND
OAuth not working:
DUCKPOND_OAUTH_USERNAME and DUCKPOND_OAUTH_PASSWORD are setBasic Auth failing:
Authorization: Basic <base64> header formatAdjust memory limits:
export DUCKPOND_MEMORY_LIMIT=8GB
export DUCKPOND_MAX_ACTIVE_USERS=5
Monitor cache usage:
const stats = await getUserStats({ userId: "user123" })
console.log(`Memory: ${stats.memoryUsage} bytes`)
R2/S3 connection errors:
aws s3 ls s3://your-bucketParquet file issues:
Contributions welcome! Please see CONTRIBUTING.md for guidelines.
MIT
Run in your terminal:
claude mcp add duckpond-mcp-server -- npx Security
Low riskAutomated heuristic from public metadata — not a security guarantee.