Skip to main content
Glama
TECHNICAL_IMPLEMENTATION_GUIDE.md22.2 kB
# 🔧 기술 구현 가이드: 기능적 페르소나 MCP 서버 **목표**: 정적 텍스트 페르소나 → 동적 기능 기반 MCP 서버 전환 --- ## 📁 파일 구조 (v3.0.0) ``` persona-mcp/ ├── src/ │ ├── index.ts (기존 - 확장 필요) │ ├── validation.ts (기존 - 유지) │ ├── sampling.ts (신규) │ ├── resources.ts (신규) │ ├── tools.ts (신규) │ ├── caching.ts (신규) │ └── personaLoader.ts (신규) ├── community/ │ ├── 101-fullstack-dev.txt (YAML Frontmatter 추가) │ ├── 410-llm-engineer.txt (YAML Frontmatter 추가) │ └── ... (142개 모두 변환) ├── examples/ │ └── 410-llm-engineer-functional.txt (✅ 완료) ├── FUNCTIONAL_PERSONA_UPGRADE_PLAN.md (✅ 완료) └── TECHNICAL_IMPLEMENTATION_GUIDE.md (현재 문서) ``` --- ## 🎯 Phase 1: 페르소나 로더 구현 ### personaLoader.ts ```typescript import fs from 'fs/promises'; import yaml from 'yaml'; import path from 'path'; export interface PersonaMetadata { name: string; id: number; version: string; category: string; domain: string; author?: string; created?: string; updated?: string; // Capabilities tools?: ToolMetadata[]; resources?: ResourceMetadata[]; prompts?: PromptMetadata[]; // Configuration sampling_enabled?: boolean; sampling_use_cases?: string[]; context_caching?: boolean; cache_breakpoints?: number; min_tokens?: number; recommended_agreement_level?: number; } export interface ToolMetadata { name: string; description: string; category: string; input_schema?: any; } export interface ResourceMetadata { uri_template: string; description: string; mime_type?: string; examples?: string[]; } export interface PromptMetadata { name: string; description: string; arguments?: Record<string, string>; } export interface PersonaContent { metadata: PersonaMetadata; markdown: string; } export class PersonaLoader { async loadPersona(filePath: string): Promise<PersonaContent> { const content = await fs.readFile(filePath, 'utf-8'); // YAML Frontmatter 파싱 if (content.startsWith('---\n')) { const parts = content.split('---\n'); if (parts.length >= 3) { const yamlContent = parts[1]; const markdownContent = parts.slice(2).join('---\n'); const metadata = yaml.parse(yamlContent) as PersonaMetadata; return { metadata, markdown: markdownContent.trim() }; } } // Legacy format (순수 Markdown) return { metadata: this.createDefaultMetadata(filePath), markdown: content }; } private createDefaultMetadata(filePath: string): PersonaMetadata { const filename = path.basename(filePath, '.txt'); const match = filename.match(/^(\d+)-(.+)$/); return { name: match ? match[2].replace(/-/g, ' ') : filename, id: match ? parseInt(match[1]) : 0, version: '2.4.0', category: 'unknown', domain: 'general' }; } async loadAllPersonas(directory: string): Promise<Map<string, PersonaContent>> { const personas = new Map<string, PersonaContent>(); const files = await fs.readdir(directory); for (const file of files) { if (file.endsWith('.txt')) { const personaId = file.replace('.txt', ''); const filePath = path.join(directory, file); const persona = await this.loadPersona(filePath); personas.set(personaId, persona); } } return personas; } } ``` --- ## 🔧 Phase 2: Tools 구현 ### tools.ts ```typescript import { CallToolRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { PersonaMetadata } from './personaLoader.js'; export interface ToolDefinition { name: string; description: string; inputSchema: { type: 'object'; properties: Record<string, any>; required?: string[]; }; handler: (args: any) => Promise<any>; } export class PersonaToolsRegistry { private tools: Map<string, Map<string, ToolDefinition>> = new Map(); registerTools(personaId: string, metadata: PersonaMetadata) { if (!metadata.tools) return; const personaTools = new Map<string, ToolDefinition>(); for (const toolMeta of metadata.tools) { const tool = this.createToolFromMetadata(personaId, toolMeta); personaTools.set(tool.name, tool); } this.tools.set(personaId, personaTools); } private createToolFromMetadata( personaId: string, toolMeta: any ): ToolDefinition { return { name: toolMeta.name, description: toolMeta.description, inputSchema: { type: 'object', properties: toolMeta.input_schema || {}, required: this.extractRequired(toolMeta.input_schema) }, handler: this.getHandler(personaId, toolMeta.name) }; } private extractRequired(schema: any): string[] { if (!schema) return []; return Object.entries(schema) .filter(([_, v]: [string, any]) => v.required === true) .map(([k, _]) => k); } private getHandler(personaId: string, toolName: string) { // Dynamic handler lookup const handlers = { '410-llm-engineer': this.getLLMEngineerHandlers(), '108-devops-engineer': this.getDevOpsHandlers(), '223-ux-researcher': this.getUXResearcherHandlers() // ... 142개 페르소나 매핑 }; const personaHandlers = handlers[personaId as keyof typeof handlers]; return personaHandlers?.[toolName] || this.defaultHandler; } private getLLMEngineerHandlers() { return { analyze_transformer_architecture: async (args: any) => { const { model_config, target_metrics = ['latency'] } = args; // 실제 분석 로직 const numParams = this.calculateParameters(model_config); const flops = this.estimateFLOPs(model_config); const memory = this.estimateMemory(model_config); const recommendations = []; if (target_metrics.includes('latency')) { recommendations.push('Use FlashAttention for 2x speedup'); } if (target_metrics.includes('memory')) { recommendations.push('Apply INT8 quantization for 4x reduction'); } return { content: [{ type: 'text', text: JSON.stringify({ model_size: `${numParams / 1e9}B parameters`, estimated_flops: `${flops / 1e12}T FLOPs`, memory_usage: `${memory} GB`, recommendations, estimated_improvements: { latency: '-40%', memory: '-75%' } }, null, 2) }] }; }, design_prompt_template: async (args: any) => { const { task_description, input_schema, output_format = 'json' } = args; const template = `<role> You are an expert specialized in ${task_description} </role> <task> ${task_description} </task> <input> ${JSON.stringify(input_schema, null, 2)} </input> <instructions> 1. Analyze the input carefully 2. Apply best practices 3. Generate output in ${output_format} format </instructions> <output_format> ${this.generateOutputFormat(output_format)} </output_format>`; return { content: [{ type: 'text', text: template }] }; }, estimate_inference_cost: async (args: any) => { const { model_name, requests_per_day, avg_input_tokens = 1000, avg_output_tokens = 500 } = args; const pricing = this.getPricing(model_name); const dailyCost = requests_per_day * ( (avg_input_tokens / 1000000) * pricing.input + (avg_output_tokens / 1000000) * pricing.output ); return { content: [{ type: 'text', text: JSON.stringify({ daily_cost: dailyCost.toFixed(2), monthly_cost: (dailyCost * 30).toFixed(2), annual_cost: (dailyCost * 365).toFixed(2), breakdown: { input_tokens_cost: (requests_per_day * avg_input_tokens / 1000000 * pricing.input).toFixed(2), output_tokens_cost: (requests_per_day * avg_output_tokens / 1000000 * pricing.output).toFixed(2) }, optimization_suggestions: [ `Implement prompt caching: save $${(dailyCost * 0.3 * 30).toFixed(0)}/month`, `Use shorter system prompts: save $${(dailyCost * 0.15 * 30).toFixed(0)}/month` ] }, null, 2) }] }; } }; } private getDevOpsHandlers() { return { diagnose_pipeline: async (args: any) => { // CI/CD 파이프라인 진단 로직 return { content: [{ type: 'text', text: 'Pipeline diagnosis complete...' }] }; } }; } private getUXResearcherHandlers() { return { design_research_plan: async (args: any) => { // UX 리서치 계획 생성 로직 return { content: [{ type: 'text', text: '# UX Research Plan\n\n...' }] }; } }; } private defaultHandler = async (args: any) => { return { content: [{ type: 'text', text: 'Tool not yet implemented' }] }; }; // 헬퍼 메서드 private calculateParameters(config: any): number { const { num_layers = 12, hidden_size = 768, num_heads = 12, vocab_size = 50257 } = config; // 간략화된 계산 return num_layers * hidden_size * hidden_size * 12 + vocab_size * hidden_size; } private estimateFLOPs(config: any): number { // 간략화된 FLOPs 추정 return this.calculateParameters(config) * 2; } private estimateMemory(config: any): number { // bytes → GB return (this.calculateParameters(config) * 4) / (1024 ** 3); } private generateOutputFormat(format: string): string { if (format === 'json') { return '{\n "result": "...",\n "confidence": 0.95\n}'; } else if (format === 'xml') { return '<result>\n <text>...</text>\n <confidence>0.95</confidence>\n</result>'; } else { return '# Result\n\n...'; } } private getPricing(model: string): { input: number, output: number } { const prices: Record<string, { input: number, output: number }> = { 'gpt-4': { input: 30, output: 60 }, 'gpt-4-turbo': { input: 10, output: 30 }, 'claude-3-opus': { input: 15, output: 75 }, 'claude-3-sonnet': { input: 3, output: 15 }, 'claude-3-haiku': { input: 0.25, output: 1.25 } }; return prices[model] || { input: 1, output: 2 }; } getToolsForPersona(personaId: string): ToolDefinition[] { const personaTools = this.tools.get(personaId); return personaTools ? Array.from(personaTools.values()) : []; } async executeTool(personaId: string, toolName: string, args: any): Promise<any> { const personaTools = this.tools.get(personaId); if (!personaTools) { throw new Error(`No tools for persona ${personaId}`); } const tool = personaTools.get(toolName); if (!tool) { throw new Error(`Tool ${toolName} not found`); } return await tool.handler(args); } } ``` --- ## 📚 Phase 3: Resources 구현 ### resources.ts ```typescript import { ReadResourceRequestSchema } from '@modelcontextprotocol/sdk/types.js'; import { PersonaMetadata } from './personaLoader.js'; export class DynamicResourceProvider { private resourceHandlers: Map<string, (uri: string) => Promise<any>> = new Map(); constructor() { this.registerDefaultHandlers(); } registerFromPersona(metadata: PersonaMetadata) { if (!metadata.resources) return; for (const resource of metadata.resources) { const prefix = this.extractPrefix(resource.uri_template); if (!this.resourceHandlers.has(prefix)) { this.resourceHandlers.set(prefix, this.createHandlerForTemplate(resource.uri_template)); } } } private registerDefaultHandlers() { // LLM Papers this.resourceHandlers.set('llm://papers/', async (uri) => { const topic = this.extractParam(uri, 'llm://papers/{topic}'); return this.fetchLLMPapers(topic); }); // Code Examples this.resourceHandlers.set('code://examples/', async (uri) => { const language = this.extractParam(uri, 'code://examples/{language}'); return this.fetchCodeExamples(language); }); // Benchmarks this.resourceHandlers.set('llm://benchmarks/', async (uri) => { const params = this.extractMultiParam(uri, 'llm://benchmarks/{model}/{task}'); return this.fetchBenchmarks(params.model, params.task); }); } private extractPrefix(template: string): string { return template.split('{')[0]; } private createHandlerForTemplate(template: string) { return async (uri: string) => { const params = this.extractAllParams(uri, template); return { template, params, uri }; }; } private extractParam(uri: string, template: string): string { const templateRegex = template.replace(/{(\w+)}/g, '([^/]+)'); const match = uri.match(new RegExp(templateRegex)); return match?.[1] || ''; } private extractMultiParam(uri: string, template: string): Record<string, string> { const paramNames = (template.match(/{(\w+)}/g) || []).map(p => p.slice(1, -1)); const templateRegex = template.replace(/{(\w+)}/g, '([^/]+)'); const match = uri.match(new RegExp(templateRegex)); const result: Record<string, string> = {}; if (match) { paramNames.forEach((name, i) => { result[name] = match[i + 1]; }); } return result; } private extractAllParams(uri: string, template: string): Record<string, string> { return this.extractMultiParam(uri, template); } async handleResourceRead(uri: string): Promise<any> { for (const [prefix, handler] of this.resourceHandlers) { if (uri.startsWith(prefix)) { return await handler(uri); } } throw new Error(`Unknown resource URI: ${uri}`); } // 실제 데이터 페칭 메서드들 private async fetchLLMPapers(topic: string): Promise<any> { // 실제 구현에서는 arXiv API 또는 로컬 캐시 사용 const papers: Record<string, any[]> = { 'transformers': [ { title: 'Attention Is All You Need', authors: ['Vaswani et al.'], year: 2017, url: 'https://arxiv.org/abs/1706.03762', key_contributions: ['Self-attention mechanism', 'No recurrence'] } ], 'prompt-engineering': [ { title: 'Chain-of-Thought Prompting Elicits Reasoning', authors: ['Wei et al.'], year: 2022, url: 'https://arxiv.org/abs/2201.11903' } ] }; return { topic, papers: papers[topic] || [], updated: new Date().toISOString() }; } private async fetchCodeExamples(language: string): Promise<any> { // GitHub API 또는 로컬 저장소 const examples: Record<string, any> = { 'python': { language: 'python', examples: [ { title: 'FastAPI Best Practices', code: 'from fastapi import FastAPI\n\napp = FastAPI()', description: 'Modern async web framework' } ] } }; return examples[language] || { language, examples: [] }; } private async fetchBenchmarks(model: string, task: string): Promise<any> { // 벤치마크 데이터베이스 const benchmarks: Record<string, any> = { 'gpt-4-coding': { model: 'gpt-4', task: 'coding', benchmarks: { 'HumanEval': { score: 85.4, rank: 1 }, 'MBPP': { score: 82.3, rank: 1 } } } }; return benchmarks[`${model}-${task}`] || { model, task, benchmarks: {} }; } } ``` --- ## 🔄 Phase 4: Sampling 구현 ### sampling.ts ```typescript export interface SamplingContext { session: { createMessage(params: { messages: Array<{ role: string; content: { type: string; text: string } }>; systemPrompt?: string; maxTokens?: number; }): Promise<{ content: { text: string } }>; }; } export class ExpertSampler { /** * ExpertPrompting: 전문가 정체성 동적 생성 */ async generateExpertIdentity( domain: string, task: string, ctx: SamplingContext ): Promise<string> { const response = await ctx.session.createMessage({ messages: [{ role: 'user', content: { type: 'text', text: `Domain: ${domain}\nTask: ${task}\n\nWhat specific expertise is needed? Generate expert identity (2-3 sentences).` } }], systemPrompt: 'You are an expert identity generator.', maxTokens: 200 }); return response.content.text; } /** * Solo Performance Prompting (SPP): 다중 페르소나 협업 */ async collaborativeReasoning( problem: string, personas: string[], ctx: SamplingContext ): Promise<string> { const proposals: string[] = []; // Phase 1: 발산 for (const persona of personas) { const response = await ctx.session.createMessage({ messages: [{ role: 'user', content: { type: 'text', text: `As a ${persona}, propose solution to: ${problem}` } }], maxTokens: 300 }); proposals.push(response.content.text); } // Phase 2: 비평 const critique = await ctx.session.createMessage({ messages: [{ role: 'user', content: { type: 'text', text: `Critically review:\n\n${proposals.join('\n\n')}\n\nIdentify weaknesses.` } }], systemPrompt: 'You are a rigorous critic.', maxTokens: 500 }); // Phase 3: 통합 const final = await ctx.session.createMessage({ messages: [{ role: 'user', content: { type: 'text', text: `Proposals:\n${proposals.join('\n\n')}\n\nCritique:\n${critique.content.text}\n\nSynthesize best solution.` } }], systemPrompt: 'You are a problem-solving expert.', maxTokens: 800 }); return final.content.text; } /** * Multi-Persona Debate */ async debate( question: string, agreementLevel: number, rounds: number, ctx: SamplingContext ): Promise<string> { // Round 1 const agent1 = await ctx.session.createMessage({ messages: [{ role: 'user', content: { type: 'text', text: question } }], systemPrompt: `Agreement level: ${agreementLevel}%`, maxTokens: 400 }); // Round 2 const agent2 = await ctx.session.createMessage({ messages: [{ role: 'user', content: { type: 'text', text: `Previous: ${agent1.content.text}\n\nYour perspective?` } }], systemPrompt: `Agreement level: ${agreementLevel}%`, maxTokens: 400 }); // Round 3: Vote const vote = await ctx.session.createMessage({ messages: [{ role: 'user', content: { type: 'text', text: `1. ${agent1.content.text}\n\n2. ${agent2.content.text}\n\nWhich is better?` } }], systemPrompt: 'You are an objective evaluator.', maxTokens: 300 }); return vote.content.text; } } ``` --- ## 📦 Phase 5: index.ts 통합 ### 주요 변경사항 ```typescript // 기존 import에 추가 import { PersonaLoader } from './personaLoader.js'; import { PersonaToolsRegistry } from './tools.js'; import { DynamicResourceProvider } from './resources.js'; import { ExpertSampler } from './sampling.js'; // 초기화 const personaLoader = new PersonaLoader(); const toolsRegistry = new PersonaToolsRegistry(); const resourceProvider = new DynamicResourceProvider(); const sampler = new ExpertSampler(); // 서버 시작 시 모든 페르소나 로드 const personas = await personaLoader.loadAllPersonas(COMMUNITY_DIR); for (const [id, persona] of personas) { toolsRegistry.registerTools(id, persona.metadata); resourceProvider.registerFromPersona(persona.metadata); } // ListToolsRequestSchema 핸들러 수정 server.setRequestHandler(ListToolsRequestSchema, async (request) => { // 현재 활성화된 페르소나의 Tools 반환 const currentPersona = getCurrentPersona(); // 컨텍스트에서 가져오기 const tools = toolsRegistry.getToolsForPersona(currentPersona); return { tools }; }); // CallToolRequestSchema 핸들러 수정 server.setRequestHandler(CallToolRequestSchema, async (request) => { const currentPersona = getCurrentPersona(); return await toolsRegistry.executeTool( currentPersona, request.params.name, request.params.arguments ); }); // ReadResourceRequestSchema 핸들러 추가 server.setRequestHandler(ReadResourceRequestSchema, async (request) => { const data = await resourceProvider.handleResourceRead(request.params.uri); return { contents: [{ uri: request.params.uri, mimeType: 'application/json', text: JSON.stringify(data, null, 2) }] }; }); ``` --- ## ✅ 구현 체크리스트 ### Week 1-2 - [ ] `personaLoader.ts` 구현 - [ ] 10개 핵심 페르소나 YAML 변환 - [ ] 통합 테스트 ### Week 3-4 - [ ] `tools.ts` 구현 (30개 Tools) - [ ] `resources.ts` 구현 (5개 URI schemes) - [ ] `sampling.ts` 프로토타입 ### Week 5-6 - [ ] Caching 전략 문서화 - [ ] Progressive Disclosure 적용 - [ ] 성능 벤치마크 ### Week 7-8 - [ ] 142개 페르소나 전체 변환 - [ ] 500+ Tools 완성 - [ ] 통합 테스트 ### Week 9-10 - [ ] 프로덕션 배포 - [ ] 문서화 완성 - [ ] v3.0.0 Release --- **다음 단계**: 10개 핵심 페르소나 변환 시작

Latest Blog Posts

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