Skip to main content
Glama

Cut-Copy-Paste Clipboard Server

clipboard-manager.ts•7.36 kB
import { randomBytes, createCipheriv, createDecipheriv } from 'crypto'; import { DatabaseManager } from './database.js'; export interface ClipboardData { content: string; sourceFile: string; startLine: number; endLine: number; operationType: 'copy' | 'cut'; cutSourceOriginalContent?: string; // Only for cut operations - stores original file state } export interface ClipboardContent extends ClipboardData { copiedAt: number; } /** * Manages clipboard operations for sessions * Provides storage and retrieval of copied/cut code blocks */ export class ClipboardManager { private dbManager: DatabaseManager; private readonly MAX_CONTENT_SIZE = 10 * 1024 * 1024; // 10MB limit private readonly ENCRYPTION_VERSION = 1; constructor(dbManager: DatabaseManager) { this.dbManager = dbManager; } /** * Store content in the clipboard for a session * Overwrites any existing clipboard content for this session * @param sessionId - The session ID * @param data - Clipboard data to store (content, source metadata, operation type) * @throws Error if content exceeds maximum size limit */ setClipboard(sessionId: string, data: ClipboardData): void { // Validate session ID if (!sessionId || typeof sessionId !== 'string') { throw new Error('Invalid session ID'); } // Validate content size const contentSize = Buffer.byteLength(data.content, 'utf-8'); if (contentSize > this.MAX_CONTENT_SIZE) { throw new Error( `Clipboard content (${(contentSize / 1024 / 1024).toFixed(2)}MB) exceeds maximum size of ${this.MAX_CONTENT_SIZE / 1024 / 1024}MB` ); } const db = this.dbManager.getConnection(); const timestamp = Date.now(); const { ciphertext, iv, authTag } = this.encryptPayload({ content: data.content, sourceFile: data.sourceFile, startLine: data.startLine, endLine: data.endLine, operationType: data.operationType, cutSourceOriginalContent: data.cutSourceOriginalContent, }); // Use INSERT OR REPLACE to handle both insert and update cases db.prepare( `INSERT OR REPLACE INTO clipboard_buffer (session_id, content, source_file, start_line, end_line, copied_at, operation_type, cut_source_original_content, encrypted_payload, encryption_iv, encryption_tag, encryption_version) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)` ).run( sessionId, '[encrypted]', '[encrypted]', 0, 0, timestamp, data.operationType, null, ciphertext, iv, authTag, this.ENCRYPTION_VERSION ); } /** * Retrieve clipboard content for a session * @param sessionId - The session ID * @returns Clipboard content or null if empty */ getClipboard(sessionId: string): ClipboardContent | null { const db = this.dbManager.getConnection(); const row = db .prepare( `SELECT content, source_file, start_line, end_line, copied_at, operation_type, cut_source_original_content, encrypted_payload, encryption_iv, encryption_tag, encryption_version FROM clipboard_buffer WHERE session_id = ?` ) .get(sessionId) as | { content: string; source_file: string; start_line: number; end_line: number; copied_at: number; operation_type: 'copy' | 'cut'; cut_source_original_content: string | null; encrypted_payload: string | null; encryption_iv: string | null; encryption_tag: string | null; encryption_version: number | null; } | undefined; if (!row) { return null; } if (row.encrypted_payload && row.encryption_iv && row.encryption_tag) { const payload = this.decryptPayload({ ciphertext: row.encrypted_payload, iv: row.encryption_iv, authTag: row.encryption_tag, version: row.encryption_version ?? this.ENCRYPTION_VERSION, }); return { content: payload.content, sourceFile: payload.sourceFile, startLine: payload.startLine, endLine: payload.endLine, copiedAt: row.copied_at, operationType: payload.operationType, cutSourceOriginalContent: payload.cutSourceOriginalContent, }; } // Fallback for legacy plaintext rows return { content: row.content, sourceFile: row.source_file, startLine: row.start_line, endLine: row.end_line, copiedAt: row.copied_at, operationType: row.operation_type, cutSourceOriginalContent: row.cut_source_original_content || undefined, }; } /** * Clear clipboard content for a session * @param sessionId - The session ID */ clearClipboard(sessionId: string): void { const db = this.dbManager.getConnection(); db.prepare('DELETE FROM clipboard_buffer WHERE session_id = ?').run(sessionId); } /** * Check if a session has clipboard content * @param sessionId - The session ID * @returns true if clipboard has content, false otherwise */ hasContent(sessionId: string): boolean { const db = this.dbManager.getConnection(); const result = db .prepare('SELECT COUNT(*) as count FROM clipboard_buffer WHERE session_id = ?') .get(sessionId) as { count: number }; return result.count > 0; } /** * Get clipboard content size in bytes * @param sessionId - The session ID * @returns Content size in bytes, or 0 if empty */ getContentSize(sessionId: string): number { const clipboard = this.getClipboard(sessionId); if (!clipboard) { return 0; } return Buffer.byteLength(clipboard.content, 'utf-8'); } private encryptPayload(payload: { content: string; sourceFile: string; startLine: number; endLine: number; operationType: 'copy' | 'cut'; cutSourceOriginalContent?: string; }): { ciphertext: string; iv: string; authTag: string } { const key = this.dbManager.getEncryptionKey(); const iv = randomBytes(12); const cipher = createCipheriv('aes-256-gcm', key, iv); const plaintext = JSON.stringify(payload); const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]); const authTag = cipher.getAuthTag(); return { ciphertext: encrypted.toString('base64'), iv: iv.toString('base64'), authTag: authTag.toString('base64'), }; } private decryptPayload(params: { ciphertext: string; iv: string; authTag: string; version: number; }): { content: string; sourceFile: string; startLine: number; endLine: number; operationType: 'copy' | 'cut'; cutSourceOriginalContent?: string; } { if (params.version !== this.ENCRYPTION_VERSION) { throw new Error(`Unsupported encryption version: ${params.version}`); } const key = this.dbManager.getEncryptionKey(); const iv = Buffer.from(params.iv, 'base64'); const authTag = Buffer.from(params.authTag, 'base64'); const encrypted = Buffer.from(params.ciphertext, 'base64'); const decipher = createDecipheriv('aes-256-gcm', key, iv); decipher.setAuthTag(authTag); const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]); return JSON.parse(decrypted.toString('utf8')); } }

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/Pr0j3c7t0dd-Ltd/cut-copy-paste-mcp'

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