UNPKG

lorehub

Version:

Capture and surface the collective wisdom of your codebase

1,320 lines 58.7 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
import { z } from 'zod';
import { Database } from '../db/database.js';
import { getDbPath } from '../cli/utils/db-config.js';
// Tool schemas
const SearchLoresSchema = z.object({
    query: z.string().describe('Search query (supports wildcards * and ?)'),
    realm_path: z.string().optional().describe('Realm path to search in (searches all realms if not specified)'),
    type: z.enum(['decree', 'wisdom', 'belief', 'constraint', 'requirement', 'risk', 'quest', 'saga', 'story', 'anomaly', 'other']).optional(),
    province: z.string().optional().describe('Filter by province (service in monorepos)'),
    limit: z.number().optional().default(50).describe('Maximum number of results'),
});
const ListLoresSchema = z.object({
    realm_path: z.string().optional().describe('Realm path to list lores from (lists from all realms if not specified)'),
    type: z.enum(['decree', 'wisdom', 'belief', 'constraint', 'requirement', 'risk', 'quest', 'saga', 'story', 'anomaly', 'other']).optional(),
    province: z.string().optional().describe('Filter by province (service in monorepos)'),
    limit: z.number().optional().default(20).describe('Maximum number of results'),
});
const GetLoreSchema = z.object({
    lore_id: z.string().describe('The ID of the lore to retrieve'),
});
const ListRealmsSchema = z.object({});
const CreateLoreSchema = z.object({
    realm_path: z.string().describe('Realm path where the lore should be created'),
    content: z.string().describe('The main content of the lore'),
    why: z.string().optional().describe('Additional context or reasoning for this lore'),
    type: z.enum(['decree', 'wisdom', 'belief', 'constraint', 'requirement', 'risk', 'quest', 'saga', 'story', 'anomaly', 'other']).describe('The type of lore'),
    provinces: z.array(z.string()).optional().describe('Provinces this lore applies to (services in monorepos)'),
    sigils: z.array(z.string()).optional().describe('Sigils for categorizing the lore (tags)'),
    confidence: z.number().min(0).max(100).optional().default(80).describe('Confidence level (0-100)'),
    origin: z.object({
        type: z.enum(['manual', 'inferred', 'imported']).default('manual'),
        reference: z.string().default('mcp'),
        context: z.string().optional(),
    }).optional().default({ type: 'manual', reference: 'mcp' }),
});
const UpdateLoreSchema = z.object({
    lore_id: z.string().describe('The ID of the lore to update'),
    content: z.string().optional().describe('Updated content of the lore'),
    why: z.string().optional().describe('Updated context or reasoning'),
    type: z.enum(['decree', 'wisdom', 'belief', 'constraint', 'requirement', 'risk', 'quest', 'saga', 'story', 'anomaly', 'other']).optional(),
    provinces: z.array(z.string()).optional().describe('Updated provinces list'),
    sigils: z.array(z.string()).optional().describe('Updated sigils list'),
    confidence: z.number().min(0).max(100).optional().describe('Updated confidence level'),
    status: z.enum(['living', 'ancient', 'whispered', 'proclaimed', 'archived']).optional().describe('Updated status'),
    origin: z.object({
        type: z.enum(['manual', 'inferred', 'imported']),
        reference: z.string(),
        context: z.string().optional(),
    }).optional(),
});
const DeleteLoreSchema = z.object({
    lore_id: z.string().describe('The ID of the lore to delete'),
    confirm: z.boolean().describe('Must be true to confirm deletion'),
});
const ArchiveLoreSchema = z.object({
    lore_id: z.string().describe('The ID of the lore to archive'),
});
const RestoreLoreSchema = z.object({
    lore_id: z.string().describe('The ID of the lore to restore'),
});
const CreateRelationSchema = z.object({
    from_lore_id: z.string().describe('The ID of the source lore'),
    to_lore_id: z.string().describe('The ID of the target lore'),
    type: z.enum(['succeeds', 'challenges', 'supports', 'depends_on', 'bound_to']).describe('The type of relationship'),
    strength: z.number().min(0).max(1).optional().default(1.0).describe('Strength of the relationship (0.0-1.0)'),
    metadata: z.record(z.unknown()).optional().describe('Additional metadata for the relationship'),
});
const DeleteRelationSchema = z.object({
    from_lore_id: z.string().describe('The ID of the source lore'),
    to_lore_id: z.string().describe('The ID of the target lore'),
    type: z.enum(['supersedes', 'contradicts', 'supports', 'depends_on', 'relates_to']).describe('The type of relationship to delete'),
});
const ListRelationsSchema = z.object({
    lore_id: z.string().describe('The ID of the lore to list relationships for'),
    direction: z.enum(['from', 'to', 'both']).optional().default('both').describe('Direction of relationships to list'),
});
const GetRealmStatsSchema = z.object({
    realm_path: z.string().describe('The realm path to get statistics for'),
});
const SemanticSearchLoresSchema = z.object({
    query: z.string().describe('Natural language query to find semantically similar lores'),
    realm_path: z.string().optional().describe('Realm path to search in (searches all realms if not specified)'),
    threshold: z.number().min(0).max(1).optional().default(0.7).describe('Similarity threshold (0-1, higher is more similar)'),
    limit: z.number().optional().default(20).describe('Maximum number of results'),
});
const FindSimilarLoresSchema = z.object({
    lore_id: z.string().describe('The ID of the lore to find similar lores for'),
    limit: z.number().optional().default(10).describe('Maximum number of similar lores to return'),
    threshold: z.number().min(0).max(1).optional().default(0.5).describe('Similarity threshold (0-1)'),
});
export class LoreHubServer {
    server;
    db;
    constructor(dbPathOrInstance) {
        this.server = new Server({
            name: 'lorehub',
            version: '0.1.0',
        }, {
            capabilities: {
                tools: {},
            },
        });
        if (typeof dbPathOrInstance === 'object' && dbPathOrInstance !== null) {
            // Assume it's a Database instance or mock
            this.db = dbPathOrInstance;
        }
        else {
            this.db = new Database(dbPathOrInstance || getDbPath());
        }
        this.setupHandlers();
    }
    setupHandlers() {
        // Handle tool listing
        this.server.setRequestHandler(ListToolsRequestSchema, async () => {
            return {
                tools: [
                    {
                        name: 'search_lores',
                        description: 'Search lores across all realms with optional filters',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                query: { type: 'string', description: 'Search query (supports wildcards * and ?)' },
                                realm_path: { type: 'string', description: 'Realm path to search in (searches all realms if not specified)' },
                                type: {
                                    type: 'string',
                                    enum: ['decree', 'assumption', 'constraint', 'requirement', 'risk', 'lesson', 'quest', 'other'],
                                    description: 'Filter by lore type'
                                },
                                province: { type: 'string', description: 'Filter by province (for monorepos)' },
                                limit: { type: 'number', description: 'Maximum number of results', default: 50 },
                            },
                            required: ['query'],
                        },
                    },
                    {
                        name: 'list_lores',
                        description: 'List lores from all realms with optional filters',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                realm_path: { type: 'string', description: 'Realm path to list lores from (lists from all realms if not specified)' },
                                type: {
                                    type: 'string',
                                    enum: ['decree', 'assumption', 'constraint', 'requirement', 'risk', 'lesson', 'quest', 'other'],
                                    description: 'Filter by lore type'
                                },
                                province: { type: 'string', description: 'Filter by province (for monorepos)' },
                                limit: { type: 'number', description: 'Maximum number of results', default: 20 },
                            },
                            required: [],
                        },
                    },
                    {
                        name: 'get_lore',
                        description: 'Get a specific lore by ID',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                lore_id: { type: 'string', description: 'The ID of the lore to retrieve' },
                            },
                            required: ['lore_id'],
                        },
                    },
                    {
                        name: 'list_realms',
                        description: 'List all realms in the LoreHub database',
                        inputSchema: {
                            type: 'object',
                            properties: {},
                        },
                    },
                    {
                        name: 'create_lore',
                        description: 'Create a new lore in a realm',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                realm_path: { type: 'string', description: 'Realm path where the lore should be created' },
                                content: { type: 'string', description: 'The main content of the lore' },
                                why: { type: 'string', description: 'Additional context or reasoning for this lore' },
                                type: {
                                    type: 'string',
                                    enum: ['decree', 'assumption', 'constraint', 'requirement', 'risk', 'lesson', 'quest', 'other'],
                                    description: 'The type of lore'
                                },
                                provinces: {
                                    type: 'array',
                                    items: { type: 'string' },
                                    description: 'Provinces this lore applies to (for monorepos)'
                                },
                                sigils: {
                                    type: 'array',
                                    items: { type: 'string' },
                                    description: 'Sigils for categorizing the lore'
                                },
                                confidence: {
                                    type: 'number',
                                    minimum: 0,
                                    maximum: 100,
                                    description: 'Confidence level (0-100)',
                                    default: 80
                                },
                                origin: {
                                    type: 'object',
                                    properties: {
                                        type: {
                                            type: 'string',
                                            enum: ['manual', 'inferred', 'imported'],
                                            default: 'manual'
                                        },
                                        reference: {
                                            type: 'string',
                                            default: 'mcp'
                                        },
                                        context: { type: 'string' }
                                    },
                                    default: { type: 'manual', reference: 'mcp' }
                                }
                            },
                            required: ['realm_path', 'content', 'type'],
                        },
                    },
                    {
                        name: 'update_lore',
                        description: 'Update an existing lore',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                lore_id: { type: 'string', description: 'The ID of the lore to update' },
                                content: { type: 'string', description: 'Updated content of the lore' },
                                why: { type: 'string', description: 'Updated context or reasoning' },
                                type: {
                                    type: 'string',
                                    enum: ['decree', 'assumption', 'constraint', 'requirement', 'risk', 'lesson', 'quest', 'other'],
                                    description: 'Updated lore type'
                                },
                                provinces: {
                                    type: 'array',
                                    items: { type: 'string' },
                                    description: 'Updated provinces list'
                                },
                                sigils: {
                                    type: 'array',
                                    items: { type: 'string' },
                                    description: 'Updated sigils list'
                                },
                                confidence: {
                                    type: 'number',
                                    minimum: 0,
                                    maximum: 100,
                                    description: 'Updated confidence level'
                                },
                                status: {
                                    type: 'string',
                                    enum: ['living', 'ancient', 'whispered', 'proclaimed', 'archived'],
                                    description: 'Updated status'
                                },
                                origin: {
                                    type: 'object',
                                    properties: {
                                        type: {
                                            type: 'string',
                                            enum: ['manual', 'inferred', 'imported']
                                        },
                                        reference: { type: 'string' },
                                        context: { type: 'string' }
                                    }
                                }
                            },
                            required: ['lore_id'],
                        },
                    },
                    {
                        name: 'delete_lore',
                        description: 'Permanently delete a lore (requires confirmation)',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                lore_id: { type: 'string', description: 'The ID of the lore to delete' },
                                confirm: { type: 'boolean', description: 'Must be true to confirm deletion' },
                            },
                            required: ['lore_id', 'confirm'],
                        },
                    },
                    {
                        name: 'archive_lore',
                        description: 'Archive a lore (soft delete)',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                lore_id: { type: 'string', description: 'The ID of the lore to archive' },
                            },
                            required: ['lore_id'],
                        },
                    },
                    {
                        name: 'restore_lore',
                        description: 'Restore an archived lore',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                lore_id: { type: 'string', description: 'The ID of the lore to restore' },
                            },
                            required: ['lore_id'],
                        },
                    },
                    {
                        name: 'create_relation',
                        description: 'Create a relationship between two lores',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                from_lore_id: { type: 'string', description: 'The ID of the source lore' },
                                to_lore_id: { type: 'string', description: 'The ID of the target lore' },
                                type: {
                                    type: 'string',
                                    enum: ['supersedes', 'contradicts', 'supports', 'depends_on', 'relates_to'],
                                    description: 'The type of relationship'
                                },
                                strength: {
                                    type: 'number',
                                    minimum: 0,
                                    maximum: 1,
                                    description: 'Strength of the relationship (0.0-1.0)',
                                    default: 1.0
                                },
                                metadata: { type: 'object', description: 'Additional metadata for the relationship' },
                            },
                            required: ['from_lore_id', 'to_lore_id', 'type'],
                        },
                    },
                    {
                        name: 'delete_relation',
                        description: 'Delete a relationship between two lores',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                from_lore_id: { type: 'string', description: 'The ID of the source lore' },
                                to_lore_id: { type: 'string', description: 'The ID of the target lore' },
                                type: {
                                    type: 'string',
                                    enum: ['supersedes', 'contradicts', 'supports', 'depends_on', 'relates_to'],
                                    description: 'The type of relationship to delete'
                                },
                            },
                            required: ['from_lore_id', 'to_lore_id', 'type'],
                        },
                    },
                    {
                        name: 'list_relations',
                        description: 'List all relationships for a lore',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                lore_id: { type: 'string', description: 'The ID of the lore to list relationships for' },
                                direction: {
                                    type: 'string',
                                    enum: ['from', 'to', 'both'],
                                    description: 'Direction of relationships to list',
                                    default: 'both'
                                },
                            },
                            required: ['lore_id'],
                        },
                    },
                    {
                        name: 'get_realm_stats',
                        description: 'Get detailed statistics about a realm',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                realm_path: { type: 'string', description: 'The realm path to get statistics for' },
                            },
                            required: ['realm_path'],
                        },
                    },
                    {
                        name: 'semantic_search_lores',
                        description: 'Search lores using semantic similarity to find conceptually related lores',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                query: { type: 'string', description: 'Natural language query to find semantically similar lores' },
                                realm_path: { type: 'string', description: 'Realm path to search in (searches all realms if not specified)' },
                                threshold: { type: 'number', description: 'Similarity threshold (0-1, higher is more similar)', default: 0.7, minimum: 0, maximum: 1 },
                                limit: { type: 'number', description: 'Maximum number of results', default: 20 },
                            },
                            required: ['query'],
                        },
                    },
                    {
                        name: 'find_similar_lores',
                        description: 'Find lores similar to a given lore',
                        inputSchema: {
                            type: 'object',
                            properties: {
                                lore_id: { type: 'string', description: 'The ID of the lore to find similar lores for' },
                                limit: { type: 'number', description: 'Maximum number of similar lores to return', default: 10 },
                                threshold: { type: 'number', description: 'Similarity threshold (0-1)', default: 0.5, minimum: 0, maximum: 1 },
                            },
                            required: ['lore_id'],
                        },
                    },
                ],
            };
        });
        // Handle tool calls
        this.server.setRequestHandler(CallToolRequestSchema, async (request) => {
            switch (request.params.name) {
                case 'search_lores':
                    return this.searchLores(request.params.arguments);
                case 'list_lores':
                    return this.listLores(request.params.arguments);
                case 'get_lore':
                    return this.getLore(request.params.arguments);
                case 'list_realms':
                    return this.listRealms(request.params.arguments);
                case 'create_lore':
                    return this.createLore(request.params.arguments);
                case 'update_lore':
                    return this.updateLore(request.params.arguments);
                case 'delete_lore':
                    return this.deleteLore(request.params.arguments);
                case 'archive_lore':
                    return this.archiveLore(request.params.arguments);
                case 'restore_lore':
                    return this.restoreLore(request.params.arguments);
                case 'create_relation':
                    return this.createRelation(request.params.arguments);
                case 'delete_relation':
                    return this.deleteRelation(request.params.arguments);
                case 'list_relations':
                    return this.listRelations(request.params.arguments);
                case 'get_realm_stats':
                    return this.getRealmStats(request.params.arguments);
                case 'semantic_search_lores':
                    return this.semanticSearchLores(request.params.arguments);
                case 'find_similar_lores':
                    return this.findSimilarLores(request.params.arguments);
                default:
                    throw new Error(`Tool not found: ${request.params.name}`);
            }
        });
    }
    async searchLores(args) {
        const params = SearchLoresSchema.parse(args);
        let realmsToSearch = this.db.listRealms();
        let requestedRealm = null;
        // If specific realm requested, filter to just that realm
        if (params.realm_path) {
            const realm = this.db.findRealmByPath(params.realm_path);
            if (!realm) {
                throw new Error(`Realm not found at path: ${params.realm_path}`);
            }
            realmsToSearch = [realm];
            requestedRealm = realm;
        }
        // Search across all selected realms
        let allLores = [];
        for (const realm of realmsToSearch) {
            const lores = this.db.searchLores(realm.id, params.query);
            allLores.push(...lores.map(l => ({
                ...l,
                realmName: realm.name,
                realmPath: realm.path
            })));
        }
        // Apply filters
        if (params.type) {
            allLores = allLores.filter(l => l.type === params.type);
        }
        if (params.province) {
            allLores = allLores.filter(l => l.provinces.includes(params.province));
        }
        // Sort by date
        allLores.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
        // Apply limit
        const limit = params.limit || 50;
        if (allLores.length > limit) {
            allLores = allLores.slice(0, limit);
        }
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        lores: allLores.map(l => ({
                            ...this.formatLore(l),
                            realm: {
                                name: l.realmName,
                                path: l.realmPath,
                            },
                        })),
                        total: allLores.length,
                        searchedRealms: requestedRealm ? 1 : realmsToSearch.length,
                    }, null, 2),
                },
            ],
        };
    }
    async listLores(args) {
        const params = ListLoresSchema.parse(args);
        let realmsToList = this.db.listRealms();
        let requestedRealm = null;
        // If specific realm requested, filter to just that realm
        if (params.realm_path) {
            const realm = this.db.findRealmByPath(params.realm_path);
            if (!realm) {
                throw new Error(`Realm not found at path: ${params.realm_path}`);
            }
            realmsToList = [realm];
            requestedRealm = realm;
        }
        // List lores from all selected realms
        let allLores = [];
        for (const realm of realmsToList) {
            let lores;
            if (params.type) {
                lores = this.db.listLoresByType(realm.id, params.type);
            }
            else if (params.province) {
                lores = this.db.listLoresByProvince(realm.id, params.province);
            }
            else {
                lores = this.db.listLoresByRealm(realm.id);
            }
            allLores.push(...lores.map(l => ({
                ...l,
                realmName: realm.name,
                realmPath: realm.path
            })));
        }
        // Sort by creation date descending
        allLores.sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime());
        // Apply limit
        const limit = params.limit || 20;
        if (allLores.length > limit) {
            allLores = allLores.slice(0, limit);
        }
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        lores: allLores.map(l => ({
                            ...this.formatLore(l),
                            realm: {
                                name: l.realmName,
                                path: l.realmPath,
                            },
                        })),
                        total: allLores.length,
                        listedRealms: requestedRealm ? 1 : realmsToList.length,
                    }, null, 2),
                },
            ],
        };
    }
    async getLore(args) {
        const params = GetLoreSchema.parse(args);
        const lore = this.db.findLore(params.lore_id);
        if (!lore) {
            throw new Error(`Lore not found with ID: ${params.lore_id}`);
        }
        const realm = this.db.listRealms().find(r => r.id === lore.realmId);
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        lore: this.formatLore(lore),
                        realm: realm ? {
                            id: realm.id,
                            name: realm.name,
                            path: realm.path,
                        } : undefined,
                    }, null, 2),
                },
            ],
        };
    }
    async listRealms(_args) {
        const realms = this.db.listRealms();
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        realms: realms.map(r => ({
                            id: r.id,
                            name: r.name,
                            path: r.path,
                            isMonorepo: r.isMonorepo,
                            provinces: r.provinces,
                            lastSeen: r.lastSeen.toISOString(),
                            createdAt: r.createdAt.toISOString(),
                            loreCount: this.db.getRealmLoreCount(r.id),
                        })),
                        total: realms.length,
                    }, null, 2),
                },
            ],
        };
    }
    formatLore(lore) {
        return {
            id: lore.id,
            content: lore.content,
            why: lore.why,
            type: lore.type,
            provinces: lore.provinces,
            sigils: lore.sigils,
            confidence: lore.confidence,
            status: lore.status,
            origin: lore.origin,
            createdAt: lore.createdAt.toISOString(),
            updatedAt: lore.updatedAt.toISOString(),
        };
    }
    async createLore(args) {
        const params = CreateLoreSchema.parse(args);
        // Find or create the realm by path
        let realm = this.db.findRealmByPath(params.realm_path);
        if (!realm) {
            // Auto-initialize the realm if it doesn't exist
            const realmName = params.realm_path.split('/').pop() || 'unknown';
            realm = this.db.createRealm({
                name: realmName,
                path: params.realm_path,
                isMonorepo: false,
                provinces: params.provinces || [],
            });
        }
        // Create the lore
        const lore = await this.db.createLore({
            realmId: realm.id,
            content: params.content,
            why: params.why,
            type: params.type,
            provinces: params.provinces,
            sigils: params.sigils,
            confidence: params.confidence,
            origin: params.origin,
        });
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        lore: this.formatLore(lore),
                        realm: {
                            id: realm.id,
                            name: realm.name,
                            path: realm.path,
                            created: !realm.lastSeen || realm.lastSeen.toISOString() === realm.createdAt.toISOString(),
                        },
                        message: !realm.lastSeen || realm.lastSeen.toISOString() === realm.createdAt.toISOString()
                            ? 'Realm initialized and lore created successfully'
                            : 'Lore created successfully',
                    }, null, 2),
                },
            ],
        };
    }
    async updateLore(args) {
        const params = UpdateLoreSchema.parse(args);
        // Verify the lore exists before updating
        const existingLore = this.db.findLore(params.lore_id);
        if (!existingLore) {
            throw new Error(`Lore not found with ID: ${params.lore_id}`);
        }
        // Update the lore
        const updatedLore = await this.db.updateLore(params.lore_id, {
            content: params.content,
            why: params.why,
            type: params.type,
            provinces: params.provinces,
            sigils: params.sigils,
            confidence: params.confidence,
            status: params.status,
            origin: params.origin,
        });
        if (!updatedLore) {
            throw new Error(`Failed to update lore with ID: ${params.lore_id}`);
        }
        // Get the realm for context
        const realm = this.db.listRealms().find(r => r.id === updatedLore.realmId);
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        lore: this.formatLore(updatedLore),
                        realm: realm ? {
                            id: realm.id,
                            name: realm.name,
                            path: realm.path,
                        } : undefined,
                        message: 'Lore updated successfully',
                    }, null, 2),
                },
            ],
        };
    }
    async deleteLore(args) {
        const params = DeleteLoreSchema.parse(args);
        // Require explicit confirmation
        if (!params.confirm) {
            throw new Error('Deletion must be confirmed by setting confirm: true');
        }
        // Verify the lore exists before deleting
        const lore = this.db.findLore(params.lore_id);
        if (!lore) {
            throw new Error(`Lore not found with ID: ${params.lore_id}`);
        }
        // Get the realm for context before deletion
        const realm = this.db.listRealms().find(r => r.id === lore.realmId);
        // Delete the lore
        this.db.deleteLore(params.lore_id);
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        deleted: true,
                        lore_id: params.lore_id,
                        realm: realm ? {
                            id: realm.id,
                            name: realm.name,
                            path: realm.path,
                        } : undefined,
                        message: 'Lore permanently deleted',
                    }, null, 2),
                },
            ],
        };
    }
    async archiveLore(args) {
        const params = ArchiveLoreSchema.parse(args);
        // Verify the lore exists before archiving
        const lore = this.db.findLore(params.lore_id);
        if (!lore) {
            throw new Error(`Lore not found with ID: ${params.lore_id}`);
        }
        // Check if already archived
        if (lore.status === 'archived') {
            throw new Error('Lore is already archived');
        }
        // Archive the lore (soft delete)
        this.db.softDeleteLore(params.lore_id);
        // Get the updated lore
        const archivedLore = this.db.findLore(params.lore_id);
        if (!archivedLore) {
            throw new Error(`Failed to archive lore with ID: ${params.lore_id}`);
        }
        // Get the realm for context
        const realm = this.db.listRealms().find(r => r.id === archivedLore.realmId);
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        lore: this.formatLore(archivedLore),
                        realm: realm ? {
                            id: realm.id,
                            name: realm.name,
                            path: realm.path,
                        } : undefined,
                        message: 'Lore archived successfully',
                    }, null, 2),
                },
            ],
        };
    }
    async restoreLore(args) {
        const params = RestoreLoreSchema.parse(args);
        // Verify the lore exists before restoring
        const lore = this.db.findLore(params.lore_id);
        if (!lore) {
            throw new Error(`Lore not found with ID: ${params.lore_id}`);
        }
        // Check if not archived
        if (lore.status !== 'archived') {
            throw new Error('Lore is not archived');
        }
        // Restore the lore
        this.db.restoreLore(params.lore_id);
        // Get the updated lore
        const restoredLore = this.db.findLore(params.lore_id);
        if (!restoredLore) {
            throw new Error(`Failed to restore lore with ID: ${params.lore_id}`);
        }
        // Get the realm for context
        const realm = this.db.listRealms().find(r => r.id === restoredLore.realmId);
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        lore: this.formatLore(restoredLore),
                        realm: realm ? {
                            id: realm.id,
                            name: realm.name,
                            path: realm.path,
                        } : undefined,
                        message: 'Lore restored successfully',
                    }, null, 2),
                },
            ],
        };
    }
    async createRelation(args) {
        const params = CreateRelationSchema.parse(args);
        // Verify both lores exist
        const fromLore = this.db.findLore(params.from_lore_id);
        if (!fromLore) {
            throw new Error(`Source lore not found with ID: ${params.from_lore_id}`);
        }
        const toLore = this.db.findLore(params.to_lore_id);
        if (!toLore) {
            throw new Error(`Target lore not found with ID: ${params.to_lore_id}`);
        }
        // Verify lores are in the same realm
        if (fromLore.realmId !== toLore.realmId) {
            throw new Error('Cannot create relation between lores in different realms');
        }
        // Create the relation
        const relation = await this.db.createRelation({
            fromLoreId: params.from_lore_id,
            toLoreId: params.to_lore_id,
            type: params.type,
            strength: params.strength,
            metadata: params.metadata,
        });
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        relation: {
                            fromLoreId: relation.fromLoreId,
                            toLoreId: relation.toLoreId,
                            type: relation.type,
                            strength: relation.strength,
                            metadata: relation.metadata,
                            createdAt: relation.createdAt.toISOString(),
                        },
                        fromLore: {
                            id: fromLore.id,
                            content: fromLore.content,
                        },
                        toLore: {
                            id: toLore.id,
                            content: toLore.content,
                        },
                        message: 'Relation created successfully',
                    }, null, 2),
                },
            ],
        };
    }
    async deleteRelation(args) {
        const params = DeleteRelationSchema.parse(args);
        // Verify the relation exists by checking if the lores exist
        const fromLore = this.db.findLore(params.from_lore_id);
        if (!fromLore) {
            throw new Error(`Source lore not found with ID: ${params.from_lore_id}`);
        }
        const toLore = this.db.findLore(params.to_lore_id);
        if (!toLore) {
            throw new Error(`Target lore not found with ID: ${params.to_lore_id}`);
        }
        // Delete the relation
        await this.db.deleteRelation(params.from_lore_id, params.to_lore_id, params.type);
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        deleted: true,
                        fromLoreId: params.from_lore_id,
                        toLoreId: params.to_lore_id,
                        type: params.type,
                        message: 'Relation deleted successfully',
                    }, null, 2),
                },
            ],
        };
    }
    async listRelations(args) {
        const params = ListRelationsSchema.parse(args);
        // Verify the lore exists
        const lore = this.db.findLore(params.lore_id);
        if (!lore) {
            throw new Error(`Lore not found with ID: ${params.lore_id}`);
        }
        // Get all relations for the lore
        const allRelations = this.db.listRelationsByLore(params.lore_id);
        // Filter by direction
        let relations = allRelations;
        if (params.direction === 'from') {
            relations = allRelations.filter(r => r.fromLoreId === params.lore_id);
        }
        else if (params.direction === 'to') {
            relations = allRelations.filter(r => r.toLoreId === params.lore_id);
        }
        // Get lore details for related lores
        const relatedLoreIds = new Set();
        relations.forEach(r => {
            if (r.fromLoreId !== params.lore_id)
                relatedLoreIds.add(r.fromLoreId);
            if (r.toLoreId !== params.lore_id)
                relatedLoreIds.add(r.toLoreId);
        });
        const relatedLores = new Map();
        for (const loreId of relatedLoreIds) {
            const relatedLore = this.db.findLore(loreId);
            if (relatedLore) {
                relatedLores.set(loreId, {
                    id: relatedLore.id,
                    content: relatedLore.content,
                    type: relatedLore.type,
                });
            }
        }
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        lore: {
                            id: lore.id,
                            content: lore.content,
                            type: lore.type,
                        },
                        relations: relations.map(r => ({
                            fromLoreId: r.fromLoreId,
                            toLoreId: r.toLoreId,
                            type: r.type,
                            strength: r.strength,
                            metadata: r.metadata,
                            createdAt: r.createdAt.toISOString(),
                            direction: r.fromLoreId === params.lore_id ? 'outgoing' : 'incoming',
                            relatedLore: relatedLores.get(r.fromLoreId === params.lore_id ? r.toLoreId : r.fromLoreId),
                        })),
                        total: relations.length,
                        direction: params.direction,
                    }, null, 2),
                },
            ],
        };
    }
    async getRealmStats(args) {
        const params = GetRealmStatsSchema.parse(args);
        // Find the realm by path
        const realm = this.db.findRealmByPath(params.realm_path);
        if (!realm) {
            throw new Error(`Realm not found at path: ${params.realm_path}`);
        }
        // Get all lores for the realm
        const allLores = this.db.listLoresByRealm(realm.id);
        // Calculate statistics
        const stats = {
            totalLores: allLores.length,
            loresByType: {},
            loresByStatus: {},
            loresByProvince: {},
            averageConfidence: 0,
            highConfidenceLores: 0,
            lowConfidenceLores: 0,
            recentLores: [],
            loreGrowth: {
                lastWeek: 0,
                lastMonth: 0,
                lastQuarter: 0,
            },
            topSigils: [],
            relationStats: {
                totalRelations: 0,
                byType: {},
            },
        };
        // Count lores by type
        for (const type of ['decree', 'wisdom', 'belief', 'constraint', 'requirement', 'risk', 'quest', 'saga', 'story', 'anomaly', 'other']) {
            stats.loresByType[type] = 0;
        }
        // Count lores by status
        for (const status of ['living', 'ancient', 'whispered', 'proclaimed', 'archived']) {
            stats.loresByStatus[status] = 0;
        }
        // Tag frequency map
        const sigilFrequency = new Map();
        // Time boundaries for growth metrics
        const now = new Date();
        const oneWeekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
        const oneMonthAgo = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000);
        const threeMonthsAgo = new Date(now.getTime() - 90 * 24 * 60 * 60 * 1000);
        let totalConfidence = 0;
        // Process lores
        for (const lore of allLores) {
            // Type counts
            stats.loresByType[lore.type]++;
            // Status counts
            const status = lore.status || 'active';
            if (!(status in stats.loresByStatus)) {
                stats.loresByStatus[status] = 0;
            }
            stats.loresByStatus[status]++;
            // Province counts
            for (const province of lore.provinces) {
                stats.loresByProvince[province] = (stats.loresByProvince[province] || 0) + 1;
            }
            // Confidence metrics
            totalConfidence += lore.confidence;
            if (lore.confidence >= 90)
                stats.highConfidenceLores++;
            if (lore.confidence < 50)
                stats.lowConfidenceLores++;
            // Sigil frequency
            for (const sigil of lore.sigils) {
                sigilFrequency.set(sigil, (sigilFrequency.get(sigil) || 0) + 1);
            }
            // Growth metrics
            if (lore.createdAt >= oneWeekAgo)
                stats.loreGrowth.lastWeek++;
            if (lore.createdAt >= oneMonthAgo)
                stats.loreGrowth.lastMonth++;
            if (lore.createdAt >= threeMonthsAgo)
                stats.loreGrowth.lastQuarter++;
        }
        // Calculate averages
        if (allLores.length > 0) {
            stats.averageConfidence = Math.round(totalConfidence / allLores.length);
        }
        // Get top 10 sigils
        const sortedSigils = Array.from(sigilFrequency.entries())
            .sort((a, b) => b[1] - a[1])
            .slice(0, 10)
            .map(([sigil, count]) => ({ sigil, count }));
        stats.topSigils = sortedSigils;
        // Get 5 most recent lores
        stats.recentLores = allLores
            .sort((a, b) => b.createdAt.getTime() - a.createdAt.getTime())
            .slice(0, 5)
            .map(l => ({
            id: l.id,
            content: l.content,
            type: l.type,
            createdAt: l.createdAt.toISOString(),
        }));
        // Get relation statistics
        const allRelations = [];
        for (const lore of allLores) {
            const relations = this.db.listRelationsByLore(lore.id);
            allRelations.push(...relations);
        }
        // Deduplicate relations (since we're getting both directions)
        const uniqueRelations = new Map();
        for (const rel of allRelations) {
            const key = `${rel.fromLoreId}-${rel.toLoreId}-${rel.type}`;
            uniqueRelations.set(key, rel);
        }
        stats.relationStats.totalRelations = uniqueRelations.size;
        for (const rel of uniqueRelations.values()) {
            stats.relationStats.byType[rel.type] = (stats.relationStats.byType[rel.type] || 0) + 1;
        }
        return {
            content: [
                {
                    type: 'text',
                    text: JSON.stringify({
                        realm: {
                            id: realm.id,
                            name: realm.name,
                            path: realm.path,
                            isMonorepo: realm.isMonorepo,
                            provinces: realm.provinces,
                            createdAt: realm.createdAt.toISOString(),
                            lastSeen: realm.lastSeen.toISOString(),
                        },
                        statistics: stats,
                    }, null, 2),
                },
            ],
        };
    }
    async semanticSearchLores(args) {
        const params = SemanticSearchLoresSchema.parse(args);
        try {
            // Perform semantic search
            const results = await this.db.semanticSearchLores(params.query, {
                realmId: params.realm_path ? this.db.findRealmByPath(params.realm_path)?.id : undefined,
                threshold: params.threshold,
                limit: params.limit,
                includeScore: true
            });
            // Enrich results with realm information
            const enrichedResults = results.map(lore => {
                const realm = this.db.listRealms().find(r => r.id === lore.realmId);
                return {
                    ...this.formatLore(lore),
                    similarity: lore.similarity,
                    realm: realm ? {
                        name: realm.name,
                        path: realm.path,
                    } : undefined,
                };
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify({
                            lores: enrichedResults,
                            total: enrichedResults.length,
                            query: params.query,
                            searchType: 'semantic',
                            threshold: params.threshold,
                        }, null, 2),
                    },
                ],
            };
        }
        catch (error) {
            // Fallback to regular search if semantic search fails
            console.error('Semantic search failed, falling back to keyword search:', error);
            return this.searchLores({
                query: params.query,
                realm_path: params.realm_path,
                limit: params.limit,
            });
        }
    }
    async findSimilarLores(args) {
        const params = FindSimilarLoresSchema.parse(args);
        // Verify the lore exists
        const lore = this.db.findLore(params.lore_id);
        if (!lore) {
            throw new Error(`Lore not found with ID: ${params.lore_id}`);
        }
        try {
            // Find similar lores
            const similarLores = await this.db.findSimilarLores(params.lore_id, {
                limit: params.limit,
                threshold: params.threshold
            });
            // Enrich results with realm information
            const enrichedResults = similarLores.map(similarLore => {
                const realm = this.db.listRealms().find(r => r.id === similarLore.realmId);
                return {
                    ...this.formatLore(similarLore),
                    similarity: similarLore.similarity,
                    realm: realm ? {
                        name: realm.name,
                        path: realm.path,
                    } : undefined,
                };
            });
            return {
                content: [
                    {
                        type: 'text',
                        text: JSON.stringify({
                            originalLore: {
                                ...this.formatLore(lore),
                                realm: this.db.listRealms().find(r => r.id === lore.realmId)?.name,
                            },
                            similarLores: enrichedResults,
                            total: enrichedResults.length,
                            threshold: params.threshold,
                        }, null, 2),
                    },
                ],
            };
        }
        catch (error) {
            console.error('Finding similar lores failed:', error);
            throw new Error(`Failed to find similar lores: ${error instanceof Error ? error.message : 'Unknown error'}`);
        }
    }
    async getServerInfo() {
        return {
            name: 'lorehub',
            version: '0.1.0',
            capabilities: {
                tools: {},
            },
        };
    }
    async listTools() {
        // Return the tools directly for testing
        return [
            {
                name: 'search_lores',
                description: 'Search lores across all realms with optional filters',
            },
            {
                name: 'list_lores',
                description: 'List lores from all realms with optional filters',
            },
            {
                name: 'get_lore',
                description: 'Get a specific lore by ID',
            },
            {
                name: 'list_realms',
                description: 'List all realms in the LoreHub database',
            },
            {
                name: 'create_lore',
                description: 'Create a new lore in a realm',
            },
            {
                name: 'update_lore',
                description: 'Update an existing lore',
            },
            {
                name: 'delete_lore',
                description: 'Permanently delete a lore (requires confirmation)',
            },
            {
                name: 'archive_lore',
                description: 'Archive a lore (soft delete)',
            },
            {
                name: 'restore_lore',
                description: 'Restore an archived lore',
            },
            {
                name: 'create_relation',
                description: 'Create a relationship between two lores',
            },
            {
                name: 'delete_relation',
                description: 'Delete a relationship between two lores',
            },
            {
                name: 'list_relations',
                description: 'List all relationships for a lore',
            },
            {
                name: 'get_realm_stats',
                description: 'Get detailed statistics about a realm',
            },
            {
                name: 'semantic_search_lores',
                description: 'Search lores using semantic similarity to find conceptually related lores',
            },
            {
                name: 'find_similar_lores',
                description: 'Find lores similar to a given lore',
            },
        ];
    }
    async callTool(name, args) {
        // Direct implementation for testing
        switch (name) {
            case 'search_lores':
                const searchResult = await this.searchLores(args);
                return JSON.parse(searchResult.content[0].text);
            case 'list_lores':
                const listResult = await this.listLores(args);
                return JSON.parse(listResult.content[0].text);
            case 'get_lore':
                const getResult = await this.getLore(args);
                return JSON.parse(getResult.content[0].text);
            case 'list_realms':
                const realmsResult = await this.listRealms(args);
                return JSON.parse(realmsResult.content[0].text);
            case 'create_lore':
                const createResult = await this.createLore(args);
                return JSON.parse(createResult.content[0].text);
            case 'update_lore':
                const updateResult = await this.updateLore(args);
                return JSON.parse(updateResult.content[0].text);
            case 'delete_lore':
                const deleteResult = await this.deleteLore(args);
                return JSON.parse(deleteResult.content[0].text);
            case 'archive_lore':
                const archiveResult = await this.archiveLore(args);
                return JSON.parse(archiveResult.content[0].text);
            case 'restore_lore':
                const restoreResult = await this.restoreLore(args);
                return JSON.parse(restoreResult.content[0].text);
            case 'create_relation':
                const createRelResult = await this.createRelation(args);
                return JSON.parse(createRelResult.content[0].text);
            case 'delete_relation':
                const deleteRelResult = await this.deleteRelation(args);
                return JSON.parse(deleteRelResult.content[0].text);
            case 'list_relations':
                const listRelResult = await this.listRelations(args);
                return JSON.parse(listRelResult.content[0].text);
            case 'get_realm_stats':
                const statsResult = await this.getRealmStats(args);
                return JSON.parse(statsResult.content[0].text);
            case 'semantic_search_lores':
                const semanticResult = await this.semanticSearchLores(args);
                return JSON.parse(semanticResult.content[0].text);
            case 'find_similar_lores':
                const similarResult = await this.findSimilarLores(args);
                return JSON.parse(similarResult.content[0].text);
            default:
                throw new Error(`Tool not found: ${name}`);
        }
    }
    async start() {
        const transport = new StdioServerTransport();
        await this.server.connect(transport);
        console.error('LoreHub MCP server started');
    }
    close() {
        this.db.close();
    }
}
//# sourceMappingURL=server.js.map