Skip to main content
Glama
MUSE-CODE-SPACE

Vibe Coding Documentation MCP (MUSE)

slack.ts6.66 kB
/** * Slack 웹훅 알림 */ import { fetchWithRetry, validateWebhookUrl, SLACK_ALLOWED_HOSTS } from '../core/security.js'; export interface SlackNotificationOptions { webhookUrl?: string; channel?: string; username?: string; iconEmoji?: string; } export interface SlackNotificationResult { success: boolean; error?: string; } // 마크다운을 Slack mrkdwn으로 변환 function markdownToSlackMrkdwn(markdown: string): string { let mrkdwn = markdown; // 헤딩 -> 볼드 mrkdwn = mrkdwn.replace(/^#{1,6}\s+(.+)$/gm, '*$1*'); // 볼드 mrkdwn = mrkdwn.replace(/\*\*([^*]+)\*\*/g, '*$1*'); // 이탤릭 mrkdwn = mrkdwn.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, '_$1_'); // 인라인 코드 mrkdwn = mrkdwn.replace(/`([^`]+)`/g, '`$1`'); // 코드 블록 mrkdwn = mrkdwn.replace(/```\w*\n([\s\S]*?)```/g, '```$1```'); // 링크 mrkdwn = mrkdwn.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<$2|$1>'); // 불릿 리스트 mrkdwn = mrkdwn.replace(/^[-*]\s+/gm, '• '); // 구분선 mrkdwn = mrkdwn.replace(/^---+$/gm, '───────────'); return mrkdwn; } export async function sendSlackNotification( message: string, options: SlackNotificationOptions = {} ): Promise<SlackNotificationResult> { try { const webhookUrl = options.webhookUrl || process.env.SLACK_WEBHOOK_URL; if (!webhookUrl) { throw new Error('SLACK_WEBHOOK_URL is not set'); } // Validate webhook URL (SSRF prevention) validateWebhookUrl(webhookUrl, SLACK_ALLOWED_HOSTS); const mrkdwn = markdownToSlackMrkdwn(message); const payload: any = { text: mrkdwn, mrkdwn: true }; if (options.channel) { payload.channel = options.channel; } if (options.username) { payload.username = options.username; } if (options.iconEmoji) { payload.icon_emoji = options.iconEmoji; } // Use fetchWithRetry for timeout and retry support const response = await fetchWithRetry(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }, { timeout: 30000, maxRetries: 3 }); if (!response.ok) { throw new Error(`Slack webhook error: ${response.status}`); } return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to send Slack notification' }; } } // 문서 발행 알림 (구조화된 메시지) export async function sendDocumentPublishedNotification( title: string, url: string, platform: string, options: SlackNotificationOptions = {} ): Promise<SlackNotificationResult> { try { const webhookUrl = options.webhookUrl || process.env.SLACK_WEBHOOK_URL; if (!webhookUrl) { throw new Error('SLACK_WEBHOOK_URL is not set'); } // Validate webhook URL (SSRF prevention) validateWebhookUrl(webhookUrl, SLACK_ALLOWED_HOSTS); const payload = { blocks: [ { type: 'header', text: { type: 'plain_text', text: '📄 새 문서가 발행되었습니다', emoji: true } }, { type: 'section', fields: [ { type: 'mrkdwn', text: `*제목:*\n${title}` }, { type: 'mrkdwn', text: `*플랫폼:*\n${platform}` } ] }, { type: 'section', text: { type: 'mrkdwn', text: `<${url}|📎 문서 보기>` } }, { type: 'context', elements: [ { type: 'mrkdwn', text: `🤖 Vibe Coding MCP로 자동 생성됨 | ${new Date().toLocaleString('ko-KR')}` } ] } ] }; const response = await fetchWithRetry(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }, { timeout: 30000, maxRetries: 3 }); if (!response.ok) { throw new Error(`Slack webhook error: ${response.status}`); } return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to send Slack notification' }; } } // 세션 요약 알림 export async function sendSessionSummaryNotification( sessionId: string, summary: string, stats: { files: number; functions: number; classes: number }, options: SlackNotificationOptions = {} ): Promise<SlackNotificationResult> { try { const webhookUrl = options.webhookUrl || process.env.SLACK_WEBHOOK_URL; if (!webhookUrl) { throw new Error('SLACK_WEBHOOK_URL is not set'); } // Validate webhook URL (SSRF prevention) validateWebhookUrl(webhookUrl, SLACK_ALLOWED_HOSTS); const payload = { blocks: [ { type: 'header', text: { type: 'plain_text', text: '🎯 코딩 세션 요약', emoji: true } }, { type: 'section', text: { type: 'mrkdwn', text: summary } }, { type: 'section', fields: [ { type: 'mrkdwn', text: `*📁 파일:* ${stats.files}` }, { type: 'mrkdwn', text: `*⚡ 함수:* ${stats.functions}` }, { type: 'mrkdwn', text: `*🏗️ 클래스:* ${stats.classes}` }, { type: 'mrkdwn', text: `*🆔 세션:* ${sessionId.slice(0, 8)}...` } ] }, { type: 'divider' }, { type: 'context', elements: [ { type: 'mrkdwn', text: `🤖 Vibe Coding MCP | ${new Date().toLocaleString('ko-KR')}` } ] } ] }; const response = await fetchWithRetry(webhookUrl, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) }, { timeout: 30000, maxRetries: 3 }); if (!response.ok) { throw new Error(`Slack webhook error: ${response.status}`); } return { success: true }; } catch (error) { return { success: false, error: error instanceof Error ? error.message : 'Failed to send Slack notification' }; } }

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/MUSE-CODE-SPACE/vibe-coding-mcp'

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