@nomadamas/k-skill 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/k-skill.js +60 -0
- package/package.json +37 -0
- package/skills/assembly-bill-vote-search/instruction.md +111 -0
- package/skills/assembly-bill-vote-search/skill.json +10 -0
- package/skills/biz-health-check/instruction.md +69 -0
- package/skills/biz-health-check/scripts/biz_health_check.py +163 -0
- package/skills/biz-health-check/skill.json +10 -0
- package/skills/bok-ecos-stats/instruction.md +95 -0
- package/skills/bok-ecos-stats/scripts/bok_ecos.py +302 -0
- package/skills/bok-ecos-stats/skill.json +10 -0
- package/skills/building-register-search/instruction.md +69 -0
- package/skills/building-register-search/scripts/building_register.py +309 -0
- package/skills/building-register-search/scripts/building_register_xml.py +32 -0
- package/skills/building-register-search/skill.json +9 -0
- package/skills/bunjang-search/instruction.md +154 -0
- package/skills/bunjang-search/skill.json +10 -0
- package/skills/catchtable-sniper/instruction.md +270 -0
- package/skills/catchtable-sniper/skill.json +9 -0
- package/skills/cheap-gas-nearby/instruction.md +107 -0
- package/skills/cheap-gas-nearby/skill.json +10 -0
- package/skills/corporate-registration-consulting/instruction.md +117 -0
- package/skills/corporate-registration-consulting/scripts/fill_official_hwp.py +106 -0
- package/skills/corporate-registration-consulting/skill.json +8 -0
- package/skills/coupang-product-search/instruction.md +219 -0
- package/skills/coupang-product-search/scripts/coupang_partners_mcp.py +146 -0
- package/skills/coupang-product-search/skill.json +10 -0
- package/skills/court-auction-notice-search/instruction.md +201 -0
- package/skills/court-auction-notice-search/skill.json +9 -0
- package/skills/court-payment-order-assistant/instruction.md +120 -0
- package/skills/court-payment-order-assistant/skill.json +10 -0
- package/skills/d2b-notice-search/instruction.md +117 -0
- package/skills/d2b-notice-search/skill.json +10 -0
- package/skills/daangn-cars-search/instruction.md +91 -0
- package/skills/daangn-cars-search/scripts/daangn_cars.py +72 -0
- package/skills/daangn-cars-search/skill.json +9 -0
- package/skills/daangn-jobs-search/instruction.md +90 -0
- package/skills/daangn-jobs-search/scripts/daangn_jobs.py +98 -0
- package/skills/daangn-jobs-search/skill.json +9 -0
- package/skills/daangn-realty-search/instruction.md +114 -0
- package/skills/daangn-realty-search/scripts/daangn_detail_ld.py +76 -0
- package/skills/daangn-realty-search/scripts/daangn_realty.py +216 -0
- package/skills/daangn-realty-search/scripts/daangn_relay_store.py +174 -0
- package/skills/daangn-realty-search/skill.json +8 -0
- package/skills/daangn-used-goods-search/instruction.md +90 -0
- package/skills/daangn-used-goods-search/scripts/daangn_used_goods.py +80 -0
- package/skills/daangn-used-goods-search/skill.json +9 -0
- package/skills/daishin-report-search/instruction.md +138 -0
- package/skills/daishin-report-search/skill.json +10 -0
- package/skills/daiso-product-search/instruction.md +172 -0
- package/skills/daiso-product-search/skill.json +8 -0
- package/skills/danawa-price-search/instruction.md +183 -0
- package/skills/danawa-price-search/scripts/danawa_search.py +354 -0
- package/skills/danawa-price-search/skill.json +8 -0
- package/skills/delivery-tracking/instruction.md +358 -0
- package/skills/delivery-tracking/skill.json +8 -0
- package/skills/donation-place-search/instruction.md +129 -0
- package/skills/donation-place-search/skill.json +9 -0
- package/skills/emergency-room-beds/instruction.md +82 -0
- package/skills/emergency-room-beds/skill.json +8 -0
- package/skills/ev-charger-nearby/instruction.md +80 -0
- package/skills/ev-charger-nearby/scripts/ev_charger.py +222 -0
- package/skills/ev-charger-nearby/skill.json +10 -0
- package/skills/ev-subsidy-status/instruction.md +165 -0
- package/skills/ev-subsidy-status/skill.json +10 -0
- package/skills/express-bus-booking/instruction.md +207 -0
- package/skills/express-bus-booking/references/kobus-http-flow.md +159 -0
- package/skills/express-bus-booking/scripts/kobus_express_booking.py +243 -0
- package/skills/express-bus-booking/skill.json +8 -0
- package/skills/fine-dust-location/instruction.md +89 -0
- package/skills/fine-dust-location/skill.json +9 -0
- package/skills/flight-ticket-search/instruction.md +237 -0
- package/skills/flight-ticket-search/scripts/flight_ticket_search.py +501 -0
- package/skills/flight-ticket-search/skill.json +9 -0
- package/skills/foresttrip-vacancy/instruction.md +167 -0
- package/skills/foresttrip-vacancy/scripts/run_foresttrip_vacancy.py +549 -0
- package/skills/foresttrip-vacancy/skill.json +10 -0
- package/skills/fsc-corporate-info/instruction.md +57 -0
- package/skills/fsc-corporate-info/scripts/fsc_corporate_info.py +113 -0
- package/skills/fsc-corporate-info/skill.json +10 -0
- package/skills/g2b-order-plan-search/instruction.md +131 -0
- package/skills/g2b-order-plan-search/scripts/g2b_order_plan.py +158 -0
- package/skills/g2b-order-plan-search/skill.json +10 -0
- package/skills/g2b-sanctioned-supplier/instruction.md +61 -0
- package/skills/g2b-sanctioned-supplier/scripts/g2b_sanctioned_supplier.py +114 -0
- package/skills/g2b-sanctioned-supplier/skill.json +10 -0
- package/skills/gangnamunni-clinic-search/instruction.md +113 -0
- package/skills/gangnamunni-clinic-search/skill.json +9 -0
- package/skills/geeknews-search/instruction.md +69 -0
- package/skills/geeknews-search/scripts/geeknews_search.py +296 -0
- package/skills/geeknews-search/skill.json +8 -0
- package/skills/gongsijiga-search/instruction.md +128 -0
- package/skills/gongsijiga-search/skill.json +9 -0
- package/skills/gov-overseas-trip-report/instruction.md +488 -0
- package/skills/gov-overseas-trip-report/scripts/gov_overseas_trip_report.py +1206 -0
- package/skills/gov-overseas-trip-report/skill.json +9 -0
- package/skills/han-river-water-level/instruction.md +83 -0
- package/skills/han-river-water-level/skill.json +10 -0
- package/skills/highway-traffic-status/instruction.md +88 -0
- package/skills/highway-traffic-status/scripts/highway_traffic.py +315 -0
- package/skills/highway-traffic-status/skill.json +11 -0
- package/skills/hipass-receipt/instruction.md +97 -0
- package/skills/hipass-receipt/skill.json +10 -0
- package/skills/hola-poke-yeoksam/instruction.md +247 -0
- package/skills/hola-poke-yeoksam/skill.json +8 -0
- package/skills/household-waste-info/instruction.md +117 -0
- package/skills/household-waste-info/skill.json +10 -0
- package/skills/housing-official-price/instruction.md +177 -0
- package/skills/housing-official-price/skill.json +10 -0
- package/skills/hwp/instruction.md +206 -0
- package/skills/hwp/skill.json +8 -0
- package/skills/intercity-bus-booking/instruction.md +189 -0
- package/skills/intercity-bus-booking/references/tmoney-intercity-http-flow.md +126 -0
- package/skills/intercity-bus-booking/scripts/intercity_bus_search.py +381 -0
- package/skills/intercity-bus-booking/skill.json +8 -0
- package/skills/iros-registry-automation/instruction.md +229 -0
- package/skills/iros-registry-automation/scripts/iros_pdf_summary.py +249 -0
- package/skills/iros-registry-automation/scripts/upstream.pin +1 -0
- package/skills/iros-registry-automation/skill.json +9 -0
- package/skills/job-posting-match/instruction.md +130 -0
- package/skills/job-posting-match/scripts/job_posting_match.py +396 -0
- package/skills/job-posting-match/scripts/test_job_posting_match.py +54 -0
- package/skills/job-posting-match/skill.json +8 -0
- package/skills/jobkorea-talent-search/instruction.md +118 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_models.py +27 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_parse.py +186 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search.py +94 -0
- package/skills/jobkorea-talent-search/scripts/jobkorea_talent_search_condition.py +136 -0
- package/skills/jobkorea-talent-search/scripts/test_jobkorea_talent_search.py +76 -0
- package/skills/jobkorea-talent-search/skill.json +9 -0
- package/skills/joseon-sillok-search/instruction.md +76 -0
- package/skills/joseon-sillok-search/scripts/sillok_search.py +552 -0
- package/skills/joseon-sillok-search/skill.json +8 -0
- package/skills/k-dart/instruction.md +406 -0
- package/skills/k-dart/skill.json +8 -0
- package/skills/k-schoollunch-menu/instruction.md +109 -0
- package/skills/k-schoollunch-menu/skill.json +9 -0
- package/skills/k-skill-cleaner/instruction.md +80 -0
- package/skills/k-skill-cleaner/scripts/k_skill_cleaner.py +410 -0
- package/skills/k-skill-cleaner/skill.json +8 -0
- package/skills/k-skill-setup/instruction.md +253 -0
- package/skills/k-skill-setup/skill.json +11 -0
- package/skills/kakao-bar-nearby/instruction.md +76 -0
- package/skills/kakao-bar-nearby/skill.json +8 -0
- package/skills/kakao-map/instruction.md +176 -0
- package/skills/kakao-map/skill.json +10 -0
- package/skills/kakaotalk-mac/instruction.md +189 -0
- package/skills/kakaotalk-mac/skill.json +8 -0
- package/skills/kbl-results/instruction.md +89 -0
- package/skills/kbl-results/skill.json +9 -0
- package/skills/kbo-results/instruction.md +82 -0
- package/skills/kbo-results/skill.json +8 -0
- package/skills/keris-academic-search/instruction.md +81 -0
- package/skills/keris-academic-search/scripts/keris_academic.py +210 -0
- package/skills/keris-academic-search/skill.json +10 -0
- package/skills/kleague-results/instruction.md +92 -0
- package/skills/kleague-results/skill.json +8 -0
- package/skills/kopis-performance-search/instruction.md +109 -0
- package/skills/kopis-performance-search/skill.json +10 -0
- package/skills/korea-weather/instruction.md +93 -0
- package/skills/korea-weather/skill.json +10 -0
- package/skills/korean-character-count/instruction.md +87 -0
- package/skills/korean-character-count/scripts/korean_character_count.js +268 -0
- package/skills/korean-character-count/skill.json +8 -0
- package/skills/korean-cinema-search/instruction.md +177 -0
- package/skills/korean-cinema-search/skill.json +8 -0
- package/skills/korean-heritage-search/instruction.md +106 -0
- package/skills/korean-heritage-search/scripts/korean_heritage_search.py +321 -0
- package/skills/korean-heritage-search/skill.json +8 -0
- package/skills/korean-holiday-calendar/instruction.md +97 -0
- package/skills/korean-holiday-calendar/skill.json +9 -0
- package/skills/korean-humanizer/instruction.md +389 -0
- package/skills/korean-humanizer/references/ai-tell-taxonomy.md +147 -0
- package/skills/korean-humanizer/skill.json +8 -0
- package/skills/korean-jangbu-for/instruction.md +133 -0
- package/skills/korean-jangbu-for/scripts/install.sh +237 -0
- package/skills/korean-jangbu-for/scripts/upstream.pin +1 -0
- package/skills/korean-jangbu-for/skill.json +8 -0
- package/skills/korean-law-search/instruction.md +126 -0
- package/skills/korean-law-search/skill.json +10 -0
- package/skills/korean-marathon-schedule/instruction.md +111 -0
- package/skills/korean-marathon-schedule/skill.json +9 -0
- package/skills/korean-middle-korean/instruction.md +79 -0
- package/skills/korean-middle-korean/scripts/korean_middle_korean.js +214 -0
- package/skills/korean-middle-korean/skill.json +8 -0
- package/skills/korean-patent-search/instruction.md +79 -0
- package/skills/korean-patent-search/scripts/patent_search.py +409 -0
- package/skills/korean-patent-search/skill.json +9 -0
- package/skills/korean-privacy-terms/instruction.md +128 -0
- package/skills/korean-privacy-terms/scripts/install.sh +108 -0
- package/skills/korean-privacy-terms/scripts/upstream.pin +1 -0
- package/skills/korean-privacy-terms/skill.json +8 -0
- package/skills/korean-scholarship-search/instruction.md +317 -0
- package/skills/korean-scholarship-search/references/report-format.md +40 -0
- package/skills/korean-scholarship-search/references/school-discovery.md +61 -0
- package/skills/korean-scholarship-search/references/search-clues.md +58 -0
- package/skills/korean-scholarship-search/references/source-patterns.md +67 -0
- package/skills/korean-scholarship-search/scripts/scholarship_filter.py +811 -0
- package/skills/korean-scholarship-search/scripts/test_scholarship_filter.py +224 -0
- package/skills/korean-scholarship-search/scripts/university_search_plan.py +148 -0
- package/skills/korean-scholarship-search/skill.json +8 -0
- package/skills/korean-slang-writing/instruction.md +181 -0
- package/skills/korean-slang-writing/scripts/_slang_http.py +91 -0
- package/skills/korean-slang-writing/scripts/slang_lookup.py +291 -0
- package/skills/korean-slang-writing/scripts/slang_search.py +284 -0
- package/skills/korean-slang-writing/skill.json +8 -0
- package/skills/korean-spell-check/instruction.md +105 -0
- package/skills/korean-spell-check/scripts/korean_spell_check.py +523 -0
- package/skills/korean-spell-check/skill.json +9 -0
- package/skills/korean-stock-search/instruction.md +194 -0
- package/skills/korean-stock-search/skill.json +10 -0
- package/skills/korean-transit-route/instruction.md +113 -0
- package/skills/korean-transit-route/skill.json +10 -0
- package/skills/kosis-stats/instruction.md +232 -0
- package/skills/kosis-stats/references/kosis-openapi-guide.md +171 -0
- package/skills/kosis-stats/scripts/run_kosis_stats.py +896 -0
- package/skills/kosis-stats/skill.json +9 -0
- package/skills/kr-whois-lookup/instruction.md +107 -0
- package/skills/kr-whois-lookup/skill.json +10 -0
- package/skills/kstartup-search/instruction.md +186 -0
- package/skills/kstartup-search/scripts/run_kstartup.py +424 -0
- package/skills/kstartup-search/skill.json +11 -0
- package/skills/ktx-booking/instruction.md +244 -0
- package/skills/ktx-booking/skill.json +10 -0
- package/skills/lck-analytics/instruction.md +192 -0
- package/skills/lck-analytics/scripts/_lib.js +103 -0
- package/skills/lck-analytics/scripts/analyze-live-game.js +52 -0
- package/skills/lck-analytics/scripts/build-match-report.js +44 -0
- package/skills/lck-analytics/scripts/sync-oracle.js +50 -0
- package/skills/lck-analytics/skill.json +9 -0
- package/skills/lh-notice-search/instruction.md +206 -0
- package/skills/lh-notice-search/skill.json +10 -0
- package/skills/library-book-search/instruction.md +139 -0
- package/skills/library-book-search/skill.json +9 -0
- package/skills/local-election-candidate-search/instruction.md +77 -0
- package/skills/local-election-candidate-search/skill.json +8 -0
- package/skills/localdata-business-status/instruction.md +64 -0
- package/skills/localdata-business-status/scripts/localdata_business_status.py +206 -0
- package/skills/localdata-business-status/skill.json +8 -0
- package/skills/lotto-results/instruction.md +80 -0
- package/skills/lotto-results/skill.json +8 -0
- package/skills/lovebug-report/instruction.md +185 -0
- package/skills/lovebug-report/skill.json +9 -0
- package/skills/market-kurly-search/instruction.md +125 -0
- package/skills/market-kurly-search/skill.json +8 -0
- package/skills/mfds-drug-safety/instruction.md +86 -0
- package/skills/mfds-drug-safety/scripts/mfds_drug_safety.py +184 -0
- package/skills/mfds-drug-safety/skill.json +10 -0
- package/skills/mfds-food-safety/instruction.md +126 -0
- package/skills/mfds-food-safety/scripts/mfds_food_safety.py +281 -0
- package/skills/mfds-food-safety/skill.json +10 -0
- package/skills/myrealtrip-search/instruction.md +239 -0
- package/skills/myrealtrip-search/scripts/myrealtrip_mcp.py +194 -0
- package/skills/myrealtrip-search/scripts/test_myrealtrip_mcp.py +99 -0
- package/skills/myrealtrip-search/skill.json +9 -0
- package/skills/naming-house/instruction.md +146 -0
- package/skills/naming-house/skill.json +8 -0
- package/skills/national-pension-workplace/instruction.md +64 -0
- package/skills/national-pension-workplace/scripts/national_pension_workplace.py +113 -0
- package/skills/national-pension-workplace/skill.json +10 -0
- package/skills/naver-ad-performance/instruction.md +108 -0
- package/skills/naver-ad-performance/scripts/naver_ad_performance.py +240 -0
- package/skills/naver-ad-performance/skill.json +9 -0
- package/skills/naver-blog-research/instruction.md +128 -0
- package/skills/naver-blog-research/scripts/_naver_http.py +58 -0
- package/skills/naver-blog-research/scripts/naver_download_images.py +233 -0
- package/skills/naver-blog-research/scripts/naver_read.py +256 -0
- package/skills/naver-blog-research/scripts/naver_search.py +192 -0
- package/skills/naver-blog-research/skill.json +8 -0
- package/skills/naver-news-search/instruction.md +103 -0
- package/skills/naver-news-search/skill.json +10 -0
- package/skills/naver-shopping-search/instruction.md +94 -0
- package/skills/naver-shopping-search/skill.json +11 -0
- package/skills/nhis-care-checkup-search/instruction.md +116 -0
- package/skills/nhis-care-checkup-search/skill.json +10 -0
- package/skills/nts-business-registration/instruction.md +115 -0
- package/skills/nts-business-registration/scripts/nts_business_registration.py +215 -0
- package/skills/nts-business-registration/skill.json +10 -0
- package/skills/nts-tax-delinquency/instruction.md +54 -0
- package/skills/nts-tax-delinquency/scripts/nts_tax_delinquency.py +150 -0
- package/skills/nts-tax-delinquency/skill.json +8 -0
- package/skills/ohou-today-deal/instruction.md +182 -0
- package/skills/ohou-today-deal/scripts/ohou_today_deal.py +369 -0
- package/skills/ohou-today-deal/skill.json +9 -0
- package/skills/olive-young-search/instruction.md +154 -0
- package/skills/olive-young-search/skill.json +8 -0
- package/skills/parking-lot-search/instruction.md +96 -0
- package/skills/parking-lot-search/skill.json +9 -0
- package/skills/popbill/instruction.md +146 -0
- package/skills/popbill/scripts/popbill_cli.py +284 -0
- package/skills/popbill/scripts/popbill_registry.py +54 -0
- package/skills/popbill/scripts/popbill_safety.py +37 -0
- package/skills/popbill/scripts/popbill_templates.py +71 -0
- package/skills/popbill/skill.json +9 -0
- package/skills/public-restroom-nearby/instruction.md +89 -0
- package/skills/public-restroom-nearby/skill.json +9 -0
- package/skills/real-estate-search/instruction.md +172 -0
- package/skills/real-estate-search/skill.json +10 -0
- package/skills/rhwp-advanced/instruction.md +145 -0
- package/skills/rhwp-advanced/skill.json +8 -0
- package/skills/rhwp-edit/instruction.md +153 -0
- package/skills/rhwp-edit/skill.json +8 -0
- package/skills/s2b-notice-search/instruction.md +66 -0
- package/skills/s2b-notice-search/skill.json +9 -0
- package/skills/saju-fortune/instruction.md +177 -0
- package/skills/saju-fortune/skill.json +8 -0
- package/skills/saramin-talent-search/instruction.md +119 -0
- package/skills/saramin-talent-search/skill.json +9 -0
- package/skills/seoul-bike/instruction.md +83 -0
- package/skills/seoul-bike/scripts/seoul_bike.py +247 -0
- package/skills/seoul-bike/skill.json +10 -0
- package/skills/seoul-density/instruction.md +109 -0
- package/skills/seoul-density/scripts/seoul_density.py +271 -0
- package/skills/seoul-density/skill.json +10 -0
- package/skills/seoul-subway-arrival/instruction.md +85 -0
- package/skills/seoul-subway-arrival/skill.json +9 -0
- package/skills/sh-notice-search/instruction.md +150 -0
- package/skills/sh-notice-search/skill.json +9 -0
- package/skills/srt-booking/instruction.md +181 -0
- package/skills/srt-booking/scripts/srt_booking.py +272 -0
- package/skills/srt-booking/scripts/srt_seats.py +156 -0
- package/skills/srt-booking/skill.json +20 -0
- package/skills/subway-lost-property/instruction.md +93 -0
- package/skills/subway-lost-property/scripts/subway_lost_property.py +244 -0
- package/skills/subway-lost-property/skill.json +8 -0
- package/skills/ticket-availability/instruction.md +175 -0
- package/skills/ticket-availability/scripts/ticket_availability.py +430 -0
- package/skills/ticket-availability/skill.json +9 -0
- package/skills/toss-securities/instruction.md +116 -0
- package/skills/toss-securities/skill.json +9 -0
- package/skills/used-car-price-search/instruction.md +109 -0
- package/skills/used-car-price-search/skill.json +8 -0
- package/skills/yebigun-training/instruction.md +177 -0
- package/skills/yebigun-training/skill.json +9 -0
- package/skills/zipcode-search/instruction.md +159 -0
- package/skills/zipcode-search/scripts/zipcode_search.py +150 -0
- package/skills/zipcode-search/skill.json +8 -0
- package/src/assemble.js +134 -0
- package/src/detect.js +18 -0
- package/templates/action-account.md +4 -0
- package/templates/action-booking.md +5 -0
- package/templates/action-commerce.md +5 -0
- package/templates/action-communication.md +5 -0
- package/templates/action-submission.md +5 -0
- package/templates/browser.md +4 -0
- package/templates/core.md +5 -0
- package/templates/hard-boundary.md +3 -0
- package/templates/local.md +2 -0
- package/templates/lookup.md +2 -0
- package/templates/proxy.md +2 -0
- package/templates/vault.md +4 -0
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""마이리얼트립 공개 Streamable HTTP MCP 서버를 호출한다.
|
|
3
|
+
|
|
4
|
+
엔드포인트는 https://docs.myrealtrip.com/#/api/mcp/overview 에 문서화되어
|
|
5
|
+
있으며 현재 별도 API 키가 필요하지 않다. 이 래퍼는 k-skill 사용자가
|
|
6
|
+
단순 조회와 도구 호출을 위해 MCP 클라이언트 코드를 직접 작성하지 않아도
|
|
7
|
+
되게 해준다.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import asyncio
|
|
14
|
+
import json
|
|
15
|
+
import os
|
|
16
|
+
import sys
|
|
17
|
+
from typing import Any, Sequence
|
|
18
|
+
|
|
19
|
+
DEFAULT_ENDPOINT = "https://mcp-servers.myrealtrip.com/mcp"
|
|
20
|
+
DEFAULT_TIMEOUT_SECONDS = 30.0
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class MyRealTripMcpError(RuntimeError):
|
|
24
|
+
"""설정 또는 MCP 호출 실패 때 발생한다."""
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def parse_json_object(raw: str, *, arg_name: str) -> dict[str, Any]:
|
|
28
|
+
try:
|
|
29
|
+
value = json.loads(raw)
|
|
30
|
+
except json.JSONDecodeError as exc:
|
|
31
|
+
raise argparse.ArgumentTypeError(f"{arg_name}은 올바른 JSON이어야 합니다: {exc}") from exc
|
|
32
|
+
if not isinstance(value, dict):
|
|
33
|
+
raise argparse.ArgumentTypeError(f"{arg_name}은 JSON 객체여야 합니다")
|
|
34
|
+
return value
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def parse_positive_float(raw: str) -> float:
|
|
38
|
+
try:
|
|
39
|
+
value = float(raw)
|
|
40
|
+
except ValueError as exc:
|
|
41
|
+
raise argparse.ArgumentTypeError("timeout은 숫자여야 합니다") from exc
|
|
42
|
+
if value <= 0:
|
|
43
|
+
raise argparse.ArgumentTypeError("timeout은 0보다 커야 합니다")
|
|
44
|
+
return value
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def parse_kv_pairs(pairs: Sequence[str]) -> dict[str, Any]:
|
|
48
|
+
args: dict[str, Any] = {}
|
|
49
|
+
for pair in pairs:
|
|
50
|
+
if "=" not in pair:
|
|
51
|
+
raise argparse.ArgumentTypeError(f"인자 '{pair}'는 key=value 형식이어야 합니다")
|
|
52
|
+
key, raw_value = pair.split("=", 1)
|
|
53
|
+
if not key:
|
|
54
|
+
raise argparse.ArgumentTypeError(f"인자 '{pair}'의 key가 비어 있습니다")
|
|
55
|
+
try:
|
|
56
|
+
value = json.loads(raw_value)
|
|
57
|
+
except json.JSONDecodeError:
|
|
58
|
+
value = raw_value
|
|
59
|
+
args[key] = value
|
|
60
|
+
return args
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def parse_args(argv: Sequence[str] | None = None) -> argparse.Namespace:
|
|
64
|
+
parser = argparse.ArgumentParser(
|
|
65
|
+
description="Streamable HTTP로 마이리얼트립 MCP 도구를 호출합니다.",
|
|
66
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
67
|
+
epilog=(
|
|
68
|
+
"예시:\n"
|
|
69
|
+
" myrealtrip_mcp.py tools\n"
|
|
70
|
+
" myrealtrip_mcp.py call getCurrentTime\n"
|
|
71
|
+
" myrealtrip_mcp.py call searchTnas --arg query='오사카 유니버설 스튜디오' --arg perPage=5\n"
|
|
72
|
+
" myrealtrip_mcp.py call searchDomesticFlights --json '{\"origin\":\"GMP\",\"destination\":\"CJU\",\"departDate\":\"2026-05-20\"}'\n"
|
|
73
|
+
),
|
|
74
|
+
)
|
|
75
|
+
parser.add_argument(
|
|
76
|
+
"--endpoint",
|
|
77
|
+
default=os.getenv("MYREALTRIP_MCP_ENDPOINT", DEFAULT_ENDPOINT),
|
|
78
|
+
help="마이리얼트립 MCP 엔드포인트(기본값: %(default)s).",
|
|
79
|
+
)
|
|
80
|
+
parser.add_argument(
|
|
81
|
+
"--timeout-seconds",
|
|
82
|
+
type=parse_positive_float,
|
|
83
|
+
default=DEFAULT_TIMEOUT_SECONDS,
|
|
84
|
+
help="MCP 연결/호출 전체 제한 시간(기본값: %(default)s초).",
|
|
85
|
+
)
|
|
86
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
87
|
+
|
|
88
|
+
subparsers.add_parser("tools", help="사용 가능한 MCP 도구와 입력 스키마를 JSON으로 출력합니다.")
|
|
89
|
+
|
|
90
|
+
call_parser = subparsers.add_parser("call", help="MCP 도구 하나를 호출하고 CallToolResult를 JSON으로 출력합니다.")
|
|
91
|
+
call_parser.add_argument("tool", help="도구명. 예: searchStays, searchTnas, searchDomesticFlights.")
|
|
92
|
+
call_parser.add_argument(
|
|
93
|
+
"--json",
|
|
94
|
+
dest="json_args",
|
|
95
|
+
type=lambda raw: parse_json_object(raw, arg_name="--json"),
|
|
96
|
+
default=None,
|
|
97
|
+
help="도구 인자를 JSON 객체로 전달합니다.",
|
|
98
|
+
)
|
|
99
|
+
call_parser.add_argument(
|
|
100
|
+
"--arg",
|
|
101
|
+
dest="kv_args",
|
|
102
|
+
action="append",
|
|
103
|
+
default=[],
|
|
104
|
+
metavar="KEY=VALUE",
|
|
105
|
+
help="도구 인자입니다. VALUE는 가능하면 JSON으로 파싱하고, 아니면 문자열로 처리합니다. 반복 지정할 수 있습니다.",
|
|
106
|
+
)
|
|
107
|
+
return parser.parse_args(argv)
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
async def _run_mcp_once(endpoint: str, command: str, tool: str | None, arguments: dict[str, Any] | None) -> Any:
|
|
111
|
+
try:
|
|
112
|
+
from mcp import ClientSession
|
|
113
|
+
from mcp.client.streamable_http import streamablehttp_client
|
|
114
|
+
except Exception as exc: # pragma: no cover - environment dependent
|
|
115
|
+
raise MyRealTripMcpError(
|
|
116
|
+
"Python 패키지 'mcp'가 필요합니다. 다음 명령으로 설치하세요: python3 -m pip install mcp"
|
|
117
|
+
) from exc
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
async with streamablehttp_client(endpoint) as (read_stream, write_stream, _):
|
|
121
|
+
async with ClientSession(read_stream, write_stream) as session:
|
|
122
|
+
await session.initialize()
|
|
123
|
+
if command == "tools":
|
|
124
|
+
result = await session.list_tools()
|
|
125
|
+
return [
|
|
126
|
+
{
|
|
127
|
+
"name": item.name,
|
|
128
|
+
"description": item.description,
|
|
129
|
+
"inputSchema": item.inputSchema,
|
|
130
|
+
}
|
|
131
|
+
for item in result.tools
|
|
132
|
+
]
|
|
133
|
+
if command == "call" and tool:
|
|
134
|
+
return await session.call_tool(tool, arguments or {})
|
|
135
|
+
except Exception as exc:
|
|
136
|
+
raise MyRealTripMcpError(f"마이리얼트립 MCP 엔드포인트 호출 실패 {endpoint}: {exc}") from exc
|
|
137
|
+
|
|
138
|
+
raise MyRealTripMcpError(f"지원하지 않는 명령입니다: {command}")
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
async def run_mcp(
|
|
142
|
+
endpoint: str,
|
|
143
|
+
command: str,
|
|
144
|
+
tool: str | None = None,
|
|
145
|
+
arguments: dict[str, Any] | None = None,
|
|
146
|
+
*,
|
|
147
|
+
timeout_seconds: float = DEFAULT_TIMEOUT_SECONDS,
|
|
148
|
+
) -> Any:
|
|
149
|
+
try:
|
|
150
|
+
return await asyncio.wait_for(
|
|
151
|
+
_run_mcp_once(endpoint, command, tool, arguments),
|
|
152
|
+
timeout=timeout_seconds,
|
|
153
|
+
)
|
|
154
|
+
except TimeoutError as exc:
|
|
155
|
+
raise MyRealTripMcpError(
|
|
156
|
+
f"마이리얼트립 MCP 엔드포인트 호출 시간이 {timeout_seconds:g}초를 초과했습니다: {endpoint}"
|
|
157
|
+
) from exc
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
def jsonable(value: Any) -> Any:
|
|
161
|
+
if hasattr(value, "model_dump"):
|
|
162
|
+
return value.model_dump(mode="json")
|
|
163
|
+
if hasattr(value, "dict"):
|
|
164
|
+
return value.dict()
|
|
165
|
+
return value
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def main(argv: Sequence[str] | None = None) -> int:
|
|
169
|
+
args = parse_args(argv)
|
|
170
|
+
tool_args: dict[str, Any] | None = None
|
|
171
|
+
if args.command == "call":
|
|
172
|
+
tool_args = dict(args.json_args or {})
|
|
173
|
+
tool_args.update(parse_kv_pairs(args.kv_args))
|
|
174
|
+
|
|
175
|
+
try:
|
|
176
|
+
result = asyncio.run(
|
|
177
|
+
run_mcp(
|
|
178
|
+
args.endpoint,
|
|
179
|
+
args.command,
|
|
180
|
+
getattr(args, "tool", None),
|
|
181
|
+
tool_args,
|
|
182
|
+
timeout_seconds=args.timeout_seconds,
|
|
183
|
+
)
|
|
184
|
+
)
|
|
185
|
+
except MyRealTripMcpError as exc:
|
|
186
|
+
print(f"myrealtrip_mcp.py: {exc}", file=sys.stderr)
|
|
187
|
+
return 2
|
|
188
|
+
|
|
189
|
+
print(json.dumps(jsonable(result), ensure_ascii=False, indent=2))
|
|
190
|
+
return 0
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
if __name__ == "__main__":
|
|
194
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import argparse
|
|
2
|
+
import asyncio
|
|
3
|
+
import importlib.util
|
|
4
|
+
import pathlib
|
|
5
|
+
import unittest
|
|
6
|
+
from unittest import mock
|
|
7
|
+
|
|
8
|
+
MODULE_PATH = pathlib.Path(__file__).with_name("myrealtrip_mcp.py")
|
|
9
|
+
spec = importlib.util.spec_from_file_location("myrealtrip_mcp", MODULE_PATH)
|
|
10
|
+
myrealtrip_mcp = importlib.util.module_from_spec(spec)
|
|
11
|
+
assert spec.loader is not None
|
|
12
|
+
spec.loader.exec_module(myrealtrip_mcp)
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ParseHelpersTest(unittest.TestCase):
|
|
16
|
+
def test_parse_json_object_requires_object(self):
|
|
17
|
+
self.assertEqual(myrealtrip_mcp.parse_json_object('{"gid": 123}', arg_name="--json"), {"gid": 123})
|
|
18
|
+
|
|
19
|
+
with self.assertRaises(argparse.ArgumentTypeError):
|
|
20
|
+
myrealtrip_mcp.parse_json_object('["not", "object"]', arg_name="--json")
|
|
21
|
+
|
|
22
|
+
def test_parse_kv_pairs_json_decodes_values_when_possible(self):
|
|
23
|
+
self.assertEqual(
|
|
24
|
+
myrealtrip_mcp.parse_kv_pairs(["query=오사카", "perPage=5", "directFlightOnly=true"]),
|
|
25
|
+
{"query": "오사카", "perPage": 5, "directFlightOnly": True},
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
def test_parse_kv_pairs_rejects_malformed_pairs(self):
|
|
29
|
+
with self.assertRaises(argparse.ArgumentTypeError):
|
|
30
|
+
myrealtrip_mcp.parse_kv_pairs(["missing_separator"])
|
|
31
|
+
|
|
32
|
+
with self.assertRaises(argparse.ArgumentTypeError):
|
|
33
|
+
myrealtrip_mcp.parse_kv_pairs(["=empty_key"])
|
|
34
|
+
|
|
35
|
+
def test_timeout_must_be_positive_number(self):
|
|
36
|
+
self.assertEqual(myrealtrip_mcp.parse_positive_float("1.5"), 1.5)
|
|
37
|
+
|
|
38
|
+
for raw in ["0", "-1", "not-a-number"]:
|
|
39
|
+
with self.subTest(raw=raw):
|
|
40
|
+
with self.assertRaises(argparse.ArgumentTypeError):
|
|
41
|
+
myrealtrip_mcp.parse_positive_float(raw)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class CliAssemblyTest(unittest.TestCase):
|
|
45
|
+
def test_json_and_arg_inputs_are_merged_before_call(self):
|
|
46
|
+
captured = {}
|
|
47
|
+
|
|
48
|
+
async def fake_run_mcp(endpoint, command, tool=None, arguments=None, *, timeout_seconds):
|
|
49
|
+
captured.update(
|
|
50
|
+
endpoint=endpoint,
|
|
51
|
+
command=command,
|
|
52
|
+
tool=tool,
|
|
53
|
+
arguments=arguments,
|
|
54
|
+
timeout_seconds=timeout_seconds,
|
|
55
|
+
)
|
|
56
|
+
return {"ok": True}
|
|
57
|
+
|
|
58
|
+
with mock.patch.object(myrealtrip_mcp, "run_mcp", side_effect=fake_run_mcp):
|
|
59
|
+
exit_code = myrealtrip_mcp.main(
|
|
60
|
+
[
|
|
61
|
+
"--endpoint",
|
|
62
|
+
"https://example.invalid/mcp",
|
|
63
|
+
"--timeout-seconds",
|
|
64
|
+
"2.5",
|
|
65
|
+
"call",
|
|
66
|
+
"searchTnas",
|
|
67
|
+
"--json",
|
|
68
|
+
'{"query":"오사카","perPage":3}',
|
|
69
|
+
"--arg",
|
|
70
|
+
"perPage=5",
|
|
71
|
+
]
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
self.assertEqual(exit_code, 0)
|
|
75
|
+
self.assertEqual(captured["endpoint"], "https://example.invalid/mcp")
|
|
76
|
+
self.assertEqual(captured["command"], "call")
|
|
77
|
+
self.assertEqual(captured["tool"], "searchTnas")
|
|
78
|
+
self.assertEqual(captured["arguments"], {"query": "오사카", "perPage": 5})
|
|
79
|
+
self.assertEqual(captured["timeout_seconds"], 2.5)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TimeoutTest(unittest.TestCase):
|
|
83
|
+
def test_run_mcp_reports_timeout(self):
|
|
84
|
+
async def never_finishes(*args, **kwargs):
|
|
85
|
+
await asyncio.sleep(60)
|
|
86
|
+
|
|
87
|
+
with mock.patch.object(myrealtrip_mcp, "_run_mcp_once", side_effect=never_finishes):
|
|
88
|
+
with self.assertRaisesRegex(myrealtrip_mcp.MyRealTripMcpError, "초과"):
|
|
89
|
+
asyncio.run(
|
|
90
|
+
myrealtrip_mcp.run_mcp(
|
|
91
|
+
"https://example.invalid/mcp",
|
|
92
|
+
"tools",
|
|
93
|
+
timeout_seconds=0.001,
|
|
94
|
+
)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == "__main__":
|
|
99
|
+
unittest.main()
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "myrealtrip-search",
|
|
3
|
+
"description": "마이리얼트립 공식 Streamable HTTP MCP 서버로 항공권, 숙소, 투어/티켓/액티비티를 검색하고 상세·가격·예약 링크를 확인한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.",
|
|
4
|
+
"profiles": [
|
|
5
|
+
"browser",
|
|
6
|
+
"action:booking"
|
|
7
|
+
],
|
|
8
|
+
"frontmatter": "name: myrealtrip-search\ndescription: 마이리얼트립 공식 Streamable HTTP MCP 서버로 항공권, 숙소, 투어/티켓/액티비티를 검색하고 상세·가격·예약 링크를 확인한다. 돌쇠에서는 공식 표면을 통한 후속 액션까지 진행한다.\nlicense: MIT\nmetadata:\n category: travel\n locale: ko-KR\n phase: v1"
|
|
9
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Naming House
|
|
2
|
+
|
|
3
|
+
## What this skill does
|
|
4
|
+
|
|
5
|
+
`naming-house` npm package로 생년월일시 기반 사주 오행, 한자/한글 획수, 발음 흐름, 사용자 선호를 함께 보아 한국 이름 후보를 추천·채점한다. 사주 계산은 기존 `saju-fortune` package의 공개 `analyzeSaju` 결과를 사용하고, 이름 쪽 획수·오행 보조 계산은 `namefyi`와 `korean-stroke`를 로컬에서 호출한다.
|
|
6
|
+
|
|
7
|
+
이 스킬은 작명 결과를 확정 운명이나 법적 이름 검증으로 제시하지 않는다. 후보별 점수, 근거, 한계를 투명하게 보여 주는 성명학 참고 워크플로우다.
|
|
8
|
+
|
|
9
|
+
## When to use
|
|
10
|
+
|
|
11
|
+
- "아이 이름 추천해줘"
|
|
12
|
+
- "사주에 맞는 이름 후보를 골라줘"
|
|
13
|
+
- "성명학으로 이 이름들이 어떤지 비교해줘"
|
|
14
|
+
- "한자 이름 후보의 획수와 오행 흐름을 보고 싶어"
|
|
15
|
+
|
|
16
|
+
## When not to use
|
|
17
|
+
|
|
18
|
+
- 법적으로 사용 가능한 인명용 한자인지 확정해야 하는 경우
|
|
19
|
+
- 사망, 질병, 불행, 성공 같은 운명을 단정해야 하는 경우
|
|
20
|
+
- 생년월일시·성씨·후보 이름 없이 일반적인 예쁜 이름만 원하는 경우
|
|
21
|
+
- 전문 작명가·법률·행정 판단을 대체해야 하는 경우
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
|
|
25
|
+
- Node.js 18+
|
|
26
|
+
- 배포 후: `npm install -g naming-house`
|
|
27
|
+
- 실행 전: `export NODE_PATH="$(npm root -g)"`
|
|
28
|
+
- 이 저장소에서 개발할 때: 루트에서 `npm install`
|
|
29
|
+
|
|
30
|
+
## Required interview inputs
|
|
31
|
+
|
|
32
|
+
1. 성씨(한글)와 가능하면 성 한자
|
|
33
|
+
2. 양력/음력 여부
|
|
34
|
+
3. 생년월일 (`YYYY-MM-DD`)
|
|
35
|
+
4. 태어난 시간 (`HH:mm`, 모르면 모른다고 기록하고 한계를 설명)
|
|
36
|
+
5. 성별 (`male` 또는 `female`으로 package 호출)
|
|
37
|
+
6. 출생 시군구(선택)
|
|
38
|
+
7. 후보 이름 목록(한글 이름 필수, 한자 이름 선택)
|
|
39
|
+
8. 선호 음절, 피하고 싶은 음절, 이름 스타일(선택)
|
|
40
|
+
|
|
41
|
+
음력 생일이면 이 패키지 안에서 변환하지 않는다. 검증된 만세력으로 양력 생년월일을 먼저 확인한 뒤 `calendar: "solar"`로 호출한다.
|
|
42
|
+
|
|
43
|
+
## Workflow
|
|
44
|
+
|
|
45
|
+
### 1. Interview first
|
|
46
|
+
|
|
47
|
+
정보가 부족하면 바로 작명하지 말고 필요한 값을 짧게 묻는다.
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
작명을 위해 성씨, 양력 생년월일, 태어난 시간, 성별, 후보 이름을 알려주세요. 한자 후보가 있으면 함께 주시면 획수·오행 분석 정확도가 올라갑니다.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
태어난 시간을 모르면:
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
태어난 시간을 모르면 시주 기반 보완 오행은 확정하지 못해요. 연·월·일 중심의 보수적 추천으로 진행하고 한계를 함께 표시할게요.
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 2. Install package when missing
|
|
60
|
+
|
|
61
|
+
`node -e 'require("naming-house")'`가 실패하면 웹 스크래핑이나 MCP 서버 실행으로 우회하지 말고 package 설치를 먼저 시도한다.
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
npm install -g naming-house
|
|
65
|
+
export NODE_PATH="$(npm root -g)"
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. Recommend from candidates
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
NODE_PATH="$(npm root -g)" node - <<'JS'
|
|
72
|
+
const { recommendNames } = require("naming-house")
|
|
73
|
+
|
|
74
|
+
recommendNames({
|
|
75
|
+
surname: "김",
|
|
76
|
+
surnameHanja: "金",
|
|
77
|
+
birthDate: "2024-05-18",
|
|
78
|
+
birthTime: "09:20",
|
|
79
|
+
calendar: "solar",
|
|
80
|
+
gender: "female",
|
|
81
|
+
birthCity: "서울",
|
|
82
|
+
preferences: { style: "modern", maxCandidates: 10 },
|
|
83
|
+
candidates: [
|
|
84
|
+
{ givenName: "서아", hanjaName: "瑞雅", tags: ["modern"] },
|
|
85
|
+
{ givenName: "하린", hanjaName: "河潾" },
|
|
86
|
+
{ givenName: "지유" }
|
|
87
|
+
]
|
|
88
|
+
}).then((result) => console.log(JSON.stringify(result, null, 2)))
|
|
89
|
+
JS
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### 4. Score one candidate
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
NODE_PATH="$(npm root -g)" node - <<'JS'
|
|
96
|
+
const { callNamingHouseTool } = require("naming-house")
|
|
97
|
+
|
|
98
|
+
callNamingHouseTool("score_name", {
|
|
99
|
+
input: {
|
|
100
|
+
surname: "박",
|
|
101
|
+
surnameHanja: "朴",
|
|
102
|
+
birthDate: "2024-05-18",
|
|
103
|
+
birthTime: "09:20",
|
|
104
|
+
calendar: "solar",
|
|
105
|
+
gender: "male",
|
|
106
|
+
candidates: [{ givenName: "서준", hanjaName: "瑞俊" }]
|
|
107
|
+
},
|
|
108
|
+
candidate: { givenName: "서준", hanjaName: "瑞俊" }
|
|
109
|
+
}).then((result) => console.log(JSON.stringify(result, null, 2)))
|
|
110
|
+
JS
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
### 5. Interpret results
|
|
114
|
+
|
|
115
|
+
응답은 아래 구조를 따른다.
|
|
116
|
+
|
|
117
|
+
1. 입력 정보와 정확도 한계: 음력 변환 여부, 태어난 시간, 한자 누락 여부를 확인한다.
|
|
118
|
+
2. 사주 오행 요약: 부족/보완 오행과 일간 중심을 짧게 설명한다.
|
|
119
|
+
3. 후보별 점수: 총점, 등급, `elementBalance`, `strokeHarmony`, `soundFlow`, `preferenceFit`를 보여 준다.
|
|
120
|
+
4. 성명학 풀이: 한자 획수 또는 한글 fallback, 오행 상생/상극, 발음 흐름을 설명한다.
|
|
121
|
+
5. 추천 순서: 점수가 높은 순서대로 장점과 주의점을 함께 적는다.
|
|
122
|
+
6. 주의 문구: 성명학은 문화적 참고이며 법적 개명·인명용 한자 검증·운명 판단을 대신하지 않는다고 말한다.
|
|
123
|
+
|
|
124
|
+
## Done when
|
|
125
|
+
|
|
126
|
+
- 필요한 출생 정보와 이름 후보를 인터뷰로 확인했다.
|
|
127
|
+
- `naming-house` package 결과 JSON을 근거로 점수와 순위를 설명했다.
|
|
128
|
+
- 사주 보완 오행, 획수 조화, 발음 흐름, 선호 반영을 분리해 설명했다.
|
|
129
|
+
- 음력, 태어난 시간 미상, 한자 누락, 한자 획수 불가 같은 한계를 명시했다.
|
|
130
|
+
- MCP 서버, proxy, 웹 스크래핑을 실행하지 않았다.
|
|
131
|
+
|
|
132
|
+
## Failure modes
|
|
133
|
+
|
|
134
|
+
- package가 설치되지 않았고 네트워크도 없으면 설치 실패를 설명하고 진행하지 않는다.
|
|
135
|
+
- 음력 또는 윤달 생일은 내장 계산으로 변환하지 않는다.
|
|
136
|
+
- 태어난 시간이 없으면 시주와 시주 기반 해석은 확정할 수 없다.
|
|
137
|
+
- 한자 이름이 없으면 `korean-stroke` 기반 한글 획수 fallback으로 채점하며 정확도가 낮다고 표시한다.
|
|
138
|
+
- 한자 획수나 오행을 확인하지 못하면 해당 후보의 한자 수리 해석을 확정하지 않는다.
|
|
139
|
+
- 후보 이름이 없으면 임의·무작위 생성 대신 선호 조건을 더 물어본다.
|
|
140
|
+
|
|
141
|
+
## Notes
|
|
142
|
+
|
|
143
|
+
- 입력한 생년월일시와 이름 후보는 풀이 중에만 사용하고 영구 저장하지 않는다.
|
|
144
|
+
- 성명학 점수는 참고용이며 의료, 투자, 법률, 행정, 가족 의사결정을 대신하지 않는다.
|
|
145
|
+
- 불안감을 키우는 흉언, 저주, 사망 예언, 질병 확정 표현은 하지 않는다.
|
|
146
|
+
- v1은 공식 인명용 한자 적합성, 불용문자, 법원 개명 가능성을 보증하지 않는다.
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "naming-house",
|
|
3
|
+
"description": "생년월일시와 성씨·후보 이름을 바탕으로 사주 오행과 성명학 점수를 참고해 어울리는 한국 이름을 추천한다.",
|
|
4
|
+
"profiles": [
|
|
5
|
+
"local"
|
|
6
|
+
],
|
|
7
|
+
"frontmatter": "name: naming-house\ndescription: 생년월일시와 성씨·후보 이름을 바탕으로 사주 오행과 성명학 점수를 참고해 어울리는 한국 이름을 추천한다.\nlicense: MIT\nmetadata:\n category: utility\n locale: ko-KR\n phase: v1"
|
|
8
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# 국민연금 가입 사업장 내역 조회
|
|
2
|
+
|
|
3
|
+
## What this skill does
|
|
4
|
+
|
|
5
|
+
공공데이터포털의 **국민연금공단_국민연금 가입 사업장 내역 서비스**(data.go.kr 3046071, V2)를 `k-skill-proxy` 경유로 호출해 다음을 조회한다.
|
|
6
|
+
|
|
7
|
+
- 가입 사업장 후보: 사업장명 + 사업자번호 앞 6자리로 매칭된 사업장 목록 (자료생성년월별 중복은 사업장당 최신 월로 정리)
|
|
8
|
+
- 단일 사업장이 특정되면 상세: 가입자수(`jnngpCnt`), 당월 고지금액(`crrmmNtcAmt`), 신규취득/상실 인원
|
|
9
|
+
- 월별 가입 현황 시계열
|
|
10
|
+
|
|
11
|
+
사업자등록번호는 **앞 6자리만 공개**(뒷자리 마스킹)되므로 사업장명이 필수이며, 후보가 여럿이면 특정하지 않고 목록 그대로 돌려준다.
|
|
12
|
+
|
|
13
|
+
## Design principles
|
|
14
|
+
|
|
15
|
+
- 점수·등급·"위험" 같은 해석 라벨을 만들지 않는다. upstream이 돌려준 사실만 담는다.
|
|
16
|
+
- 후보가 여럿이면 동일성을 단정하지 않는다.
|
|
17
|
+
|
|
18
|
+
## When to use
|
|
19
|
+
|
|
20
|
+
- "○○ 회사 직원 규모가 얼마나 돼? 국민연금 가입자수로 보자"
|
|
21
|
+
- "이 사업장 당월 국민연금 고지금액이 얼마야?"
|
|
22
|
+
- "최근 인원이 늘었는지 줄었는지 월별로 보자"
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- 인터넷 연결, `python3`
|
|
27
|
+
- `scripts/national_pension_workplace.py` helper
|
|
28
|
+
- hosted/self-host `k-skill-proxy`의 `/v1/national-pension/workplace` route 접근 가능
|
|
29
|
+
|
|
30
|
+
## Credential requirements
|
|
31
|
+
|
|
32
|
+
- 사용자 측 필수 시크릿 없음.
|
|
33
|
+
- `KSKILL_PROXY_BASE_URL` — self-host 프록시를 쓸 때만 설정. 비우면 hosted `https://k-skill-proxy.nomadamas.org` 사용.
|
|
34
|
+
- `DATA_GO_KR_API_KEY` 는 프록시 운영 서버 환경에만 둔다. 공공데이터포털에서 `국민연금공단_국민연금 가입 사업장 내역` 활용신청이 되어 있어야 한다.
|
|
35
|
+
|
|
36
|
+
## Inputs
|
|
37
|
+
|
|
38
|
+
- `--name`: 사업장명(상호) — 필수
|
|
39
|
+
- `--b-no`: 사업자등록번호(하이픈 허용). 앞 6자리만 prefix 필터로 쓰인다.
|
|
40
|
+
|
|
41
|
+
## Privacy boundary
|
|
42
|
+
|
|
43
|
+
- 국민연금 데이터는 사업자번호 앞 6자리만 공개되므로, 6자리 일치 + 상호 유사 후보를 나열할 뿐 사업장 동일성을 단정하지 않는다.
|
|
44
|
+
- 공개 범위는 법인·근로자 일정 규모 이상 사업장 위주이며, 소규모/개인 사업장은 미공개일 수 있다.
|
|
45
|
+
|
|
46
|
+
## CLI examples
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
python3 national-pension-workplace/scripts/national_pension_workplace.py \
|
|
50
|
+
--name "삼성전자(주)" --b-no 124-81-00998
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Failure modes
|
|
54
|
+
|
|
55
|
+
- `400 bad_request`: 사업장명을 주지 않음.
|
|
56
|
+
- `503 upstream_not_configured`: 프록시 서버에 `DATA_GO_KR_API_KEY` 없음.
|
|
57
|
+
- `502 upstream_forbidden`: 프록시 키가 3046071에 활용신청되지 않음.
|
|
58
|
+
- 후보 다수: `selected_candidate`가 `null` — 사용자가 후보 목록에서 특정한다.
|
|
59
|
+
|
|
60
|
+
## Official surfaces
|
|
61
|
+
|
|
62
|
+
- 공공데이터포털: <https://www.data.go.kr/data/3046071/openapi.do>
|
|
63
|
+
- upstream: `https://apis.data.go.kr/B552015/NpsBplcInfoInqireServiceV2` (요청 파라미터 camelCase)
|
|
64
|
+
- 프록시 route: `GET /v1/national-pension/workplace`
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"""National Pension Service workplace-coverage lookup via k-skill-proxy.
|
|
2
|
+
|
|
3
|
+
The proxy holds DATA_GO_KR_API_KEY server-side; this helper only builds the
|
|
4
|
+
query and reads the structured response. No user secret is required.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import json
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
import urllib.error
|
|
15
|
+
import urllib.parse
|
|
16
|
+
import urllib.request
|
|
17
|
+
from typing import Any
|
|
18
|
+
|
|
19
|
+
PROXY_BASE_URL_ENV_VAR = "KSKILL_PROXY_BASE_URL"
|
|
20
|
+
DEFAULT_PROXY_BASE_URL = "https://k-skill-proxy.nomadamas.org"
|
|
21
|
+
PROXY_DOWN_MSG = "설정된 k-skill-proxy 서버가 응답하지 않습니다. 잠시 후 재시도하거나 운영자에게 문의하세요."
|
|
22
|
+
PROXY_KEY_NOT_CONFIGURED_MSG = "k-skill-proxy에 필요한 API 키가 설정되어 있지 않습니다. 운영자에게 문의하세요."
|
|
23
|
+
ROUTE = "/v1/national-pension/workplace"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class ApiError(RuntimeError):
|
|
27
|
+
def __init__(self, message: str, *, status_code: int | None = None):
|
|
28
|
+
super().__init__(message)
|
|
29
|
+
self.status_code = status_code
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _text_or_none(value: Any) -> str | None:
|
|
33
|
+
if value is None:
|
|
34
|
+
return None
|
|
35
|
+
text = str(value).strip()
|
|
36
|
+
return text or None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def resolve_proxy_base_url(explicit: str | None = None, env: dict[str, str] | None = None) -> str:
|
|
40
|
+
env = os.environ if env is None else env
|
|
41
|
+
candidate = _text_or_none(explicit or env.get(PROXY_BASE_URL_ENV_VAR))
|
|
42
|
+
if candidate and candidate.casefold() in {"off", "false", "0", "disable", "disabled", "none"}:
|
|
43
|
+
raise ValueError("KSKILL_PROXY_BASE_URL 가 비활성화되어 있습니다.")
|
|
44
|
+
if candidate and candidate != "replace-me":
|
|
45
|
+
return candidate.rstrip("/")
|
|
46
|
+
return DEFAULT_PROXY_BASE_URL
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def read_json_response(request: urllib.request.Request) -> dict[str, Any]:
|
|
50
|
+
try:
|
|
51
|
+
with urllib.request.urlopen(request, timeout=30) as response:
|
|
52
|
+
try:
|
|
53
|
+
payload = json.loads(response.read().decode("utf-8"))
|
|
54
|
+
except json.JSONDecodeError as error:
|
|
55
|
+
raise ApiError("national-pension proxy returned invalid JSON.") from error
|
|
56
|
+
if not isinstance(payload, dict):
|
|
57
|
+
raise ApiError("national-pension proxy returned a non-object JSON payload.")
|
|
58
|
+
return payload
|
|
59
|
+
except urllib.error.HTTPError as error:
|
|
60
|
+
body = error.read().decode("utf-8", errors="replace")
|
|
61
|
+
try:
|
|
62
|
+
payload = json.loads(body)
|
|
63
|
+
except json.JSONDecodeError:
|
|
64
|
+
payload = None
|
|
65
|
+
if error.code == 503 and isinstance(payload, dict) and payload.get("error") == "upstream_not_configured":
|
|
66
|
+
raise ApiError(PROXY_KEY_NOT_CONFIGURED_MSG, status_code=error.code) from error
|
|
67
|
+
if isinstance(payload, dict) and payload.get("message"):
|
|
68
|
+
raise ApiError(str(payload["message"]), status_code=error.code) from error
|
|
69
|
+
raise ApiError(f"national-pension proxy request failed with HTTP {error.code}", status_code=error.code) from error
|
|
70
|
+
except urllib.error.URLError as error:
|
|
71
|
+
raise ApiError(f"{PROXY_DOWN_MSG} (상세: {error.reason})") from error
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def query_workplace(name: str, b_no: str | None = None, *, base_url: str | None = None,
|
|
75
|
+
read_json: Any = read_json_response) -> dict[str, Any]:
|
|
76
|
+
name = _text_or_none(name)
|
|
77
|
+
if not name:
|
|
78
|
+
raise ValueError("사업장명(상호)을 입력하세요. 국민연금 API는 사업자번호 앞 6자리만 공개해 상호가 필수입니다.")
|
|
79
|
+
params = {"name": name}
|
|
80
|
+
if _text_or_none(b_no):
|
|
81
|
+
digits = re.sub(r"\D", "", str(b_no))
|
|
82
|
+
if not re.fullmatch(r"\d{10}", digits):
|
|
83
|
+
raise ValueError("사업자등록번호는 숫자 10자리여야 합니다 (하이픈 허용).")
|
|
84
|
+
params["b_no"] = digits
|
|
85
|
+
url = f"{resolve_proxy_base_url(base_url)}{ROUTE}?{urllib.parse.urlencode(params)}"
|
|
86
|
+
request = urllib.request.Request(url, headers={
|
|
87
|
+
"Accept": "application/json",
|
|
88
|
+
"User-Agent": "k-skill-national-pension-workplace/1.0",
|
|
89
|
+
}, method="GET")
|
|
90
|
+
return read_json(request)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def build_parser() -> argparse.ArgumentParser:
|
|
94
|
+
parser = argparse.ArgumentParser(description="국민연금 가입 사업장 내역 조회 (k-skill-proxy 경유)")
|
|
95
|
+
parser.add_argument("--name", required=True, help="사업장명(상호) — 필수")
|
|
96
|
+
parser.add_argument("--b-no", help="사업자등록번호(앞 6자리만 prefix 필터로 사용)")
|
|
97
|
+
parser.add_argument("--proxy-base-url")
|
|
98
|
+
return parser
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def main(argv: list[str] | None = None) -> int:
|
|
102
|
+
args = build_parser().parse_args(argv)
|
|
103
|
+
try:
|
|
104
|
+
result = query_workplace(args.name, args.b_no, base_url=args.proxy_base_url)
|
|
105
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
106
|
+
return 0
|
|
107
|
+
except (ValueError, ApiError) as error:
|
|
108
|
+
print(json.dumps({"error": str(error)}, ensure_ascii=False, indent=2), file=sys.stderr)
|
|
109
|
+
return 1
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
if __name__ == "__main__":
|
|
113
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "national-pension-workplace",
|
|
3
|
+
"description": "국민연금공단 국민연금 가입 사업장 내역을 공공데이터포털 API(k-skill-proxy 경유)로 조회한다. 사업장명으로 가입자수·당월 고지금액·월별 취득/상실 추이를 확인해 그 회사의 직원 규모와 변화를 본다.",
|
|
4
|
+
"profiles": [
|
|
5
|
+
"proxy",
|
|
6
|
+
"vault",
|
|
7
|
+
"lookup"
|
|
8
|
+
],
|
|
9
|
+
"frontmatter": "name: national-pension-workplace\ndescription: 국민연금공단 국민연금 가입 사업장 내역을 공공데이터포털 API(k-skill-proxy 경유)로 조회한다. 사업장명으로 가입자수·당월 고지금액·월별 취득/상실 추이를 확인해 그 회사의 직원 규모와 변화를 본다.\nlicense: MIT\nmetadata:\n category: business\n locale: ko-KR\n phase: v1"
|
|
10
|
+
}
|