Skip to main content
Glama

RAGmonsters Custom PostgreSQL MCP Server

monsters.js13.8 kB
/** * Monster-related MCP tools */ import { executeQuery } from '../utils/db.js'; import logger from '../utils/logger.js'; // Module-level database pool let dbPool = null; /** * Initialize the monsters module with a database pool * @param {Object} pool - PostgreSQL connection pool */ export function initializeTools(pool) { dbPool = pool; logger.info('Monsters module initialized with database pool'); } /** * Get a list of monsters with optional filtering, sorting, and pagination * * @param {Object} params - Tool parameters * @param {Object} [params.filters] - Optional filters for the query * @param {string} [params.filters.category] - Filter by monster category * @param {string} [params.filters.habitat] - Filter by monster habitat * @param {string} [params.filters.rarity] - Filter by monster rarity * @param {Object} [params.sort] - Optional sorting parameters * @param {string} [params.sort.field] - Field to sort by (e.g., 'name', 'category') * @param {string} [params.sort.direction] - Sort direction ('asc' or 'desc') * @param {number} [params.limit] - Maximum number of results to return * @param {number} [params.offset] - Number of results to skip (for pagination) * @returns {Promise<Array>} List of monsters */ export async function getMonsters(params = {}) { try { if (!dbPool) { throw new Error('Database pool not initialized. Call initialize() first.'); } logger.info(`getMonsters called with params: ${JSON.stringify(params)}`); const { filters = {}, sort = {}, limit = 10, offset = 0 } = params; // Start building the query let query = ` SELECT m.monster_id, m.name, m.category, m.habitat, m.rarity, m.primary_power, m.secondary_power, m.special_ability FROM monsters m WHERE 1=1 `; // Build parameter array for prepared statement const queryParams = []; // Add filters if (filters.category) { queryParams.push(filters.category); query += ` AND m.category = $${queryParams.length}`; } if (filters.habitat) { queryParams.push(filters.habitat); query += ` AND m.habitat = $${queryParams.length}`; } if (filters.rarity) { queryParams.push(filters.rarity); query += ` AND m.rarity = $${queryParams.length}`; } // Add sorting if (sort.field) { // Validate sort field to prevent SQL injection const validSortFields = ['name', 'category', 'habitat', 'rarity']; const sortField = validSortFields.includes(sort.field) ? sort.field : 'name'; // Validate sort direction const sortDirection = sort.direction?.toLowerCase() === 'desc' ? 'DESC' : 'ASC'; query += ` ORDER BY m.${sortField} ${sortDirection}`; } else { // Default sort by name query += ` ORDER BY m.name ASC`; } // Add pagination query += ` LIMIT $${queryParams.length + 1} OFFSET $${queryParams.length + 2}`; queryParams.push(limit, offset); const monsters = await executeQuery(dbPool, query, queryParams); logger.info(`getMonsters returning ${monsters.length} monsters`); logger.debug(`First monster in results: ${JSON.stringify(monsters[0] || {})}`); // Format the response return { content:monsters.map(monster => ({ type: 'text', text: JSON.stringify({ id: monster.monster_id, name: monster.name, category: monster.category, habitat: monster.habitat, rarity: monster.rarity, powers: { primary: monster.primary_power, secondary: monster.secondary_power, special: monster.special_ability } }), }))}; } catch (error) { logger.error(`Error in getMonsters: ${error.message}`); logger.error(error.stack); throw error; } } /** * Get detailed information about a specific monster by ID * * @param {Object} params - Tool parameters * @param {number} params.monsterId - ID of the monster to retrieve * @returns {Promise<Object>} Detailed monster information */ export async function getMonsterById(params) { try { if (!dbPool) { throw new Error('Database pool not initialized. Call initialize() first.'); } logger.info(`getMonsterById called with params: ${JSON.stringify(params)}`); const { monsterId } = params; if (!monsterId) { logger.error('Monster ID is required but was not provided'); throw new Error('Monster ID is required'); } logger.debug(`Fetching monster with ID: ${monsterId}`); // Get basic monster information const monsterQuery = ` SELECT m.* FROM monsters m WHERE m.monster_id = $1 `; const monsters = await executeQuery(dbPool, monsterQuery, [monsterId]); if (monsters.length === 0) { throw new Error(`Monster with ID ${monsterId} not found`); } const monster = monsters[0]; // Get monster's abilities and keywords const abilitiesQuery = ` SELECT k.keyword_name, k.rating, a.ability_name, a.mastery_value FROM questworlds_stats qs JOIN keywords k ON qs.stats_id = k.stats_id JOIN abilities a ON k.keyword_id = a.keyword_id WHERE qs.monster_id = $1 ORDER BY k.keyword_name, a.ability_name `; const abilities = await executeQuery(dbPool, abilitiesQuery, [monsterId]); // Get monster's flaws const flawsQuery = ` SELECT f.flaw_name, f.rating FROM questworlds_stats qs JOIN flaws f ON qs.stats_id = f.stats_id WHERE qs.monster_id = $1 ORDER BY f.rating DESC `; const flaws = await executeQuery(dbPool, flawsQuery, [monsterId]); // Get monster's strengths (augments) const strengthsQuery = ` SELECT target_name, modifier FROM augments WHERE monster_id = $1 `; const strengths = await executeQuery(dbPool, strengthsQuery, [monsterId]); // Get monster's weaknesses (hindrances) const weaknessesQuery = ` SELECT target_name, modifier FROM hindrances WHERE monster_id = $1 `; const weaknesses = await executeQuery(dbPool, weaknessesQuery, [monsterId]); // Organize abilities by keyword const keywordAbilities = {}; abilities.forEach(item => { if (!keywordAbilities[item.keyword_name]) { keywordAbilities[item.keyword_name] = { name: item.keyword_name, rating: item.rating, abilities: [] }; } keywordAbilities[item.keyword_name].abilities.push({ name: item.ability_name, mastery: item.mastery_value }); }); // Format the response return { content: [{ type: 'text', text: JSON.stringify({ id: monster.monster_id, name: monster.name, category: monster.category, habitat: monster.habitat, rarity: monster.rarity, discovery: monster.discovery, physicalAttributes: { height: monster.height, weight: monster.weight, appearance: monster.appearance }, powers: { primary: monster.primary_power, secondary: monster.secondary_power, special: monster.special_ability }, keywords: Object.values(keywordAbilities), flaws: flaws.map(flaw => ({ name: flaw.flaw_name, rating: flaw.rating })), strengths: strengths.map(strength => ({ target: strength.target_name, modifier: strength.modifier })), weaknesses: weaknesses.map(weakness => ({ target: weakness.target_name, modifier: weakness.modifier })) }) }] }; } catch (error) { logger.error(`Error in getMonsterById: ${error.message}`); logger.error(error.stack); throw new Error(`Failed to retrieve monster details: ${error.message}`); } } /** * Get a list of all available habitats in the database * * @returns {Promise<Array>} List of all habitat names */ export async function getHabitats() { try { if (!dbPool) { throw new Error('Database pool not initialized. Call initialize() first.'); } logger.info('getHabitats called'); // Query to get distinct habitats const query = ` SELECT DISTINCT habitat FROM monsters WHERE habitat IS NOT NULL ORDER BY habitat ASC `; const results = await executeQuery(dbPool, query); // Extract habitat names const habitats = results.map(row => row.habitat); logger.info(`getHabitats returning ${habitats.length} habitats`); // Format the response return { content: [{ type: 'text', text: JSON.stringify(habitats) }] }; } catch (error) { logger.error(`Error in getHabitats: ${error.message}`); logger.error(error.stack); throw new Error(`Failed to retrieve habitats: ${error.message}`); } } /** * Get monsters by habitat (exact match only) * * @param {Object} params - Tool parameters * @param {string} params.habitat - Exact habitat name * @param {number} [params.limit=10] - Maximum number of results to return * @returns {Promise<Object>} Monsters matching the habitat */ export async function getMonsterByHabitat(params) { try { if (!dbPool) { throw new Error('Database pool not initialized. Call initialize() first.'); } logger.info(`getMonsterByHabitat called with params: ${JSON.stringify(params)}`); const { habitat, limit = 10 } = params; if (!habitat) { throw new Error('Habitat parameter is required'); } // Query monsters with the exact habitat name const query = ` SELECT m.monster_id, m.name, m.category, m.habitat, m.rarity, m.primary_power, m.secondary_power, m.special_ability FROM monsters m WHERE m.habitat = $1 ORDER BY m.name ASC LIMIT $2 `; const monsters = await executeQuery(dbPool, query, [habitat, limit]); logger.info(`getMonsterByHabitat returning ${monsters.length} monsters for habitat "${habitat}"`); // Format the response return { content: [{ type: 'text', text: JSON.stringify({ monsters: monsters.map(monster => ({ id: monster.monster_id, name: monster.name, category: monster.category, habitat: monster.habitat, rarity: monster.rarity, powers: { primary: monster.primary_power, secondary: monster.secondary_power, special: monster.special_ability } })), habitat: habitat, count: monsters.length }) }] }; } catch (error) { logger.error(`Error in getMonsterByHabitat: ${error.message}`); logger.error(error.stack); throw new Error(`Failed to retrieve monsters by habitat: ${error.message}`); } } /** * Get a monster by its name (partial match) * * @param {Object} params - Tool parameters * @param {string} params.name - Name of the monster to search for (can be partial) * @returns {Promise<Object>} Monster information if found */ export async function getMonsterByName(params) { try { if (!dbPool) { throw new Error('Database pool not initialized. Call initialize() first.'); } logger.info(`getMonsterByName called with params: ${JSON.stringify(params)}`); const { name } = params; if (!name) { throw new Error('Monster name is required'); } // Simple partial match query (case insensitive) const query = ` SELECT m.monster_id, m.name, m.category, m.habitat, m.rarity, m.primary_power, m.secondary_power, m.special_ability FROM monsters m WHERE LOWER(m.name) LIKE LOWER($1) ORDER BY m.name ASC LIMIT 5 `; const monsters = await executeQuery(dbPool, query, [`%${name}%`]); if (monsters.length === 0) { logger.info(`No monsters found with name: ${name}`); return { content: [{ type: 'text', text: JSON.stringify({ found: false, message: `No monsters found with name: ${name}` }) }] }; } // Format the response for the matches logger.info(`Found ${monsters.length} monsters matching name: ${name}`); return { content: [{ type: 'text', text: JSON.stringify({ found: true, count: monsters.length, monsters: monsters.map(monster => ({ id: monster.monster_id, name: monster.name, category: monster.category, habitat: monster.habitat, rarity: monster.rarity, powers: { primary: monster.primary_power, secondary: monster.secondary_power, special: monster.special_ability } })) }) }] }; } catch (error) { logger.error(`Error in getMonsterByName: ${error.message}`); logger.error(error.stack); throw new Error(`Failed to retrieve monster by name: ${error.message}`); } }

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/LostInBrittany/RAGmonsters-mcp-pg'

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