Skip to main content
Glama
action-executor.service.ts11.3 kB
// Internal services and utilities import { BaseMemoryAgent } from '../base/base-memory-agent'; // Type imports import type { ToolHandlerContext } from '../../../mcp/types/sdk-custom'; /** * Service responsible for executing individual optimization actions * Handles delete, merge, update, and move operations on entities */ export class ActionExecutorService extends BaseMemoryAgent { /** * Execute individual optimization action */ async executeAction( mcpContext: ToolHandlerContext, clientProjectRoot: string, repository: string, branch: string, action: any, ): Promise<void> { const actionLogger = this.createOperationLogger('executeAction', { actionType: action.type, entityId: action.entityId, targetEntityId: action.targetEntityId, }); actionLogger.info(`Executing ${action.type} action on ${action.entityId}`, { reason: action.reason, priority: action.priority, }); try { switch (action.type) { case 'delete': await this.executeDeleteAction( mcpContext, clientProjectRoot, repository, branch, action, actionLogger, ); break; case 'merge': await this.executeMergeAction( mcpContext, clientProjectRoot, repository, branch, action, actionLogger, ); break; case 'update': await this.executeUpdateAction( mcpContext, clientProjectRoot, repository, branch, action, actionLogger, ); break; case 'move': await this.executeMoveAction( mcpContext, clientProjectRoot, repository, branch, action, actionLogger, ); break; case 'delete-component': case 'delete-decision': case 'delete-rule': case 'delete-file': case 'delete-context': case 'delete-tag': await this.executeDeleteAction( mcpContext, clientProjectRoot, repository, branch, action, actionLogger, ); break; default: throw new Error(`Unsupported action type: ${action.type}`); } actionLogger.info(`Successfully executed ${action.type} action on ${action.entityId}`); } catch (error) { actionLogger.error(`Failed to execute ${action.type} action on ${action.entityId}:`, error); throw error; } } /** * Execute delete action using MemoryService */ private async executeDeleteAction( mcpContext: ToolHandlerContext, clientProjectRoot: string, repository: string, branch: string, action: any, logger: any, ): Promise<void> { // Extract entity information const entityId = action.entityId; const entityType = this.determineEntityTypeFromAction(action.type, entityId, action); logger.info(`Deleting ${entityType} entity: ${entityId}`, { entityId, entityType, actionType: action.type, }); // Execute deletion based on entity type const entityService = await this.memoryService.entity; if (!entityService) { throw new Error('EntityService not initialized in MemoryService'); } switch (entityType) { case 'component': await entityService.deleteComponent( mcpContext, clientProjectRoot, repository, branch, entityId, ); break; case 'decision': await entityService.deleteDecision( mcpContext, clientProjectRoot, repository, branch, entityId, ); break; case 'rule': await entityService.deleteRule(mcpContext, clientProjectRoot, repository, branch, entityId); break; case 'file': await entityService.deleteFile(mcpContext, clientProjectRoot, repository, branch, entityId); break; case 'context': await entityService.deleteContext( mcpContext, clientProjectRoot, repository, branch, entityId, ); break; case 'tag': await entityService.deleteTag(mcpContext, clientProjectRoot, repository, branch, entityId); break; default: throw new Error(`Unsupported entity type for deletion: ${entityType}`); } logger.info(`Successfully deleted ${entityType} entity: ${entityId}`, { entityId, entityType, }); } /** * Execute merge action (merge source entity into target entity) */ private async executeMergeAction( mcpContext: ToolHandlerContext, clientProjectRoot: string, repository: string, branch: string, action: any, logger: any, ): Promise<void> { const sourceEntityId = action.entityId; const targetEntityId = action.targetEntityId; if (!targetEntityId) { throw new Error(`Merge action requires targetEntityId for entity ${sourceEntityId}`); } logger.info(`Merging entity ${sourceEntityId} into ${targetEntityId}`); // For merge operations, we need to: // 1. Get the source entity data // 2. Merge relevant data into the target entity // 3. Update relationships to point to the target entity // 4. Delete the source entity const kuzuClient = await this.memoryService.getKuzuClient(mcpContext, clientProjectRoot); // Get source entity data const sourceQuery = ` MATCH (source {id: $sourceId, repository: $repository, branch: $branch}) RETURN source, labels(source) AS sourceLabels `; const sourceResult = await kuzuClient.executeQuery(sourceQuery, { sourceId: sourceEntityId, repository, branch, }); if (sourceResult.length === 0) { throw new Error(`Source entity not found: ${sourceEntityId}`); } const sourceEntity = sourceResult[0].source; const sourceLabels = sourceResult[0].sourceLabels; // Update relationships to point to target entity const relationshipType = sourceLabels[0] === 'Component' ? 'DEPENDS_ON' : 'RELATED_TO'; const updateRelationshipsQuery = ` MATCH (source {id: $sourceId, repository: $repository, branch: $branch})-[r]-(other) MATCH (target {id: $targetId, repository: $repository, branch: $branch}) WHERE NOT (target)-[r]-(other) CREATE (target)-[newR:${relationshipType}]->(other) SET newR = properties(r) `; await kuzuClient.executeQuery(updateRelationshipsQuery, { sourceId: sourceEntityId, targetId: targetEntityId, repository, branch, }); // Delete the source entity (this will also delete its relationships) const entityType = this.determineEntityType(sourceEntityId, action); await this.executeDeleteAction( mcpContext, clientProjectRoot, repository, branch, { ...action, type: 'delete', entityId: sourceEntityId }, logger, ); logger.info(`Successfully merged entity ${sourceEntityId} into ${targetEntityId}`); } /** * Execute update action (update entity properties) */ private async executeUpdateAction( mcpContext: ToolHandlerContext, clientProjectRoot: string, repository: string, branch: string, action: any, logger: any, ): Promise<void> { const entityId = action.entityId; const updates = action.updates || {}; logger.info(`Updating entity ${entityId}`, { updates }); const kuzuClient = await this.memoryService.getKuzuClient(mcpContext, clientProjectRoot); // Define allowed fields for updates const allowedFields = [ 'name', 'description', 'status', 'metadata', 'updated_at', 'kind', 'depends_on', 'content', 'triggers', 'date', 'context', ]; // Filter and validate fields const validUpdates = Object.keys(updates) .filter((key) => allowedFields.includes(key)) .reduce((obj, key) => ({ ...obj, [key]: updates[key] }), {}); if (Object.keys(validUpdates).length === 0) { logger.warn(`No valid updates specified for entity ${entityId}`); return; } // Build update query const updateFields = Object.keys(validUpdates) .map((key) => `n.${key} = $${key}`) .join(', '); const updateQuery = ` MATCH (n {id: $entityId, repository: $repository, branch: $branch}) SET ${updateFields} RETURN n `; const params = { entityId, repository, branch, ...validUpdates, }; const result = await kuzuClient.executeQuery(updateQuery, params); if (result.length === 0) { throw new Error(`Entity not found for update: ${entityId}`); } logger.info(`Successfully updated entity ${entityId}`); } /** * Execute move action (change entity relationships or hierarchy) */ private async executeMoveAction( mcpContext: ToolHandlerContext, clientProjectRoot: string, repository: string, branch: string, action: any, logger: any, ): Promise<void> { const entityId = action.entityId; const targetEntityId = action.targetEntityId; if (!targetEntityId) { throw new Error(`Move action requires targetEntityId for entity ${entityId}`); } logger.info(`Moving entity ${entityId} to be related to ${targetEntityId}`); const kuzuClient = await this.memoryService.getKuzuClient(mcpContext, clientProjectRoot); // Remove existing relationships of the specified type const removeQuery = ` MATCH (source {id: $sourceId, repository: $repository, branch: $branch})-[r:DEPENDS_ON]-() DELETE r `; await kuzuClient.executeQuery(removeQuery, { sourceId: entityId, repository, branch, }); // Create new relationship to target const createQuery = ` MATCH (source {id: $sourceId, repository: $repository, branch: $branch}) MATCH (target {id: $targetId, repository: $repository, branch: $branch}) CREATE (source)-[:DEPENDS_ON]->(target) `; await kuzuClient.executeQuery(createQuery, { sourceId: entityId, targetId: targetEntityId, repository, branch, }); logger.info(`Successfully moved entity ${entityId} to depend on ${targetEntityId}`); } /** * Determine entity type from action type or entity ID */ private determineEntityTypeFromAction( actionType: string, entityId: string, action?: any, ): string { // Handle specific delete action types switch (actionType) { case 'delete-component': return 'component'; case 'delete-decision': return 'decision'; case 'delete-rule': return 'rule'; case 'delete-file': return 'file'; case 'delete-context': return 'context'; case 'delete-tag': return 'tag'; default: // Fall back to the base class method for generic 'delete' actions return this.determineEntityType(entityId, action); } } }

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/Jakedismo/KuzuMem-MCP'

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