Skip to main content
Glama
SCHEMA_VALIDATION.md8.03 kB
# Schema Validation Architecture This document describes the Zod-based validation system used in the Ghost MCP Server. ## Overview The Ghost MCP Server uses [Zod](https://zod.dev/) as its primary validation library. All MCP tool inputs are validated through Zod schemas before processing, providing: - **Type Safety**: Runtime type checking for all inputs - **Security**: Built-in HTML sanitization for XSS prevention - **Clear Errors**: Detailed validation error messages - **Consistency**: Unified validation across all tools ## File Organization ``` src/schemas/ ├── index.js # Centralized exports ├── common.js # Shared validators (IDs, emails, URLs, HTML) ├── postSchemas.js # Post creation, update, query schemas ├── pageSchemas.js # Page schemas ├── tagSchemas.js # Tag schemas ├── memberSchemas.js # Member schemas ├── newsletterSchemas.js # Newsletter schemas └── tierSchemas.js # Tier/membership schemas ``` ## Common Validators (`common.js`) ### Basic Type Validators ```javascript import { emailSchema, urlSchema, ghostIdSchema, slugSchema } from './schemas/index.js'; // Email validation emailSchema.parse('user@example.com'); // ✓ emailSchema.parse('invalid-email'); // ✗ "Invalid email format" // Ghost ID validation (24-character hex) ghostIdSchema.parse('507f1f77bcf86cd799439011'); // ✓ ghostIdSchema.parse('invalid'); // ✗ "Invalid Ghost ID format" // URL validation urlSchema.parse('https://example.com'); // ✓ urlSchema.parse('not-a-url'); // ✗ "Invalid URL format" // Slug validation slugSchema.parse('my-post-slug'); // ✓ slugSchema.parse('Invalid Slug'); // ✗ Must be lowercase with hyphens ``` ### Security Validators #### NQL Filter Validation Prevents injection attacks in Ghost Query Language filters: ```javascript import { nqlFilterSchema } from './schemas/index.js'; // Safe patterns nqlFilterSchema.parse('status:published'); // ✓ nqlFilterSchema.parse('tag:javascript+status:draft'); // ✓ // Dangerous patterns are rejected nqlFilterSchema.parse('tag:<script>'); // ✗ Disallowed characters ``` ### Pagination Validators ```javascript import { limitSchema, pageSchema, paginationSchema } from './schemas/index.js'; // Limit validation (1-100, default: 15) limitSchema.parse(50); // ✓ limitSchema.parse(200); // ✗ "Limit cannot exceed 100" // Page validation (1+, default: 1) pageSchema.parse(1); // ✓ pageSchema.parse(0); // ✗ "Page must be at least 1" ``` ## HTML Sanitization The most critical security feature is automatic HTML sanitization integrated into content schemas. ### How It Works ```javascript import { htmlContentSchema } from './schemas/index.js'; // Input with potentially dangerous content const userInput = '<p>Hello</p><script>alert("xss")</script>'; // Sanitization happens automatically during validation const sanitized = htmlContentSchema.parse(userInput); // Result: '<p>Hello</p>' (script tag removed) ``` ### Allowed HTML Tags The following tags are permitted: - Headers: `h1`, `h2`, `h3`, `h4`, `h5`, `h6` - Content: `p`, `blockquote`, `pre`, `code`, `hr`, `br` - Lists: `ul`, `ol`, `li` - Formatting: `b`, `i`, `strong`, `em`, `strike`, `span` - Structure: `div`, `figure`, `figcaption` - Links & Images: `a`, `img` ### Allowed Attributes ```javascript { a: ['href', 'name', 'target', 'rel', 'title'], img: ['src', 'alt', 'title', 'width', 'height'], '*': ['class', 'id'] // All elements can have class/id } ``` ### Allowed URL Schemes Only safe protocols are allowed in `href` and `src` attributes: - `http:` - `https:` - `mailto:` ## Per-Resource Schemas ### Post Schemas (`postSchemas.js`) ```javascript import { createPostSchema, updatePostSchema, postQuerySchema } from './schemas/index.js'; // Create post validation const postData = createPostSchema.parse({ title: 'My Post', html: '<p>Content here</p>', status: 'draft', tags: ['javascript', 'tutorial'], }); // Update post validation (all fields optional except id) const updateData = updatePostSchema.parse({ title: 'Updated Title', }); // Query validation const queryData = postQuerySchema.parse({ status: 'published', limit: 10, page: 1, }); ``` ### Tag Schemas (`tagSchemas.js`) ```javascript import { createTagSchema, updateTagSchema, tagQuerySchema } from './schemas/index.js'; // Create tag const tagData = createTagSchema.parse({ name: 'JavaScript', description: 'Posts about JS', slug: 'javascript', // Optional, auto-generated if omitted }); ``` ### Member Schemas (`memberSchemas.js`) ```javascript import { createMemberSchema, updateMemberSchema, memberQuerySchema } from './schemas/index.js'; // Create member const memberData = createMemberSchema.parse({ email: 'user@example.com', name: 'John Doe', labels: ['Premium'], newsletters: ['newsletter-id-here'], }); ``` ## Using Schemas in MCP Tools The `validateToolInput` utility standardizes schema validation in tool handlers: ```javascript import { validateToolInput } from './utils/validation.js'; import { createPostSchema } from './schemas/index.js'; server.tool('ghost_create_post', 'Creates a post', createPostSchema, async (rawInput) => { // Validate input const validation = validateToolInput(createPostSchema, rawInput, 'ghost_create_post'); if (!validation.success) { return validation.errorResponse; // Returns formatted error } // Use validated data const input = validation.data; // input.html is already sanitized // input.title is validated // etc. }); ``` ## Error Handling ### Validation Error Response When validation fails, a structured error is returned: ```javascript { content: [{ type: 'text', text: JSON.stringify({ error: 'ValidationError', message: 'Invalid input for ghost_create_post', details: [ { field: 'title', message: 'Title cannot be empty' }, { field: 'html', message: 'HTML content cannot be empty' } ] }, null, 2) }], isError: true } ``` ### Converting Zod Errors The `ValidationError.fromZod()` method converts Zod errors to a consistent format: ```javascript import { ValidationError } from './errors/index.js'; try { createPostSchema.parse(invalidData); } catch (error) { if (error.name === 'ZodError') { const validationError = ValidationError.fromZod(error, 'Post creation'); // Returns structured ValidationError with all field errors } } ``` ## Adding New Schemas When adding new resource types or tools: 1. **Create a schema file** in `src/schemas/`: ```javascript // src/schemas/newResourceSchemas.js import { z } from 'zod'; import { ghostIdSchema, titleSchema } from './common.js'; export const createNewResourceSchema = z.object({ name: titleSchema, // ... other fields }); export const updateNewResourceSchema = createNewResourceSchema.partial(); ``` 2. **Export from index.js**: ```javascript // src/schemas/index.js export * from './newResourceSchemas.js'; ``` 3. **Use in MCP tool**: ```javascript import { createNewResourceSchema } from './schemas/index.js'; import { validateToolInput } from './utils/validation.js'; server.tool('ghost_create_resource', 'Description', createNewResourceSchema, async (rawInput) => { const validation = validateToolInput(createNewResourceSchema, rawInput, 'ghost_create_resource'); // ... }); ``` ## Best Practices 1. **Always use `validateToolInput`** for consistent error handling 2. **Leverage HTML sanitization** - use `htmlContentSchema` for user-provided HTML 3. **Use `ghostIdSchema`** for all Ghost resource IDs 4. **Apply `nqlFilterSchema`** for filter parameters to prevent injection 5. **Set sensible defaults** using Zod's `.default()` method 6. **Use `.optional()`** for non-required fields 7. **Provide clear error messages** in schema definitions ## Related Documentation - [Tools Reference](./TOOLS_REFERENCE.md) - Complete tool documentation - [Error Handling](./ERROR_HANDLING.md) - Error types and patterns

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/jgardner04/Ghost-MCP-Server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server