algokit-mcp 1.0.1 → 1.0.3
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 +284 -24
- package/dist/api/boj-scraper.js +1 -1
- package/dist/api/boj-scraper.js.map +1 -1
- package/dist/api/programmers-scraper.d.ts +0 -5
- package/dist/api/programmers-scraper.d.ts.map +1 -1
- package/dist/api/programmers-scraper.js +59 -90
- package/dist/api/programmers-scraper.js.map +1 -1
- package/dist/index.js +38 -35
- package/dist/index.js.map +1 -1
- package/dist/tools/boj/analyze-code-submission-boj.d.ts +67 -0
- package/dist/tools/boj/analyze-code-submission-boj.d.ts.map +1 -0
- package/dist/tools/boj/analyze-code-submission-boj.js +91 -0
- package/dist/tools/boj/analyze-code-submission-boj.js.map +1 -0
- package/dist/tools/boj/analyze-problem-boj.d.ts +48 -0
- package/dist/tools/boj/analyze-problem-boj.d.ts.map +1 -0
- package/dist/tools/boj/analyze-problem-boj.js +52 -0
- package/dist/tools/boj/analyze-problem-boj.js.map +1 -0
- package/dist/tools/boj/fetch-problem-content-boj.d.ts +49 -0
- package/dist/tools/boj/fetch-problem-content-boj.d.ts.map +1 -0
- package/dist/tools/boj/fetch-problem-content-boj.js +95 -0
- package/dist/tools/boj/fetch-problem-content-boj.js.map +1 -0
- package/dist/tools/boj/generate-hint-boj.d.ts +42 -0
- package/dist/tools/boj/generate-hint-boj.d.ts.map +1 -0
- package/dist/tools/boj/generate-hint-boj.js +82 -0
- package/dist/tools/boj/generate-hint-boj.js.map +1 -0
- package/dist/tools/boj/generate-review-template-boj.d.ts +48 -0
- package/dist/tools/boj/generate-review-template-boj.d.ts.map +1 -0
- package/dist/tools/boj/generate-review-template-boj.js +52 -0
- package/dist/tools/boj/generate-review-template-boj.js.map +1 -0
- package/dist/tools/boj/get-problem-boj.d.ts +22 -0
- package/dist/tools/boj/get-problem-boj.d.ts.map +1 -0
- package/dist/tools/boj/get-problem-boj.js +89 -0
- package/dist/tools/boj/get-problem-boj.js.map +1 -0
- package/dist/tools/boj/search-problems-boj.d.ts +42 -0
- package/dist/tools/boj/search-problems-boj.d.ts.map +1 -0
- package/dist/tools/boj/search-problems-boj.js +143 -0
- package/dist/tools/boj/search-problems-boj.js.map +1 -0
- package/dist/tools/boj/search-tags-boj.d.ts +22 -0
- package/dist/tools/boj/search-tags-boj.d.ts.map +1 -0
- package/dist/tools/boj/search-tags-boj.js +70 -0
- package/dist/tools/boj/search-tags-boj.js.map +1 -0
- package/dist/tools/get-problem-boj.d.ts +22 -0
- package/dist/tools/get-problem-boj.d.ts.map +1 -0
- package/dist/tools/get-problem-boj.js +89 -0
- package/dist/tools/get-problem-boj.js.map +1 -0
- package/dist/tools/programmers/analyze-code-submission-programmers.d.ts +60 -0
- package/dist/tools/programmers/analyze-code-submission-programmers.d.ts.map +1 -0
- package/dist/tools/programmers/analyze-code-submission-programmers.js +85 -0
- package/dist/tools/programmers/analyze-code-submission-programmers.js.map +1 -0
- package/dist/tools/programmers/analyze-problem-programmers.d.ts +48 -0
- package/dist/tools/programmers/analyze-problem-programmers.d.ts.map +1 -0
- package/dist/tools/programmers/analyze-problem-programmers.js +53 -0
- package/dist/tools/programmers/analyze-problem-programmers.js.map +1 -0
- package/dist/tools/programmers/fetch-problem-content-programmers.d.ts +46 -0
- package/dist/tools/programmers/fetch-problem-content-programmers.d.ts.map +1 -0
- package/dist/tools/programmers/fetch-problem-content-programmers.js +76 -0
- package/dist/tools/programmers/fetch-problem-content-programmers.js.map +1 -0
- package/dist/tools/programmers/generate-hint-programmers.d.ts +42 -0
- package/dist/tools/programmers/generate-hint-programmers.d.ts.map +1 -0
- package/dist/tools/programmers/generate-hint-programmers.js +80 -0
- package/dist/tools/programmers/generate-hint-programmers.js.map +1 -0
- package/dist/tools/programmers/generate-review-template-programmers.d.ts +48 -0
- package/dist/tools/programmers/generate-review-template-programmers.d.ts.map +1 -0
- package/dist/tools/programmers/generate-review-template-programmers.js +53 -0
- package/dist/tools/programmers/generate-review-template-programmers.js.map +1 -0
- package/dist/tools/programmers/get-problem-programmers.d.ts +53 -0
- package/dist/tools/programmers/get-problem-programmers.d.ts.map +1 -0
- package/dist/tools/programmers/get-problem-programmers.js +163 -0
- package/dist/tools/programmers/get-problem-programmers.js.map +1 -0
- package/dist/tools/programmers/search-problems-programmers.d.ts +73 -0
- package/dist/tools/programmers/search-problems-programmers.d.ts.map +1 -0
- package/dist/tools/programmers/search-problems-programmers.js +173 -0
- package/dist/tools/programmers/search-problems-programmers.js.map +1 -0
- package/dist/tools/search-problems-boj.d.ts +42 -0
- package/dist/tools/search-problems-boj.d.ts.map +1 -0
- package/dist/tools/search-problems-boj.js +143 -0
- package/dist/tools/search-problems-boj.js.map +1 -0
- package/dist/tools/search-tags-boj.d.ts +22 -0
- package/dist/tools/search-tags-boj.d.ts.map +1 -0
- package/dist/tools/search-tags-boj.js +70 -0
- package/dist/tools/search-tags-boj.js.map +1 -0
- package/dist/utils/agent-skill-installer.d.ts +9 -0
- package/dist/utils/agent-skill-installer.d.ts.map +1 -0
- package/dist/utils/agent-skill-installer.js +94 -0
- package/dist/utils/agent-skill-installer.js.map +1 -0
- package/dist/utils/claudecode-skill-installer.d.ts +6 -0
- package/dist/utils/claudecode-skill-installer.d.ts.map +1 -0
- package/dist/utils/claudecode-skill-installer.js +70 -0
- package/dist/utils/claudecode-skill-installer.js.map +1 -0
- package/dist/utils/skill-installer.d.ts +6 -0
- package/dist/utils/skill-installer.d.ts.map +1 -0
- package/dist/utils/skill-installer.js +70 -0
- package/dist/utils/skill-installer.js.map +1 -0
- package/dist/utils/tier-converter.d.ts.map +1 -1
- package/dist/utils/tier-converter.js +3 -0
- package/dist/utils/tier-converter.js.map +1 -1
- package/package.json +3 -4
- package/skills/algo:code-review/SKILL.md +134 -0
- package/skills/algo:fetch/SKILL.md +102 -0
- package/skills/algo:hint/SKILL.md +111 -0
- package/skills/algo:review/SKILL.md +123 -0
- package/skills/algo:search/SKILL.md +122 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# AlgoKit-mcp
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**AI 에이전트와 함께하는 알고리즘 학습 MCP 툴킷 (백준, 프로그래머스 지원)**
|
|
4
4
|
|
|
5
5
|
AI 기반 힌트 생성, 문제 분석, 복습 템플릿 제공으로 효율적인 알고리즘 학습을 지원합니다.
|
|
6
6
|
|
|
@@ -12,9 +12,117 @@ AI 기반 힌트 생성, 문제 분석, 복습 템플릿 제공으로 효율적
|
|
|
12
12
|
- 📝 **복습 템플릿**: 자동 생성되는 마크다운 복습 문서
|
|
13
13
|
- 🧪 **코드 분석**: 제출 코드의 시간/공간 복잡도 분석 및 개선 제안
|
|
14
14
|
|
|
15
|
+
## 플랫폼별 설정 경로
|
|
16
|
+
|
|
17
|
+
| 항목 | Claude Code | Codex | Gemini CLI |
|
|
18
|
+
|------|-------------|-------|------------|
|
|
19
|
+
| **Skills 기능명** | Skills | Agent Skills | Agent Skills |
|
|
20
|
+
| **Skills 호출** | `/skill-name` | `$skill-name` or 자동 | 모델 자율 판단 |
|
|
21
|
+
| **MCP 설정 (프로젝트)** | `.mcp.json` | `.codex/config.toml` | `.gemini/settings.json` |
|
|
22
|
+
| **MCP 설정 (전역)** | `~/.claude.json` | `~/.codex/config.toml` | `~/.gemini/settings.json` |
|
|
23
|
+
| **Skills (프로젝트)** | `.claude/skills/` | `.codex/skills/` | `.gemini/skills/` |
|
|
24
|
+
| **Skills (전역)** | `~/.claude/skills/` | `~/.codex/skills/` | `~/.gemini/skills/` |
|
|
25
|
+
|
|
26
|
+
> Skills는 MCP 서버 첫 기동 시 감지된 플랫폼에 자동 설치됩니다.
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
15
30
|
## 설치
|
|
16
31
|
|
|
17
|
-
|
|
32
|
+
> **Skills 자동 설치**: MCP 서버 첫 기동 시 설치된 AI 에이전트를 자동으로 감지하여 Skills를 설치합니다.
|
|
33
|
+
|
|
34
|
+
### Claude Code
|
|
35
|
+
|
|
36
|
+
#### 자동 설치
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
# 전역 설정 (모든 프로젝트에서 사용)
|
|
40
|
+
claude mcp add --scope user algokit -- npx -y algokit-mcp
|
|
41
|
+
|
|
42
|
+
# 프로젝트 설정 (.mcp.json 생성)
|
|
43
|
+
claude mcp add --scope project algokit -- npx -y algokit-mcp
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
#### 수동 설치
|
|
49
|
+
|
|
50
|
+
`~/.claude.json` (전역) 편집:
|
|
51
|
+
|
|
52
|
+
```json
|
|
53
|
+
{
|
|
54
|
+
"mcpServers": {
|
|
55
|
+
"algokit": {
|
|
56
|
+
"command": "npx",
|
|
57
|
+
"args": ["-y", "algokit-mcp"]
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
또는 프로젝트 루트에 `.mcp.json` 생성 ([예시](.claude/examples/claude-code-config.json)):
|
|
64
|
+
|
|
65
|
+
```json
|
|
66
|
+
{
|
|
67
|
+
"mcpServers": {
|
|
68
|
+
"algokit": {
|
|
69
|
+
"command": "npx",
|
|
70
|
+
"args": ["-y", "algokit-mcp"]
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
#### Skills 위치
|
|
77
|
+
|
|
78
|
+
| 등록 방식 | Skills 위치 |
|
|
79
|
+
|----------|------------|
|
|
80
|
+
| `--scope user` (전역) | `~/.claude/skills/` |
|
|
81
|
+
| `--scope project` (`.mcp.json` 있음) | `<프로젝트>/.claude/skills/` |
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
### Codex
|
|
87
|
+
|
|
88
|
+
#### 자동 설치
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# 전역 설치
|
|
92
|
+
codex mcp add algokit -- npx -y algokit-mcp
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
#### 수동 설치
|
|
96
|
+
|
|
97
|
+
`~/.codex/config.toml` (전역) 또는 `.codex/config.toml` (프로젝트) 편집 ([예시](.claude/examples/codex-config.toml)):
|
|
98
|
+
|
|
99
|
+
```toml
|
|
100
|
+
[mcp.servers.algokit]
|
|
101
|
+
command = "npx"
|
|
102
|
+
args = ["-y", "algokit-mcp"]
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
#### Skills 위치
|
|
106
|
+
|
|
107
|
+
| 등록 방식 | Skills 위치 |
|
|
108
|
+
|----------|------------|
|
|
109
|
+
| 프로젝트 (`.codex/` 있음) | `<프로젝트>/.codex/skills/` |
|
|
110
|
+
| 전역 (`~/.codex/` 있음) | `~/.codex/skills/` |
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
### Gemini CLI
|
|
115
|
+
|
|
116
|
+
#### 자동 설치
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# 전역 설치
|
|
120
|
+
gemini mcp add algokit -- npx -y algokit-mcp
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
#### 수동 설치
|
|
124
|
+
|
|
125
|
+
`~/.gemini/settings.json` (전역) 또는 `.gemini/settings.json` (프로젝트) 편집 ([예시](.claude/examples/gemini-config.json)):
|
|
18
126
|
|
|
19
127
|
```json
|
|
20
128
|
{
|
|
@@ -27,15 +135,22 @@ AI 기반 힌트 생성, 문제 분석, 복습 템플릿 제공으로 효율적
|
|
|
27
135
|
}
|
|
28
136
|
```
|
|
29
137
|
|
|
138
|
+
#### Skills 위치
|
|
139
|
+
|
|
140
|
+
| 등록 방식 | Skills 위치 |
|
|
141
|
+
|----------|------------|
|
|
142
|
+
| 프로젝트 (`.gemini/` 있음) | `<프로젝트>/.gemini/skills/` |
|
|
143
|
+
| 전역 (`~/.gemini/` 있음) | `~/.gemini/skills/` |
|
|
144
|
+
|
|
30
145
|
## MCP 도구
|
|
31
146
|
|
|
32
147
|
### 백준 (BOJ)
|
|
33
148
|
|
|
34
149
|
| 도구 | 설명 |
|
|
35
150
|
|------|------|
|
|
36
|
-
| `
|
|
37
|
-
| `
|
|
38
|
-
| `
|
|
151
|
+
| `search_problems_boj` | 문제 검색 (티어, 태그, 키워드) |
|
|
152
|
+
| `get_problem_boj` | 문제 상세 조회 |
|
|
153
|
+
| `search_tags_boj` | 알고리즘 태그 검색 |
|
|
39
154
|
| `fetch_problem_content_boj` | 문제 본문 스크래핑 |
|
|
40
155
|
| `analyze_problem_boj` | 문제 분석 및 힌트 가이드 |
|
|
41
156
|
| `generate_hint_boj` | 3단계 힌트 생성 |
|
|
@@ -46,45 +161,190 @@ AI 기반 힌트 생성, 문제 분석, 복습 템플릿 제공으로 효율적
|
|
|
46
161
|
|
|
47
162
|
| 도구 | 설명 |
|
|
48
163
|
|------|------|
|
|
49
|
-
| `
|
|
50
|
-
| `
|
|
164
|
+
| `search_problems_programmers` | 문제 검색 (난이도, 카테고리) |
|
|
165
|
+
| `get_problem_programmers` | 문제 상세 조회 |
|
|
51
166
|
| `fetch_problem_content_programmers` | 문제 본문 스크래핑 |
|
|
52
167
|
| `analyze_problem_programmers` | 문제 분석 |
|
|
53
168
|
| `generate_hint_programmers` | 힌트 생성 |
|
|
54
169
|
| `generate_review_template_programmers` | 복습 템플릿 |
|
|
55
170
|
| `analyze_code_submission_programmers` | 코드 분석 |
|
|
56
171
|
|
|
57
|
-
##
|
|
172
|
+
## Skills
|
|
58
173
|
|
|
59
|
-
**Skill 이름**: `algo:`
|
|
174
|
+
**Skill 이름**: `algo:` (BOJ/프로그래머스 자동 선택)
|
|
60
175
|
|
|
61
|
-
|
|
176
|
+
Claude Code, Codex, Gemini CLI 모두 동일한 `algo:` Skills를 지원합니다.
|
|
177
|
+
MCP 서버 첫 기동 시 감지된 에이전트에 자동으로 설치되며, 수동으로도 설치할 수 있습니다.
|
|
62
178
|
|
|
63
|
-
### 설치
|
|
179
|
+
### 수동 설치
|
|
64
180
|
|
|
65
181
|
```bash
|
|
66
|
-
#
|
|
67
|
-
cp skills/*
|
|
182
|
+
# Claude Code
|
|
183
|
+
cp -r skills/* ~/.claude/skills/ # 전역
|
|
184
|
+
cp -r skills/* .claude/skills/ # 프로젝트
|
|
185
|
+
|
|
186
|
+
# Codex
|
|
187
|
+
cp -r skills/* ~/.codex/skills/ # 전역
|
|
188
|
+
cp -r skills/* .codex/skills/ # 프로젝트
|
|
68
189
|
|
|
69
|
-
#
|
|
70
|
-
cp skills/* ~/.
|
|
190
|
+
# Gemini CLI
|
|
191
|
+
cp -r skills/* ~/.gemini/skills/ # 전역
|
|
192
|
+
cp -r skills/* .gemini/skills/ # 프로젝트
|
|
71
193
|
```
|
|
72
194
|
|
|
73
195
|
### 사용 예시
|
|
74
196
|
|
|
197
|
+
#### `algo:search` - 문제 검색
|
|
198
|
+
|
|
199
|
+
```
|
|
200
|
+
You: "골드 난이도 DP 문제 추천해줘"
|
|
201
|
+
|
|
202
|
+
Claude: "백준에서 골드 티어의 DP 문제를 검색했습니다.
|
|
203
|
+
|
|
204
|
+
**추천 문제 3개:**
|
|
205
|
+
1. 🥇 [G5] 12865번 - 평범한 배낭 (✅ 16,234명)
|
|
206
|
+
2. 🥇 [G4] 9251번 - LCS (✅ 13,892명)
|
|
207
|
+
3. 🥇 [G3] 1932번 - 정수 삼각형 (✅ 18,234명)
|
|
208
|
+
|
|
209
|
+
어떤 문제부터 시작하시겠어요?"
|
|
75
210
|
```
|
|
76
|
-
You: "백준 골드 난이도 DP 문제 3개 추천해줘"
|
|
77
|
-
Claude: [자동으로 BOJ search_problems 호출]
|
|
78
211
|
|
|
79
|
-
|
|
80
|
-
|
|
212
|
+
```
|
|
213
|
+
You: "https://www.acmicpc.net/problem/1927"
|
|
214
|
+
|
|
215
|
+
Claude: "백준 1927번 '최소 힙' 문제를 조회했습니다.
|
|
216
|
+
|
|
217
|
+
**문제 정보:**
|
|
218
|
+
- 난이도: 🥈 Silver I
|
|
219
|
+
- 분류: 자료구조, 우선순위 큐
|
|
220
|
+
- 해결: 28,456명 | 정답률: 47.2%
|
|
221
|
+
|
|
222
|
+
본문을 보시겠어요? (/algo:fetch)"
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### `algo:hint` - 단계별 힌트
|
|
226
|
+
|
|
227
|
+
```
|
|
228
|
+
You: "1927번 힌트 줘"
|
|
229
|
+
|
|
230
|
+
Claude: "**💡 Level 1 힌트 (접근 방향)**
|
|
231
|
+
|
|
232
|
+
이 문제는 **우선순위 큐(Priority Queue)** 자료구조를 사용합니다.
|
|
233
|
+
Python의 `heapq` 모듈 또는 C++의 `priority_queue`를 활용하면
|
|
234
|
+
O(log N) 시간에 최솟값을 찾을 수 있어요.
|
|
235
|
+
|
|
236
|
+
더 구체적인 힌트가 필요하신가요?"
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
```
|
|
240
|
+
You: "더 구체적으로 알려줘"
|
|
241
|
+
|
|
242
|
+
Claude: "**💡 Level 2 힌트 (구현 방법)**
|
|
243
|
+
|
|
244
|
+
1. **최소 힙** 사용 (Python: heapq, C++: priority_queue<int, greater<int>>)
|
|
245
|
+
2. x가 0이 아니면 → `heappush(x)`
|
|
246
|
+
3. x가 0이면 → `heappop()` (빈 경우 0 출력)
|
|
247
|
+
|
|
248
|
+
시간복잡도: O(N log N)
|
|
249
|
+
공간복잡도: O(N)
|
|
250
|
+
|
|
251
|
+
코드 예시를 보시겠어요? (/algo:hint 정답)"
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
#### `algo:code-review` - 코드 분석
|
|
255
|
+
|
|
256
|
+
```
|
|
257
|
+
You: "1927번 내 코드 분석해줘"
|
|
258
|
+
You: [Python 코드 붙여넣기]
|
|
259
|
+
|
|
260
|
+
Claude: "코드를 분석했습니다! ✨
|
|
261
|
+
|
|
262
|
+
**✅ 좋은 점:**
|
|
263
|
+
- heapq 모듈을 올바르게 사용
|
|
264
|
+
- 시간복잡도 O(N log N) 달성
|
|
265
|
+
|
|
266
|
+
**⚠️ 개선 가능:**
|
|
267
|
+
1. `sys.stdin.readline()` 사용 → 입력 속도 개선 (30% 빠름)
|
|
268
|
+
2. 빈 힙 체크를 `if heap:` 대신 `if len(heap):`로 명시
|
|
269
|
+
|
|
270
|
+
**예상 성능:**
|
|
271
|
+
- 현재: 약 300ms
|
|
272
|
+
- 개선 후: 약 200ms
|
|
273
|
+
|
|
274
|
+
수정 코드를 보여드릴까요?"
|
|
275
|
+
```
|
|
81
276
|
|
|
82
|
-
|
|
83
|
-
Claude: [search_programmers_problems 자동 호출]
|
|
277
|
+
#### `algo:review` - 복습 템플릿
|
|
84
278
|
|
|
85
|
-
You: "힌트 더 줘"
|
|
86
|
-
Claude: [이전 대화 분석 → 적절한 Level 힌트 제공]
|
|
87
279
|
```
|
|
280
|
+
You: "1927번 복습 문서 만들어줘"
|
|
281
|
+
|
|
282
|
+
Claude: "복습 템플릿을 생성했습니다! 📝
|
|
283
|
+
|
|
284
|
+
# 백준 1927번 - 최소 힙
|
|
285
|
+
|
|
286
|
+
## 문제 요약
|
|
287
|
+
최솟값을 빠르게 찾는 자료구조 구현
|
|
288
|
+
|
|
289
|
+
## 핵심 알고리즘
|
|
290
|
+
- **우선순위 큐** (최소 힙)
|
|
291
|
+
- 삽입: O(log N), 삭제: O(log N)
|
|
292
|
+
|
|
293
|
+
## 풀이 과정
|
|
294
|
+
1. heapq 모듈 import
|
|
295
|
+
2. 입력에 따라 push/pop 수행
|
|
296
|
+
...
|
|
297
|
+
|
|
298
|
+
이 템플릿을 파일로 저장하시겠어요?"
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
---
|
|
302
|
+
|
|
303
|
+
## 학습 워크플로우
|
|
304
|
+
|
|
305
|
+
### 새 문제 접근하기
|
|
306
|
+
|
|
307
|
+
```
|
|
308
|
+
1. You: "골드 3~5 DP 문제 추천"
|
|
309
|
+
→ Claude: [문제 3개 추천]
|
|
310
|
+
|
|
311
|
+
2. You: "1932번 본문 보여줘"
|
|
312
|
+
→ Claude: [문제 본문 표시]
|
|
313
|
+
|
|
314
|
+
3. You: "힌트 줘"
|
|
315
|
+
→ Claude: [Level 1 힌트]
|
|
316
|
+
|
|
317
|
+
4. You: "더 구체적으로"
|
|
318
|
+
→ Claude: [Level 2 힌트]
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
### 풀이 후 복습하기
|
|
322
|
+
|
|
323
|
+
```
|
|
324
|
+
1. You: "1932번 내 코드 분석해줘" + [코드]
|
|
325
|
+
→ Claude: [시간복잡도, 개선점 제시]
|
|
326
|
+
|
|
327
|
+
2. You: "복습 문서 만들어줘"
|
|
328
|
+
→ Claude: [마크다운 템플릿 생성]
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## 사용 방법
|
|
334
|
+
|
|
335
|
+
### 일반 사용 (자연스러운 대화)
|
|
336
|
+
```
|
|
337
|
+
You: "골드 DP 문제 추천해줘"
|
|
338
|
+
→ Claude가 맥락 파악 → MCP 도구 자동 호출 → 결과 제공
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Skill 사용 (명시적 호출, 권장)
|
|
342
|
+
```
|
|
343
|
+
You: "/algo:search 골드 DP 문제 추천해줘"
|
|
344
|
+
→ Skill이 의도 명확히 파악 → 더 정확한 MCP 도구 선택
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
**차이점**: 둘 다 MCP 도구를 호출하지만, Skill 사용 시 더 명시적이고 정확한 응답을 받을 수 있습니다.
|
|
88
348
|
|
|
89
349
|
## 개발
|
|
90
350
|
|
|
@@ -131,7 +391,7 @@ MIT
|
|
|
131
391
|
|
|
132
392
|
<div align="center">
|
|
133
393
|
|
|
134
|
-
**[GitHub](https://github.com/itsme-shawn/AlgoKit-mcp)** • **[Issues](https://github.com/itsme-shawn/algoKit-mcp/issues)** • **[NPM](https://www.npmjs.com/package/algokit-mcp
|
|
394
|
+
**[GitHub](https://github.com/itsme-shawn/AlgoKit-mcp)** • **[Issues](https://github.com/itsme-shawn/algoKit-mcp/issues)** • **[NPM](https://www.npmjs.com/package/algokit-mcp)**
|
|
135
395
|
|
|
136
396
|
|
|
137
397
|
</div>
|
package/dist/api/boj-scraper.js
CHANGED
|
@@ -79,7 +79,7 @@ export class BOJScraper {
|
|
|
79
79
|
// 캐시 확인
|
|
80
80
|
const cached = this.cache.get(problemId);
|
|
81
81
|
if (cached !== undefined) {
|
|
82
|
-
|
|
82
|
+
process.stderr.write(`[BOJScraper] 캐시 히트: 문제 ${problemId}\n`);
|
|
83
83
|
return cached;
|
|
84
84
|
}
|
|
85
85
|
// Rate Limiting (백업 보호, 캐시 미스 시에만)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"boj-scraper.js","sourceRoot":"","sources":["../../src/api/boj-scraper.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,0BAA0B,CAAC;AACvD,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD;;GAEG;AACH,MAAM,UAAU,GAAG;IACjB,yBAAyB;IACzB,QAAQ,EAAE,iCAAiC;IAC3C,6CAA6C;IAC7C,UAAU,EAAE,iHAAiH;IAC7H,iBAAiB;IACjB,OAAO,EAAE,KAAK;IACd,aAAa;IACb,WAAW,EAAE,CAAC;IACd,iCAAiC;IACjC,gBAAgB,EAAE,IAAI;CACd,CAAC;AAEX;;GAEG;AACH,MAAM,OAAO,aAAc,SAAQ,KAAK;IAG7B;IACA;IAHT,YACE,OAAe,EACR,IAA+D,EAC/D,aAAuB;QAE9B,KAAK,CAAC,OAAO,CAAC,CAAC;QAHR,SAAI,GAAJ,IAAI,CAA2D;QAC/D,kBAAa,GAAb,aAAa,CAAU;QAG9B,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,UAAU;IACrB,qCAAqC;IAC7B,eAAe,GAAG,CAAC,CAAC;IAE5B,2BAA2B;IACnB,WAAW,CAAc;IAEjC,wBAAwB;IAChB,KAAK,CAA2B;IAExC;QACE,0CAA0C;QAC1C,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC;YACjC,QAAQ,EAAE,CAAC;YACX,UAAU,EAAE,CAAC;YACb,WAAW,EAAE,KAAK;SACnB,CAAC,CAAC;QAEH,2BAA2B;QAC3B,IAAI,CAAC,KAAK,GAAG,IAAI,QAAQ,CAAC,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC;IAC1D,CAAC;IAED;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CAAC,gBAAgB,CAAC,SAAiB;QACtC,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,SAAS,CAAC,IAAI,SAAS,IAAI,CAAC,EAAE,CAAC;YACnD,MAAM,IAAI,aAAa,CACrB,kBAAkB,SAAS,EAAE,EAC7B,aAAa,CACd,CAAC;QACJ,CAAC;QAED,QAAQ;QACR,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACzC,IAAI,MAAM,KAAK,SAAS,EAAE,CAAC;YACzB,OAAO,CAAC,
|
|
1
|
+
{"version":3,"file":"boj-scraper.js","sourceRoot":"","sources":["../../src/api/boj-scraper.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,7 +11,6 @@ export declare class ProgrammersScrapeError extends Error {
|
|
|
11
11
|
* 프로그래머스 스크래퍼
|
|
12
12
|
*/
|
|
13
13
|
export declare class ProgrammersScraper {
|
|
14
|
-
private browserPool;
|
|
15
14
|
private rateLimiter;
|
|
16
15
|
private searchCache;
|
|
17
16
|
private problemCache;
|
|
@@ -25,10 +24,6 @@ export declare class ProgrammersScraper {
|
|
|
25
24
|
* @throws {ProgrammersScrapeError}
|
|
26
25
|
*/
|
|
27
26
|
searchProblems(options?: ProgrammersSearchOptions): Promise<ProgrammersProblemSummary[]>;
|
|
28
|
-
/**
|
|
29
|
-
* 검색 URL 생성
|
|
30
|
-
*/
|
|
31
|
-
private buildSearchUrl;
|
|
32
27
|
/**
|
|
33
28
|
* 문제 상세 페이지 HTML 가져오기 (fetch 기반, BOJScraper 패턴)
|
|
34
29
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"programmers-scraper.d.ts","sourceRoot":"","sources":["../../src/api/programmers-scraper.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"programmers-scraper.d.ts","sourceRoot":"","sources":["../../src/api/programmers-scraper.ts"],"names":[],"mappings":"AAQA,OAAO,EACL,wBAAwB,EACxB,yBAAyB,EACzB,wBAAwB,EACzB,MAAM,yBAAyB,CAAC;AAGjC;;GAEG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAGtC,IAAI,EACP,SAAS,GACT,oBAAoB,GACpB,kBAAkB,GAClB,aAAa;IACV,aAAa,CAAC,EAAE,OAAO;gBAN9B,OAAO,EAAE,MAAM,EACR,IAAI,EACP,SAAS,GACT,oBAAoB,GACpB,kBAAkB,GAClB,aAAa,EACV,aAAa,CAAC,EAAE,OAAO,YAAA;CAKjC;AAED;;GAEG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,WAAW,CAAgD;IACnE,OAAO,CAAC,YAAY,CAA6C;IACjE,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsC;;IAc9D;;;;;;OAMG;IACG,cAAc,CAClB,OAAO,GAAE,wBAA6B,GACrC,OAAO,CAAC,yBAAyB,EAAE,CAAC;IAgJvC;;;;;;OAMG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IA8C1D;;OAEG;YACW,iBAAiB;IAsE/B;;OAEG;IACH,OAAO,CAAC,MAAM;IAId;;;;OAIG;IACH,aAAa;;;;IAOb;;OAEG;IACH,UAAU,IAAI,IAAI;IAKlB;;;;;;OAMG;IACG,UAAU,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,CAAC;CA2BvE"}
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
|
+
* 프로그래머스 웹 스크래핑 클라이언트
|
|
3
|
+
*
|
|
4
|
+
* 검색: 프로그래머스 내부 JSON API (fetch 기반)
|
|
5
|
+
* 상세 페이지: cheerio (fetch 기반)
|
|
6
|
+
*/
|
|
2
7
|
import { RateLimiter } from '../utils/rate-limiter.js';
|
|
3
8
|
import { LRUCache } from '../utils/lru-cache.js';
|
|
4
9
|
import { parseProgrammersProblemContent } from '../utils/html-parser.js';
|
|
@@ -19,13 +24,11 @@ export class ProgrammersScrapeError extends Error {
|
|
|
19
24
|
* 프로그래머스 스크래퍼
|
|
20
25
|
*/
|
|
21
26
|
export class ProgrammersScraper {
|
|
22
|
-
browserPool;
|
|
23
27
|
rateLimiter;
|
|
24
28
|
searchCache;
|
|
25
29
|
problemCache;
|
|
26
30
|
baseUrl = 'https://school.programmers.co.kr';
|
|
27
31
|
constructor() {
|
|
28
|
-
this.browserPool = BrowserPool.getInstance();
|
|
29
32
|
// 초당 1회 요청 (보수적)
|
|
30
33
|
this.rateLimiter = new RateLimiter({
|
|
31
34
|
capacity: 2,
|
|
@@ -50,121 +53,87 @@ export class ProgrammersScraper {
|
|
|
50
53
|
// 캐시 확인
|
|
51
54
|
const cached = this.searchCache.get(cacheKey);
|
|
52
55
|
if (cached !== undefined) {
|
|
53
|
-
|
|
56
|
+
process.stderr.write('[ProgrammersScraper] 캐시 히트: 검색 결과\n');
|
|
54
57
|
// limit 적용 후 반환
|
|
55
58
|
return limit && limit > 0 ? cached.slice(0, limit) : cached;
|
|
56
59
|
}
|
|
57
60
|
// Rate limiting (캐시 미스 시에만)
|
|
58
61
|
await this.rateLimiter.acquire();
|
|
59
|
-
let browser = null;
|
|
60
62
|
try {
|
|
61
|
-
// 1.
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
page,
|
|
69
|
-
query,
|
|
70
|
-
});
|
|
71
|
-
console.log(`[ProgrammersScraper] 검색 URL: ${searchUrl}`);
|
|
72
|
-
// 3. 페이지 이동 및 로딩 대기
|
|
73
|
-
try {
|
|
74
|
-
await browserPage.goto(searchUrl, {
|
|
75
|
-
waitUntil: 'networkidle2',
|
|
76
|
-
timeout: 30000,
|
|
77
|
-
});
|
|
63
|
+
// 1. 프로그래머스 내부 API URL 생성
|
|
64
|
+
const params = new URLSearchParams();
|
|
65
|
+
params.set('page', String(page));
|
|
66
|
+
params.set('perPage', '20');
|
|
67
|
+
params.set('order', order);
|
|
68
|
+
for (const level of levels) {
|
|
69
|
+
params.append('levels[]', String(level));
|
|
78
70
|
}
|
|
79
|
-
|
|
80
|
-
|
|
71
|
+
if (query) {
|
|
72
|
+
params.set('search', query);
|
|
81
73
|
}
|
|
82
|
-
|
|
74
|
+
const apiUrl = `${this.baseUrl}/api/v2/school/challenges/?${params.toString()}`;
|
|
75
|
+
process.stderr.write(`[ProgrammersScraper] API URL: ${apiUrl}\n`);
|
|
76
|
+
// 2. API 호출
|
|
77
|
+
const controller = new AbortController();
|
|
78
|
+
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
|
79
|
+
let apiResponse;
|
|
83
80
|
try {
|
|
84
|
-
await
|
|
85
|
-
|
|
81
|
+
const response = await fetch(apiUrl, {
|
|
82
|
+
signal: controller.signal,
|
|
83
|
+
headers: {
|
|
84
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
85
|
+
Accept: 'application/json',
|
|
86
|
+
'Accept-Language': 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7',
|
|
87
|
+
},
|
|
86
88
|
});
|
|
89
|
+
clearTimeout(timeoutId);
|
|
90
|
+
if (!response.ok) {
|
|
91
|
+
throw new ProgrammersScrapeError(`API 요청 실패: HTTP ${response.status}`, 'NAVIGATION_ERROR');
|
|
92
|
+
}
|
|
93
|
+
apiResponse = await response.json();
|
|
87
94
|
}
|
|
88
95
|
catch (error) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
console.log('[ProgrammersScraper] 검색 결과 없음');
|
|
96
|
-
await browserPage.close();
|
|
97
|
-
return [];
|
|
96
|
+
clearTimeout(timeoutId);
|
|
97
|
+
if (error instanceof ProgrammersScrapeError) {
|
|
98
|
+
throw error;
|
|
99
|
+
}
|
|
100
|
+
if (error.name === 'AbortError') {
|
|
101
|
+
throw new ProgrammersScrapeError('요청이 타임아웃되었습니다 (10000ms 초과)', 'TIMEOUT', error);
|
|
98
102
|
}
|
|
99
|
-
throw new ProgrammersScrapeError(
|
|
103
|
+
throw new ProgrammersScrapeError(`API 요청 실패: ${error.message}`, 'NAVIGATION_ERROR', error);
|
|
100
104
|
}
|
|
101
|
-
//
|
|
105
|
+
// 3. API 응답 → ProgrammersProblemSummary 매핑
|
|
102
106
|
let problems;
|
|
103
107
|
try {
|
|
104
|
-
problems =
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
const title = titleLink?.textContent?.trim() || '';
|
|
114
|
-
const category = categoryEl?.textContent?.trim() || '기타';
|
|
115
|
-
const levelClass = levelSpan?.className || '';
|
|
116
|
-
const level = parseInt(levelClass.match(/level-(\d+)/)?.[1] || '0');
|
|
117
|
-
const finishedText = finishedEl?.textContent?.trim() || '0명';
|
|
118
|
-
const finishedCount = parseInt(finishedText.replace(/,/g, '').replace('명', '') || '0');
|
|
119
|
-
const rateText = rateEl?.textContent?.trim() || '0%';
|
|
120
|
-
const acceptanceRate = parseInt(rateText.replace('%', '') || '0');
|
|
121
|
-
return {
|
|
122
|
-
problemId,
|
|
123
|
-
title,
|
|
124
|
-
level,
|
|
125
|
-
category,
|
|
126
|
-
url: `https://school.programmers.co.kr${href}`,
|
|
127
|
-
finishedCount,
|
|
128
|
-
acceptanceRate,
|
|
129
|
-
};
|
|
130
|
-
});
|
|
131
|
-
});
|
|
108
|
+
problems = (apiResponse.result ?? []).map((item) => ({
|
|
109
|
+
problemId: String(item.id),
|
|
110
|
+
title: item.title,
|
|
111
|
+
level: item.level,
|
|
112
|
+
category: item.partTitle,
|
|
113
|
+
finishedCount: item.finishedCount,
|
|
114
|
+
acceptanceRate: item.acceptanceRate,
|
|
115
|
+
url: `${this.baseUrl}/learn/courses/30/lessons/${item.id}`,
|
|
116
|
+
}));
|
|
132
117
|
}
|
|
133
118
|
catch (error) {
|
|
134
119
|
throw new ProgrammersScrapeError('문제 목록 파싱 실패', 'PARSE_ERROR', error);
|
|
135
120
|
}
|
|
136
|
-
|
|
137
|
-
// 6. 캐시에 저장 (limit 적용 전 전체 결과 저장)
|
|
121
|
+
// 4. 캐시에 저장 (limit 적용 전 전체 결과 저장)
|
|
138
122
|
this.searchCache.set(cacheKey, problems);
|
|
139
|
-
//
|
|
123
|
+
// 5. limit 적용
|
|
140
124
|
if (limit && limit > 0) {
|
|
141
125
|
problems = problems.slice(0, limit);
|
|
142
126
|
}
|
|
143
|
-
|
|
127
|
+
process.stderr.write(`[ProgrammersScraper] ${problems.length}개 문제 검색 완료\n`);
|
|
144
128
|
return problems;
|
|
145
129
|
}
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
await this.browserPool.release(browser);
|
|
130
|
+
catch (error) {
|
|
131
|
+
if (error instanceof ProgrammersScrapeError) {
|
|
132
|
+
throw error;
|
|
150
133
|
}
|
|
134
|
+
throw new ProgrammersScrapeError(`검색 실패: ${error.message}`, 'NAVIGATION_ERROR', error);
|
|
151
135
|
}
|
|
152
136
|
}
|
|
153
|
-
/**
|
|
154
|
-
* 검색 URL 생성
|
|
155
|
-
*/
|
|
156
|
-
buildSearchUrl(options) {
|
|
157
|
-
const params = new URLSearchParams();
|
|
158
|
-
params.set('order', options.order);
|
|
159
|
-
params.set('page', options.page.toString());
|
|
160
|
-
if (options.levels.length > 0) {
|
|
161
|
-
params.set('levels', options.levels.join(','));
|
|
162
|
-
}
|
|
163
|
-
if (options.query) {
|
|
164
|
-
params.set('query', options.query);
|
|
165
|
-
}
|
|
166
|
-
return `${this.baseUrl}/learn/challenges?${params.toString()}`;
|
|
167
|
-
}
|
|
168
137
|
/**
|
|
169
138
|
* 문제 상세 페이지 HTML 가져오기 (fetch 기반, BOJScraper 패턴)
|
|
170
139
|
*
|
|
@@ -283,7 +252,7 @@ export class ProgrammersScraper {
|
|
|
283
252
|
// 캐시 확인
|
|
284
253
|
const cached = this.problemCache.get(problemId);
|
|
285
254
|
if (cached !== undefined) {
|
|
286
|
-
|
|
255
|
+
process.stderr.write(`[ProgrammersScraper] 캐시 히트: 문제 ${problemId}\n`);
|
|
287
256
|
return cached;
|
|
288
257
|
}
|
|
289
258
|
// 1. HTML 가져오기 (캐시 미스 시에만)
|