Record MCP Server
A Model Context Protocol (MCP) server for storing and managing dynamic review records with user-defined schemas. Perfect for organizing reviews of coffee, whisky, wine, or any other category you can think of!
Features
- Dynamic Schemas: Create review types with custom fields on-the-fly
- Flexible Storage: Local filesystem (dev) or Cloudflare R2 (production)
- Type-Safe: Built with TypeScript and runtime validation
- Extensible: Add new fields to existing review types
- Easy Migration: Switch from local to cloud storage with one environment variable
Quick Start
Installation
# Install dependencies
npm install
# Build the project
npm run build
Configuration
Copy the example environment file:
cp .env.example .env
For local development (default):
STORAGE_PROVIDER=local
LOCAL_DATA_PATH=./data
For production with Cloudflare R2:
STORAGE_PROVIDER=r2
R2_ACCOUNT_ID=your_account_id
R2_ACCESS_KEY_ID=your_access_key
R2_SECRET_ACCESS_KEY=your_secret_key
R2_BUCKET_NAME=review-records
Running the Server
Development mode (with auto-reload):
npm run dev
Production mode:
npm run build
npm start
Running Tests
npm test
MCP Tools
The server provides the following MCP tools:
1. list_review_types
List all review types with their schemas and record counts.
Parameters: None
Example Response:
{
"types": [
{
"name": "coffee",
"schema": [
{ "name": "flavor", "type": "string" },
{ "name": "aroma", "type": "string" },
{ "name": "acidity", "type": "string" }
],
"recordCount": 5,
"createdAt": "2025-11-16T10:00:00Z",
"updatedAt": "2025-11-16T12:00:00Z"
}
]
}
2. get_review_type
Get detailed information about a specific review type including all records.
Parameters:
typeName(string): Name of the review type
Example:
{
"typeName": "coffee"
}
3. add_review_type
Create a new review type with a custom schema.
Parameters:
name(string): Name of the review type (e.g., "coffee", "whisky")fields(array): Array of field definitions
Supported Field Types:
string: Text valuesnumber: Numeric valuesboolean: True/false valuesdate: ISO 8601 date strings
Example:
{
"name": "coffee",
"fields": [
{ "name": "flavor", "type": "string" },
{ "name": "aroma", "type": "string" },
{ "name": "acidity", "type": "string" },
{ "name": "rating", "type": "number" }
]
}
4. add_field_to_type
Add a new field to an existing review type's schema.
Parameters:
typeName(string): Name of the review typefieldName(string): Name of the new fieldfieldType(string): Type of the field (string, number, boolean, date)
Example:
{
"typeName": "coffee",
"fieldName": "body",
"fieldType": "string"
}
5. add_review_record
Add a new review record to a type.
Parameters:
typeName(string): Name of the review typedata(object): Review data matching the type's schema
Example:
{
"typeName": "coffee",
"data": {
"flavor": "nutty",
"aroma": "strong",
"acidity": "medium",
"rating": 8.5
}
}
Usage Examples
Complete Workflow
// 1. Create a new review type
await mcp.callTool("add_review_type", {
name: "whisky",
fields: [
{ name: "taste", type: "string" },
{ name: "age", type: "number" },
{ name: "peated", type: "boolean" },
{ name: "tasted_on", type: "date" }
]
});
// 2. Add a review
await mcp.callTool("add_review_record", {
typeName: "whisky",
data: {
taste: "smoky and complex",
age: 12,
peated: true,
tasted_on: "2025-11-16T10:00:00Z"
}
});
// 3. Add more fields later
await mcp.callTool("add_field_to_type", {
typeName: "whisky",
fieldName: "region",
fieldType: "string"
});
// 4. List all types and their data
const result = await mcp.callTool("list_review_types", {});
Architecture
Project Structure
record-mcp/
├── src/
│ ├── index.ts # MCP server entry point
│ ├── types.ts # TypeScript type definitions
│ ├── storage/
│ │ ├── interface.ts # Storage provider interface
│ │ ├── local.ts # Local file system storage
│ │ ├── r2.ts # Cloudflare R2 storage
│ │ └── factory.ts # Storage provider factory
│ ├── tools/
│ │ ├── list-types.ts # List and get review types
│ │ ├── add-type.ts # Create new review type
│ │ ├── add-field.ts # Add field to type
│ │ └── add-record.ts # Add review record
│ └── utils/
│ └── validation.ts # Schema and data validation
├── data/ # Local storage (when using local provider)
│ ├── types/
│ │ ├── coffee.json
│ │ └── whisky.json
│ └── index.json
└── tests/
├── storage.test.ts # Storage provider tests
└── tools.test.ts # MCP tools tests
Storage Abstraction
The server uses a storage abstraction layer that allows easy switching between local files and Cloudflare R2:
- Local Storage (Development): Uses Node.js
fs/promisesto store JSON files - R2 Storage (Production): Uses AWS S3-compatible API to store in Cloudflare R2
Both providers implement the same StorageProvider interface, making migration seamless.
Data Format
Each review type is stored as a separate JSON file:
{
"name": "coffee",
"schema": [
{ "name": "flavor", "type": "string" },
{ "name": "aroma", "type": "string" }
],
"records": [
{
"id": "1234567890-abc123",
"data": {
"flavor": "nutty",
"aroma": "strong"
},
"createdAt": "2025-11-16T10:00:00Z"
}
],
"createdAt": "2025-11-15T09:00:00Z",
"updatedAt": "2025-11-16T10:00:00Z"
}
Migration from Local to R2
When you're ready to move to production:
- Set up your Cloudflare R2 bucket
- Update your
.envfile with R2 credentials - Change
STORAGE_PROVIDER=r2 - Restart the server
Optional: Use a migration script to copy existing data:
// Copy all local files to R2
const localStorage = new LocalStorageProvider('./data');
const r2Storage = new R2StorageProvider(r2Config);
const types = await localStorage.listTypes();
for (const typeName of types) {
const data = await localStorage.readType(typeName);
await r2Storage.writeType(typeName, data);
}
Validation
The server provides comprehensive validation:
- Type Names: Alphanumeric, hyphens, and underscores only
- Field Types: Must be one of: string, number, boolean, date
- Required Fields: All schema fields must be present in records
- Extra Fields: Records cannot have fields not in the schema
- Type Checking: Field values must match their declared types
Error Handling
All tools return structured error messages:
{
"error": "Review type \"coffee\" already exists"
}
Common errors:
- Duplicate type names
- Duplicate field names
- Missing required fields in records
- Type mismatches
- Invalid type/field names
Development
Building
npm run build
Watching for Changes
npm run watch
Testing
Run all tests:
npm test
Run specific test file:
tsx tests/storage.test.ts
tsx tests/tools.test.ts
License
MIT
Contributing
Contributions welcome! Please ensure tests pass before submitting PRs.
Support
For issues or questions, please open a GitHub issue.