Skip to main content
Glama
noelserdna

CV Recruitment Assistant

by noelserdna
crypto.ts6.99 kB
import { generateKeyPairSync, createHash, sign, verify } from 'node:crypto'; // Tipos para configuración de claves interface CryptoConfig { privateKey: string; publicKey: string; serverId: string; } // Tipo para respuesta firmada del MCP export interface SignedMCPResponse { data: any; // Datos originales de la herramienta hash: string; // SHA-256 hash de los datos JSON signature: string; // Firma digital en base64 timestamp: string; // ISO 8601 timestamp publicKey: string; // Clave pública para verificación serverId: string; // Identificador único del servidor version: string; // Versión del formato de firma } // Tipo para resultado de verificación export interface VerificationResult { isValid: boolean; error?: string; details: { hashMatch: boolean; signatureValid: boolean; timestamp: string; serverId: string; }; } /** * Obtiene la configuración de claves según el entorno */ function getCryptoConfig(env?: any): CryptoConfig { // Producción: usar secrets de Cloudflare Workers if (env?.MCP_PRIVATE_KEY) { return { privateKey: env.MCP_PRIVATE_KEY.replace(/\\n/g, '\n'), publicKey: env.MCP_PUBLIC_KEY.replace(/\\n/g, '\n'), serverId: env.MCP_SERVER_ID || 'cv-mcp-server' }; } // Local: usar variables de entorno del proceso if (process.env.MCP_PRIVATE_KEY) { return { privateKey: process.env.MCP_PRIVATE_KEY.replace(/\\n/g, '\n'), publicKey: process.env.MCP_PUBLIC_KEY!.replace(/\\n/g, '\n'), serverId: process.env.MCP_SERVER_ID || 'cv-mcp-server-local' }; } // Fallback: generar claves temporales para desarrollo rápido console.warn('⚠️ Generando claves temporales. Ejecuta "npm run setup-keys" para desarrollo persistente.'); return generateTemporaryKeys(); } /** * Genera claves temporales para desarrollo sin configuración */ function generateTemporaryKeys(): CryptoConfig { const { publicKey, privateKey } = generateKeyPairSync('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' } }); return { privateKey, publicKey, serverId: 'cv-mcp-server-temp' }; } /** * Crea hash SHA-256 de datos JSON */ function createDataHash(data: any): string { const jsonString = JSON.stringify(data, null, 0); // Sin espacios para hash consistente return createHash('sha256').update(jsonString).digest('hex'); } /** * Firma un hash con la clave privada */ function signHash(hash: string, privateKey: string): string { const signature = sign('sha256', Buffer.from(hash, 'hex'), { key: privateKey, format: 'pem' }); return signature.toString('base64'); } /** * Verifica una firma con la clave pública */ function verifySignature(hash: string, signature: string, publicKey: string): boolean { try { return verify( 'sha256', Buffer.from(hash, 'hex'), { key: publicKey, format: 'pem' }, Buffer.from(signature, 'base64') ); } catch (error) { console.error('Error verificando firma:', error); return false; } } /** * Clase principal para operaciones criptográficas */ export class MCPCrypto { private config: CryptoConfig; constructor(env?: any) { this.config = getCryptoConfig(env); } /** * Firma una respuesta MCP con todos los metadatos necesarios */ signResponse(data: any): SignedMCPResponse { // Crear hash de los datos const hash = createDataHash(data); // Firmar el hash const signature = signHash(hash, this.config.privateKey); // Crear respuesta firmada completa const signedResponse: SignedMCPResponse = { data, hash, signature, timestamp: new Date().toISOString(), publicKey: this.config.publicKey, serverId: this.config.serverId, version: '1.0' }; return signedResponse; } /** * Verifica la integridad y autenticidad de una respuesta firmada */ verifySignedResponse(signedResponse: SignedMCPResponse): VerificationResult { try { // Verificar que tenga todos los campos necesarios if (!signedResponse.data || !signedResponse.hash || !signedResponse.signature) { return { isValid: false, error: 'Respuesta firmada incompleta', details: { hashMatch: false, signatureValid: false, timestamp: signedResponse.timestamp || 'missing', serverId: signedResponse.serverId || 'missing' } }; } // Verificar hash de los datos const computedHash = createDataHash(signedResponse.data); const hashMatch = computedHash === signedResponse.hash; // Verificar firma digital const signatureValid = verifySignature( signedResponse.hash, signedResponse.signature, signedResponse.publicKey ); const isValid = hashMatch && signatureValid; return { isValid, error: isValid ? undefined : 'Hash o firma inválidos', details: { hashMatch, signatureValid, timestamp: signedResponse.timestamp, serverId: signedResponse.serverId } }; } catch (error) { return { isValid: false, error: `Error durante verificación: ${error}`, details: { hashMatch: false, signatureValid: false, timestamp: signedResponse.timestamp || 'unknown', serverId: signedResponse.serverId || 'unknown' } }; } } /** * Obtiene la clave pública para compartir con clientes */ getPublicKey(): string { return this.config.publicKey; } /** * Obtiene el ID del servidor */ getServerId(): string { return this.config.serverId; } } /** * Helper para crear respuesta MCP firmada de forma simple */ export function createSignedMCPResponse(data: any, env?: any): SignedMCPResponse { const crypto = new MCPCrypto(env); return crypto.signResponse(data); }

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/noelserdna/cv-dinamic-mcp'

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