korean-law-mcp 2.3.0 → 2.3.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/README.md +128 -43
- package/build/lib/api-client.js +3 -22
- package/build/lib/article-parser.d.ts +15 -0
- package/build/lib/article-parser.js +51 -0
- package/build/lib/errors.d.ts +0 -15
- package/build/lib/errors.js +7 -40
- package/build/lib/fetch-with-retry.js +16 -4
- package/build/lib/schemas.d.ts +0 -8
- package/build/lib/schemas.js +0 -15
- package/build/lib/three-tier-parser.js +110 -115
- package/build/lib/types.d.ts +10 -7
- package/build/server/http-server.js +4 -2
- package/build/tool-registry.d.ts +0 -4
- package/build/tool-registry.js +7 -1
- package/build/tools/admin-appeals.js +6 -16
- package/build/tools/admin-rule.js +13 -24
- package/build/tools/advanced-search.js +3 -2
- package/build/tools/annex.js +8 -8
- package/build/tools/article-compare.js +5 -9
- package/build/tools/article-detail.js +5 -6
- package/build/tools/article-link-parser.js +1 -1
- package/build/tools/article-with-precedents.js +2 -3
- package/build/tools/autocomplete.js +4 -4
- package/build/tools/batch-articles.js +10 -44
- package/build/tools/chains.js +20 -0
- package/build/tools/committee-decisions.js +7 -11
- package/build/tools/comparison.js +5 -5
- package/build/tools/constitutional-decisions.js +7 -19
- package/build/tools/customs-interpretations.js +7 -7
- package/build/tools/english-law.js +4 -11
- package/build/tools/external-links.js +6 -6
- package/build/tools/historical-law.js +8 -14
- package/build/tools/interpretations.js +5 -15
- package/build/tools/kb-utils.js +2 -2
- package/build/tools/knowledge-base.js +30 -34
- package/build/tools/law-history.js +2 -1
- package/build/tools/law-linkage.d.ts +24 -4
- package/build/tools/law-statistics.js +2 -2
- package/build/tools/law-system-tree.js +6 -8
- package/build/tools/law-text.js +15 -62
- package/build/tools/law-tree.js +2 -2
- package/build/tools/legal-terms.js +2 -10
- package/build/tools/life-law.js +6 -13
- package/build/tools/meta-tools.js +8 -0
- package/build/tools/ordinance-search.js +2 -2
- package/build/tools/ordinance.js +4 -11
- package/build/tools/precedent-keywords.js +1 -1
- package/build/tools/precedent-summary.js +1 -1
- package/build/tools/precedents.js +11 -18
- package/build/tools/search.js +1 -1
- package/build/tools/similar-precedents.js +1 -1
- package/build/tools/tax-tribunal-decisions.js +9 -9
- package/build/tools/three-tier.js +7 -7
- package/build/tools/treaties.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -14,12 +14,24 @@
|
|
|
14
14
|
|
|
15
15
|
---
|
|
16
16
|
|
|
17
|
-
## v2.3
|
|
17
|
+
## v2.3 변경사항
|
|
18
18
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
19
|
+
**원격 MCP 주소** (`your-key` 부분에 [법제처 Open API](https://open.law.go.kr/LSO/openApi/guideResult.do)에서 발급받은 본인 인증키(OC)를 넣으세요):
|
|
20
|
+
|
|
21
|
+
| 프로필 | URL | 도구 수 | 용도 |
|
|
22
|
+
|--------|-----|---------|------|
|
|
23
|
+
| lite | `https://korean-law-mcp.fly.dev/mcp?profile=lite&oc=your-key` | 14개 | Claude.ai 등 웹 클라이언트 (컨텍스트 87% 절감) |
|
|
24
|
+
| full | `https://korean-law-mcp.fly.dev/mcp?oc=your-key` | 89개 | Claude Desktop, Cursor 등 네이티브 클라이언트 |
|
|
25
|
+
|
|
26
|
+
예시: 발급받은 인증키가 `honggildong`이면 → `https://korean-law-mcp.fly.dev/mcp?profile=lite&oc=honggildong`
|
|
27
|
+
|
|
28
|
+
> lite는 체인 8개 + 핵심 4개 + 메타 2개로 동일 기능 커버. 특수 도구는 `discover_tools` → `execute_tool`로 접근.
|
|
29
|
+
|
|
30
|
+
- **도구 프로필 (lite/full)** — 89개 → 14개 자동 축소. 체인 도구가 내부에서 하위 도구를 직접 호출하므로 기능 손실 없음.
|
|
31
|
+
- **URL 쿼리 API 키** — `?oc=your-key`로 세션 전체에 API 키 자동 적용. 커스텀 헤더 설정이 어려운 웹 클라이언트에서 필수.
|
|
32
|
+
- **체인 자동 전문 조회** — `chain_ordinance_compare`가 자치법규 검색 후 상위 1건 전문을 자동 조회. 별도 `get_ordinance` 호출 불필요.
|
|
33
|
+
- **lite 도구 라우팅 개선** — 체인/메타 도구 description을 사용자 질문 의도 기반으로 재작성 + 예시 포함. Claude 웹이 "광진구 복무 조례" 같은 자치법규 질문에도 정확한 도구 선택.
|
|
34
|
+
- **도구 힌트 통일** — 비lite 도구 안내를 `execute_tool()` 호출 예시로 변경. 존재하지 않는 도구 직접 호출 문제 방지.
|
|
23
35
|
- **kordoc 통합 파서** — 자체 HWP5/HWPX/PDF 파서 5개를 [kordoc](https://github.com/chrisryugj/kordoc) 통합 파서로 교체. 의존성 경량화.
|
|
24
36
|
|
|
25
37
|
<details>
|
|
@@ -54,81 +66,154 @@
|
|
|
54
66
|
|
|
55
67
|
---
|
|
56
68
|
|
|
57
|
-
##
|
|
69
|
+
## 설치 및 사용법
|
|
58
70
|
|
|
59
|
-
###
|
|
71
|
+
### 0단계: API 키 발급 (무료, 1분)
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
|
|
73
|
+
모든 방법에 공통으로 필요한 **법제처 Open API 인증키(OC)**를 먼저 발급받으세요.
|
|
74
|
+
|
|
75
|
+
1. [법제처 Open API 신청 페이지](https://open.law.go.kr/LSO/openApi/guideList.do)에 접속합니다.
|
|
76
|
+
2. 회원가입 후 로그인합니다.
|
|
77
|
+
3. **"Open API 사용 신청"** 버튼을 누릅니다.
|
|
78
|
+
4. 신청서를 작성하면 **인증키(OC)**가 발급됩니다. (예: `honggildong`)
|
|
79
|
+
5. 이 인증키를 아래 설정에서 사용합니다.
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
### 방법 1: Claude.ai 웹에서 바로 사용 (설치 없음, 가장 쉬움)
|
|
84
|
+
|
|
85
|
+
아무것도 설치하지 않고, 주소 하나만 입력하면 됩니다. Claude Pro/Max/Team/Enterprise 요금제가 필요합니다 (Free는 커넥터 1개만 가능).
|
|
86
|
+
|
|
87
|
+
**커넥터 추가 방법:**
|
|
88
|
+
|
|
89
|
+
1. [claude.ai](https://claude.ai)에 로그인합니다.
|
|
90
|
+
2. 왼쪽 사이드바 하단의 **본인 이름**을 클릭합니다.
|
|
91
|
+
3. **"설정"** (또는 Settings)을 선택합니다.
|
|
92
|
+
4. **"커넥터"** (또는 Connectors) 메뉴로 들어갑니다.
|
|
93
|
+
5. **"커스텀 커넥터"** 영역에서 **"커스텀 커넥터 추가"** 버튼을 클릭합니다.
|
|
94
|
+
6. 아래 내용을 입력합니다:
|
|
95
|
+
- **이름**: `korean-law` (원하는 이름 아무거나 OK)
|
|
96
|
+
- **URL**: 아래 주소를 붙여넣으세요. `honggildong` 부분을 **0단계에서 발급받은 본인 인증키**로 바꾸세요:
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
https://korean-law-mcp.fly.dev/mcp?profile=lite&oc=honggildong
|
|
63
100
|
```
|
|
64
101
|
|
|
65
|
-
|
|
102
|
+
7. **추가** 버튼을 누르면 등록 완료!
|
|
103
|
+
|
|
104
|
+
**도구 활성화 (중요!):**
|
|
105
|
+
|
|
106
|
+
8. 추가한 커넥터의 **"구성"** (또는 Configure)을 클릭합니다.
|
|
107
|
+
9. 도구 목록이 나오면, 모든 도구를 **"항상 사용"** (또는 Always allow)으로 설정합니다.
|
|
108
|
+
10. 이렇게 하면 매번 승인할 필요 없이 AI가 바로 법령을 검색할 수 있습니다.
|
|
109
|
+
|
|
110
|
+
**사용하기:**
|
|
111
|
+
|
|
112
|
+
11. 채팅 화면으로 돌아가서 "근로기준법 제74조 알려줘"라고 입력하면 끝!
|
|
113
|
+
|
|
114
|
+
> **참고**: 커넥터 URL을 수정하려면 삭제 후 다시 추가해야 합니다.
|
|
115
|
+
|
|
116
|
+
> **lite vs full 차이**: 위 주소는 lite 모드(14개 도구)입니다. 14개로도 89개 전체 기능을 사용할 수 있어요 — AI가 필요할 때 나머지 도구를 알아서 꺼내 씁니다. 모든 도구를 직접 보고 싶으면 주소에서 `profile=lite&`를 빼면 됩니다.
|
|
117
|
+
|
|
118
|
+
---
|
|
119
|
+
|
|
120
|
+
### 방법 2: AI 데스크톱 앱에서 사용 (설치 없음)
|
|
121
|
+
|
|
122
|
+
Claude Desktop, Cursor, Windsurf 같은 **데스크톱 앱**을 쓰고 있다면, 설정 파일에 아래 내용을 추가하세요.
|
|
123
|
+
|
|
124
|
+
**설정 파일 위치 찾기:**
|
|
125
|
+
|
|
126
|
+
| 앱 이름 | Windows | Mac |
|
|
127
|
+
|---------|---------|-----|
|
|
128
|
+
| Claude Desktop | `%APPDATA%\Claude\claude_desktop_config.json` | `~/Library/Application Support/Claude/claude_desktop_config.json` |
|
|
129
|
+
| Cursor | 프로젝트 폴더 안 `.cursor/mcp.json` | 프로젝트 폴더 안 `.cursor/mcp.json` |
|
|
130
|
+
| Windsurf | 프로젝트 폴더 안 `.windsurf/mcp.json` | 프로젝트 폴더 안 `.windsurf/mcp.json` |
|
|
131
|
+
|
|
132
|
+
**설정 파일에 추가할 내용** (`honggildong`을 본인 인증키로 바꾸세요):
|
|
66
133
|
|
|
67
134
|
```json
|
|
68
135
|
{
|
|
69
136
|
"mcpServers": {
|
|
70
137
|
"korean-law": {
|
|
71
|
-
"
|
|
72
|
-
"env": {
|
|
73
|
-
"LAW_OC": "your-api-key"
|
|
74
|
-
}
|
|
138
|
+
"url": "https://korean-law-mcp.fly.dev/mcp?oc=honggildong"
|
|
75
139
|
}
|
|
76
140
|
}
|
|
77
141
|
}
|
|
78
142
|
```
|
|
79
143
|
|
|
80
|
-
|
|
144
|
+
> 이미 다른 MCP 서버가 설정되어 있다면, `"mcpServers": { ... }` 안에 `"korean-law": { ... }` 부분만 추가하면 됩니다.
|
|
81
145
|
|
|
82
|
-
|
|
83
|
-
|-----------|----------|
|
|
84
|
-
| Claude Desktop | `%APPDATA%\Claude\claude_desktop_config.json` (Win) / `~/Library/Application Support/Claude/claude_desktop_config.json` (Mac) |
|
|
85
|
-
| Cursor | `.cursor/mcp.json` |
|
|
86
|
-
| Windsurf | `.windsurf/mcp.json` |
|
|
87
|
-
| Continue | `~/.continue/config.json` |
|
|
88
|
-
| Zed | `~/.config/zed/settings.json` |
|
|
146
|
+
저장 후 앱을 **재시작**하면 법령 도구가 활성화됩니다.
|
|
89
147
|
|
|
90
|
-
|
|
148
|
+
---
|
|
91
149
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
150
|
+
### 방법 3: 내 컴퓨터에 직접 설치 (오프라인 가능)
|
|
151
|
+
|
|
152
|
+
인터넷 없이 쓰고 싶거나, 원격 서버를 거치지 않으려면 직접 설치할 수 있습니다.
|
|
153
|
+
|
|
154
|
+
**사전 준비:** [Node.js](https://nodejs.org) 18 이상이 설치되어 있어야 합니다.
|
|
155
|
+
|
|
156
|
+
**1. 터미널(명령 프롬프트)을 열고 설치합니다:**
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm install -g korean-law-mcp
|
|
100
160
|
```
|
|
101
161
|
|
|
102
|
-
**
|
|
162
|
+
**2. AI 앱 설정 파일에 아래 내용을 추가합니다** (`honggildong`을 본인 인증키로 바꾸세요):
|
|
103
163
|
|
|
104
164
|
```json
|
|
105
165
|
{
|
|
106
166
|
"mcpServers": {
|
|
107
167
|
"korean-law": {
|
|
108
|
-
"
|
|
168
|
+
"command": "korean-law-mcp",
|
|
169
|
+
"env": {
|
|
170
|
+
"LAW_OC": "honggildong"
|
|
171
|
+
}
|
|
109
172
|
}
|
|
110
173
|
}
|
|
111
174
|
}
|
|
112
175
|
```
|
|
113
176
|
|
|
114
|
-
|
|
177
|
+
**3. 앱을 재시작하면 완료!**
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
### 방법 4: 터미널(CLI)에서 직접 사용
|
|
115
182
|
|
|
116
|
-
|
|
183
|
+
개발자라면 터미널에서 직접 법령을 검색할 수 있습니다.
|
|
117
184
|
|
|
118
185
|
```bash
|
|
186
|
+
# 설치
|
|
119
187
|
npm install -g korean-law-mcp
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
korean-law
|
|
188
|
+
|
|
189
|
+
# 인증키 설정 (honggildong을 본인 키로 바꾸세요)
|
|
190
|
+
export LAW_OC=honggildong # Mac/Linux
|
|
191
|
+
set LAW_OC=honggildong # Windows CMD
|
|
192
|
+
$env:LAW_OC="honggildong" # Windows PowerShell
|
|
193
|
+
|
|
194
|
+
# 사용 예시
|
|
195
|
+
korean-law "민법 제1조" # 자연어로 바로 조회
|
|
196
|
+
korean-law search_law --query "관세법" # 도구 직접 호출
|
|
197
|
+
korean-law list # 89개 전체 도구 목록
|
|
198
|
+
korean-law list --category 판례 # 카테고리별 필터
|
|
199
|
+
korean-law help search_law # 도구별 도움말
|
|
128
200
|
```
|
|
129
201
|
|
|
130
202
|
---
|
|
131
203
|
|
|
204
|
+
### API 키 전달 방법 정리
|
|
205
|
+
|
|
206
|
+
여러 방법으로 인증키를 전달할 수 있습니다. 위에서부터 우선 적용됩니다:
|
|
207
|
+
|
|
208
|
+
| 방법 | 사용법 | 언제 쓰나 |
|
|
209
|
+
|------|--------|-----------|
|
|
210
|
+
| URL에 포함 | 주소 끝에 `?oc=내키` | 웹 클라이언트에서 가장 간편 |
|
|
211
|
+
| HTTP 헤더 | `apikey: 내키` | 프로그래밍으로 연동할 때 |
|
|
212
|
+
| 환경변수 | `LAW_OC=내키` | 로컬 설치(방법 3, 4) |
|
|
213
|
+
| 도구 파라미터 | `apiKey: "내키"` | 특정 요청만 다른 키 쓸 때 |
|
|
214
|
+
|
|
215
|
+
---
|
|
216
|
+
|
|
132
217
|
## 사용 예시
|
|
133
218
|
|
|
134
219
|
```
|
package/build/lib/api-client.js
CHANGED
|
@@ -81,28 +81,9 @@ export class LawApiClient {
|
|
|
81
81
|
const response = await fetchWithRetry(url);
|
|
82
82
|
this.throwIfError(response, "getLawText");
|
|
83
83
|
const text = await response.text();
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
errorMsg += "\n\n💡 개선 방법:";
|
|
88
|
-
errorMsg += "\n 1. 전체 법령 조회 (조문 범위 확인):";
|
|
89
|
-
if (params.mst) {
|
|
90
|
-
errorMsg += `\n get_law_text(mst="${params.mst}")`;
|
|
91
|
-
}
|
|
92
|
-
else if (params.lawId) {
|
|
93
|
-
errorMsg += `\n get_law_text(lawId="${params.lawId}")`;
|
|
94
|
-
}
|
|
95
|
-
errorMsg += "\n\n 2. 키워드 검색:";
|
|
96
|
-
errorMsg += `\n search_all(query="관련 키워드")`;
|
|
97
|
-
errorMsg += "\n\n 3. 법령 검색:";
|
|
98
|
-
errorMsg += `\n search_law(query="법령명")`;
|
|
99
|
-
errorMsg += "\n\n ℹ️ 일부 법령은 조문 수가 적습니다 (예: 약사법 시행령 제1~39조)";
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
errorMsg += " MST 또는 법령명을 확인해주세요.";
|
|
103
|
-
}
|
|
104
|
-
throw new Error(errorMsg);
|
|
105
|
-
}
|
|
84
|
+
this.checkHtmlError(text, params.jo
|
|
85
|
+
? `법령 조문(${params.jo})을 찾을 수 없습니다. MST/lawId와 조문번호를 확인해주세요.`
|
|
86
|
+
: "법령을 찾을 수 없습니다. MST 또는 법령명을 확인해주세요.");
|
|
106
87
|
return text;
|
|
107
88
|
}
|
|
108
89
|
/**
|
|
@@ -5,5 +5,20 @@
|
|
|
5
5
|
export declare function flattenContent(value: any): string;
|
|
6
6
|
/** 항 배열에서 내용 추출 (재귀적으로 호/목 처리) */
|
|
7
7
|
export declare function extractHangContent(hangInput: any[] | any): string;
|
|
8
|
+
/**
|
|
9
|
+
* 조문단위 객체를 텍스트로 포맷팅 (law-text, batch-articles, article-detail 공통)
|
|
10
|
+
* 조문 헤더(제X조 제목) + 본문 + 항/호/목을 결합하여 반환
|
|
11
|
+
*/
|
|
12
|
+
export declare function formatArticleUnit(unit: {
|
|
13
|
+
조문여부?: string;
|
|
14
|
+
조문번호?: string;
|
|
15
|
+
조문가지번호?: string;
|
|
16
|
+
조문제목?: string;
|
|
17
|
+
조문내용?: unknown;
|
|
18
|
+
항?: unknown;
|
|
19
|
+
}): {
|
|
20
|
+
header: string;
|
|
21
|
+
body: string;
|
|
22
|
+
} | null;
|
|
8
23
|
/** HTML 정리 - 엔티티 디코딩 순서 중요: & 최후 처리 (이중 인코딩 방지) */
|
|
9
24
|
export declare function cleanHtml(text: string): string;
|
|
@@ -61,6 +61,57 @@ export function extractHangContent(hangInput) {
|
|
|
61
61
|
}
|
|
62
62
|
return content;
|
|
63
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* 조문단위 객체를 텍스트로 포맷팅 (law-text, batch-articles, article-detail 공통)
|
|
66
|
+
* 조문 헤더(제X조 제목) + 본문 + 항/호/목을 결합하여 반환
|
|
67
|
+
*/
|
|
68
|
+
export function formatArticleUnit(unit) {
|
|
69
|
+
if (unit.조문여부 !== "조문")
|
|
70
|
+
return null;
|
|
71
|
+
const joNum = unit.조문번호 || "";
|
|
72
|
+
const joBranch = unit.조문가지번호 || "";
|
|
73
|
+
const joTitle = unit.조문제목 || "";
|
|
74
|
+
// 헤더
|
|
75
|
+
let header = "";
|
|
76
|
+
if (joNum) {
|
|
77
|
+
const displayNum = joBranch && joBranch !== "0" ? `제${joNum}조의${joBranch}` : `제${joNum}조`;
|
|
78
|
+
header = joTitle ? `${displayNum} ${joTitle}` : displayNum;
|
|
79
|
+
}
|
|
80
|
+
// 본문: 조문내용에서 헤더 패턴 제거
|
|
81
|
+
let mainContent = "";
|
|
82
|
+
if (unit.조문내용) {
|
|
83
|
+
const contentStr = flattenContent(unit.조문내용);
|
|
84
|
+
if (contentStr) {
|
|
85
|
+
const headerMatch = contentStr.match(/^(제\d+조(?:의\d+)?\s*(?:\([^)]+\))?)[\s\S]*/);
|
|
86
|
+
if (headerMatch) {
|
|
87
|
+
const bodyPart = contentStr.substring(headerMatch[1].length).trim();
|
|
88
|
+
mainContent = bodyPart || contentStr;
|
|
89
|
+
}
|
|
90
|
+
else {
|
|
91
|
+
mainContent = contentStr;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
// 항/호/목
|
|
96
|
+
let paraContent = "";
|
|
97
|
+
if (unit.항) {
|
|
98
|
+
paraContent = extractHangContent(unit.항);
|
|
99
|
+
}
|
|
100
|
+
// 결합
|
|
101
|
+
let body = "";
|
|
102
|
+
if (mainContent) {
|
|
103
|
+
body = mainContent;
|
|
104
|
+
if (paraContent)
|
|
105
|
+
body += "\n" + paraContent;
|
|
106
|
+
}
|
|
107
|
+
else {
|
|
108
|
+
body = paraContent;
|
|
109
|
+
}
|
|
110
|
+
// HTML 정리
|
|
111
|
+
if (body)
|
|
112
|
+
body = cleanHtml(body);
|
|
113
|
+
return { header, body };
|
|
114
|
+
}
|
|
64
115
|
/** HTML 정리 - 엔티티 디코딩 순서 중요: & 최후 처리 (이중 인코딩 방지) */
|
|
65
116
|
export function cleanHtml(text) {
|
|
66
117
|
return text
|
package/build/lib/errors.d.ts
CHANGED
|
@@ -21,9 +21,6 @@ export declare class LawApiError extends Error {
|
|
|
21
21
|
code: ErrorCode;
|
|
22
22
|
suggestions: string[];
|
|
23
23
|
constructor(message: string, code: ErrorCode, suggestions?: string[]);
|
|
24
|
-
/**
|
|
25
|
-
* 사용자 친화적 포맷
|
|
26
|
-
*/
|
|
27
24
|
format(): string;
|
|
28
25
|
}
|
|
29
26
|
/**
|
|
@@ -35,15 +32,3 @@ export declare class LawApiError extends Error {
|
|
|
35
32
|
* 💡 제안: ...
|
|
36
33
|
*/
|
|
37
34
|
export declare function formatToolError(error: unknown, context?: string): ToolResponse;
|
|
38
|
-
/**
|
|
39
|
-
* 법령 없음 에러
|
|
40
|
-
*/
|
|
41
|
-
export declare function notFoundError(lawName: string, suggestions?: string[]): LawApiError;
|
|
42
|
-
/**
|
|
43
|
-
* API 에러
|
|
44
|
-
*/
|
|
45
|
-
export declare function apiError(status: number, endpoint?: string): LawApiError;
|
|
46
|
-
/**
|
|
47
|
-
* 파라미터 검증 에러
|
|
48
|
-
*/
|
|
49
|
-
export declare function invalidParamError(param: string, expected: string): LawApiError;
|
package/build/lib/errors.js
CHANGED
|
@@ -22,15 +22,12 @@ export class LawApiError extends Error {
|
|
|
22
22
|
this.code = code;
|
|
23
23
|
this.suggestions = suggestions;
|
|
24
24
|
}
|
|
25
|
-
/**
|
|
26
|
-
* 사용자 친화적 포맷
|
|
27
|
-
*/
|
|
28
25
|
format() {
|
|
29
|
-
let result =
|
|
26
|
+
let result = `[ERROR] ${this.message}`;
|
|
30
27
|
if (this.suggestions.length > 0) {
|
|
31
|
-
result += "\n
|
|
28
|
+
result += "\n제안:";
|
|
32
29
|
this.suggestions.forEach((s, i) => {
|
|
33
|
-
result += `\n
|
|
30
|
+
result += `\n ${i + 1}. ${s}`;
|
|
34
31
|
});
|
|
35
32
|
}
|
|
36
33
|
return result;
|
|
@@ -73,49 +70,19 @@ export function formatToolError(error, context) {
|
|
|
73
70
|
msg = String(error);
|
|
74
71
|
suggestions = [];
|
|
75
72
|
}
|
|
76
|
-
// 구조화된 텍스트 조립
|
|
77
73
|
const lines = [];
|
|
78
|
-
lines.push(
|
|
74
|
+
lines.push(`[${code}] ${msg}`);
|
|
79
75
|
if (context) {
|
|
80
|
-
lines.push(
|
|
76
|
+
lines.push(`도구: ${context}`);
|
|
81
77
|
}
|
|
82
78
|
if (suggestions.length > 0) {
|
|
83
|
-
lines.push("
|
|
79
|
+
lines.push("제안:");
|
|
84
80
|
suggestions.forEach((s, i) => {
|
|
85
|
-
lines.push(`
|
|
81
|
+
lines.push(` ${i + 1}. ${s}`);
|
|
86
82
|
});
|
|
87
83
|
}
|
|
88
|
-
else {
|
|
89
|
-
lines.push("💡 제안: (없음)");
|
|
90
|
-
}
|
|
91
84
|
return {
|
|
92
85
|
content: [{ type: "text", text: lines.join("\n") }],
|
|
93
86
|
isError: true,
|
|
94
87
|
};
|
|
95
88
|
}
|
|
96
|
-
/**
|
|
97
|
-
* 법령 없음 에러
|
|
98
|
-
*/
|
|
99
|
-
export function notFoundError(lawName, suggestions) {
|
|
100
|
-
return new LawApiError(`'${lawName}'을(를) 찾을 수 없습니다.`, ErrorCodes.NOT_FOUND, suggestions || [
|
|
101
|
-
`search_law(query="${lawName}")로 법령 검색`,
|
|
102
|
-
"법령명 철자 확인",
|
|
103
|
-
]);
|
|
104
|
-
}
|
|
105
|
-
/**
|
|
106
|
-
* API 에러
|
|
107
|
-
*/
|
|
108
|
-
export function apiError(status, endpoint) {
|
|
109
|
-
const suggestions = status === 429
|
|
110
|
-
? ["잠시 후 다시 시도", "요청 빈도 줄이기"]
|
|
111
|
-
: status >= 500
|
|
112
|
-
? ["법제처 API 상태 확인", "잠시 후 다시 시도"]
|
|
113
|
-
: ["요청 파라미터 확인"];
|
|
114
|
-
return new LawApiError(`API 오류 (${status})${endpoint ? ` - ${endpoint}` : ""}`, status === 429 ? ErrorCodes.RATE_LIMITED : ErrorCodes.API_ERROR, suggestions);
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* 파라미터 검증 에러
|
|
118
|
-
*/
|
|
119
|
-
export function invalidParamError(param, expected) {
|
|
120
|
-
return new LawApiError(`잘못된 파라미터: ${param}`, ErrorCodes.INVALID_PARAM, [`${param}는 ${expected} 형식이어야 합니다.`]);
|
|
121
|
-
}
|
|
@@ -28,8 +28,7 @@ export async function fetchWithRetry(url, options = {}) {
|
|
|
28
28
|
}
|
|
29
29
|
// Retryable error - check if we have retries left
|
|
30
30
|
if (attempt < retries) {
|
|
31
|
-
const
|
|
32
|
-
const delay = baseDelay + Math.random() * baseDelay * 0.5;
|
|
31
|
+
const delay = getRetryDelay(response, retryDelay, attempt);
|
|
33
32
|
await sleep(delay);
|
|
34
33
|
continue;
|
|
35
34
|
}
|
|
@@ -49,8 +48,7 @@ export async function fetchWithRetry(url, options = {}) {
|
|
|
49
48
|
}
|
|
50
49
|
// Retry on network errors
|
|
51
50
|
if (attempt < retries) {
|
|
52
|
-
const
|
|
53
|
-
const delay = baseDelay + Math.random() * baseDelay * 0.5;
|
|
51
|
+
const delay = getRetryDelay(null, retryDelay, attempt);
|
|
54
52
|
await sleep(delay);
|
|
55
53
|
continue;
|
|
56
54
|
}
|
|
@@ -58,6 +56,20 @@ export async function fetchWithRetry(url, options = {}) {
|
|
|
58
56
|
}
|
|
59
57
|
throw lastError || new Error("Request failed after retries");
|
|
60
58
|
}
|
|
59
|
+
/** Retry-After 헤더 우선, 없으면 exponential backoff + jitter */
|
|
60
|
+
function getRetryDelay(response, retryDelay, attempt) {
|
|
61
|
+
if (response) {
|
|
62
|
+
const retryAfter = response.headers.get("Retry-After");
|
|
63
|
+
if (retryAfter) {
|
|
64
|
+
const seconds = Number(retryAfter);
|
|
65
|
+
if (!isNaN(seconds) && seconds > 0) {
|
|
66
|
+
return seconds * 1000;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const baseDelay = retryDelay * Math.pow(2, attempt);
|
|
71
|
+
return baseDelay + Math.random() * baseDelay * 0.5;
|
|
72
|
+
}
|
|
61
73
|
function sleep(ms) {
|
|
62
74
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
63
75
|
}
|
package/build/lib/schemas.d.ts
CHANGED
|
@@ -10,10 +10,6 @@ export declare const dateSchema: z.ZodString;
|
|
|
10
10
|
* 선택적 날짜 스키마
|
|
11
11
|
*/
|
|
12
12
|
export declare const optionalDateSchema: z.ZodOptional<z.ZodString>;
|
|
13
|
-
/**
|
|
14
|
-
* API 키 스키마
|
|
15
|
-
*/
|
|
16
|
-
export declare const apiKeySchema: z.ZodOptional<z.ZodString>;
|
|
17
13
|
/**
|
|
18
14
|
* 페이지네이션 스키마
|
|
19
15
|
*/
|
|
@@ -21,10 +17,6 @@ export declare const paginationSchema: z.ZodObject<{
|
|
|
21
17
|
display: z.ZodDefault<z.ZodNumber>;
|
|
22
18
|
page: z.ZodDefault<z.ZodNumber>;
|
|
23
19
|
}, z.core.$strip>;
|
|
24
|
-
/**
|
|
25
|
-
* 날짜 포맷터 (YYYYMMDD → "2024년 1월 1일")
|
|
26
|
-
*/
|
|
27
|
-
export declare function formatDateKorean(dateStr: string | undefined | null): string;
|
|
28
20
|
/**
|
|
29
21
|
* 응답 크기 제한 (50KB)
|
|
30
22
|
*/
|
package/build/lib/schemas.js
CHANGED
|
@@ -30,10 +30,6 @@ export const dateSchema = z
|
|
|
30
30
|
* 선택적 날짜 스키마
|
|
31
31
|
*/
|
|
32
32
|
export const optionalDateSchema = dateSchema.optional();
|
|
33
|
-
/**
|
|
34
|
-
* API 키 스키마
|
|
35
|
-
*/
|
|
36
|
-
export const apiKeySchema = z.string().optional().describe("API 키 (생략시 환경변수 사용)");
|
|
37
33
|
/**
|
|
38
34
|
* 페이지네이션 스키마
|
|
39
35
|
*/
|
|
@@ -41,17 +37,6 @@ export const paginationSchema = z.object({
|
|
|
41
37
|
display: z.number().min(1).max(100).default(20).describe("결과 수 (기본:20, 최대:100)"),
|
|
42
38
|
page: z.number().min(1).default(1).describe("페이지 번호 (기본:1)"),
|
|
43
39
|
});
|
|
44
|
-
/**
|
|
45
|
-
* 날짜 포맷터 (YYYYMMDD → "2024년 1월 1일")
|
|
46
|
-
*/
|
|
47
|
-
export function formatDateKorean(dateStr) {
|
|
48
|
-
if (!dateStr || dateStr.length < 8)
|
|
49
|
-
return dateStr || "N/A";
|
|
50
|
-
const y = dateStr.substring(0, 4);
|
|
51
|
-
const m = parseInt(dateStr.substring(4, 6), 10);
|
|
52
|
-
const d = parseInt(dateStr.substring(6, 8), 10);
|
|
53
|
-
return `${y}년 ${m}월 ${d}일`;
|
|
54
|
-
}
|
|
55
40
|
/**
|
|
56
41
|
* 응답 크기 제한 (50KB)
|
|
57
42
|
*/
|