UNPKG

epl-mcp-server

Version:

EPL 축구 소식을 위한 MCP Server

547 lines (453 loc) 12.7 kB
# EPL MCP Server 웹 API 사용 가이드 ## 설치 및 실행 ### 1. 프로젝트 설정 ```bash # 웹 API 전용 디렉토리 생성 mkdir epl-mcp-web-api cd epl-mcp-web-api # 파일들 생성 후 npm install # 환경변수 설정 (.env) SUPABASE_URL=your-supabase-url SUPABASE_ANON_KEY=your-anon-key PORT=3001 ``` ### 2. 개발 모드 실행 ```bash npm run dev ``` ### 3. 프로덕션 빌드 및 실행 ```bash npm run serve ``` ## API 엔드포인트 사용법 ### 기본 URL ``` http://localhost:3001/api ``` ### 1. 핫 이슈 조회 ```bash # GET /api/hot-issues curl "http://localhost:3001/api/hot-issues?timeframe=today&limit=5" # JavaScript에서 사용 const response = await fetch('http://localhost:3001/api/hot-issues?timeframe=week&limit=10'); const data = await response.json(); console.log(data.data.hot_issues); ``` ### 2. 선수 뉴스 조회 ```bash # GET /api/player-news curl "http://localhost:3001/api/player-news?player_name=손흥민&timeframe=7days" # JavaScript에서 사용 const playerNews = await fetch('http://localhost:3001/api/player-news?player_name=Son Heung-min&timeframe=1month') .then(res => res.json()); ``` ### 3. 이적 뉴스 조회 ```bash # GET /api/transfer-news curl "http://localhost:3001/api/transfer-news?timeframe=1month&player_name=Haaland" ``` ### 4. 경기 이슈 조회 ```bash # GET /api/match-issues curl "http://localhost:3001/api/match-issues?date_range=2024-06-20:2024-06-28&teams=Arsenal,Chelsea" ``` ### 5. 트윗 검색 ```bash # POST /api/search-tweets curl -X POST "http://localhost:3001/api/search-tweets" \ -H "Content-Type: application/json" \ -d '{ "keywords": ["goal", "victory", "match"], "timeframe": "7days", "limit": 15 }' # JavaScript에서 사용 const searchResults = await fetch('http://localhost:3001/api/search-tweets', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ keywords: ['transfer', 'signing'], timeframe: '1month', limit: 20 }) }).then(res => res.json()); ``` ### 6. 카테고리별 트렌드 ```bash # GET /api/category-trends/:category curl "http://localhost:3001/api/category-trends/player?timeframe=1week" curl "http://localhost:3001/api/category-trends/transfer?timeframe=1month" ``` ### 7. 해시태그 추천 ```bash # POST /api/suggest-hashtags curl -X POST "http://localhost:3001/api/suggest-hashtags" \ -H "Content-Type: application/json" \ -d '{ "content": "Arsenal signed a new striker from Brighton", "category": "transfer" }' ``` ### 8. 포스트 템플릿 ```bash # GET /api/post-templates/:type curl "http://localhost:3001/api/post-templates/transfer_news" curl "http://localhost:3001/api/post-templates/match_preview" ``` ## 프론트엔드 통합 예시 ### React 컴포넌트 예시 ```jsx import React, { useState, useEffect } from 'react'; const EPLHotIssues = () => { const [hotIssues, setHotIssues] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { const fetchHotIssues = async () => { try { const response = await fetch('http://localhost:3001/api/hot-issues?timeframe=today&limit=10'); const data = await response.json(); if (data.success) { setHotIssues(data.data.hot_issues); } } catch (error) { console.error('Failed to fetch hot issues:', error); } finally { setLoading(false); } }; fetchHotIssues(); }, []); if (loading) return <div>Loading...</div>; return ( <div> <h2>오늘의 EPL 핫 이슈</h2> {hotIssues.map((issue, index) => ( <div key={issue.id || index} className="issue-card"> <p>{issue.content}</p> <small>by {issue.author} • {issue.engagement} engagements</small> <div> {issue.hashtags.map(tag => ( <span key={tag} className="hashtag">#{tag}</span> ))} </div> </div> ))} </div> ); }; export default EPLHotIssues; ``` ### Vue.js 컴포넌트 예시 ```vue <template> <div> <h2>선수 뉴스 검색</h2> <input v-model="playerName" placeholder="선수 이름 입력" /> <button @click="searchPlayerNews">검색</button> <div v-if="playerNews.length > 0"> <div v-for="news in playerNews" :key="news.id" class="news-item"> <p>{{ news.content }}</p> <small>{{ news.created_at }} • {{ news.engagement }} engagements</small> </div> </div> </div> </template> <script> export default { data() { return { playerName: '', playerNews: [] } }, methods: { async searchPlayerNews() { if (!this.playerName) return; try { const response = await fetch( `http://localhost:3001/api/player-news?player_name=${encodeURIComponent(this.playerName)}&timeframe=1month` ); const data = await response.json(); if (data.success) { this.playerNews = data.data.player_news; } } catch (error) { console.error('Failed to fetch player news:', error); } } } } </script> ``` ## 배포 옵션 ### 1. Vercel (권장) ```bash # vercel.json 생성 { "version": 2, "builds": [ { "src": "dist/web-api-server.js", "use": "@vercel/node" } ], "routes": [ { "src": "/(.*)", "dest": "dist/web-api-server.js" } ] } ``` ### 2. Railway ```bash # railway up ``` ### 3. Render ```bash # render.yaml 생성 services: - type: web name: epl-mcp-api env: node buildCommand: npm run build startCommand: npm start ``` ## 보안 고려사항 ### 1. CORS 설정 ```typescript app.use(cors({ origin: ['http://localhost:3000', 'https://yourdomain.com'], credentials: true })); ``` ### 2. Rate Limiting ```typescript import rateLimit from 'express-rate-limit'; const limiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15분 max: 100, // 최대 100요청 message: 'Too many requests from this IP' }); app.use('/api/', limiter); ``` ### 3. API 키 인증 (선택사항) ```typescript const authenticateApiKey = (req: any, res: any, next: any) => { const apiKey = req.headers['x-api-key']; if (!apiKey || apiKey !== process.env.API_KEY) { return res.status(401).json({ success: false, error: 'Invalid API key' }); } next(); }; // 보호된 엔드포인트에 적용 app.use('/api/admin/', authenticateApiKey); ``` ## 모니터링 및 로깅 ### 1. 요청 로깅 ```typescript import morgan from 'morgan'; app.use(morgan('combined')); ``` ### 2. 에러 추적 ```typescript import * as Sentry from '@sentry/node'; Sentry.init({ dsn: process.env.SENTRY_DSN }); app.use(Sentry.Handlers.errorHandler()); ``` ## 성능 최적화 ### 1. 캐싱 구현 ```typescript import NodeCache from 'node-cache'; const cache = new NodeCache({ stdTTL: 600 }); // 10분 캐시 app.get('/api/hot-issues', async (req, res) => { const cacheKey = `hot-issues-${req.query.timeframe}-${req.query.limit}`; const cached = cache.get(cacheKey); if (cached) { return res.json({ success: true, data: cached, cached: true }); } const result = await eplService.getHotIssues(/* ... */); cache.set(cacheKey, result); res.json({ success: true, data: result }); }); ``` ### 2. 데이터베이스 연결 풀링 ```typescript // Supabase는 자동으로 연결 풀링을 관리하지만, // 필요시 커스텀 설정 가능 const supabase = createClient(url, key, { db: { schema: 'public' }, auth: { autoRefreshToken: false, persistSession: false } }); ``` ## 웹훅 지원 (실시간 업데이트) ### 1. Supabase 실시간 구독 ```typescript // 실시간 트윗 업데이트 웹훅 app.post('/api/webhook/tweet-update', async (req, res) => { const { record, eventType } = req.body; // 클라이언트들에게 실시간 업데이트 전송 io.emit('tweet-update', { type: eventType, data: record }); res.json({ success: true }); }); ``` ### 2. WebSocket 지원 ```typescript import { Server } from 'socket.io'; import http from 'http'; const server = http.createServer(app); const io = new Server(server, { cors: { origin: "*", methods: ["GET", "POST"] } }); io.on('connection', (socket) => { console.log('Client connected:', socket.id); socket.on('subscribe-hot-issues', () => { socket.join('hot-issues'); }); socket.on('disconnect', () => { console.log('Client disconnected:', socket.id); }); }); ``` ## 배치 작업 API ### 1. 대량 데이터 처리 ```typescript app.post('/api/batch/analyze-trends', async (req, res) => { try { const { categories, timeframe } = req.body; const results = []; for (const category of categories) { const trends = await eplService.getCategoryTrends(category, timeframe); results.push(trends); } res.json({ success: true, data: results }); } catch (error) { res.status(500).json({ success: false, error: error.message }); } }); ``` ### 2. 스케줄된 작업 ```typescript import cron from 'node-cron'; // 매시간 캐시 클리어 cron.schedule('0 * * * *', () => { cache.flushAll(); console.log('Cache cleared'); }); // 매일 트렌드 분석 리포트 생성 cron.schedule('0 9 * * *', async () => { try { const dailyReport = await generateDailyReport(); console.log('Daily report generated:', dailyReport); } catch (error) { console.error('Failed to generate daily report:', error); } }); ``` ## 프론트엔드 라이브러리/SDK ### 1. JavaScript SDK 생성 ```typescript // epl-mcp-sdk.js class EPLMCPClient { constructor(baseURL = 'http://localhost:3001/api', apiKey = null) { this.baseURL = baseURL; this.apiKey = apiKey; } async request(endpoint, options = {}) { const url = `${this.baseURL}${endpoint}`; const headers = { 'Content-Type': 'application/json', ...(this.apiKey && { 'X-API-Key': this.apiKey }), ...options.headers }; const response = await fetch(url, { ...options, headers }); const data = await response.json(); if (!data.success) { throw new Error(data.error); } return data.data; } // 편의 메서드들 getHotIssues(timeframe = 'today', limit = 10) { return this.request(`/hot-issues?timeframe=${timeframe}&limit=${limit}`); } getPlayerNews(playerName, timeframe = '7days') { return this.request(`/player-news?player_name=${encodeURIComponent(playerName)}&timeframe=${timeframe}`); } searchTweets(keywords, timeframe = '7days', limit = 20) { return this.request('/search-tweets', { method: 'POST', body: JSON.stringify({ keywords, timeframe, limit }) }); } getCategoryTrends(category, timeframe = '7days') { return this.request(`/category-trends/${category}?timeframe=${timeframe}`); } } // 사용법 const client = new EPLMCPClient('https://your-api-domain.com/api'); const hotIssues = await client.getHotIssues('week', 15); ``` ### 2. React Hook ```typescript // useEPLData.js import { useState, useEffect } from 'react'; export const useEPLData = (endpoint, params = {}) => { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { const fetchData = async () => { try { setLoading(true); const client = new EPLMCPClient(); const result = await client.request(endpoint, { params }); setData(result); } catch (err) { setError(err.message); } finally { setLoading(false); } }; fetchData(); }, [endpoint, JSON.stringify(params)]); return { data, loading, error }; }; // 사용법 const HotIssuesComponent = () => { const { data, loading, error } = useEPLData('/hot-issues', { timeframe: 'today' }); if (loading) return <div>Loading...</div>; if (error) return <div>Error: {error}</div>; return ( <div> {data?.hot_issues?.map(issue => ( <div key={issue.id}>{issue.content}</div> ))} </div> ); }; ``` ## 결론 이제 EPL MCP Server를 다음 두 가지 방식으로 사용할 수 있습니다: ### 1. Claude Desktop (MCP Protocol) - 직접적인 도구 연동 - 자연어 질의 가능 - AI 모델과의 원활한 상호작용 ### 2. 웹 API (HTTP REST) - 웹 애플리케이션에서 사용 - 모바일 앱 개발 가능 - 다양한 프론트엔드 프레임워크 연동 - 서드파티 통합 가능 두 방식 모두 동일한 Supabase 데이터베이스를 사용하므로 데이터 일관성이 보장되며, 용도에 따라 적절한 방식을 선택하여 사용할 수 있습니다.