Storage Server
The storage server (servers/storage/) provides S3-compatible file storage through MCP tools and HTTP endpoints. It connects to MinIO (or any S3-compatible service) for object storage and tracks file metadata in SQLite.
Architecture
┌──────────────────────────────────────┐
│ MCP Tools Layer │
│ crow_upload_file crow_list_files │
│ crow_get_file_url crow_delete_file │
│ crow_storage_stats │
├──────────────────────────────────────┤
│ Gateway HTTP Layer │
│ POST /storage/upload (multipart) │
│ GET /storage/file/:key (presigned) │
├──────────────────────────────────────┤
│ s3-client.js │
│ MinIO SDK wrapper, presigned URLs │
├──────────────────────────────────────┤
│ SQLite (storage_files) │ MinIO │
│ Metadata + index │ Blobs │
└──────────────────────────────────────┘Factory Pattern
Like all Crow servers, the storage server uses a factory function:
// servers/storage/server.js
export function createStorageServer(dbPath) {
const server = new McpServer({ name: "crow-storage", version: "1.0.0" });
// ... tool registrations
return server;
}server.js— Factory function with all tool definitionsindex.js— Wires the factory to stdio transports3-client.js— MinIO/S3 client wrapper
The gateway imports createStorageServer() and wires it to HTTP transport alongside the other servers.
s3-client.js
Wraps the MinIO SDK with Crow-specific defaults:
import { Client } from 'minio';
export function createS3Client(config) {
// Returns configured MinIO client
}
export async function uploadObject(client, bucket, key, buffer, metadata) { }
export async function getPresignedUrl(client, bucket, key, expiry) { }
export async function deleteObject(client, bucket, key) { }
export async function listObjects(client, bucket, prefix) { }
export async function getBucketSize(client, bucket) { }Presigned URLs default to 1-hour expiry. The expiry is configurable per request.
Database Table
CREATE TABLE storage_files (
id INTEGER PRIMARY KEY AUTOINCREMENT,
s3_key TEXT NOT NULL UNIQUE, -- S3 object key (e.g., "1234567890-photo.jpg")
original_name TEXT NOT NULL, -- Original filename at upload
mime_type TEXT, -- Validated MIME type
size_bytes INTEGER, -- File size
bucket TEXT DEFAULT 'crow-files', -- S3 bucket name
uploaded_by TEXT, -- Who uploaded (optional)
reference_type TEXT, -- What this file is attached to (e.g., blog_post)
reference_id INTEGER, -- ID of the referenced item
created_at TEXT DEFAULT (datetime('now'))
);The s3_key column is the canonical identifier used across MCP tools and HTTP endpoints.
MCP Tools
crow_upload_file
Uploads a small file via base64 (under 1MB) or generates a presigned upload URL for larger files. Validates the MIME type, checks quota, uploads to MinIO, and inserts a metadata row.
Parameters:
file_name(string, max 500) — Original file namemime_type(string, max 200, optional) — MIME type (e.g.,image/png)data_base64(string, max 1500000, optional) — Base64-encoded file data (for files under 1MB)bucket(string, max 100, optional) — Target bucket (default:crow-files)reference_type(string, max 100, optional) — What this file is attached to (e.g.,blog_post,research_source)reference_id(number, optional) — ID of the referenced item
crow_list_files
Lists files with optional filtering by bucket, MIME type prefix, or reference.
Parameters:
bucket(string, max 100, optional) — Filter by bucketmime_type(string, max 200, optional) — Filter by MIME type prefix (e.g.,image/)reference_type(string, max 100, optional) — Filter by reference typereference_id(number, optional) — Filter by reference IDlimit(number, min 1, max 100, optional, default 50)
crow_get_file_url
Generates a presigned download URL for temporary access to a file.
Parameters:
s3_key(string, max 500) — S3 object keyexpiry(number, min 60, max 86400, optional, default 3600) — URL expiry in secondsbucket(string, max 100, optional) — Bucket name (default:crow-files)
crow_delete_file
Removes a file from both MinIO and the database.
Parameters:
s3_key(string, max 500) — S3 object key to deletebucket(string, max 100, optional) — Bucket name (default:crow-files)
crow_storage_stats
Returns storage usage summary: total files, total size, quota remaining. No parameters.
Gateway HTTP Routes
POST /storage/upload
Multipart file upload. Accepts file field and optional folder field. Returns the file key and metadata. Protected by OAuth when enabled.
GET /storage/file/:key
Redirects to a presigned MinIO URL for the requested file. The key is URL-encoded in the path. Returns 404 if the file doesn't exist in the database.
Quota Enforcement
Before every upload, the server queries total storage usage:
SELECT COALESCE(SUM(size_bytes), 0) as total FROM storage_files;If total + new_file_size > CROW_STORAGE_QUOTA_MB * 1024 * 1024, the upload is rejected with a clear error message showing current usage and quota.
MIME Validation
Uploads are validated against an allowlist of MIME types. The server checks both the file extension and the detected MIME type (using magic bytes when available). Mismatches are rejected.
Allowed categories:
image/*— JPEG, PNG, GIF, WebP, SVGapplication/pdftext/*— Plain text, Markdown, HTML, CSVapplication/json,application/xmlaudio/*— MP3, WAV, OGG
Executables, scripts, and archive formats are rejected by default.