Skip to main content
Glama
devbrother2024

TypeScript MCP Server Boilerplate

index.ts12.7 kB
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js' import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js' import { z } from 'zod' import { InferenceClient } from '@huggingface/inference' // Configuration schema for Smithery export const configSchema = z.object({ HF_TOKEN: z .string() .optional() .describe('Hugging Face API 토큰 (이미지 생성 기능에 필요)') }) // Create server function (required for Smithery) export default function createServer({ config }: { config?: z.infer<typeof configSchema> }) { // Create server instance const server = new McpServer({ name: 'greeting-server', version: '1.0.0' }) // Hugging Face Inference Client 초기화 const hfToken = config?.HF_TOKEN || process.env.HF_TOKEN || '' const hfClient = new InferenceClient(hfToken) // Blob을 base64로 변환하는 헬퍼 함수 async function blobToBase64(blob: Blob): Promise<string> { const arrayBuffer = await blob.arrayBuffer() const buffer = Buffer.from(arrayBuffer) return buffer.toString('base64') } // 언어별 인사말 정의 const greetings: Record<string, (name: string) => string> = { ko: name => `안녕하세요, ${name}님! 만나서 반갑습니다.`, en: name => `Hello, ${name}! Nice to meet you.`, ja: name => `こんにちは、${name}さん!はじめまして。`, zh: name => `你好,${name}!很高兴认识你。`, es: name => `¡Hola, ${name}! Encantado de conocerte.`, fr: name => `Bonjour, ${name} ! Enchanté de vous rencontrer.`, de: name => `Hallo, ${name}! Freut mich, Sie kennenzulernen.` } // greeting 도구 등록 server.tool( 'greeting', '사용자의 이름과 언어를 입력받아 해당 언어로 인사말을 반환합니다.', { name: z.string().describe('인사할 사용자의 이름'), language: z .enum(['ko', 'en', 'ja', 'zh', 'es', 'fr', 'de']) .describe( '인사말 언어 (ko: 한국어, en: 영어, ja: 일본어, zh: 중국어, es: 스페인어, fr: 프랑스어, de: 독일어)' ) }, async ({ name, language }) => { const greetingFn = greetings[language] const message = greetingFn(name) return { content: [ { type: 'text', text: message } ] } } ) // calc 도구 등록 server.tool( 'calc', '두 개의 숫자와 연산자를 입력받아 계산 결과를 반환합니다.', { a: z.number().describe('첫 번째 숫자'), b: z.number().describe('두 번째 숫자'), operator: z .enum(['+', '-', '*', '/']) .describe('연산자 (+: 덧셈, -: 뺄셈, *: 곱셈, /: 나눗셈)') }, async ({ a, b, operator }) => { let result: number switch (operator) { case '+': result = a + b break case '-': result = a - b break case '*': result = a * b break case '/': if (b === 0) { return { content: [ { type: 'text', text: '오류: 0으로 나눌 수 없습니다.' } ], isError: true } } result = a / b break } return { content: [ { type: 'text', text: `${a} ${operator} ${b} = ${result}` } ] } } ) // getCurrentTime 도구 등록 server.tool( 'getCurrentTime', '타임존을 입력받아 해당 타임존의 현재 시간을 반환합니다.', { timezone: z .string() .describe( 'IANA 타임존 형식 (예: Asia/Seoul, America/New_York, Europe/London, UTC 등)' ) }, async ({ timezone }) => { try { const now = new Date() const formatter = new Intl.DateTimeFormat('ko-KR', { timeZone: timezone, year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }) const formattedTime = formatter.format(now) const timeZoneName = new Intl.DateTimeFormat('ko-KR', { timeZone: timezone, timeZoneName: 'long' }) .formatToParts(now) .find(part => part.type === 'timeZoneName')?.value || timezone return { content: [ { type: 'text', text: `${timezone}의 현재 시간: ${formattedTime} (${timeZoneName})` } ] } } catch (error) { return { content: [ { type: 'text', text: `오류: 유효하지 않은 타임존입니다. (${timezone})` } ], isError: true } } } ) // 가짜 서버 정보 리소스 등록 server.resource( 'fake-server-info', 'server://fake-info', { description: '테스트용 가짜 서버 상태를 반환합니다.', mimeType: 'application/json' }, async () => { const now = new Date() const info = { id: 'srv-demo-001', name: 'Demo Application Server', region: 'ap-northeast-2', status: 'healthy', uptimeSeconds: 86_400, activeConnections: 128, cpuUsage: 37, memoryUsage: 58, lastDeployment: new Date( now.getTime() - 3_600_000 ).toISOString(), reportedAt: now.toISOString() } return { contents: [ { uri: 'server://fake-info', mimeType: 'application/json', text: JSON.stringify(info, null, 2) } ] } } ) // code_review 프롬프트 등록 server.prompt( 'code_review', '사용자가 제공한 코드 스니펫을 리뷰하기 위한 프롬프트를 생성합니다.', { language: z .string() .optional() .describe('코드 언어 (예: TypeScript, Python 등)'), code: z.string().describe('리뷰할 코드 스니펫') }, async ({ language, code }) => { const languageLabel = language ? `${language}` : '제공된' const promptText = [ `${languageLabel} 코드에 대한 종합적인 코드 리뷰를 작성하세요.`, '', '요구 사항:', '1. 잠재적인 버그나 예외 상황을 우선적으로 지적할 것', '2. 성능, 보안, 가독성, 테스트 관점에서 개선점을 제안할 것', '3. 근거가 필요한 경우 간단한 코드 조각을 제시할 것', '', '코드:', '```', code, '```' ].join('\n') return { description: '코드 리뷰를 위한 사용자 메시지', messages: [ { role: 'user' as const, content: { type: 'text' as const, text: promptText } } ] } } ) // generateImage 도구 등록 server.tool( 'generateImage', '텍스트 프롬프트를 입력받아 AI로 이미지를 생성합니다.', { prompt: z.string().describe('이미지 생성을 위한 텍스트 프롬프트') }, async ({ prompt }) => { try { // HF_TOKEN이 없으면 에러 반환 if (!hfToken) { return { content: [ { type: 'text', text: '오류: HF_TOKEN이 설정되지 않았습니다. Smithery 설정에서 HF_TOKEN을 구성해주세요.' } ], isError: true } } // Hugging Face Inference API로 이미지 생성 const imageResult = await hfClient.textToImage({ provider: 'auto', model: 'black-forest-labs/FLUX.1-schnell', inputs: prompt, parameters: { num_inference_steps: 5 } }) // Blob인지 확인하고 base64로 변환 let base64Data: string let mimeType: string // 타입 가드: Blob인지 확인 const isBlob = (value: unknown): value is Blob => { return ( typeof value === 'object' && value !== null && 'arrayBuffer' in value && 'type' in value && typeof (value as Blob).arrayBuffer === 'function' ) } if (isBlob(imageResult)) { base64Data = await blobToBase64(imageResult) mimeType = imageResult.type || 'image/png' } else if (typeof imageResult === 'string') { // 이미 base64 문자열이거나 URL인 경우 // URL인 경우 fetch로 가져와야 하지만, 일단 base64로 가정 base64Data = imageResult mimeType = 'image/png' } else { throw new Error('예상치 못한 이미지 형식입니다.') } return { content: [ { type: 'image', data: base64Data, mimeType: mimeType } ], annotations: { audience: ['user'], priority: 0.9 } } } catch (error) { return { content: [ { type: 'text', text: `오류: 이미지 생성 중 오류가 발생했습니다. ${ (error as Error).message }` } ], isError: true } } } ) // Return the MCP server object (required for Smithery) return server.server } // Local development support (optional - for testing without Smithery) if (import.meta.url === `file://${process.argv[1]}`) { async function main() { const serverInstance = createServer({}) const transport = new StdioServerTransport() await serverInstance.connect(transport) console.error('MCP Server running on stdio') } main().catch(console.error) }

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/devbrother2024/my-mcp-server-251127'

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