Skip to main content
Glama
mcp-relation-handler.ts6.47 kB
/** * MCP Relation Handler * Single responsibility: handle relation-related MCP requests */ import { DIContainer } from '../../container/di-container'; import { getErrorMessage } from '../../infrastructure/utilities'; import { MCPValidationError, MCPErrorCodes } from '../../infrastructure/errors'; export class McpRelationHandler { private container: DIContainer; private initializationPromise: Promise<void> | null = null; constructor() { this.container = DIContainer.getInstance(); } /** * ZERO-FALLBACK: Thread-safe database initialization * Uses Promise-based singleton pattern to prevent race conditions */ private async ensureDatabaseInitialized(): Promise<void> { if (!this.initializationPromise) { this.initializationPromise = this.container.initializeDatabase() .catch((error) => { // Reset promise on failure to allow retry this.initializationPromise = null; throw error; }); } return this.initializationPromise; } async handleRelationManage(request: { operation: 'create' | 'delete'; relations: Array<{ fromId: string; toId: string; relationType: string; strength?: number; source?: 'agent' | 'user' | 'system'; }>; }): Promise<any> { // Input validation - fail fast this.validateRelationRequest(request); await this.ensureDatabaseInitialized(); const currentDb = this.container.getCurrentDatabase(); const relationUseCase = this.container.getManageRelationsUseCase(); const results = []; for (const relation of request.relations) { try { if (request.operation === 'create') { await relationUseCase.createRelation(relation); } else { await relationUseCase.deleteRelation(relation); } results.push({ fromId: relation.fromId, toId: relation.toId, relationType: relation.relationType, status: request.operation === 'create' ? "created" : "deleted" }); } catch (error) { results.push({ fromId: relation.fromId, toId: relation.toId, relationType: relation.relationType, status: "failed", error: getErrorMessage(error) }); } } return { success: true, results, summary: { requested: request.relations.length, succeeded: results.filter(r => r.status === "created" || r.status === "deleted").length, failed: results.filter(r => r.status === "failed").length }, _meta: { database: currentDb.database, operation: request.operation, timestamp: new Date().toISOString() } }; } /** * Validate relation management request * ZERO-FALLBACK: Invalid requests fail immediately */ private validateRelationRequest(request: { operation: 'create' | 'delete'; relations: Array<{ fromId: string; toId: string; relationType: string; strength?: number; source?: 'agent' | 'user' | 'system'; }>; }): void { if (!request.operation) { throw new MCPValidationError( 'Operation is required', MCPErrorCodes.VALIDATION_FAILED ); } if (!['create', 'delete'].includes(request.operation)) { throw new MCPValidationError( `Invalid operation: ${request.operation}. Valid operations: create, delete`, MCPErrorCodes.VALIDATION_FAILED ); } if (!request.relations || !Array.isArray(request.relations) || request.relations.length === 0) { throw new MCPValidationError( 'Relations array is required and cannot be empty', MCPErrorCodes.EMPTY_ARRAY ); } for (let i = 0; i < request.relations.length; i++) { const relation = request.relations[i]; if (!relation || typeof relation !== 'object') { throw new MCPValidationError( `Relation at index ${i} must be an object`, MCPErrorCodes.VALIDATION_FAILED ); } // Validate required fields if (!relation.fromId || typeof relation.fromId !== 'string' || relation.fromId.trim().length === 0) { throw new MCPValidationError( `Relation at index ${i} must have a non-empty fromId`, MCPErrorCodes.INVALID_ID_FORMAT ); } if (relation.fromId.length !== 18) { throw new MCPValidationError( `Relation at index ${i} has invalid fromId format (expected 18 characters)`, MCPErrorCodes.INVALID_MEMORY_ID_LENGTH ); } if (!relation.toId || typeof relation.toId !== 'string' || relation.toId.trim().length === 0) { throw new MCPValidationError( `Relation at index ${i} must have a non-empty toId`, MCPErrorCodes.INVALID_ID_FORMAT ); } if (relation.toId.length !== 18) { throw new MCPValidationError( `Relation at index ${i} has invalid toId format (expected 18 characters)`, MCPErrorCodes.INVALID_MEMORY_ID_LENGTH ); } if (!relation.relationType || typeof relation.relationType !== 'string' || relation.relationType.trim().length === 0) { throw new MCPValidationError( `Relation at index ${i} must have a non-empty relationType`, MCPErrorCodes.INVALID_TYPE ); } // Validate optional fields if (relation.strength !== undefined) { if (typeof relation.strength !== 'number' || relation.strength < 0 || relation.strength > 1) { throw new MCPValidationError( `Relation at index ${i} has invalid strength (must be a number between 0 and 1)`, MCPErrorCodes.INVALID_STRENGTH ); } } if (relation.source !== undefined) { if (!['agent', 'user', 'system'].includes(relation.source)) { throw new MCPValidationError( `Relation at index ${i} has invalid source (must be 'agent', 'user', or 'system')`, MCPErrorCodes.VALIDATION_FAILED ); } } // Prevent self-referencing relations if (relation.fromId === relation.toId) { throw new MCPValidationError( `Relation at index ${i} cannot be self-referencing (fromId and toId are the same)`, MCPErrorCodes.INVALID_SELF_REFERENCE ); } } } }

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/sylweriusz/mcp-neo4j-memory-server'

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