@minjunkwon/public-housing-mcp-server
Version:
MCP server for public housing information in South Korea - 한국 공공주택 정보 조회 MCP 서버
237 lines • 11.5 kB
JavaScript
#!/usr/bin/env node
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
import { PublicHousingAPI } from './services/api.js';
import { REGION_CODES, DISTRICT_CODES, HOUSING_TYPES, SUPPLY_TYPES, RENT_RANGES } from './constants/codes.js';
const server = new Server({
name: 'public-housing-server',
version: '1.0.0',
});
const housingAPI = new PublicHousingAPI();
// 도구 목록 제공
server.setRequestHandler(ListToolsRequestSchema, async () => {
return {
tools: [
{
name: 'get_rental_housing',
description: '공공임대주택 모집공고를 조회합니다. 사용자가 지역, 공급유형, 임대료 등을 자연어로 요청하면 해당하는 코드로 변환하여 조회합니다.',
inputSchema: {
type: 'object',
properties: {
region: {
type: 'string',
description: '광역시도명 (예: 서울특별시, 경기도, 부산광역시 등). 사용자가 "서울", "경기", "부산" 등으로 말해도 인식 가능',
enum: REGION_CODES.map(r => r.name)
},
district: {
type: 'string',
description: '시군구명 (예: 강남구, 수원시, 성남시 등). 선택사항이며 지역이 지정된 경우에만 사용',
},
supplyType: {
type: 'string',
description: '공급유형 (예: 국민임대, 행복주택, 장기전세 등). 사용자가 "국민임대", "행복주택" 등으로 요청',
enum: SUPPLY_TYPES.map(s => s.name)
},
isLongTermLease: {
type: 'boolean',
description: '전세형 여부. 사용자가 "전세", "전세형" 등을 언급하면 true로 설정'
},
rentRange: {
type: 'string',
description: '월임대료 구간 (예: 5만원 미만, 10~20만원 미만 등). 사용자가 임대료 범위를 언급할 때 사용',
enum: RENT_RANGES.map(r => r.name)
},
pageSize: {
type: 'number',
description: '한 페이지당 조회할 데이터 개수 (기본값: 10, 최대: 100)',
default: 10,
minimum: 1,
maximum: 100
},
pageNumber: {
type: 'number',
description: '조회할 페이지 번호 (기본값: 1)',
default: 1,
minimum: 1
}
},
required: ['region']
}
},
{
name: 'get_sale_housing',
description: '공공분양주택 모집공고를 조회합니다. 사용자가 지역, 주택유형 등을 자연어로 요청하면 해당하는 코드로 변환하여 조회합니다.',
inputSchema: {
type: 'object',
properties: {
region: {
type: 'string',
description: '광역시도명 (예: 서울특별시, 경기도, 부산광역시 등). 사용자가 "서울", "경기", "부산" 등으로 말해도 인식 가능',
enum: REGION_CODES.map(r => r.name)
},
district: {
type: 'string',
description: '시군구명 (예: 강남구, 수원시, 성남시 등). 선택사항이며 지역이 지정된 경우에만 사용',
},
housingType: {
type: 'string',
description: '주택유형 (예: 연립주택, 다세대주택, 단독주택, 오피스텔, 다가구주택)',
enum: HOUSING_TYPES.map(h => h.name)
},
pageSize: {
type: 'number',
description: '한 페이지당 조회할 데이터 개수 (기본값: 10, 최대: 100)',
default: 10,
minimum: 1,
maximum: 100
},
pageNumber: {
type: 'number',
description: '조회할 페이지 번호 (기본값: 1)',
default: 1,
minimum: 1
}
},
required: ['region']
}
}
]
};
});
// 도구 실행 핸들러
server.setRequestHandler(CallToolRequestSchema, async (request) => {
const { name, arguments: args } = request.params;
if (!args) {
throw new Error('Arguments are required');
}
try {
switch (name) {
case 'get_rental_housing': {
// 지역명을 코드로 변환
const region = args.region;
const regionCode = REGION_CODES.find(r => r.name === region ||
r.name.includes(region) ||
region.includes(r.name.replace(/특별시|광역시|특별자치시|특별자치도|도$/g, '')))?.code;
if (!regionCode) {
return {
content: [
{
type: 'text',
text: `지원하지 않는 지역입니다. 사용 가능한 지역: ${REGION_CODES.map(r => r.name).join(', ')}`
}
]
};
}
// 시군구 코드 변환 (있는 경우)
let districtCode;
if (args.district) {
const district = DISTRICT_CODES.find(d => d.parentCode === regionCode &&
(d.name === args.district || d.name.includes(args.district)));
districtCode = district?.code;
}
// 공급유형 코드 변환 (있는 경우)
let supplyTypeCode;
if (args.supplyType) {
const supplyType = SUPPLY_TYPES.find(s => s.name === args.supplyType);
supplyTypeCode = supplyType?.code;
}
// 임대료 범위 코드 변환 (있는 경우)
let rentRangeCode;
if (args.rentRange) {
const rentRange = RENT_RANGES.find(r => r.name === args.rentRange);
rentRangeCode = rentRange?.code;
}
const params = {
brtcCode: regionCode,
...(districtCode && { signguCode: districtCode }),
...(supplyTypeCode && { suplyTy: supplyTypeCode }),
...(args.isLongTermLease !== undefined && { lfstsTyAt: args.isLongTermLease ? 'Y' : 'N' }),
...(rentRangeCode && { bassMtRntchrgSe: rentRangeCode }),
numOfRows: args.pageSize || 10,
pageNo: args.pageNumber || 1
};
const result = await housingAPI.getRentalHousingAnnouncements(params);
const formattedData = housingAPI.formatRentalHousingData(result.body.items);
return {
content: [
{
type: 'text',
text: `공공임대주택 모집공고 조회 결과 (총 ${result.body.totalCount}건)\n\n${formattedData}`
}
]
};
}
case 'get_sale_housing': {
// 지역명을 코드로 변환
const region = args.region;
const regionCode = REGION_CODES.find(r => r.name === region ||
r.name.includes(region) ||
region.includes(r.name.replace(/특별시|광역시|특별자치시|특별자치도|도$/g, '')))?.code;
if (!regionCode) {
return {
content: [
{
type: 'text',
text: `지원하지 않는 지역입니다. 사용 가능한 지역: ${REGION_CODES.map(r => r.name).join(', ')}`
}
]
};
}
// 시군구 코드 변환 (있는 경우)
let districtCode;
if (args.district) {
const district = DISTRICT_CODES.find(d => d.parentCode === regionCode &&
(d.name === args.district || d.name.includes(args.district)));
districtCode = district?.code;
}
// 주택유형 코드 변환 (있는 경우)
let housingTypeCode;
if (args.housingType) {
const housingType = HOUSING_TYPES.find(h => h.name === args.housingType);
housingTypeCode = housingType?.code;
}
const params = {
brtcCode: regionCode,
...(districtCode && { signguCode: districtCode }),
...(housingTypeCode && { houseTy: housingTypeCode }),
numOfRows: args.pageSize || 10,
pageNo: args.pageNumber || 1
};
const result = await housingAPI.getSaleHousingAnnouncements(params);
const formattedData = housingAPI.formatSaleHousingData(result.body.items);
return {
content: [
{
type: 'text',
text: `공공분양주택 모집공고 조회 결과 (총 ${result.body.totalCount}건)\n\n${formattedData}`
}
]
};
}
default:
throw new Error(`Unknown tool: ${name}`);
}
}
catch (error) {
return {
content: [
{
type: 'text',
text: `오류가 발생했습니다: ${error instanceof Error ? error.message : '알 수 없는 오류'}`
}
],
isError: true
};
}
});
async function main() {
const transport = new StdioServerTransport();
await server.connect(transport);
console.error('Public Housing MCP Server running on stdio');
}
main().catch((error) => {
console.error('Server error:', error);
process.exit(1);
});
//# sourceMappingURL=index.js.map