korean-law-mcp 2.1.1 → 2.1.2
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/build/tools/chains.js +61 -29
- package/package.json +1 -1
package/build/tools/chains.js
CHANGED
|
@@ -34,36 +34,60 @@ handler, apiClient, input) {
|
|
|
34
34
|
return { text: `오류: ${e instanceof Error ? e.message : String(e)}`, isError: true };
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
|
+
/** 법령명이 아닌 부가 키워드 제거 (법제처 lawSearch API는 법령명 검색이므로) */
|
|
38
|
+
const NON_LAW_NAME_RE = /\s*(과태료|절차|비용|처벌|기준|허가|신청|부과|근거|위반|방법|요건|조건|처분|수수료|신고|등록|면허|인가|승인|취소|정지|벌칙|벌금|과징금|이행강제금|시정명령|체계|구조|3단|판례|해석|개정|별표|시행령|시행규칙|서식|수입|수출|통관|반환|납부|감면|면제|제한|금지|의무|권리|자격|종류|기간|대상|범위|적용)\s*/g;
|
|
39
|
+
function stripNonLawKeywords(query) {
|
|
40
|
+
return query.replace(NON_LAW_NAME_RE, " ").trim();
|
|
41
|
+
}
|
|
42
|
+
/** XML에서 법령 정보 파싱 */
|
|
43
|
+
function parseLawXml(xmlText, max) {
|
|
44
|
+
const lawRegex = /<law[^>]*>([\s\S]*?)<\/law>/g;
|
|
45
|
+
const results = [];
|
|
46
|
+
let match;
|
|
47
|
+
while ((match = lawRegex.exec(xmlText)) !== null && results.length < max) {
|
|
48
|
+
const content = match[1];
|
|
49
|
+
const lawName = extractTag(content, "법령명한글");
|
|
50
|
+
if (!lawName)
|
|
51
|
+
continue; // 빈 법령명 제외
|
|
52
|
+
results.push({
|
|
53
|
+
lawName,
|
|
54
|
+
lawId: extractTag(content, "법령ID"),
|
|
55
|
+
mst: extractTag(content, "법령일련번호"),
|
|
56
|
+
lawType: extractTag(content, "법령구분명"),
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
return results;
|
|
60
|
+
}
|
|
37
61
|
async function findLaws(apiClient, query, apiKey, max = 3) {
|
|
62
|
+
// 1차: 원본 쿼리로 검색
|
|
63
|
+
let results = [];
|
|
38
64
|
try {
|
|
39
65
|
const xmlText = await apiClient.searchLaw(query, apiKey);
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// 쿼리와 법령명 관련도 기반 정렬 (정확 매칭 > 부분 매칭 > 나머지)
|
|
53
|
-
if (results.length > 1) {
|
|
54
|
-
const queryWords = query.replace(/\s*(시행령|시행규칙|별표|판례|개정|체계|3단|구조|절차|비용|처벌|기준|허가|신청)\s*/g, " ")
|
|
55
|
-
.trim().split(/\s+/).filter(w => w.length > 0);
|
|
56
|
-
results.sort((a, b) => {
|
|
57
|
-
const scoreA = scoreLawRelevance(a.lawName, query, queryWords);
|
|
58
|
-
const scoreB = scoreLawRelevance(b.lawName, query, queryWords);
|
|
59
|
-
return scoreB - scoreA;
|
|
60
|
-
});
|
|
66
|
+
results = parseLawXml(xmlText, max);
|
|
67
|
+
}
|
|
68
|
+
catch { /* 2차 시도로 진행 */ }
|
|
69
|
+
// 2차: 결과 없으면 부가 키워드 제거 후 재시도
|
|
70
|
+
if (results.length === 0) {
|
|
71
|
+
const stripped = stripNonLawKeywords(query);
|
|
72
|
+
if (stripped && stripped !== query) {
|
|
73
|
+
try {
|
|
74
|
+
const xmlText = await apiClient.searchLaw(stripped, apiKey);
|
|
75
|
+
results = parseLawXml(xmlText, max);
|
|
76
|
+
}
|
|
77
|
+
catch { /* 빈 결과 반환 */ }
|
|
61
78
|
}
|
|
62
|
-
return results;
|
|
63
79
|
}
|
|
64
|
-
|
|
65
|
-
|
|
80
|
+
// 쿼리와 법령명 관련도 기반 정렬 (정확 매칭 > 부분 매칭 > 나머지)
|
|
81
|
+
if (results.length > 1) {
|
|
82
|
+
const queryWords = query.replace(NON_LAW_NAME_RE, " ")
|
|
83
|
+
.trim().split(/\s+/).filter(w => w.length > 0);
|
|
84
|
+
results.sort((a, b) => {
|
|
85
|
+
const scoreA = scoreLawRelevance(a.lawName, query, queryWords);
|
|
86
|
+
const scoreB = scoreLawRelevance(b.lawName, query, queryWords);
|
|
87
|
+
return scoreB - scoreA;
|
|
88
|
+
});
|
|
66
89
|
}
|
|
90
|
+
return results;
|
|
67
91
|
}
|
|
68
92
|
/** 쿼리 대비 법령명 관련도 점수 (높을수록 관련) */
|
|
69
93
|
function scoreLawRelevance(lawName, query, queryWords) {
|
|
@@ -98,6 +122,13 @@ function detectExpansions(query) {
|
|
|
98
122
|
exp.push("interpretation");
|
|
99
123
|
return exp;
|
|
100
124
|
}
|
|
125
|
+
/** 조례 쿼리에서 지역명·조례 키워드 제거 → 상위법 검색용 */
|
|
126
|
+
function stripOrdinanceKeywords(query) {
|
|
127
|
+
return query
|
|
128
|
+
.replace(/(?:서울|부산|대구|인천|광주|대전|울산|세종|경기|강원|충북|충남|전북|전남|경북|경남|제주)(?:시|도|특별시|광역시|특별자치시|특별자치도)?/g, "")
|
|
129
|
+
.replace(/\s*(조례|규칙|자치법규)\s*/g, " ")
|
|
130
|
+
.trim();
|
|
131
|
+
}
|
|
101
132
|
function detectDomain(query) {
|
|
102
133
|
if (/관세|수출|수입|통관|FTA|원산지/.test(query))
|
|
103
134
|
return "customs";
|
|
@@ -334,9 +365,9 @@ export const chainOrdinanceCompareSchema = z.object({
|
|
|
334
365
|
export async function chainOrdinanceCompare(apiClient, input) {
|
|
335
366
|
try {
|
|
336
367
|
const parts = [`═══ 조례 비교 연구: ${input.query} ═══`];
|
|
337
|
-
// Step 1: 상위 법령 확인
|
|
338
|
-
const parentQuery = input.parentLaw || input.query;
|
|
339
|
-
const laws = await findLaws(apiClient, parentQuery, input.apiKey, 2);
|
|
368
|
+
// Step 1: 상위 법령 확인 (조례/지역명은 법령 검색에서 제거)
|
|
369
|
+
const parentQuery = input.parentLaw || stripOrdinanceKeywords(input.query);
|
|
370
|
+
const laws = parentQuery ? await findLaws(apiClient, parentQuery, input.apiKey, 2) : [];
|
|
340
371
|
if (laws.length > 0) {
|
|
341
372
|
const p = laws[0];
|
|
342
373
|
parts.push(sec("상위 법령", `${p.lawName} (${p.lawType}) | MST: ${p.mst}`));
|
|
@@ -345,8 +376,9 @@ export async function chainOrdinanceCompare(apiClient, input) {
|
|
|
345
376
|
if (!threeTier.isError)
|
|
346
377
|
parts.push(sec("위임 체계 (법률·시행령·시행규칙)", threeTier.text));
|
|
347
378
|
}
|
|
348
|
-
// Step 2: 조례 검색 (
|
|
349
|
-
const
|
|
379
|
+
// Step 2: 조례 검색 — "조례"/"규칙" 제거 (이미 조례 DB에서 검색하므로)
|
|
380
|
+
const ordinanceQuery = input.query.replace(/\s*(조례|규칙|자치법규)\s*/g, " ").trim() || input.query;
|
|
381
|
+
const ordinances = await callTool(searchOrdinance, apiClient, { query: ordinanceQuery, display: 20, apiKey: input.apiKey });
|
|
350
382
|
if (!ordinances.isError)
|
|
351
383
|
parts.push(sec("전국 자치법규 검색 결과", ordinances.text));
|
|
352
384
|
// 키워드 확장
|