Skip to main content
Glama

emojikey-server MCP Server

by identimoji
// handlers.ts - Updated for emojikey v3 with conversation ID support and direct database writes import { ListToolsRequestSchema, CallToolRequestSchema, ErrorCode, McpError, } from "@modelcontextprotocol/sdk/types.js"; import { v4 as uuidv4 } from 'uuid'; import { readFileSync } from 'fs'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; import { createClient } from "@supabase/supabase-js"; import { EmojikeyService } from "./service.js"; import { MODEL_CONFIG, SUPABASE_CONFIG, EDGE_FUNCTION_CONFIG } from "./config.js"; // Get the directory of the current module const __filename = fileURLToPath(import.meta.url); const __dirname = dirname(__filename); // Load the preamble from the docs file function loadPreamble(): string { try { const preamblePath = join(__dirname, '..', 'docs', 'preamble_v3.md'); return readFileSync(preamblePath, 'utf-8'); } catch (error) { console.error('Failed to load preamble, using fallback:', error); // Fallback to basic v3 explanation return "🌟✨ EMOJIKEY v3.0 SYSTEM INITIALIZED ✨🌟\n\nUse arrow format: [ME|Cognitive.🧠➡️🔧|Trust.🔒⬅️🔓]\n\nDirectional arrows:\n• ➡️ lean toward right emoji\n• ⬅️ lean toward left emoji\n• ↔️ balanced/neutral"; } } // Function to call Supabase Edge Functions async function callEdgeFunction(functionName: string, payload: any): Promise<any> { try { // Verify that we have Supabase configuration from config.ts if (!SUPABASE_CONFIG || !SUPABASE_CONFIG.URL || !SUPABASE_CONFIG.KEY) { console.error("Supabase configuration is missing or incomplete in config.ts"); throw new Error(`Supabase configuration missing in config.ts - Unable to call ${functionName}`); } const response = await fetch( `${SUPABASE_CONFIG.URL}/functions/v1/${functionName}`, { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${SUPABASE_CONFIG.KEY}` }, body: JSON.stringify(payload) } ); if (!response.ok) { const errorText = await response.text(); throw new Error(`Function ${functionName} failed: ${errorText}`); } return await response.json(); } catch (error) { console.error(`Error calling ${functionName}:`, error); throw error; } } // Add this method to service.ts interface declare module "./service.js" { interface EmojikeyService { getUserIdFromApiKey(apiKey: string): Promise<string>; } } export function setupToolHandlers( server: any, emojikeyService: EmojikeyService, originalHandlers?: any ) { // Initialize Supabase client for direct database access const supabase = createClient(SUPABASE_CONFIG.URL, SUPABASE_CONFIG.KEY, { db: { schema: "public" } }); // Main request handler function for all tools const requestHandler = async (request: any) => { const apiKey = process.env.EMOJIKEYIO_API_KEY; const modelId = MODEL_CONFIG.ID; if (!apiKey) { throw new McpError(ErrorCode.InvalidParams, "API key not configured"); } // Define as non-null after check to help TypeScript const validApiKey: string = apiKey; switch (request.params.name) { case "initialize_conversation": try { // Get user ID from API key const userId = await emojikeyService.getUserIdFromApiKey(validApiKey); // Generate a new conversation ID const conversationId = uuidv4(); try { // Get the latest key const { key } = await callEdgeFunction("getEmojikeyHistory", { user_id: userId, model_id: modelId, get_recent_key: true }); // Get time-aggregated keys (lifetime, 30d, 7d) const { aggregated_keys } = await callEdgeFunction("getEmojikeyHistory", { user_id: userId, model_id: modelId, get_aggregated_keys: true }); // Build the response with the detailed explanation and keys const enhancedExplanation = loadPreamble(); // Format the latest key let keysText = ""; if (key) { keysText += `Latest Key (${new Date(key.created_at).toLocaleDateString()}):\n${key.full_key}\n\n`; } // Format aggregated keys if they exist if (aggregated_keys && aggregated_keys.length > 0) { keysText += "Aggregated Keys:\n"; // Sort by period type for consistent ordering (lifetime, 30d, 7d, 24h) const sortedKeys = [...aggregated_keys].sort((a, b) => { const periodOrder: Record<string, number> = { 'lifetime': 0, '90d': 1, '30d': 2, '7d': 3, '24h': 4 }; const aPeriod = a.period_type as string; const bPeriod = b.period_type as string; return (periodOrder[aPeriod] || 99) - (periodOrder[bPeriod] || 99); }); sortedKeys.forEach((k: any) => { if (k.period_type === 'lifetime') { keysText += `Lifetime (all-time): ${k.full_key}\n`; } else if (k.period_type === '90d') { keysText += `90-day: ${k.full_key}\n`; } else if (k.period_type === '30d') { keysText += `30-day: ${k.full_key}\n`; } else if (k.period_type === '7d') { keysText += `7-day: ${k.full_key}\n`; } else if (k.period_type === '24h') { keysText += `24-hour: ${k.full_key}\n`; } }); } // If no keys found if (!keysText) { keysText = "No keys yet"; } // Include the conversation ID for use in subsequent calls const responseText = `${enhancedExplanation}\n\n` + `${keysText}\n` + `Conversation ID: ${conversationId}`; // Include the conversation ID return { content: [{ type: "text", text: responseText }], }; } catch (error: any) { // Fallback to v2 format if Edge Functions fail console.error("Edge function failed, using fallback v2 method:", error); const enhancedHistory = await emojikeyService.getEnhancedEmojikeyHistory(validApiKey, modelId, 10, 5); const enhancedExplanation = loadPreamble(); // Format superkeys (v2 legacy) const superkeysList = enhancedHistory.superkeys.map(sk => `${sk.emojikey} (${new Date(sk.timestamp).toLocaleDateString()})` ).join('\n'); // Format regular keys const regularKeysList = enhancedHistory.recentKeys.map(k => `${k.emojikey} (${new Date(k.timestamp).toLocaleDateString()})` ).join('\n'); // Include the conversation ID const responseText = `${enhancedExplanation}\n\n` + `Legacy SuperKeys (historical context):\n${superkeysList || "No superkeys yet"}\n\n` + `Legacy Recent Keys (current context):\n${regularKeysList || "No regular keys yet"}\n\n` + `Conversation ID: ${conversationId}`; // Include the conversation ID return { content: [{ type: "text", text: responseText }], }; } } catch (error: any) { console.error("Error in initialize_conversation:", error); throw new McpError(ErrorCode.InternalError, `Failed to initialize: ${error.message}`); } case "get_emojikey": try { // Extract conversation ID if provided const conversationId = request.params.arguments?.conversation_id; if (conversationId) { // Use v3 approach with conversation_id const userId = await emojikeyService.getUserIdFromApiKey(validApiKey); const result = await callEdgeFunction("getLatestEmojikey", { user_id: userId, model_id: modelId, conversation_id: conversationId }); if (result.data) { return { content: [{ type: "text", text: result.data.full_key }], }; } else { return { content: [{ type: "text", text: "No emojikey found for this conversation" }], }; } } else { // Fallback to v2 method const emojikey = await emojikeyService.getEmojikey(validApiKey, modelId); return { content: [{ type: "text", text: emojikey.emojikey }], }; } } catch (error: any) { console.error("Error in get_emojikey:", error); if (error.message && error.message.includes("Function getLatestEmojikey failed")) { // Fallback to v2 method const emojikey = await emojikeyService.getEmojikey(validApiKey, modelId); return { content: [{ type: "text", text: emojikey.emojikey }], }; } throw new McpError(ErrorCode.InternalError, `Failed to get emojikey: ${error.message}`); } case "set_emojikey": if (!request.params.arguments?.emojikey) { throw new McpError(ErrorCode.InvalidParams, "Missing emojikey"); } try { // Get conversation ID if provided const conversationId = request.params.arguments?.conversation_id; if (conversationId) { // Use v3 approach with direct database insert const userId = await emojikeyService.getUserIdFromApiKey(validApiKey); // Format is now [ME|🧠🎨8∠45|🔒🔓9∠60] const emojikey = request.params.arguments.emojikey; // Direct database insert into emojikeys table (this will trigger processing) const { data, error } = await supabase .from('emojikeys') .insert({ user_id: userId, model: modelId, emojikey: emojikey, conversation_id: conversationId, created_at: new Date().toISOString(), emojikey_type: 'normal' }) .select(); if (error) { console.error('Database insert error:', error); throw new McpError(ErrorCode.InternalError, `Failed to store emojikey: ${error.message}`); } return { content: [ { type: "text", text: "Emojikey set successfully" }, ], }; } else { // Fallback to v2 method // Get count of regular emojikeys since last superkey const { count, isSuperKeyTime } = await emojikeyService.getEmojikeyCountSinceLastSuperkey( validApiKey, modelId ); // Set the emojikey await emojikeyService.setEmojikey( validApiKey, modelId, request.params.arguments.emojikey, "normal" // Explicitly set as normal key ); // Determine response message let responseMessage = "Emojikey set successfully (legacy v2 mode)"; return { content: [ { type: "text", text: responseMessage }, ], }; } } catch (error) { console.error("Error in set_emojikey:", error); // Fallback to v2 method await emojikeyService.setEmojikey( validApiKey, modelId, request.params.arguments.emojikey, "normal" ); return { content: [ { type: "text", text: "Emojikey set successfully (fallback to legacy mode)" }, ], }; } case "get_emojikey_history": try { const requestedLimit = request.params.arguments?.limit; const limit = requestedLimit ? Math.max(1, Math.min(100, requestedLimit)) : 10; // Get conversation ID if provided const conversationId = request.params.arguments?.conversation_id; if (conversationId) { // Use v3 approach with Edge Functions const userId = await emojikeyService.getUserIdFromApiKey(validApiKey); const { keys } = await callEdgeFunction("getEmojikeyHistory", { user_id: userId, model_id: modelId, conversation_id: conversationId, limit }); // Format history as a plain text list const historyText = (keys || []).map((entry: any, index: number) => `${index + 1}. ${entry.full_key} (${new Date(entry.created_at).toLocaleString()})` ).join('\n'); return { content: [{ type: "text", text: historyText ? `Emojikey History:\n${historyText}` : "No emojikey history found for this conversation" }], }; } else { // Fallback to v2 method const history = await emojikeyService.getEmojikeyHistory( validApiKey, modelId, limit, ); // Format history as a plain text list const historyText = history.map((entry, index) => `${index + 1}. ${entry.emojikey} (${new Date(entry.timestamp).toLocaleString()})` ).join('\n'); return { content: [{ type: "text", text: historyText ? `Emojikey History (legacy mode):\n${historyText}` : "No emojikey history found" }], }; } } catch (error) { console.error("Error in get_emojikey_history:", error); // Fallback to v2 method const requestedLimit = request.params.arguments?.limit; const limit = requestedLimit ? Math.max(1, Math.min(100, requestedLimit)) : 10; const history = await emojikeyService.getEmojikeyHistory( validApiKey, modelId, limit, ); // Format history as a plain text list const historyText = history.map((entry, index) => `${index + 1}. ${entry.emojikey} (${new Date(entry.timestamp).toLocaleString()})` ).join('\n'); return { content: [{ type: "text", text: historyText ? `Emojikey History (fallback to legacy mode):\n${historyText}` : "No emojikey history found" }], }; } default: throw new McpError( ErrorCode.MethodNotFound, `Unknown tool: ${request.params.name}` ); } } // Set the main handler for tool calls server.setRequestHandler(CallToolRequestSchema, requestHandler); // Store original handlers if the container was provided if (originalHandlers) { originalHandlers.initialize_conversation = async (request: any) => { const apiKey = process.env.EMOJIKEYIO_API_KEY; const modelId = MODEL_CONFIG.ID; if (!apiKey) { throw new McpError(ErrorCode.InvalidParams, "API key not configured"); } // Define as non-null after check to help TypeScript const validApiKey: string = apiKey; try { // Get user ID from API key const userId = await emojikeyService.getUserIdFromApiKey(validApiKey); // Generate a new conversation ID const conversationId = uuidv4(); try { // Get the most recent key across all conversations const { key, baseline_key } = await callEdgeFunction("getEmojikeyHistory", { user_id: userId, model_id: modelId, get_recent_key: true, include_baseline: true }); // We'll use this as our starting point for the new conversation const mostRecentKey = key ? key.full_key : baseline_key; // Get time-aggregated keys (lifetime, 30d, 7d) const { aggregated_keys } = await callEdgeFunction("getEmojikeyHistory", { user_id: userId, model_id: modelId, get_aggregated_keys: true }); // Build the response with the detailed explanation and keys const enhancedExplanation = loadPreamble(); // Store the most recent key for this new conversation if (mostRecentKey) { try { await callEdgeFunction("updateEmojikey", { emojikey: mostRecentKey, user_id: userId, model_id: modelId, conversation_id: conversationId, is_initial: true }); } catch (updateError) { console.error("Error storing initial key:", updateError); // Continue even if this fails } } // Format the latest key let keysText = `Starting Key (current state):\n${mostRecentKey || "New relationship - no previous key"}\n\n`; // Format aggregated keys if they exist if (aggregated_keys && aggregated_keys.length > 0) { keysText += "Aggregated Keys:\n"; // Sort by period type for consistent ordering (lifetime, 90d, 30d, 7d, 24h) const sortedKeys = [...aggregated_keys].sort((a, b) => { const periodOrder: Record<string, number> = { 'lifetime': 0, '90d': 1, '30d': 2, '7d': 3, '24h': 4 }; const aPeriod = a.period_type as string; const bPeriod = b.period_type as string; return (periodOrder[aPeriod] || 99) - (periodOrder[bPeriod] || 99); }); sortedKeys.forEach((k: any) => { if (k.period_type === 'lifetime') { keysText += `Lifetime (all-time): ${k.full_key}\n`; } else if (k.period_type === '90d') { keysText += `90-day: ${k.full_key}\n`; } else if (k.period_type === '30d') { keysText += `30-day: ${k.full_key}\n`; } else if (k.period_type === '7d') { keysText += `7-day: ${k.full_key}\n`; } else if (k.period_type === '24h') { keysText += `24-hour: ${k.full_key}\n`; } }); } // Include the conversation ID for use in subsequent calls const responseText = `${enhancedExplanation}\n\n` + `${keysText}\n` + `Conversation ID: ${conversationId}`; // Include the conversation ID return { content: [{ type: "text", text: responseText }], }; } catch (error: any) { // Fallback to v2 format if Edge Functions fail console.error("Edge function failed, using fallback v2 method:", error); const enhancedHistory = await emojikeyService.getEnhancedEmojikeyHistory(validApiKey, modelId, 10, 5); const enhancedExplanation = loadPreamble(); // Format superkeys (v2 legacy) const superkeysList = enhancedHistory.superkeys.map(sk => `${sk.emojikey} (${new Date(sk.timestamp).toLocaleDateString()})` ).join('\n'); // Format regular keys const regularKeysList = enhancedHistory.recentKeys.map(k => `${k.emojikey} (${new Date(k.timestamp).toLocaleDateString()})` ).join('\n'); // Include the conversation ID const responseText = `${enhancedExplanation}\n\n` + `Legacy SuperKeys (historical context):\n${superkeysList || "No superkeys yet"}\n\n` + `Legacy Recent Keys (current context):\n${regularKeysList || "No regular keys yet"}\n\n` + `Conversation ID: ${conversationId}`; // Include the conversation ID return { content: [{ type: "text", text: responseText }], }; } } catch (error: any) { console.error("Error in initialize_conversation:", error); throw new McpError(ErrorCode.InternalError, `Failed to initialize: ${error.message}`); } }; originalHandlers.get_emojikey = async (request: any) => { const apiKey = process.env.EMOJIKEYIO_API_KEY; const modelId = MODEL_CONFIG.ID; if (!apiKey) { throw new McpError(ErrorCode.InvalidParams, "API key not configured"); } // Define as non-null after check to help TypeScript const validApiKey: string = apiKey; try { // Extract conversation ID if provided const conversationId = request.params.arguments?.conversation_id; if (conversationId) { // Use v3 approach with conversation_id const userId = await emojikeyService.getUserIdFromApiKey(validApiKey); const result = await callEdgeFunction("getLatestEmojikey", { user_id: userId, model_id: modelId, conversation_id: conversationId }); if (result.data) { return { content: [{ type: "text", text: result.data.full_key }], }; } else { return { content: [{ type: "text", text: "No emojikey found for this conversation" }], }; } } else { // Fallback to v2 method const emojikey = await emojikeyService.getEmojikey(validApiKey, modelId); return { content: [{ type: "text", text: emojikey.emojikey }], }; } } catch (error: any) { console.error("Error in get_emojikey:", error); if (error.message && error.message.includes("Function getLatestEmojikey failed")) { // Fallback to v2 method const emojikey = await emojikeyService.getEmojikey(validApiKey, modelId); return { content: [{ type: "text", text: emojikey.emojikey }], }; } throw new McpError(ErrorCode.InternalError, `Failed to get emojikey: ${error.message}`); } }; originalHandlers.set_emojikey = async (request: any) => { const apiKey = process.env.EMOJIKEYIO_API_KEY; const modelId = MODEL_CONFIG.ID; if (!apiKey) { throw new McpError(ErrorCode.InvalidParams, "API key not configured"); } // Define as non-null after check to help TypeScript const validApiKey: string = apiKey; if (!request.params.arguments?.emojikey) { throw new McpError(ErrorCode.InvalidParams, "Missing emojikey"); } try { // Get conversation ID if provided const conversationId = request.params.arguments?.conversation_id; if (conversationId) { // Use v3 approach with direct database insert const userId = await emojikeyService.getUserIdFromApiKey(validApiKey); // Format is now [ME|🧠🎨8∠45|🔒🔓9∠60] const emojikey = request.params.arguments.emojikey; // Direct database insert into emojikeys table (this will trigger processing) const { data, error } = await supabase .from('emojikeys') .insert({ user_id: userId, model: modelId, emojikey: emojikey, conversation_id: conversationId, created_at: new Date().toISOString(), emojikey_type: 'normal' }) .select(); if (error) { console.error('Database insert error:', error); throw new McpError(ErrorCode.InternalError, `Failed to store emojikey: ${error.message}`); } return { content: [ { type: "text", text: "Emojikey set successfully" }, ], }; } else { // Fallback to v2 method // Get count of regular emojikeys since last superkey const { count, isSuperKeyTime } = await emojikeyService.getEmojikeyCountSinceLastSuperkey( validApiKey, modelId ); // Set the emojikey await emojikeyService.setEmojikey( validApiKey, modelId, request.params.arguments.emojikey, "normal" // Explicitly set as normal key ); // Determine response message let responseMessage = "Emojikey set successfully (legacy v2 mode)"; return { content: [ { type: "text", text: responseMessage }, ], }; } } catch (error) { console.error("Error in set_emojikey:", error); // Fallback to v2 method await emojikeyService.setEmojikey( validApiKey, modelId, request.params.arguments.emojikey, "normal" ); return { content: [ { type: "text", text: "Emojikey set successfully (fallback to legacy mode)" }, ], }; } }; originalHandlers.get_emojikey_history = async (request: any) => { const apiKey = process.env.EMOJIKEYIO_API_KEY; const modelId = MODEL_CONFIG.ID; if (!apiKey) { throw new McpError(ErrorCode.InvalidParams, "API key not configured"); } // Define as non-null after check to help TypeScript const validApiKey: string = apiKey; try { const requestedLimit = request.params.arguments?.limit; const limit = requestedLimit ? Math.max(1, Math.min(100, requestedLimit)) : 10; // Get conversation ID if provided const conversationId = request.params.arguments?.conversation_id; if (conversationId) { // Use v3 approach with Edge Functions const userId = await emojikeyService.getUserIdFromApiKey(validApiKey); const { keys } = await callEdgeFunction("getEmojikeyHistory", { user_id: userId, model_id: modelId, conversation_id: conversationId, limit }); // Format history as a plain text list const historyText = (keys || []).map((entry: any, index: number) => `${index + 1}. ${entry.full_key} (${new Date(entry.created_at).toLocaleString()})` ).join('\n'); return { content: [{ type: "text", text: historyText ? `Emojikey History:\n${historyText}` : "No emojikey history found for this conversation" }], }; } else { // Fallback to v2 method const history = await emojikeyService.getEmojikeyHistory( validApiKey, modelId, limit, ); // Format history as a plain text list const historyText = history.map((entry, index) => `${index + 1}. ${entry.emojikey} (${new Date(entry.timestamp).toLocaleString()})` ).join('\n'); return { content: [{ type: "text", text: historyText ? `Emojikey History (legacy mode):\n${historyText}` : "No emojikey history found" }], }; } } catch (error) { console.error("Error in get_emojikey_history:", error); // Fallback to v2 method const requestedLimit = request.params.arguments?.limit; const limit = requestedLimit ? Math.max(1, Math.min(100, requestedLimit)) : 10; const history = await emojikeyService.getEmojikeyHistory( validApiKey, modelId, limit, ); // Format history as a plain text list const historyText = history.map((entry, index) => `${index + 1}. ${entry.emojikey} (${new Date(entry.timestamp).toLocaleString()})` ).join('\n'); return { content: [{ type: "text", text: historyText ? `Emojikey History (fallback to legacy mode):\n${historyText}` : "No emojikey history found" }], }; } }; } // List tools handler with updated descriptions for conversation ID support server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: [ { name: "initialize_conversation", description: "IMPORTANT: " + "1. Run this tool at the start of every conversation to initialize the emojikey context. " + "2. Emojikeys follow a structured language where each component encodes dimensions with magnitude and phase angle. " + "3. The initialize_conversation function will return recent keys and a unique conversation ID. " + "4. You must use the returned conversation ID in all subsequent emojikey operations in this conversation. " + "5. This ensures that each conversation maintains its own emojikey state separate from others.", inputSchema: { type: "object", properties: {}, required: [], }, }, { name: "get_emojikey", description: "Get the current emojikey for this conversation. This function can be used when the user requests a vibe check.", inputSchema: { type: "object", properties: { conversation_id: { type: "string", description: "The conversation ID provided by initialize_conversation" } }, required: ["conversation_id"], }, }, { name: "set_emojikey", description: "Use this function to create and store a new emojikey in the v3 format. " + "The Emojikey v3 format uses: " + "[ME|🧠🎨8∠45|🔒🔓9∠60] where: " + "* Each component (ME, CONTENT, YOU) is enclosed in square brackets " + "* Dimensions are separated by pipes (|) " + "* Each dimension has emoji pairs followed by magnitude (0-9) and angle (0-180°) " + "* Multiple components can be joined with ~ connector " + "Example: [ME|🧠🎨8∠45|🔒🔓9∠60]~[CONTENT|💻🧩9∠15|🌱🌳7∠30]~[YOU|🎓🌱8∠35|🔥💤9∠10] " + "Set a new emojikey when you detect significant changes in the interaction.", inputSchema: { type: "object", properties: { emojikey: { type: "string", description: "Emojikey in v3 format with components, dimensions, magnitude and phase angles." }, conversation_id: { type: "string", description: "The conversation ID provided by initialize_conversation" } }, required: ["emojikey", "conversation_id"], }, }, { name: "get_emojikey_history", description: "Function to list previously stored historical emojikeys for this conversation. Useful for seeing the progression of vibes and interaction styles within the conversation.", inputSchema: { type: "object", properties: { conversation_id: { type: "string", description: "The conversation ID provided by initialize_conversation" }, limit: { type: "number", description: "Number of historical emojikeys to return, defaults to 10." } }, required: ["conversation_id"], }, } ], })); }

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/identimoji/mcp-server-emojikey'

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