Skip to main content
Glama

Vibe-Coder MCP Server

document-storage.ts8.59 kB
/** * @file document-storage.ts * @version 1.0.0 * @status STABLE - DO NOT MODIFY WITHOUT TESTS * @lastModified 2023-03-23 * * Document storage module for Vibe-Coder MCP Server. * Provides both in-memory and file-based storage for documents * generated during the feature development process. * * IMPORTANT: * - Path validation is critical for security * - All file operations must be properly error-handled * - Document metadata must be maintained consistently * * Functionality: * - Store documents in memory and file system * - Retrieve documents from storage * - Path resolution and validation * - Error handling for file operations */ import * as fs from 'fs/promises'; import * as path from 'path'; import { Feature } from './types.js'; /** * Document types that can be stored */ export enum DocumentType { PRD = 'prd', IMPLEMENTATION_PLAN = 'implementation-plan' } /** * Document storage options */ export interface DocumentStorageOptions { /** Root directory for document storage */ rootDir: string; /** Whether to automatically save documents to files */ autoSave: boolean; /** Whether to create directories if they don't exist */ createDirs: boolean; } /** * Document metadata */ export interface DocumentMetadata { /** Document type */ type: DocumentType; /** Feature ID this document belongs to */ featureId: string; /** Timestamp when the document was generated */ generatedAt: Date; /** Path to the file, if saved */ filePath?: string; /** Whether the document has been saved to a file */ isSaved: boolean; } /** * Document with its content and metadata */ export interface Document { /** Document content */ content: string; /** Document metadata */ metadata: DocumentMetadata; } /** * Default document storage options */ const DEFAULT_OPTIONS: DocumentStorageOptions = { rootDir: path.join(process.cwd(), 'documents'), autoSave: true, createDirs: true }; /** * Document storage class */ export class DocumentStorage { private options: DocumentStorageOptions; private documents: Map<string, Document> = new Map(); /** * Create a new document storage instance * @param options Storage options */ constructor(options: Partial<DocumentStorageOptions> = {}) { this.options = { ...DEFAULT_OPTIONS, ...options }; } /** * Generate a document key * @param featureId Feature ID * @param type Document type * @returns Document key */ private getDocumentKey(featureId: string, type: DocumentType): string { return `${featureId}:${type}`; } /** * Generate a file path for a document * @param featureId Feature ID * @param type Document type * @returns File path */ private getDocumentPath(featureId: string, type: DocumentType): string { const featureDir = path.join(this.options.rootDir, featureId); const filename = `${type}.md`; return path.join(featureDir, filename); } /** * Validate a file path to prevent directory traversal attacks * @param filePath File path to validate * @returns Normalized file path if valid, throws an error otherwise */ private validatePath(filePath: string): string { // Normalize the path to resolve .. and . segments const normalizedPath = path.normalize(filePath); // Convert both paths to absolute paths for comparison const absolutePath = path.isAbsolute(normalizedPath) ? normalizedPath : path.join(process.cwd(), normalizedPath); const rootDir = path.isAbsolute(this.options.rootDir) ? this.options.rootDir : path.join(process.cwd(), this.options.rootDir); // Check if the path is outside the root directory if (!absolutePath.startsWith(rootDir)) { throw new Error(`Invalid file path: Path must be within ${rootDir}`); } return normalizedPath; } /** * Validate a custom file path provided by a client * @param filePath File path to validate * @returns Normalized file path if valid, throws an error otherwise */ public validateCustomPath(filePath: string): string { // First validate that it's within the root directory const normalizedPath = this.validatePath(filePath); // Additional validation for custom paths if (path.extname(normalizedPath) !== '.md') { throw new Error('Invalid file path: File must have .md extension'); } return normalizedPath; } /** * Ensure directory exists, creating it if necessary and allowed * @param dirPath Directory path */ private async ensureDir(dirPath: string): Promise<void> { try { await fs.access(dirPath); } catch (error) { if (this.options.createDirs) { await fs.mkdir(dirPath, { recursive: true }); } else { throw new Error(`Directory does not exist: ${dirPath}`); } } } /** * Store a document * @param featureId Feature ID * @param type Document type * @param content Document content * @param autoSave Whether to automatically save the document to a file * @returns Stored document */ public async storeDocument( featureId: string, type: DocumentType, content: string, autoSave: boolean = this.options.autoSave ): Promise<Document> { const key = this.getDocumentKey(featureId, type); const filePath = this.getDocumentPath(featureId, type); const metadata: DocumentMetadata = { type, featureId, generatedAt: new Date(), filePath, isSaved: false }; const document: Document = { content, metadata }; this.documents.set(key, document); if (autoSave) { try { await this.saveDocumentToFile(featureId, type); } catch (error) { console.error(`Failed to auto-save document: ${error}`); // Note that we still store the document in memory even if file save fails } } return document; } /** * Get a document * @param featureId Feature ID * @param type Document type * @returns Document or undefined if not found */ public getDocument(featureId: string, type: DocumentType): Document | undefined { const key = this.getDocumentKey(featureId, type); return this.documents.get(key); } /** * Check if a document exists * @param featureId Feature ID * @param type Document type * @returns True if the document exists */ public hasDocument(featureId: string, type: DocumentType): boolean { const key = this.getDocumentKey(featureId, type); return this.documents.has(key); } /** * Save a document to a file using its default path * @param featureId Feature ID * @param type Document type * @returns Path to the saved file */ public async saveDocumentToFile(featureId: string, type: DocumentType): Promise<string> { const document = this.getDocument(featureId, type); if (!document) { throw new Error(`Document not found: ${featureId}:${type}`); } const filePath = document.metadata.filePath!; const dirPath = path.dirname(filePath); await this.ensureDir(dirPath); await fs.writeFile(filePath, document.content, 'utf-8'); // Update metadata document.metadata.isSaved = true; return filePath; } /** * Save a document to a custom file path * @param featureId Feature ID * @param type Document type * @param customPath Custom file path * @returns Path to the saved file */ public async saveDocumentToCustomPath( featureId: string, type: DocumentType, customPath: string ): Promise<string> { const document = this.getDocument(featureId, type); if (!document) { throw new Error(`Document not found: ${featureId}:${type}`); } const validatedPath = this.validateCustomPath(customPath); const dirPath = path.dirname(validatedPath); await this.ensureDir(dirPath); await fs.writeFile(validatedPath, document.content, 'utf-8'); // Don't update metadata.filePath as we want to keep the default path // Just mark it as saved document.metadata.isSaved = true; return validatedPath; } /** * Get the default file path for a document * @param featureId Feature ID * @param type Document type * @returns Default file path */ public getDefaultFilePath(featureId: string, type: DocumentType): string { return this.getDocumentPath(featureId, type); } } // Create a singleton instance export const documentStorage = new DocumentStorage();

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/crazyrabbitLTC/mcp-vibecoder'

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