TOEIC Speaking MCP Server
by doy00
README.md
# π― TOEIC Speaking MCP Server
ν μ΅ μ€νΌνΉ λ§λ₯λ¬Έμ₯ μκΈ° ν
μ€νΈλ₯Ό μν MCP μλ²μ
λλ€. Claude Desktop μ±κ³Ό μ°λνμ¬ μμ±μΌλ‘ ν μ΅ μ€νΌνΉ λ¬Έμ₯μ νμ΅νκ³ μ¦μ νΌλλ°±μ λ°μ μ μμ΅λλ€.
**μΆμ²**: [μ νλΈ μκ³ν λΌμ λμ€](https://youtu.be/A14nJjAGqbY?si=T-7GGcWc9rilQd0G)
---
## π λͺ©μ°¨
- [μ£Όμ νΉμ§](#-μ£Όμ-νΉμ§)
- [κΈ°μ μ€ν](#οΈ-κΈ°μ -μ€ν)
- [μ€μΉ λ° μ€μ ](#-μ€μΉ-λ°-μ€μ )
- [MCP λꡬ](#οΈ-mcp-λꡬ)
- [μ¬μ© μμ](#-μ¬μ©-μμ)
- [νλ‘μ νΈ κ΅¬μ‘°](#-νλ‘μ νΈ-ꡬ쑰)
- [νΈλ¬λΈμν
](#-νΈλ¬λΈμν
)
---
## β¨ μ£Όμ νΉμ§
- π€ **μμ± νμ΅**: Claude Desktop μμ± μΈμμΌλ‘ μμ°μ€λ¬μ΄ νμ΅
- π§ **μ μν μΆμ **: mastery_score κΈ°λ° μ·¨μ½ λ¬Έμ₯ μ°μ μΆμ
- β‘ **μ¦μ νΌλλ°±**: ν€μλ λ§€μΉ λΆμμΌλ‘ μ€μκ° νμ΅ ν¨κ³Ό νμΈ
- π **μλ μ μ κ΄λ¦¬**: λ΅λ³ μ νλμ λ°λ₯Έ μλ μ μ μ
λ°μ΄νΈ (0-100)
- π **νμ
μμ μ±**: TypeScript strict λͺ¨λ + Zod μ€ν€λ§ κ²μ¦
## ποΈ κΈ°μ μ€ν
- **Framework**: NestJS 11 + TypeScript 5 (strict mode)
- **Protocol**: Model Context Protocol (MCP) SDK
- **Database**: Supabase PostgreSQL
- **Validation**: Zod schema validation
- **Architecture**: Domain-driven layered architecture
## π μ€μΉ λ° μ€μ
### 1. μμ‘΄μ± μ€μΉ
```bash
npm install
```
### 2. νκ²½ λ³μ μ€μ
`.env` νμΌ νμΈ:
```env
SUPABASE_URL=your_supabase_url
SUPABASE_ANON_KEY=your_supabase_anon_key
```
### 3. λ°μ΄ν°λ² μ΄μ€ λ§μ΄κ·Έλ μ΄μ
Supabase SQL Editorμμ `migrate.sql` μ€ν:
```sql
-- mastery_score, last_reviewed_at, review_count μ»¬λΌ μΆκ°
-- μΈλ±μ€ μμ±
```
### 4. μν λ°μ΄ν° μ½μ
(μ ν μ¬ν)
```bash
node seed.js
```
### 5. λΉλ
```bash
npm run build
```
### 6. Claude Desktop μ€μ
`~/Library/Application Support/Claude/claude_desktop_config.json`:
```json
{
"mcpServers": {
"toeic-speaking": {
"command": "node",
"args": ["/μ λ/κ²½λ‘/dist/main.js"],
"env": {
"SUPABASE_URL": "your_url",
"SUPABASE_ANON_KEY": "your_key"
}
}
}
}
```
β οΈ κ²½λ‘λ₯Ό μ€μ νλ‘μ νΈ κ²½λ‘λ‘ μμ ν Claude Desktop μ¬μμ
---
## π οΈ MCP λꡬ
### 1. `get_quiz_sentence` - λ¬Έμ μΆμ
**νλΌλ―Έν°**:
- `mode` (μ ν): `'weak_first'` | `'random'` (κΈ°λ³Έ: `'weak_first'`)
- `part` (μ ν): ννΈ λ²νΈ νν°
**μλ΅**:
```json
{
"id": "uuid",
"sentence_ko": "μ΄ μ¬μ§μ 곡μμμ μ°ν μ¬μ§μ
λλ€.",
"part": 2,
"current_mastery": 0
}
```
### 2. `verify_answer` - λ΅λ³ κ²μ¦
**νλΌλ―Έν°** (νμ):
- `sentence_id`: λ¬Έμ₯ ID
- `user_answer`: μ¬μ©μ λ΅λ³ (μμ΄ λ¬Έμ₯)
**μλ΅**:
```json
{
"success": true,
"original_sentence": "This is a picture taken at a park.",
"matched_keywords": ["picture", "taken", "park"],
"missing_keywords": [],
"match_rate": "100.0%",
"score_change": 10,
"feedback": "Perfect! All keywords matched. Score increased by 10 points."
}
```
**μ μ κ·μΉ**:
| λ§€μΉλ₯ | μ μ λ³ν |
|--------|-----------|
| 100% | +10 |
| 70-99% | +5 |
| 50-69% | 0 |
| 0-49% | -5 |
---
## π¬ μ¬μ© μμ
```
π€ "ν μ΅ μ€νΌνΉ μ°μ΅ μμν κ²μ"
π€ [get_quiz_sentence νΈμΆ]
λ¬Έμ : "μ΄ μ¬μ§μ 곡μμμ μ°ν μ¬μ§μ
λλ€."
(νμ¬ μλ ¨λ: 0μ )
π€ [μμ±] "This is a picture taken at a park"
π€ [verify_answer νΈμΆ]
β
μλ²½ν©λλ€!
- ν¬ν¨λ ν€μλ: picture, taken, park (3/3)
- λ§€μΉλ₯ : 100.0%
- μ μ λ³ν: +10μ
```
---
## π νλ‘μ νΈ κ΅¬μ‘°
```
src/
βββ main.ts # MCP μλ² μνΈλ¦¬ν¬μΈνΈ
βββ app.module.ts # λ£¨νΈ λͺ¨λ
βββ config/
β βββ supabase.config.ts # Supabase DI Provider
βββ types/
β βββ sentence.interface.ts # TypeScript μΈν°νμ΄μ€
βββ mcp/
β βββ mcp.module.ts
β βββ mcp.service.ts # MCP λꡬ μ μ + Zod κ²μ¦
βββ quiz/ # Quiz λλ©μΈ
β βββ quiz.module.ts
β βββ quiz.service.ts # λ¬Έμ μΆμ λ‘μ§
β βββ quiz.repository.ts # DB 쿼리
βββ answer/ # Answer λλ©μΈ
βββ answer.module.ts
βββ answer.service.ts # ν€μλ λ§€μΉ μκ³ λ¦¬μ¦
βββ answer.repository.ts # μ μ μ
λ°μ΄νΈ
```
**μν€ν
μ²**:
```
Claude Desktop (stdio)
β
MCP Layer (Zod κ²μ¦)
β
Domain Layer (Quiz/Answer)
β
Supabase PostgreSQL
```
---
## ποΈ λ°μ΄ν°λ² μ΄μ€ μ€ν€λ§
| μ»¬λΌ | νμ
| μ€λͺ
|
|------|------|------|
| `id` | uuid | PK |
| `part` | integer | TOEIC ννΈ λ²νΈ |
| `sentence_en` | text | μμ΄ μλ¬Έ (μ λ΅) |
| `sentence_ko` | text | νκ΅μ΄ λ²μ (λ¬Έμ ) |
| `keywords` | text[] | ν΅μ¬ ν€μλ λ°°μ΄ |
| `mastery_score` | integer | νμ΅ μλ ¨λ (0-100) |
| `last_reviewed_at` | timestamptz | λ§μ§λ§ λ³΅μ΅ μκ° |
| `review_count` | integer | μ΄ λ³΅μ΅ νμ |
**μΈλ±μ€**:
- `idx_mastery_score`: mastery_score
- `idx_part_mastery`: (part, mastery_score)
---
## π§ νΈλ¬λΈμν
### MCP μλ²κ° 보μ΄μ§ μλ κ²½μ°
1. λΉλ νμΈ: `npm run build`
2. νμΌ νμΈ: `ls dist/main.js`
3. μλ μ€ν: `node dist/main.js`
4. Claude Desktop λ‘κ·Έ νμΈ: `View > Toggle Developer Tools > Console`
5. Claude Desktop μμ ν μ¬μμ
### λ°μ΄ν°λ² μ΄μ€ μ°κ²° μ€λ₯
1. `claude_desktop_config.json`μ `env` μΉμ
νμΈ
2. stdio νκ²½μμλ `.env` νμΌ λμ μ€μ νμΌμ νκ²½ λ³μ νμ
3. Supabase νλ‘μ νΈ νμ± μν νμΈ
4. `migrate.sql` μ€ν νμΈ
### νμ
μλ¬
```bash
rm -rf node_modules package-lock.json
npm install
npm run build
```
---
## π οΈ κ°λ° λͺ
λ Ήμ΄
```bash
# λΉλ
npm run build
# κ°λ° λͺ¨λ
npm run start:dev
# μ€ν
npm start
# μν λ°μ΄ν°
node seed.js
# νμ
체ν¬
npx tsc --noEmit
```
---
## π νμ΅ ν
1. **λ§€μΌ 10-15λΆ** κΎΈμ€ν μ°μ΅
2. **weak_first λͺ¨λ** νμ©μΌλ‘ μ·¨μ½ λ¬Έμ₯ μ§μ€ 곡λ΅
3. **ν€μλ μ€μ¬** νμ΅ (μ 체 λ¬Έμ₯λ³΄λ€ ν΅μ¬ ν€μλ λ¨Όμ μκΈ°)
4. **μμ± μ
λ ₯** μ¬μ©μΌλ‘ λ°μ μ°μ΅ λ³ν
5. μ μ 80μ μ΄μ λλ©΄ μλμΌλ‘ λ€λ₯Έ λ¬Έμ₯ μΆμ
---
## π ν₯ν κ³ν
- [ ] 187κ° μ 체 λ¬Έμ₯ λ°μ΄ν° μΆκ°
- [ ] Part 3, 4, 5 μ§μ
- [ ] νμ΅ ν΅κ³ λμ보λ
- [ ] μ¬μ©μλ³ νμ΅ κΈ°λ‘
- [ ] λ°μ νκ° API μ°λ
---
## π μ°Έκ³ μλ£
- [MCP 곡μ λ¬Έμ](https://modelcontextprotocol.io/)
- [NestJS 곡μ λ¬Έμ](https://docs.nestjs.com/)
- [Supabase λ¬Έμ](https://supabase.com/docs)
---
## π λΌμ΄μ μ€
ISC
---
**Version**: 1.0.0 | **Last Updated**: 2026-03-15
TDQS
A3.7/5.0
Scored across 2 tools
Disambiguation5/5
The two tools have clearly distinct roles: one generates a quiz question, the other validates the user's answer. There is no functional overlap between them.
Naming Consistency5/5
Both tool names follow a consistent verb_noun pattern: 'get_quiz_sentence' and 'verify_answer'. This predictable naming makes the tool set easy to navigate.
Tool Count3/5
With only 2 tools, the server is on the thin side, but the narrow quiz-taking purpose makes this count borderline acceptable. It covers the essential actions without being bloated.
Completeness4/5
The tools form a complete core workflow: retrieve a question and verify a response. Minor gaps exist, such as no explicit score retrieval or session management, but the primary quiz loop is fully covered.
Maintenance
ActivityInactive
ResponsivenessNo issues