Skip to main content
Glama

Bizinfo MCP Server

by kwanGDss
bizinfo.js8.77 kB
import { URLSearchParams } from "node:url"; export const API_URL = "https://www.bizinfo.go.kr/uss/rss/bizinfoApi.do"; export const DEFAULT_API_KEY = "QP6yn2"; export const REGION_ALIASES = Object.freeze({ "서울특별시": "서울", "서울": "서울", "부산광역시": "부산", "부산": "부산", "대구광역시": "대구", "대구": "대구", "인천광역시": "인천", "인천": "인천", "광주광역시": "광주", "광주": "광주", "대전광역시": "대전", "대전": "대전", "울산광역시": "울산", "울산": "울산", "세종특별자치시": "세종", "세종": "세종", "경기도": "경기도", "경기": "경기도", "강원특별자치도": "강원도", "강원도": "강원도", "강원": "강원도", "충청북도": "충청북도", "충북": "충청북도", "청주시": "청주", "청주": "청주", "충청남도": "충청남도", "충남": "충청남도", "전라북도": "전라북도", "전북": "전라북도", "전라남도": "전라남도", "전남": "전라남도", "경상북도": "경상북도", "경북": "경상북도", "경상남도": "경상남도", "경남": "경상남도", "제주특별자치도": "제주특별자치도", "제주도": "제주특별자치도", "제주": "제주특별자치도", }); export const TARGET_ALIASES = Object.freeze({ "소상공인": "소상공인", "중소기업": "중소기업", "중견기업": "중견기업", "벤처기업": "벤처기업", "창업기업": "창업기업", "예비창업자": "예비창업자", "청년": "청년", "여성": "여성", "농업인": "농업인", "어업인": "어업인", "자영업자": "자영업자", }); const DEFAULT_TARGET = undefined; const BASE_URL = "https://www.bizinfo.go.kr"; export function getApiKey() { return process.env.BIZINFO_API_KEY && process.env.BIZINFO_API_KEY.trim() ? process.env.BIZINFO_API_KEY.trim() : DEFAULT_API_KEY; } function normalize(value) { return (value ?? "").toString().trim(); } function containsText(haystack, needle) { if (!haystack || !needle) { return false; } return haystack.includes(needle); } function normalizeKorean(text) { return (text ?? "").replace(/\s+/g, ""); } function findAlias(text, mapping) { if (!text) return undefined; const normalized = normalizeKorean(text); for (const [alias, canonical] of Object.entries(mapping)) { if (normalized.includes(normalizeKorean(alias))) { return canonical; } } return undefined; } function extractKeywords(text) { if (!text) return undefined; const match = text.match(/(?:키워드|검색어|분야)\s*(?:은|는|이|가|:|=)?\s*([가-힣0-9,\s]+)/i); if (!match) return undefined; const words = match[1] .split(/[\s,]+/) .map((w) => w.trim()) .filter(Boolean); return words.length ? words.join(",") : undefined; } function extractEnglishKeywords(text) { if (!text) return undefined; const tokens = text.match(/[A-Za-z]+/g); if (!tokens) return undefined; const unique = []; const seen = new Set(); for (const token of tokens) { const upper = token.toUpperCase(); if (!seen.has(upper)) { seen.add(upper); unique.push(upper); } } return unique.length ? unique.join(",") : undefined; } function extractNumbers(text) { let page = 1; let pageSize = 20; if (!text) return { page, pageSize }; const pageMatch = text.match(/(\d+)\s*(?:페이지|page)/i); if (pageMatch) { page = Math.max(1, Number.parseInt(pageMatch[1], 10)); } const sizeMatch = text.match(/(\d+)\s*(?:개|건)/); if (sizeMatch) { const parsed = Number.parseInt(sizeMatch[1], 10); pageSize = Math.min(50, Math.max(1, parsed)); } return { page, pageSize }; } export function parseNaturalQuery(prompt) { const region = findAlias(prompt, REGION_ALIASES); let target = findAlias(prompt, TARGET_ALIASES); if (!target) { const match = prompt.match(/(?:대상|지원대상)\s*(?:은|는|이|가|:|=)?\s*([가-힣0-9,\s]+)/); if (match) { target = findAlias(match[1], TARGET_ALIASES); } } let keywords = extractKeywords(prompt); if (!keywords) { keywords = extractEnglishKeywords(prompt); } const { page, pageSize } = extractNumbers(prompt); let resolvedRegion = region; if (!resolvedRegion) { const regionMatch = prompt.match(/([가-힣]+(?:특별자치시|특별자치도|광역시|특별시|자치시|자치도|시|군|구|도))/); if (regionMatch) { const rawRegion = regionMatch[1]; resolvedRegion = REGION_ALIASES[rawRegion] ?? rawRegion; } } return { region: resolvedRegion, target: target ?? DEFAULT_TARGET, keywords, page, pageSize, }; } async function fetchBizinfo({ region, page, pageSize }) { const apiKey = getApiKey(); if (!apiKey) { throw new Error("BIZINFO_API_KEY가 설정되어 있지 않습니다."); } const params = new URLSearchParams({ crtfcKey: apiKey, dataType: "json", pageIndex: String(page), pageUnit: String(pageSize), }); if (region) { params.set("hashtags", region); } const response = await fetch(`${API_URL}?${params.toString()}`); if (!response.ok) { throw new Error(`기업마당 API 호출 실패 (${response.status})`); } const data = await response.json(); if (data && typeof data === "object" && data !== null && "jsonArray" in data) { return data.jsonArray; } return data; } function toArray(value) { if (!value) return []; return Array.isArray(value) ? value : [value]; } function postFilter(items, { region, target, keywords }) { const keywordList = (keywords ?? "") .split(",") .map((kw) => kw.trim()) .filter(Boolean); return (items ?? []).reduce((acc, raw) => { const item = raw ?? {}; const title = normalize(item.title) || normalize(item.pblancNm); const desc = normalize(item.description) || normalize(item.bsnsSumryCn); const tags = normalize(item.hashTags); const targetName = normalize(item.trgetNm); const regionHit = [tags, title, desc].some((field) => containsText(field, region)); if (!regionHit) { return acc; } let targetHit = true; if (target) { targetHit = [targetName, title, desc, tags].some((field) => containsText(field, target)); } let keywordHit = true; if (keywordList.length) { keywordHit = keywordList.every((kw) => [title, desc, tags].some((field) => containsText(field, kw))); } if (targetHit && keywordHit) { const link = normalize(item.link) || normalize(item.pblancUrl); acc.push({ title, link: link.startsWith("http") ? link : link ? `${BASE_URL}${link}` : "", pblancId: item.seq ?? item.pblancId ?? "", agency: normalize(item.author) || normalize(item.jrsdInsttNm), executor: normalize(item.excInsttNm), category: normalize(item.lcategory) || normalize(item.pldirSportRealmLclasCodeNm), pubDate: normalize(item.pubDate) || normalize(item.creatPnttm), period: normalize(item.reqstDt) || normalize(item.reqstBeginEndDe), target: targetName, tags, summary: desc, }); } return acc; }, []); } export function formatBizinfoResults(items, { region, target, keywords }) { if (!items.length) { return `검색 결과가 없습니다. (region='${region}', target='${target ?? ""}', keywords='${keywords ?? ""}')`; } const lines = items.slice(0, 10).map((item, index) => ( `${index + 1}. ${item.title} \n` + ` - 기관: ${item.agency || ""} / 수행기관: ${item.executor || ""} / 분류: ${item.category || ""} \n` + ` - 모집기간: ${item.period || ""} / 등록일: ${item.pubDate || ""} \n` + ` - 대상: ${item.target || ""} \n` + ` - 링크: ${item.link || ""} \n` )); return lines.join("\n"); } export async function searchBizinfo({ region, target, keywords, page = 1, pageSize = 20 }) { if (!region) { throw new Error("region 파라미터가 필요합니다."); } const raw = await fetchBizinfo({ region, page, pageSize }); const items = Array.isArray(raw?.item) || Array.isArray(raw) ? toArray(raw.item ?? raw) : toArray(raw?.item ?? raw); return postFilter(items, { region, target, keywords }); } export async function searchBizinfoText(params) { const results = await searchBizinfo(params); return formatBizinfoResults(results, params); } export async function searchBizinfoNaturalText(prompt) { const params = parseNaturalQuery(prompt ?? ""); if (!params.region) { return "지역을 인식하지 못했습니다. 예: '서울', '부산', '충청남도'"; } return searchBizinfoText(params); }

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