Skip to main content
Glama
oksure

Bible Korean MCP Server

by oksure
index.ts19.1 kB
#!/usr/bin/env node import { Server } from "@modelcontextprotocol/sdk/server/index.js"; import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js"; import { CallToolRequestSchema, ListToolsRequestSchema, Tool, } from "@modelcontextprotocol/sdk/types.js"; import fetch from "node-fetch"; import * as cheerio from "cheerio"; // Bible book mappings const BIBLE_BOOKS: Record<string, { code: string; korean: string; testament: string }> = { // Old Testament "Genesis": { code: "gen", korean: "창세기", testament: "OT" }, "Exodus": { code: "exo", korean: "출애굽기", testament: "OT" }, "Leviticus": { code: "lev", korean: "레위기", testament: "OT" }, "Numbers": { code: "num", korean: "민수기", testament: "OT" }, "Deuteronomy": { code: "deu", korean: "신명기", testament: "OT" }, "Joshua": { code: "jos", korean: "여호수아", testament: "OT" }, "Judges": { code: "jdg", korean: "사사기", testament: "OT" }, "Ruth": { code: "rut", korean: "룻기", testament: "OT" }, "1 Samuel": { code: "1sa", korean: "사무엘상", testament: "OT" }, "2 Samuel": { code: "2sa", korean: "사무엘하", testament: "OT" }, "1 Kings": { code: "1ki", korean: "열왕기상", testament: "OT" }, "2 Kings": { code: "2ki", korean: "열왕기하", testament: "OT" }, "1 Chronicles": { code: "1ch", korean: "역대상", testament: "OT" }, "2 Chronicles": { code: "2ch", korean: "역대하", testament: "OT" }, "Ezra": { code: "ezr", korean: "에스라", testament: "OT" }, "Nehemiah": { code: "neh", korean: "느헤미야", testament: "OT" }, "Esther": { code: "est", korean: "에스더", testament: "OT" }, "Job": { code: "job", korean: "욥기", testament: "OT" }, "Psalms": { code: "psa", korean: "시편", testament: "OT" }, "Proverbs": { code: "pro", korean: "잠언", testament: "OT" }, "Ecclesiastes": { code: "ecc", korean: "전도서", testament: "OT" }, "Song of Solomon": { code: "sng", korean: "아가", testament: "OT" }, "Isaiah": { code: "isa", korean: "이사야", testament: "OT" }, "Jeremiah": { code: "jer", korean: "예레미야", testament: "OT" }, "Lamentations": { code: "lam", korean: "예레미야애가", testament: "OT" }, "Ezekiel": { code: "ezk", korean: "에스겔", testament: "OT" }, "Daniel": { code: "dan", korean: "다니엘", testament: "OT" }, "Hosea": { code: "hos", korean: "호세아", testament: "OT" }, "Joel": { code: "jol", korean: "요엘", testament: "OT" }, "Amos": { code: "amo", korean: "아모스", testament: "OT" }, "Obadiah": { code: "oba", korean: "오바댜", testament: "OT" }, "Jonah": { code: "jon", korean: "요나", testament: "OT" }, "Micah": { code: "mic", korean: "미가", testament: "OT" }, "Nahum": { code: "nam", korean: "나훔", testament: "OT" }, "Habakkuk": { code: "hab", korean: "하박국", testament: "OT" }, "Zephaniah": { code: "zep", korean: "스바냐", testament: "OT" }, "Haggai": { code: "hag", korean: "학개", testament: "OT" }, "Zechariah": { code: "zec", korean: "스가랴", testament: "OT" }, "Malachi": { code: "mal", korean: "말라기", testament: "OT" }, // New Testament "Matthew": { code: "mat", korean: "마태복음", testament: "NT" }, "Mark": { code: "mrk", korean: "마가복음", testament: "NT" }, "Luke": { code: "luk", korean: "누가복음", testament: "NT" }, "John": { code: "jhn", korean: "요한복음", testament: "NT" }, "Acts": { code: "act", korean: "사도행전", testament: "NT" }, "Romans": { code: "rom", korean: "로마서", testament: "NT" }, "1 Corinthians": { code: "1co", korean: "고린도전서", testament: "NT" }, "2 Corinthians": { code: "2co", korean: "고린도후서", testament: "NT" }, "Galatians": { code: "gal", korean: "갈라디아서", testament: "NT" }, "Ephesians": { code: "eph", korean: "에베소서", testament: "NT" }, "Philippians": { code: "php", korean: "빌립보서", testament: "NT" }, "Colossians": { code: "col", korean: "골로새서", testament: "NT" }, "1 Thessalonians": { code: "1th", korean: "데살로니가전서", testament: "NT" }, "2 Thessalonians": { code: "2th", korean: "데살로니가후서", testament: "NT" }, "1 Timothy": { code: "1ti", korean: "디모데전서", testament: "NT" }, "2 Timothy": { code: "2ti", korean: "디모데후서", testament: "NT" }, "Titus": { code: "tit", korean: "디도서", testament: "NT" }, "Philemon": { code: "phm", korean: "빌레몬서", testament: "NT" }, "Hebrews": { code: "heb", korean: "히브리서", testament: "NT" }, "James": { code: "jas", korean: "야고보서", testament: "NT" }, "1 Peter": { code: "1pe", korean: "베드로전서", testament: "NT" }, "2 Peter": { code: "2pe", korean: "베드로후서", testament: "NT" }, "1 John": { code: "1jn", korean: "요한일서", testament: "NT" }, "2 John": { code: "2jn", korean: "요한이서", testament: "NT" }, "3 John": { code: "3jn", korean: "요한삼서", testament: "NT" }, "Jude": { code: "jud", korean: "유다서", testament: "NT" }, "Revelation": { code: "rev", korean: "요한계시록", testament: "NT" }, }; // Translation versions const TRANSLATIONS: Record<string, string> = { "GAE": "개역개정 (Revised Korean)", "GAE1": "개역한글 (Korean Revised Version)", "NIR": "새번역성경 (New Korean Revised Version)", "KOR": "공동번역 (Common Translation)", "CEV": "CEV (Contemporary English Version)", }; interface Verse { number: number; text: string; } interface Chapter { book: string; bookKorean: string; chapter: number; version: string; versionName: string; verses: Verse[]; } // Helper function to find book code function findBookCode(bookName: string): string | null { const normalized = bookName.toLowerCase().trim(); // Try direct match for (const [name, info] of Object.entries(BIBLE_BOOKS)) { if (name.toLowerCase() === normalized || info.korean === bookName || info.code === normalized) { return info.code; } } // Try partial match for (const [name, info] of Object.entries(BIBLE_BOOKS)) { if (name.toLowerCase().includes(normalized) || info.korean.includes(bookName)) { return info.code; } } return null; } // Helper function to get book info by code function getBookInfo(code: string): { name: string; korean: string } | null { for (const [name, info] of Object.entries(BIBLE_BOOKS)) { if (info.code === code) { return { name, korean: info.korean }; } } return null; } // Fetch and parse chapter async function fetchChapter( bookCode: string, chapter: number, version: string = "GAE" ): Promise<Chapter> { const url = `https://www.bskorea.or.kr/bible/korbibReadpage.php?version=${version}&book=${bookCode}&chap=${chapter}`; const response = await fetch(url); const html = await response.text(); const $ = cheerio.load(html); const verses: Verse[] = []; // Parse verses from span elements // The website uses span elements where verse text starts with verse number $("span").each((i, elem) => { const text = $(elem).text().trim(); // Look for pattern: number followed by spaces and text const match = text.match(/^(\d+)\s+(.+)$/s); if (match) { const verseNum = parseInt(match[1]); let verseText = match[2]; // Remove footnote markers (like 1), 2), etc.) verseText = verseText.replace(/\d+\)/g, "").trim(); // Remove explanatory text that comes after line breaks (like "또는 ...") const lines = verseText.split("\n"); verseText = lines[0].trim(); // Avoid duplicate verses (website has multiple spans per verse) if (!verses.find((v) => v.number === verseNum)) { verses.push({ number: verseNum, text: verseText, }); } } }); const bookInfo = getBookInfo(bookCode); return { book: bookInfo?.name || bookCode, bookKorean: bookInfo?.korean || "", chapter, version, versionName: TRANSLATIONS[version] || version, verses, }; } // Search verses async function searchVerses( query: string, version: string = "GAE", books?: string[] ): Promise<Array<{ book: string; chapter: number; verse: number; text: string }>> { const results: Array<{ book: string; chapter: number; verse: number; text: string }> = []; const searchLower = query.toLowerCase(); // Determine which books to search const booksToSearch = books || Object.keys(BIBLE_BOOKS); // For demo purposes, we'll search Genesis and Matthew only // In production, you'd want to implement proper search or use the website's search feature const limitedBooks = booksToSearch.slice(0, 2); for (const bookName of limitedBooks) { const bookInfo = BIBLE_BOOKS[bookName]; if (!bookInfo) continue; // Search first few chapters (limit to avoid too many requests) for (let chapter = 1; chapter <= 3; chapter++) { try { const chapterData = await fetchChapter(bookInfo.code, chapter, version); for (const verse of chapterData.verses) { if (verse.text.toLowerCase().includes(searchLower)) { results.push({ book: chapterData.book, chapter: chapterData.chapter, verse: verse.number, text: verse.text, }); } } } catch (error) { // Skip chapters that don't exist break; } } } return results; } // Create MCP server const server = new Server( { name: "bible-ko-mcp", version: "1.0.0", }, { capabilities: { tools: {}, }, } ); // Define tools const tools: Tool[] = [ { name: "get-chapter", description: "Get all verses from a specific chapter of the Korean Bible", inputSchema: { type: "object", properties: { book: { type: "string", description: "Book name (English or Korean) or code (e.g., 'Genesis', '창세기', 'gen')", }, chapter: { type: "number", description: "Chapter number", }, version: { type: "string", description: "Bible translation version (default: GAE)", enum: ["GAE", "GAE1", "NIR", "KOR", "CEV"], default: "GAE", }, }, required: ["book", "chapter"], }, }, { name: "get-verses", description: "Get specific verse(s) from a chapter", inputSchema: { type: "object", properties: { book: { type: "string", description: "Book name (English or Korean) or code", }, chapter: { type: "number", description: "Chapter number", }, verseStart: { type: "number", description: "Starting verse number", }, verseEnd: { type: "number", description: "Ending verse number (optional, defaults to verseStart)", }, version: { type: "string", description: "Bible translation version (default: GAE)", enum: ["GAE", "GAE1", "NIR", "KOR", "CEV"], default: "GAE", }, }, required: ["book", "chapter", "verseStart"], }, }, { name: "search-bible", description: "Search for verses containing specific keywords (searches limited books for demo)", inputSchema: { type: "object", properties: { query: { type: "string", description: "Search query (Korean or English)", }, version: { type: "string", description: "Bible translation version (default: GAE)", enum: ["GAE", "GAE1", "NIR", "KOR", "CEV"], default: "GAE", }, }, required: ["query"], }, }, { name: "list-books", description: "List all available books in the Bible", inputSchema: { type: "object", properties: { testament: { type: "string", description: "Filter by testament (OT/NT, optional)", enum: ["OT", "NT"], }, }, }, }, { name: "compare-translations", description: "Compare a verse across different Korean translations", inputSchema: { type: "object", properties: { book: { type: "string", description: "Book name (English or Korean) or code", }, chapter: { type: "number", description: "Chapter number", }, verse: { type: "number", description: "Verse number", }, versions: { type: "array", items: { type: "string" }, description: "Array of version codes to compare (default: all versions)", }, }, required: ["book", "chapter", "verse"], }, }, ]; // List tools handler server.setRequestHandler(ListToolsRequestSchema, async () => { return { tools }; }); // Call tool handler server.setRequestHandler(CallToolRequestSchema, async (request) => { try { const { name, arguments: args } = request.params; switch (name) { case "get-chapter": { const { book, chapter, version = "GAE" } = args as { book: string; chapter: number; version?: string; }; const bookCode = findBookCode(book); if (!bookCode) { return { content: [ { type: "text", text: `Error: Book '${book}' not found. Use list-books to see available books.`, }, ], }; } const chapterData = await fetchChapter(bookCode, chapter, version); let result = `# ${chapterData.book} (${chapterData.bookKorean}) ${chapterData.chapter}\n`; result += `**Translation:** ${chapterData.versionName}\n\n`; for (const verse of chapterData.verses) { result += `**${verse.number}.** ${verse.text}\n\n`; } return { content: [{ type: "text", text: result }], }; } case "get-verses": { const { book, chapter, verseStart, verseEnd, version = "GAE" } = args as { book: string; chapter: number; verseStart: number; verseEnd?: number; version?: string; }; const bookCode = findBookCode(book); if (!bookCode) { return { content: [ { type: "text", text: `Error: Book '${book}' not found.`, }, ], }; } const chapterData = await fetchChapter(bookCode, chapter, version); const endVerse = verseEnd || verseStart; const selectedVerses = chapterData.verses.filter( (v) => v.number >= verseStart && v.number <= endVerse ); let result = `# ${chapterData.book} ${chapterData.chapter}:${verseStart}`; if (endVerse !== verseStart) result += `-${endVerse}`; result += `\n**Translation:** ${chapterData.versionName}\n\n`; for (const verse of selectedVerses) { result += `**${verse.number}.** ${verse.text}\n\n`; } return { content: [{ type: "text", text: result }], }; } case "search-bible": { const { query, version = "GAE" } = args as { query: string; version?: string; }; const results = await searchVerses(query, version); if (results.length === 0) { return { content: [ { type: "text", text: `No results found for "${query}" (searched limited books for demo)`, }, ], }; } let result = `# Search Results for "${query}"\n`; result += `Found ${results.length} verses:\n\n`; for (const verse of results) { result += `**${verse.book} ${verse.chapter}:${verse.verse}**\n`; result += `${verse.text}\n\n`; } return { content: [{ type: "text", text: result }], }; } case "list-books": { const { testament } = args as { testament?: string }; let result = "# Bible Books\n\n"; const filtered = Object.entries(BIBLE_BOOKS).filter( ([, info]) => !testament || info.testament === testament ); result += "## Old Testament\n"; filtered .filter(([, info]) => info.testament === "OT") .forEach(([name, info]) => { result += `- **${name}** (${info.korean}) - code: \`${info.code}\`\n`; }); result += "\n## New Testament\n"; filtered .filter(([, info]) => info.testament === "NT") .forEach(([name, info]) => { result += `- **${name}** (${info.korean}) - code: \`${info.code}\`\n`; }); return { content: [{ type: "text", text: result }], }; } case "compare-translations": { const { book, chapter, verse, versions } = args as { book: string; chapter: number; verse: number; versions?: string[]; }; const bookCode = findBookCode(book); if (!bookCode) { return { content: [ { type: "text", text: `Error: Book '${book}' not found.`, }, ], }; } const versionsToCompare = versions || Object.keys(TRANSLATIONS); let result = `# ${book} ${chapter}:${verse} - Translation Comparison\n\n`; for (const versionCode of versionsToCompare) { try { const chapterData = await fetchChapter(bookCode, chapter, versionCode); const verseData = chapterData.verses.find((v) => v.number === verse); if (verseData) { result += `## ${TRANSLATIONS[versionCode] || versionCode}\n`; result += `${verseData.text}\n\n`; } } catch (error) { result += `## ${TRANSLATIONS[versionCode] || versionCode}\n`; result += `(Error loading this version)\n\n`; } } return { content: [{ type: "text", text: result }], }; } default: return { content: [ { type: "text", text: `Unknown tool: ${name}`, }, ], }; } } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `Error: ${errorMessage}`, }, ], }; } }); // Start server async function main() { const transport = new StdioServerTransport(); await server.connect(transport); console.error("Bible Korean MCP Server running on stdio"); } main().catch((error) => { console.error("Fatal error:", error); process.exit(1); });

Implementation Reference

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/oksure/bible-ko-mcp'

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