Skip to main content
Glama

Promptopia MCP

by lumile
MIT License
18
2
prompts.handler.ts5.9 kB
import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { PromptsService } from '../services/prompts.service.js' import { Prompt, McpPrompt, McpGetPromptResult, isMultiMessagePrompt, isSingleContentPrompt } from '../types/index.js' import path from 'path' import { fileURLToPath } from 'url' import { watch } from 'fs/promises' import { handleServiceError } from '../utils/error-handler.js' import { PROMPTS_CONFIG } from '../config/index.js' const __filename = fileURLToPath(import.meta.url) const __dirname = path.dirname(__filename) export class PromptsHandler { private readonly promptsService: PromptsService private readonly server: Server private fileWatcher: AsyncIterable<unknown> | null = null private abortController: AbortController | null = null constructor(server: Server, promptsService: PromptsService) { this.server = server this.promptsService = promptsService } async listPrompts(): Promise<{ prompts: McpPrompt[] }> { try { const prompts = await this.promptsService.listPrompts() // Convert internal prompts to MCP prompt format const mcpPrompts = prompts.map(prompt => this.toMcpPrompt(prompt)) return { prompts: mcpPrompts } } catch (error) { const mcpError = handleServiceError(error) throw mcpError } } async getPrompt(name: string, args?: Record<string, string>): Promise<McpGetPromptResult> { try { // For now, we'll use the first prompt that matches the name // In a real implementation, we might want to use IDs instead const prompts = await this.promptsService.listPrompts() const prompt = prompts.find(p => p.name === name) if (!prompt) { throw new Error(`Prompt not found: ${name}`) } // If arguments are provided, apply them to the prompt if (args) { const result = await this.promptsService.applyPrompt({ id: prompt.id, variables: args }) if (isMultiMessagePrompt(prompt) && result.messages) { // Return multi-message format return { messages: result.messages.map(message => ({ role: message.role, content: { type: message.content.type, text: message.content.text || message.content.image } })) } } else { // Return single message format for single content prompts return { messages: [{ role: 'user', content: { type: 'text', text: result.result } }] } } } // If no arguments, return the prompt content as is if (isMultiMessagePrompt(prompt)) { // Return multi-message format return { messages: prompt.messages.map(message => ({ role: message.role, content: { type: message.content.type, text: message.content.text || message.content.image } })) } } else { // Return single content format return { messages: [{ role: 'user', content: { type: 'text', text: prompt.content } }] } } } catch (error) { const mcpError = handleServiceError(error) throw mcpError } } async setupFileWatcher(): Promise<void> { if (this.abortController) { // Clean up existing watcher if any this.abortController.abort() } this.abortController = new AbortController() const { signal } = this.abortController try { const promptsDir = path.join(process.cwd(), PROMPTS_CONFIG.promptsDir) // Watch for changes in the prompts directory this.fileWatcher = watch(promptsDir, { recursive: true, // Watch subdirectories as well signal }) console.error(`Watching for changes in prompts directory: ${promptsDir}`) // Process file changes for await (const event of this.fileWatcher) { const fsEvent = event as { eventType: string; filename?: string } // Send notification for any file system event that might affect the prompts list if (fsEvent.eventType === 'change' || fsEvent.eventType === 'rename') { // Log the change for debugging console.error(`Prompts directory change detected: ${fsEvent.eventType} - ${fsEvent.filename || 'unknown file'}`) // Send the notification this.sendListChangedNotification() } } } catch (error) { if ((error as Error).name !== 'AbortError') { console.error('Error watching prompts directory:', error) } } } sendListChangedNotification(): void { try { // Send notification using the server's notification method // Format according to MCP specification this.server.notification({ method: 'notifications/prompts/list_changed', params: {} // Empty params object as per specification }) console.error('Sent prompts/list_changed notification') } catch (error) { console.error('Failed to send prompts list changed notification:', error) } } // Convert internal Prompt to MCP Prompt format private toMcpPrompt(prompt: Prompt): McpPrompt { const description = isMultiMessagePrompt(prompt) ? prompt.description : prompt.description return { name: prompt.name, description: description || undefined, arguments: prompt.variables.map(varName => ({ name: varName, required: true })) } } // Clean up resources async dispose(): Promise<void> { if (this.abortController) { this.abortController.abort() this.abortController = null } this.fileWatcher = null } }

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/lumile/promptopia-mcp'

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