Skip to main content
Glama

Bizinfo MCP Server

by kwanGDss
server.mjs6.3 kB
#!/usr/bin/env node import http from "node:http"; import process from "node:process"; import path from "node:path"; import { URL, fileURLToPath } from "node:url"; import { config as loadEnv } from "dotenv"; import { parseNaturalQuery, searchBizinfo, formatBizinfoResults, } from "./lib/bizinfo.js"; const __filename = fileURLToPath(import.meta.url); loadEnv({ path: path.resolve(process.cwd(), ".env"), override: false }); const PORT = Number.parseInt(process.env.PORT ?? "3000", 10); const HOST = process.env.HOST ?? "0.0.0.0"; function setCorsHeaders(res) { res.setHeader("Access-Control-Allow-Origin", "*"); res.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS"); res.setHeader("Access-Control-Allow-Headers", "Content-Type"); } function sendJson(res, statusCode, payload) { const body = JSON.stringify(payload); res.statusCode = statusCode; res.setHeader("Content-Type", "application/json; charset=utf-8"); res.setHeader("Content-Length", Buffer.byteLength(body)); setCorsHeaders(res); res.end(body); } function sendError(res, statusCode, message, details) { sendJson(res, statusCode, { error: { message, ...(details ? { details } : {}), }, }); } function parseInteger(value, fallback) { if (value === null || value === undefined || value === "") { return fallback; } const parsed = Number.parseInt(value, 10); return Number.isFinite(parsed) ? parsed : fallback; } async function readJsonBody(req) { return new Promise((resolve, reject) => { let raw = ""; req.setEncoding("utf8"); req.on("data", (chunk) => { raw += chunk; if (raw.length > 1_000_000) { req.destroy(); reject(new Error("요청 본문이 너무 큽니다.")); } }); req.on("end", () => { if (!raw.trim()) { resolve({}); return; } try { resolve(JSON.parse(raw)); } catch (error) { reject(new Error("JSON 형식이 올바르지 않습니다.")); } }); req.on("error", (error) => reject(error)); }); } async function handleSearch(query) { const params = { region: query.get("region") ?? undefined, target: query.get("target") ?? undefined, keywords: query.get("keywords") ?? undefined, page: parseInteger(query.get("page"), 1), pageSize: parseInteger(query.get("page_size") ?? query.get("pageSize"), 20), }; if (!params.region) { throw new HttpError(400, "region 쿼리 파라미터가 필요합니다."); } const items = await searchBizinfo(params); return { params, count: items.length, items, summary: formatBizinfoResults(items, params), }; } async function handleSearchPost(body) { const params = { region: body?.region, target: body?.target, keywords: body?.keywords, page: parseInteger(body?.page, 1), pageSize: parseInteger(body?.page_size ?? body?.pageSize, 20), }; if (!params.region) { throw new HttpError(400, "region 필드는 필수입니다."); } const items = await searchBizinfo(params); return { params, count: items.length, items, summary: formatBizinfoResults(items, params), }; } async function handleSearchNatural(body) { const prompt = body?.prompt; if (!prompt || !prompt.trim()) { throw new HttpError(400, "prompt 필드는 필수입니다."); } const params = parseNaturalQuery(prompt); if (!params.region) { throw new HttpError(400, "지역을 인식하지 못했습니다. 예: '서울', '부산', '충청남도'"); } const items = await searchBizinfo(params); return { prompt, params, count: items.length, items, summary: formatBizinfoResults(items, params), }; } class HttpError extends Error { constructor(statusCode, message) { super(message); this.name = "HttpError"; this.statusCode = statusCode; } } async function requestListener(req, res) { setCorsHeaders(res); if (req.method === "OPTIONS") { res.statusCode = 204; res.end(); return; } const origin = req.headers.host ? `http://${req.headers.host}` : "http://localhost"; const url = new URL(req.url ?? "/", origin); try { if (req.method === "GET" && url.pathname === "/health") { sendJson(res, 200, { status: "ok" }); return; } if (req.method === "GET" && url.pathname === "/search") { const payload = await handleSearch(url.searchParams); sendJson(res, 200, payload); return; } if (req.method === "POST" && url.pathname === "/search") { const body = await readJsonBody(req); const payload = await handleSearchPost(body); sendJson(res, 200, payload); return; } if (req.method === "POST" && url.pathname === "/search-natural") { const body = await readJsonBody(req); const payload = await handleSearchNatural(body); sendJson(res, 200, payload); return; } sendError(res, 404, "요청한 경로를 찾을 수 없습니다."); } catch (error) { if (error instanceof HttpError) { sendError(res, error.statusCode, error.message); return; } console.error(error); sendError(res, 500, "서버 내부 오류가 발생했습니다."); } } function startServer() { return new Promise((resolve, reject) => { const server = http.createServer((req, res) => { requestListener(req, res).catch((error) => { console.error(error); if (!res.headersSent) { sendError(res, 500, "서버 내부 오류가 발생했습니다."); } else { res.end(); } }); }); const onError = (error) => { server.off("error", onError); reject(error); }; server.once("error", onError); server.listen(PORT, HOST, () => { server.off("error", onError); console.log(`Bizinfo HTTP 서버가 http://${HOST}:${PORT} 에서 대기 중입니다.`); server.on("error", (error) => { console.error("HTTP 서버 오류:", error); }); resolve(server); }); }); } const isMainModule = process.argv[1] && path.resolve(process.argv[1]) === __filename; if (isMainModule) { startServer().catch((error) => { console.error(error); process.exit(1); }); } export { startServer };

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/kwanGDss/mcp-bizinfo'

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