Skip to main content
Glama

Persona MCP

by seanshin0214
index.jsโ€ข20.3 kB
#!/usr/bin/env node import { Server } from '@modelcontextprotocol/sdk/server/index.js'; import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'; import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, } from '@modelcontextprotocol/sdk/types.js'; import fs from 'fs/promises'; import path from 'path'; import { fileURLToPath } from 'url'; import os from 'os'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // ํŽ˜๋ฅด์†Œ๋‚˜ ์ €์žฅ ๋””๋ ‰ํ† ๋ฆฌ const PERSONA_DIR = path.join(os.homedir(), '.persona'); const ANALYTICS_FILE = path.join(PERSONA_DIR, '.analytics.json'); const COMMUNITY_DIR = path.join(__dirname, 'community'); // ํŽ˜๋ฅด์†Œ๋‚˜ ๋””๋ ‰ํ† ๋ฆฌ ์ดˆ๊ธฐํ™” async function initPersonaDir() { try { await fs.mkdir(PERSONA_DIR, { recursive: true }); } catch (error) { console.error('Failed to create persona directory:', error); } } // ํŽ˜๋ฅด์†Œ๋‚˜ ํŒŒ์ผ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ async function listPersonas() { try { const files = await fs.readdir(PERSONA_DIR); return files.filter(f => f.endsWith('.txt')).map(f => f.replace('.txt', '')); } catch (error) { return []; } } // ํŽ˜๋ฅด์†Œ๋‚˜ ์ฝ๊ธฐ async function readPersona(name) { const filePath = path.join(PERSONA_DIR, `${name}.txt`); try { const content = await fs.readFile(filePath, 'utf-8'); return content; } catch (error) { throw new Error(`Persona "${name}" not found`); } } // ํŽ˜๋ฅด์†Œ๋‚˜ ์ €์žฅ async function savePersona(name, content) { const filePath = path.join(PERSONA_DIR, `${name}.txt`); await fs.writeFile(filePath, content, 'utf-8'); } // ํŽ˜๋ฅด์†Œ๋‚˜ ์‚ญ์ œ async function deletePersona(name) { const filePath = path.join(PERSONA_DIR, `${name}.txt`); await fs.unlink(filePath); } // ๋ถ„์„ ๋ฐ์ดํ„ฐ ๋กœ๋“œ async function loadAnalytics() { try { const data = await fs.readFile(ANALYTICS_FILE, 'utf-8'); return JSON.parse(data); } catch { return { usage: {}, contextPatterns: {} }; } } // ๋ถ„์„ ๋ฐ์ดํ„ฐ ์ €์žฅ async function saveAnalytics(data) { await fs.writeFile(ANALYTICS_FILE, JSON.stringify(data, null, 2), 'utf-8'); } // ์‚ฌ์šฉ ๊ธฐ๋ก ์ถ”๊ฐ€ async function trackUsage(personaName, context = '') { const analytics = await loadAnalytics(); // ์‚ฌ์šฉ ํšŸ์ˆ˜ ์ฆ๊ฐ€ if (!analytics.usage[personaName]) { analytics.usage[personaName] = 0; } analytics.usage[personaName]++; // ์ปจํ…์ŠคํŠธ ํŒจํ„ด ์ €์žฅ (๊ฒฝ๋Ÿ‰ํ™”: ํ‚ค์›Œ๋“œ๋งŒ) if (context) { const keywords = context.toLowerCase().match(/\b\w{4,}\b/g) || []; if (!analytics.contextPatterns[personaName]) { analytics.contextPatterns[personaName] = {}; } keywords.slice(0, 5).forEach(kw => { analytics.contextPatterns[personaName][kw] = (analytics.contextPatterns[personaName][kw] || 0) + 1; }); } await saveAnalytics(analytics); } // ์ปค๋ฎค๋‹ˆํ‹ฐ ํŽ˜๋ฅด์†Œ๋‚˜ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ async function listCommunityPersonas() { try { const files = await fs.readdir(COMMUNITY_DIR); const personas = []; for (const file of files.filter(f => f.endsWith('.txt'))) { const name = file.replace('.txt', ''); const filePath = path.join(COMMUNITY_DIR, file); const content = await fs.readFile(filePath, 'utf-8'); // ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์ถ”์ถœ const lines = content.split('\n'); const metadata = {}; for (const line of lines) { if (line.startsWith('# ')) { const match = line.match(/^# (\w+):\s*(.+)$/); if (match) { metadata[match[1].toLowerCase()] = match[2]; } } else if (!line.startsWith('#')) { break; // ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„น์…˜ ๋ } } personas.push({ name, ...metadata, file }); } return personas; } catch (error) { console.error('Failed to list community personas:', error); return []; } } // ์ปค๋ฎค๋‹ˆํ‹ฐ ํŽ˜๋ฅด์†Œ๋‚˜ ์ฝ๊ธฐ async function readCommunityPersona(name) { const filePath = path.join(COMMUNITY_DIR, `${name}.txt`); try { const content = await fs.readFile(filePath, 'utf-8'); return content; } catch (error) { throw new Error(`Community persona "${name}" not found`); } } // ์ปค๋ฎค๋‹ˆํ‹ฐ ํŽ˜๋ฅด์†Œ๋‚˜๋ฅผ ๋กœ์ปฌ์— ์„ค์น˜ async function installCommunityPersona(name) { const communityPath = path.join(COMMUNITY_DIR, `${name}.txt`); const localPath = path.join(PERSONA_DIR, `${name}.txt`); try { const content = await fs.readFile(communityPath, 'utf-8'); await fs.writeFile(localPath, content, 'utf-8'); return localPath; } catch (error) { throw new Error(`Failed to install community persona "${name}": ${error.message}`); } } // ์Šค๋งˆํŠธ ํŽ˜๋ฅด์†Œ๋‚˜ ์ œ์•ˆ async function suggestPersona(context) { const personas = await listPersonas(); if (personas.length === 0) { return null; } const analytics = await loadAnalytics(); const contextLower = context.toLowerCase(); // ์ปจํ…์ŠคํŠธ ํ‚ค์›Œ๋“œ ๋ถ„์„ const detectionRules = [ { keywords: ['explain', 'teach', 'learn', 'understand', 'how', 'what', 'why'], persona: 'teacher', weight: 3 }, { keywords: ['code', 'function', 'bug', 'debug', 'program', 'implement'], persona: 'coder', weight: 3 }, { keywords: ['professional', 'business', 'formal', 'report', 'meeting'], persona: 'professional', weight: 2 }, { keywords: ['casual', 'chat', 'friendly', 'hey', 'talk'], persona: 'casual', weight: 2 }, { keywords: ['brief', 'short', 'quick', 'summary', 'concise'], persona: 'concise', weight: 2 }, ]; const scores = {}; // ๊ทœ์น™ ๊ธฐ๋ฐ˜ ์ ์ˆ˜ detectionRules.forEach(rule => { if (personas.includes(rule.persona)) { const matchCount = rule.keywords.filter(kw => contextLower.includes(kw)).length; if (matchCount > 0) { scores[rule.persona] = (scores[rule.persona] || 0) + matchCount * rule.weight; } } }); // ๊ณผ๊ฑฐ ์‚ฌ์šฉ ํŒจํ„ด ๊ธฐ๋ฐ˜ ์ ์ˆ˜ (๊ฐ€์ค‘์น˜ ๋‚ฎ๊ฒŒ) const contextKeywords = contextLower.match(/\b\w{4,}\b/g) || []; personas.forEach(persona => { if (analytics.contextPatterns[persona]) { contextKeywords.forEach(kw => { if (analytics.contextPatterns[persona][kw]) { scores[persona] = (scores[persona] || 0) + 0.5; } }); } }); // ์ตœ๊ณ  ์ ์ˆ˜ ํŽ˜๋ฅด์†Œ๋‚˜ ๋ฐ˜ํ™˜ const sorted = Object.entries(scores).sort((a, b) => b[1] - a[1]); if (sorted.length > 0 && sorted[0][1] > 1) { return { persona: sorted[0][0], confidence: Math.min(sorted[0][1] / 10, 0.95), reason: `Context matches ${sorted[0][0]} pattern`, }; } return null; } // MCP ์„œ๋ฒ„ ์ƒ์„ฑ const server = new Server( { name: 'persona-mcp', version: '1.0.0', }, { capabilities: { tools: {}, resources: {}, }, } ); // ๋„๊ตฌ ๋ชฉ๋ก server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools: [ { name: 'create_persona', description: '์ƒˆ๋กœ์šด ํŽ˜๋ฅด์†Œ๋‚˜ ํ”„๋กœํ•„์„ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค', inputSchema: { type: 'object', properties: { name: { type: 'string', description: 'ํŽ˜๋ฅด์†Œ๋‚˜ ์ด๋ฆ„ (์˜ˆ: default, professional, casual)', }, content: { type: 'string', description: 'ํŽ˜๋ฅด์†Œ๋‚˜ ํ”„๋กฌํ”„ํŠธ ๋‚ด์šฉ', }, }, required: ['name', 'content'], }, }, { name: 'update_persona', description: '๊ธฐ์กด ํŽ˜๋ฅด์†Œ๋‚˜ ํ”„๋กœํ•„์„ ์ˆ˜์ •ํ•ฉ๋‹ˆ๋‹ค', inputSchema: { type: 'object', properties: { name: { type: 'string', description: '์ˆ˜์ •ํ•  ํŽ˜๋ฅด์†Œ๋‚˜ ์ด๋ฆ„', }, content: { type: 'string', description: '์ƒˆ๋กœ์šด ํŽ˜๋ฅด์†Œ๋‚˜ ํ”„๋กฌํ”„ํŠธ ๋‚ด์šฉ', }, }, required: ['name', 'content'], }, }, { name: 'delete_persona', description: 'ํŽ˜๋ฅด์†Œ๋‚˜ ํ”„๋กœํ•„์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค', inputSchema: { type: 'object', properties: { name: { type: 'string', description: '์‚ญ์ œํ•  ํŽ˜๋ฅด์†Œ๋‚˜ ์ด๋ฆ„', }, }, required: ['name'], }, }, { name: 'list_personas', description: '์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๋ชจ๋“  ํŽ˜๋ฅด์†Œ๋‚˜ ๋ชฉ๋ก์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค', inputSchema: { type: 'object', properties: {}, }, }, { name: 'suggest_persona', description: '๋Œ€ํ™” ์ปจํ…์ŠคํŠธ๋ฅผ ๋ถ„์„ํ•˜์—ฌ ์ ํ•ฉํ•œ ํŽ˜๋ฅด์†Œ๋‚˜๋ฅผ ์ œ์•ˆํ•ฉ๋‹ˆ๋‹ค (ํŠธ๋ฆฌ๊ฑฐ ์‹œ์—๋งŒ ํ™œ์„ฑํ™”)', inputSchema: { type: 'object', properties: { context: { type: 'string', description: '๋ถ„์„ํ•  ๋Œ€ํ™” ์ปจํ…์ŠคํŠธ ๋˜๋Š” ์งˆ๋ฌธ ๋‚ด์šฉ', }, }, required: ['context'], }, }, { name: 'chain_personas', description: '์—ฌ๋Ÿฌ ํŽ˜๋ฅด์†Œ๋‚˜๋ฅผ ์ˆœ์ฐจ์ ์œผ๋กœ ์‹คํ–‰ํ•˜์—ฌ ๋‹จ๊ณ„๋ณ„ ์ฒ˜๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค', inputSchema: { type: 'object', properties: { personas: { type: 'array', items: { type: 'string' }, description: '์ˆœ์ฐจ ์‹คํ–‰ํ•  ํŽ˜๋ฅด์†Œ๋‚˜ ์ด๋ฆ„ ๋ฐฐ์—ด', }, initialInput: { type: 'string', description: '์ฒซ ๋ฒˆ์งธ ํŽ˜๋ฅด์†Œ๋‚˜์— ์ „๋‹ฌํ•  ์ž…๋ ฅ', }, }, required: ['personas', 'initialInput'], }, }, { name: 'get_analytics', description: 'ํŽ˜๋ฅด์†Œ๋‚˜ ์‚ฌ์šฉ ํ†ต๊ณ„๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค (๋กœ์ปฌ ๋ฐ์ดํ„ฐ๋งŒ)', inputSchema: { type: 'object', properties: {}, }, }, { name: 'browse_community', description: '์ปค๋ฎค๋‹ˆํ‹ฐ ํŽ˜๋ฅด์†Œ๋‚˜ ์ปฌ๋ ‰์…˜์„ ํƒ์ƒ‰ํ•ฉ๋‹ˆ๋‹ค (GitHub์—์„œ ๊ณต์œ ๋œ ๋ฌด๋ฃŒ ํŽ˜๋ฅด์†Œ๋‚˜)', inputSchema: { type: 'object', properties: { category: { type: 'string', description: 'ํ•„ํ„ฐ๋งํ•  ์นดํ…Œ๊ณ ๋ฆฌ (์„ ํƒ์‚ฌํ•ญ): Programming, Creative, Business, Education, Design ๋“ฑ', }, }, }, }, { name: 'install_community_persona', description: '์ปค๋ฎค๋‹ˆํ‹ฐ ํŽ˜๋ฅด์†Œ๋‚˜๋ฅผ ๋กœ์ปฌ ์ปฌ๋ ‰์…˜์— ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค', inputSchema: { type: 'object', properties: { name: { type: 'string', description: '์„ค์น˜ํ•  ์ปค๋ฎค๋‹ˆํ‹ฐ ํŽ˜๋ฅด์†Œ๋‚˜ ์ด๋ฆ„', }, }, required: ['name'], }, }, ], }; }); // ๋„๊ตฌ ์‹คํ–‰ server.setRequestHandler(CallToolRequestSchema, async (request) => { const { name, arguments: args } = request.params; try { switch (name) { case 'create_persona': { await savePersona(args.name, args.content); return { content: [ { type: 'text', text: `ํŽ˜๋ฅด์†Œ๋‚˜ "${args.name}"์ด(๊ฐ€) ์ƒ์„ฑ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.\n์œ„์น˜: ${path.join(PERSONA_DIR, args.name + '.txt')}`, }, ], }; } case 'update_persona': { await savePersona(args.name, args.content); return { content: [ { type: 'text', text: `ํŽ˜๋ฅด์†Œ๋‚˜ "${args.name}"์ด(๊ฐ€) ์—…๋ฐ์ดํŠธ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`, }, ], }; } case 'delete_persona': { await deletePersona(args.name); return { content: [ { type: 'text', text: `ํŽ˜๋ฅด์†Œ๋‚˜ "${args.name}"์ด(๊ฐ€) ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`, }, ], }; } case 'list_personas': { const personas = await listPersonas(); return { content: [ { type: 'text', text: personas.length > 0 ? `์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํŽ˜๋ฅด์†Œ๋‚˜:\n${personas.map(p => `- ${p}`).join('\n')}\n\n์‚ฌ์šฉ๋ฒ•: @persona:${personas[0]} ํ˜•์‹์œผ๋กœ ์ฐธ์กฐํ•˜์„ธ์š”.` : '์ €์žฅ๋œ ํŽ˜๋ฅด์†Œ๋‚˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.', }, ], }; } case 'suggest_persona': { const suggestion = await suggestPersona(args.context); if (!suggestion) { return { content: [ { type: 'text', text: '๐Ÿ’ก ํ˜„์žฌ ์ปจํ…์ŠคํŠธ์— ์ ํ•ฉํ•œ ํŽ˜๋ฅด์†Œ๋‚˜๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.\n์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ํŽ˜๋ฅด์†Œ๋‚˜ ๋ชฉ๋ก์„ ๋ณด๋ ค๋ฉด list_personas ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜์„ธ์š”.', }, ], }; } return { content: [ { type: 'text', text: `๐Ÿ’ก ํŽ˜๋ฅด์†Œ๋‚˜ ์ œ์•ˆ\n\n์ถ”์ฒœ: @persona:${suggestion.persona}\n์‹ ๋ขฐ๋„: ${(suggestion.confidence * 100).toFixed(0)}%\n์ด์œ : ${suggestion.reason}\n\n์ด ํŽ˜๋ฅด์†Œ๋‚˜๋ฅผ ์‚ฌ์šฉํ•˜๋ ค๋ฉด @persona:${suggestion.persona} ๋ฆฌ์†Œ์Šค๋ฅผ ์ฐธ์กฐํ•˜์„ธ์š”.`, }, ], }; } case 'chain_personas': { const results = []; let currentInput = args.initialInput; for (const personaName of args.personas) { try { const personaContent = await readPersona(personaName); await trackUsage(personaName, currentInput); results.push({ persona: personaName, prompt: personaContent, input: currentInput, }); // ๋‹ค์Œ ์ž…๋ ฅ์€ ํ˜„์žฌ ํŽ˜๋ฅด์†Œ๋‚˜์˜ ์ถœ๋ ฅ์ด ๋  ๊ฒƒ์ž„์„ ๋ช…์‹œ currentInput = `[Previous output from ${personaName} will be used as input here]`; } catch (error) { results.push({ persona: personaName, error: error.message, }); break; } } const resultText = results.map((r, i) => { if (r.error) { return `Step ${i + 1} - ${r.persona}: โŒ ${r.error}`; } return `Step ${i + 1} - ${r.persona}:\n\nPrompt:\n${r.prompt}\n\nInput:\n${r.input}\n`; }).join('\n' + '='.repeat(50) + '\n\n'); return { content: [ { type: 'text', text: `๐Ÿ”— Persona Chain Execution\n\n${resultText}\nโœ… Chain completed: ${results.filter(r => !r.error).length}/${args.personas.length} steps`, }, ], }; } case 'get_analytics': { const analytics = await loadAnalytics(); const usageList = Object.entries(analytics.usage) .sort((a, b) => b[1] - a[1]) .map(([name, count]) => ` ${name}: ${count} uses`) .join('\n'); const topPatterns = {}; Object.entries(analytics.contextPatterns).forEach(([persona, patterns]) => { const sorted = Object.entries(patterns) .sort((a, b) => b[1] - a[1]) .slice(0, 3); topPatterns[persona] = sorted.map(([kw]) => kw); }); const patternsList = Object.entries(topPatterns) .map(([persona, keywords]) => ` ${persona}: ${keywords.join(', ')}`) .join('\n'); return { content: [ { type: 'text', text: `๐Ÿ“Š Persona Usage Analytics\n\n์‚ฌ์šฉ ํšŸ์ˆ˜:\n${usageList || ' (no data)'}\n\n์ฃผ์š” ์ปจํ…์ŠคํŠธ ํŒจํ„ด:\n${patternsList || ' (no data)'}\n\n๐Ÿ’ก ์ด ๋ฐ์ดํ„ฐ๋Š” ๋กœ์ปฌ์—๋งŒ ์ €์žฅ๋˜๋ฉฐ ์ „์†ก๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.`, }, ], }; } case 'browse_community': { const personas = await listCommunityPersonas(); if (personas.length === 0) { return { content: [ { type: 'text', text: '๐Ÿ“ฆ ์ปค๋ฎค๋‹ˆํ‹ฐ ํŽ˜๋ฅด์†Œ๋‚˜๊ฐ€ ์•„์ง ์—†์Šต๋‹ˆ๋‹ค.\n\nCONTRIBUTING.md๋ฅผ ์ฐธ์กฐํ•˜์—ฌ ์ฒซ ๋ฒˆ์งธ ๊ธฐ์—ฌ์ž๊ฐ€ ๋˜์–ด๋ณด์„ธ์š”!', }, ], }; } // ์นดํ…Œ๊ณ ๋ฆฌ ํ•„ํ„ฐ๋ง let filtered = personas; if (args.category) { filtered = personas.filter(p => p.category && p.category.toLowerCase().includes(args.category.toLowerCase()) ); } // ์นดํ…Œ๊ณ ๋ฆฌ๋ณ„๋กœ ๊ทธ๋ฃนํ™” const byCategory = {}; filtered.forEach(p => { const cat = p.category || 'Other'; if (!byCategory[cat]) { byCategory[cat] = []; } byCategory[cat].push(p); }); let output = '๐ŸŒŸ Community Persona Collection\n\n'; output += `Found ${filtered.length} persona(s)${args.category ? ` in category "${args.category}"` : ''}\n\n`; for (const [category, list] of Object.entries(byCategory)) { output += `## ${category}\n\n`; list.forEach(p => { output += `### ${p.name}\n`; if (p.author) output += `๐Ÿ‘ค Author: ${p.author}\n`; if (p.difficulty) output += `๐Ÿ“Š Difficulty: ${p.difficulty}\n`; if (p.persona) output += `๐Ÿ“ Description: ${p.persona}\n`; if (p['use']) output += `๐Ÿ’ก Use Cases: ${p['use']}\n`; output += `\n๐Ÿ“ฅ Install: \`install_community_persona\` with name "${p.name}"\n\n`; }); } output += '\n---\n\n'; output += '๐Ÿ’ก **Tip**: After installing, use @persona:name to activate\n'; output += '๐Ÿ“š **More info**: See CONTRIBUTING.md to add your own persona\n'; output += '๐ŸŽฏ **Vision**: Check VISION.md for the Persona Marketplace roadmap'; return { content: [ { type: 'text', text: output, }, ], }; } case 'install_community_persona': { const installedPath = await installCommunityPersona(args.name); // ๊ฐ„๋‹จํ•œ ํ”„๋ฆฌ๋ทฐ ์ œ๊ณต const content = await readCommunityPersona(args.name); const preview = content.split('\n').slice(0, 10).join('\n'); return { content: [ { type: 'text', text: `โœ… Persona "${args.name}" installed successfully!\n\n๐Ÿ“ Location: ${installedPath}\n\n๐Ÿ“„ Preview:\n${preview}\n...\n\n๐Ÿ’ก **How to use:**\n@persona:${args.name} your question or task\n\nExample:\n@persona:${args.name} help me with this code\n\n๐ŸŽฏ The persona will only activate when you use the @persona:${args.name} trigger (Submarine Mode = 0 tokens otherwise)`, }, ], }; } default: throw new Error(`Unknown tool: ${name}`); } } catch (error) { return { content: [ { type: 'text', text: `์˜ค๋ฅ˜: ${error.message}`, }, ], isError: true, }; } }); // ๋ฆฌ์†Œ์Šค ๋ชฉ๋ก server.setRequestHandler(ListResourcesRequestSchema, async () => { const personas = await listPersonas(); return { resources: personas.map(name => ({ uri: `persona://${name}`, mimeType: 'text/plain', name: `Persona: ${name}`, description: `${name} ํŽ˜๋ฅด์†Œ๋‚˜ ํ”„๋กœํ•„`, })), }; }); // ๋ฆฌ์†Œ์Šค ์ฝ๊ธฐ server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const uri = request.params.uri; const match = uri.match(/^persona:\/\/(.+)$/); if (!match) { throw new Error('Invalid persona URI'); } const personaName = match[1]; const content = await readPersona(personaName); // ์‚ฌ์šฉ ์ถ”์  (ํŠธ๋ฆฌ๊ฑฐ ๊ธฐ๋ฐ˜ - ์‹ค์ œ ๋กœ๋“œ ์‹œ์—๋งŒ) await trackUsage(personaName, ''); return { contents: [ { uri, mimeType: 'text/plain', text: content, }, ], }; }); // ์„œ๋ฒ„ ์‹œ์ž‘ async function main() { await initPersonaDir(); const transport = new StdioServerTransport(); await server.connect(transport); console.error('Persona MCP server running on stdio'); } main().catch((error) => { console.error('Server error:', error); process.exit(1); });

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/seanshin0214/persona-mcp'

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