Skip to main content
Glama
metadata.repository.ts8.48 kB
import { KuzuDBClient } from '../db/kuzu'; import { ToolHandlerContext } from '../mcp/types/sdk-custom'; import { Metadata } from '../types'; import { loggers } from '../utils/logger'; import { RepositoryRepository } from './repository.repository'; /** * Repository for Metadata, using KuzuDB and Cypher queries. * Each instance is now tied to a specific KuzuDBClient. */ export class MetadataRepository { private logger = loggers.repository(); private kuzuClient: KuzuDBClient; // Instance-specific client private repositoryRepo: RepositoryRepository; // Added private member /** * Constructor requires an initialized KuzuDBClient instance. * @param kuzuClient An initialized KuzuDBClient. * @param repositoryRepo A RepositoryRepository instance. */ public constructor(kuzuClient: KuzuDBClient, repositoryRepo: RepositoryRepository) { // Made public if (!kuzuClient) { throw new Error('MetadataRepository requires an initialized KuzuDBClient instance.'); } this.kuzuClient = kuzuClient; this.repositoryRepo = repositoryRepo; // Assign to member } private escapeStr(value: any): string { if (value === undefined || value === null) { return 'null'; } return String(value).replace(/'/g, "\\'").replace(/\\/g, '\\\\'); } private escapeJsonProp(value: any): string { if (value === undefined || value === null) { return 'null'; } try { const jsonString = JSON.stringify(value); return `'${this.escapeStr(jsonString)}'`; } catch (e) { this.logger.error('Failed to stringify JSON for escapeJsonProp', value, e); return 'null'; } } /** * Get metadata for a repository, branch, and metadata logical ID (usually 'meta'). * @param mcpContext The McpServerRequestContext for logging. * @param repositoryName The logical name of the repository (e.g., 'my-project'). * @param branch The branch for the metadata (e.g., 'main', 'dev'). * @param metadataId The logical ID of the metadata (typically 'meta'). */ async findMetadata( mcpContext: ToolHandlerContext, repositoryName: string, branch: string, metadataId: string = 'meta', ): Promise<Metadata | null> { const logger = mcpContext.logger || console; const graphUniqueId = `${repositoryName}:${branch}:${metadataId}`; logger.debug( `[MetadataRepository] findMetadata called with repositoryName=${repositoryName}, branch=${branch}, metadataId=${metadataId}, GID=${graphUniqueId}`, ); const query = `MATCH (m:Metadata {graph_unique_id: $graphUniqueId}) RETURN m`; logger.debug(`[MetadataRepository] Executing findMetadata query for GID: ${graphUniqueId}`); try { const result = await this.kuzuClient.executeQuery(query, { graphUniqueId }); if (result && result.length > 0) { const metadataNode = result[0].m.properties || result[0].m; let content = metadataNode.content; if (typeof content === 'string') { try { content = JSON.parse(content); } catch (e: any) { logger.error( `[MetadataRepository] Failed to parse metadata content for ${graphUniqueId}: ${e.message}`, { rawContent: metadataNode.content }, ); } } logger.info(`[MetadataRepository] Found metadata ${graphUniqueId}`); return { ...metadataNode, id: metadataNode.id?.toString(), graph_unique_id: metadataNode.graph_unique_id?.toString(), content: content, created_at: metadataNode.created_at ? new Date(metadataNode.created_at) : undefined, updated_at: metadataNode.updated_at ? new Date(metadataNode.updated_at) : undefined, } as Metadata; } logger.warn(`[MetadataRepository] Metadata not found for GID: ${graphUniqueId}`); return null; } catch (error: any) { logger.error( `[MetadataRepository] Error finding metadata ${graphUniqueId}: ${error.message}`, { stack: error.stack }, ); return null; } } /** * Creates or updates metadata for a repository. * The input `metadata.repository` is the Repository node's PK (e.g., 'my-project:main'). * The `metadata.branch` is the branch this metadata applies to. * The `metadata.id` is the logical ID (e.g., 'meta'). */ async upsertMetadata( mcpContext: ToolHandlerContext, metadata: Metadata, ): Promise<Metadata | null> { const logger = mcpContext.logger || console; let repoNameForGid: string; let branchForGid: string; // Debug logging for input metadata logger.debug('[MetadataRepository] upsertMetadata called with metadata:', { id: metadata.id, repository: metadata.repository, branch: metadata.branch, name: metadata.name, contentKeys: metadata.content ? Object.keys(metadata.content) : 'null', }); if (metadata.repository && metadata.repository.includes(':')) { const parts = metadata.repository.split(':'); repoNameForGid = parts[0]; branchForGid = parts.length > 1 ? parts[1] : metadata.branch; } else { logger.warn( '[MetadataRepository] metadata.repository format unexpected for GID construction', { repository: metadata.repository }, ); repoNameForGid = metadata.repository || 'unknown_repo'; branchForGid = metadata.branch || 'unknown_branch'; } const graph_unique_id_val = `${repoNameForGid}:${branchForGid}:${metadata.id}`; logger.debug(`[MetadataRepository] Constructed graph_unique_id: ${graph_unique_id_val}`); const flatParams = { gid: graph_unique_id_val, id_val: metadata.id, branch_val: metadata.branch, name_val: metadata.name, content_val: JSON.stringify(metadata.content || {}), created_val: (metadata.created_at instanceof Date ? metadata.created_at : new Date(metadata.created_at || Date.now()) ).toISOString(), updated_val: new Date().toISOString(), }; // MERGE to prevent duplicate metadata entries. const query = ` MERGE (m:Metadata {graph_unique_id: $gid}) ON CREATE SET m.id = $id_val, m.branch = $branch_val, m.name = $name_val, m.content = $content_val, m.created_at = $created_val, m.updated_at = $updated_val ON MATCH SET m.name = $name_val, m.content = $content_val, m.updated_at = $updated_val `; logger.debug(`[MetadataRepository] Upserting metadata (PK for query: ${graph_unique_id_val})`, { params: Object.keys(flatParams), }); try { await this.kuzuClient.executeQuery(query, flatParams); logger.info( `[MetadataRepository] Upsert executed for metadata (PK: ${graph_unique_id_val}). Attempting to find...`, ); const foundMetadata = await this.findMetadata( mcpContext, repoNameForGid, branchForGid, metadata.id, ); if (foundMetadata) { logger.info( `[MetadataRepository] Metadata created and found successfully (PK: ${graph_unique_id_val})`, ); return foundMetadata; } // If we can't find it after creation, construct a return value logger.warn( `[MetadataRepository] Created metadata but could not find it afterwards (PK: ${graph_unique_id_val}), returning constructed value`, ); return { id: metadata.id, repository: metadata.repository, branch: metadata.branch, name: metadata.name, content: metadata.content, graph_unique_id: graph_unique_id_val, created_at: new Date(), updated_at: new Date(), } as Metadata; } catch (error: any) { logger.error( `[MetadataRepository] Error creating metadata (PK for query: ${graph_unique_id_val}): ${error.message}`, { stack: error.stack }, ); // On error, try to return a constructed value logger.warn( `[MetadataRepository] Returning constructed metadata value after error (PK: ${graph_unique_id_val})`, ); return { id: metadata.id, repository: metadata.repository, branch: metadata.branch, name: metadata.name, content: metadata.content, graph_unique_id: graph_unique_id_val, created_at: new Date(), updated_at: new Date(), } as Metadata; } } }

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