Skip to main content
Glama
SolarLLM

Solar Human Proofreader

Official
by SolarLLM
README.md
# Solar Human Proofreader

**사람 편집자가 빨간 펜으로 손본 원고를 돌려주는 MCP 서버.**
한국어 원고를 읽기 쉽게 교정합니다. [Upstage **Solar Pro 4**](https://www.upstage.ai)가 문장을 손보고,
사실·인용·문체가 그대로인지는 코드가 판정합니다. 내용은 더하지도 빼지도 않습니다.

Vercel에 올려 쓰는 서버사이드 MCP입니다. 하루 **100콜까지 무료**, 그 이상은
본인 키(BYOK)로 씁니다.

---

## 무엇이 다른가

AI 티를 지우는 도구가 아닙니다. **독자가 두 번 읽게 되는 자리를 없애는** 도구입니다.
그래서 작업 순서도 탐지·치환이 아니라 사람 편집자의 순서를 따릅니다.

| 단계 | 누가 | 하는 일 |
|---|---|---|
| 1. 통독 | 코드 | 문장 길이 분포, 만연체, 번역투·피동·상투구, 리듬 편차를 먼저 잰다 |
| 2. 소견 | Solar | 수치가 못 보는 것을 읽는다 — 끊긴 논지, 겹치는 문단, 죽은 리듬 |
| 3. 교정 | Solar | 소견에서 짚은 자리만 겨냥해 손본다 |
| 4. 대조 | 코드 | 원본 옆에 놓고 맞춰 본다. 없던 수치·바뀐 인용·뒤집힌 문체를 잡는다 |
| 5. 보정 | Solar | 대조에서 걸린 자리만 국소 수정 |

**모델에게 "네가 얼마나 고쳤는지 말해봐"라고 묻지 않습니다.** 자기가 방금 쓴 글을
자기가 채점하면 언제나 후하기 때문입니다. 변경률·수치 보존·인용 대조·문체 판정은
전부 코드가 셉니다. 같은 입력에는 언제나 같은 판정이 나옵니다.

### 편집의 원칙

- **빨간 펜 원칙** — 고칠 이유를 한 문장으로 말할 수 없으면 손대지 않습니다.
- **덜어내는 쪽으로** — 좋은 교정은 덧붙이지 않습니다. 분량이 늘면 경고가 뜹니다.
- **말투는 필자의 것** — 한다체를 합쇼체로 올리는 것도 개작입니다. 뒤집히면 채택을 막습니다.
- **사실은 불변** — 원본에 없던 문장이나 수치가 하나라도 생기면 그 교정본은 채택 불가입니다.

---

## 도구

| 도구 | 하는 일 | 비용 |
|---|---|---|
| `proofread` | 원고를 통독하고 교정본을 돌려준다. 본체. | 1~3콜 |
| `read_through` | 고치지 않고, 왜 안 읽히는지 소견만 준다. | 1콜 |
| `readability` | 문장 길이·번역투·피동·리듬을 0~100점으로 잰다. | **무료** |
| `compare` | 원본과 교정본을 대조해 훼손을 잡는다. 다른 모델이 고친 원고도 검증한다. | **무료** |
| `suggest` | 한 대목을 결이 다른 여러 안으로 다시 써 준다. | 1콜 |
| `usage` | 오늘 남은 무료 한도를 확인한다. | **무료** |

### `proofread` 인자

| 인자 | 설명 |
|---|---|
| `text` | 교정할 원고 (최대 20,000자) |
| `genre` | `칼럼` `리포트` `블로그` `뉴스레터` `공적` `에세이` `자동`(기본) |
| `strength` | `보수`(≈10%) `기본`(15~25%) `적극`(~35%). 생략하면 원고 상태에 맞춘다 |
| `depth` | `light`(1콜) `standard`(2콜) `deep`(3콜) `auto`(기본) |
| `audience` | 읽는 사람. 예: `비전공 임원` |
| `instructions` | 따로 부탁할 것. 예: `마지막 문단은 짧게` |
| `preserve` | 한 글자도 바뀌면 안 되는 문장들. 대조 단계에서 강제한다 |

---

## 연결하기

Streamable HTTP를 지원하는 클라이언트(Claude Code, Cursor 등)는 URL만 넣으면 됩니다.

```json
{
  "mcpServers": {
    "human-proofreader": {
      "url": "https://<your-deployment>.vercel.app/api/mcp"
    }
  }
}
```

Claude Code는 명령 한 줄로도 됩니다.

```bash
claude mcp add --transport http human-proofreader https://<your-deployment>.vercel.app/api/mcp
```

stdio만 되는 클라이언트는 `npx -y mcp-remote <url>`을 씁니다.

### AI에게 그대로 넘기기

배포본 URL만 던지면 설치가 끝나도록, 기계가 읽는 안내를 함께 제공합니다.
두 경로 모두 **배포본의 실제 주소를 요청 헤더에서 읽어** 담기 때문에, 자리표시자가
그대로 복사되는 일이 없습니다.

| 경로 | 무엇 |
|---|---|
| `/llms.txt` | 클라이언트별 설치 명령, 도구 목록, 무료 한도·BYOK 규칙, 결과 전달 방법까지 담은 산문 안내서 |
| `/mcp.json` | 같은 내용의 구조화 매니페스트. 설정에 바로 꽂을 수 있는 완성형 조각 포함 |

---

## 무료 100콜, 그 다음은 본인 키

무료 한도는 **Solar 호출 1회 단위**로 셉니다. `proofread` 한 번이 통독 1콜 +
교정 1콜이면 2회가 차감됩니다. 도구 호출 단위로 세면 같은 한도가 사람마다 열 배씩
차이 나서, 쓰는 쪽도 내는 쪽도 예측할 수 없기 때문입니다.
`readability`와 `compare`는 Solar를 부르지 않아 차감되지 않습니다.
초기화는 **KST 자정**입니다.

한도를 넘겨 쓰려면 본인 키를 **헤더**에 넣습니다. 도구 인자가 아니라 헤더인 이유는,
인자로 받으면 키가 클라이언트 대화 기록에 평문으로 남기 때문입니다.

```json
{
  "mcpServers": {
    "human-proofreader": {
      "url": "https://<your-deployment>.vercel.app/api/mcp",
      "headers": {
        "X-Upstage-Api-Key": "up_..."
      }
    }
  }
}
```

| 헤더 | 경로 | 키 발급 |
|---|---|---|
| `X-Upstage-Api-Key` | Upstage 직접 (`api.upstage.ai`, 모델 `solar-pro4`) | https://console.upstage.ai |
| `X-OpenRouter-Api-Key` | OpenRouter 경유 (모델 `upstage/solar-pro4`) | https://openrouter.ai/keys |

본인 키로 붙으면 하루 한도가 없고, 요금은 해당 제공자 계정으로 청구됩니다.

---

## 직접 띄우기

```bash
git clone https://github.com/SolarLLM/human-proofreader-mcp
cd human-proofreader-mcp && npm install
cp .env.example .env.local   # OPENROUTER_API_KEY 또는 UPSTAGE_API_KEY 를 채운다
npm run dev                  # http://localhost:3000/api/mcp
```

```bash
vercel deploy --prod
```

### 환경변수

| 변수 | 설명 |
|---|---|
| `OPENROUTER_API_KEY` / `UPSTAGE_API_KEY` | 무료 티어용 서버 키. 둘 중 하나. |
| `FREE_DAILY_LIMIT` | 하루 무료 콜 수 (기본 100) |
| `UPSTASH_REDIS_REST_URL` / `_TOKEN` | 한도 집계 저장소. 없으면 인스턴스 메모리로 근사 집계하고, 응답에 그 사실을 표시한다. |
| `QUOTA_SALT` | 사용량 식별자 해시에 섞는 소금. 배포마다 다른 값으로. |
| `MAX_INPUT_CHARS` | 한 번에 받을 최대 글자 수 (기본 20000) |

**Redis 없이도 동작하지만**, 서버리스는 인스턴스가 여럿이라 한도가 느슨해집니다.
실제로 100콜을 지키려면 Upstash Redis를 붙이세요.

**Vercel 플랜과 `depth`**: Hobby는 함수 실행이 60초에서 잘립니다. `deep`(3콜)은
긴 원고에서 이를 넘길 수 있으니 Hobby에서는 `standard` 이하를 쓰거나 Pro(최대 300초)로
올리세요.

---

## 개발

```bash
npm test        # 결정적 계층(가독성·대조·변경률) 회귀 테스트
npm run typecheck
```

LLM을 부르는 부분은 테스트하지 않습니다. 대신 **판정하는 코드 전부**를 테스트합니다 —
게이트가 조용히 통과하는 것이 이 서버에서 가장 위험한 고장이기 때문입니다.

### 변경률을 어떻게 재는가

교정을 얼마나 했는지가 과교정 판정의 기준값입니다. 어절 단위로 세면 조사 하나만
바뀌어도 그 어절이 통째로 "바뀐 것"이 되어 실제의 두 배가 나옵니다
(실측: 문자 22.8% → 어절 46.9%). 그래서 문자 단위로 셉니다.

문장이나 문단을 먼저 대응시키는 방식은 전부 버렸습니다. 좋은 교정은 긴 문장을
쪼개고 짧은 문장을 붙이고 문단을 나눕니다. 무엇을 단위로 잡든 1:1 대응은 그
쪼개기·합치기를 "대응 실패"로 읽어 정상 교정을 재작성으로 오판합니다
(실측: 한 문단을 넷으로 나눈 공지문이 75%로 나왔습니다 — 실제로는 27%).

전문에 그대로 LCS를 돌리는 것이 정확하고 비용도 감당됩니다 — 2만 자 대 2만 자가
1.1초입니다. Solar 호출 한 번이 20초를 넘는 마당에 이 정도는 쌉니다.
결과는 Python `difflib`의 문자 기준값과 정확히 일치합니다(실측 0.228 = 0.228, 0.680 = 0.68).

임계값은 **경고 30% · 중단 50%**입니다. 한국어는 조사·어미 음절이 겹쳐서 내용이
전혀 다른 글도 0.65~0.70이 바닥입니다. 중단선 0.50은 그 바닥값보다 낮고, 정상적인
강한 교정(0.25~0.45)보다는 높게 잡은 값입니다.

### 대조 게이트가 잡는 것

| 코드 | 판정 | 무엇을 |
|---|---|---|
| `content_injected` | 채택 불가 | 원고에 없던 **문장**이 생김 |
| `number_injected` | 채택 불가 | 원고에 없던 수치가 생김 |
| `quote_altered` | 채택 불가 | 직접 인용이 원형 그대로 남지 않음 |
| `register_switched` | 채택 불가 | 말투가 뒤집힘 (한다체 → 합쇼체 등) |
| `preserve_broken` | 채택 불가 | 보존 지정한 문장이 바뀜 |
| `over_edited` | 채택 불가 | 변경률 50% 이상 — 교정이 아니라 재집필 |
| `heavy_edit` | 경고 | 변경률 30~50% |
| `cliche_injected` | 경고 | 없던 상투구를 새로 심음 |
| `register_raised` / `colloquial_erased` | 경고 | 격식 상향 / 구어 종결 격감 |
| `heading_lost` · `length_grown` | 경고 | 소제목 소실 / 분량 15% 이상 증가 |
| `new_terms` · `number_dropped` | 관찰 | 새로 들어온 낱말 / 사라진 수치 목록 (막지 않고 보여만 줌) |

문장 통째 주입은 확실히 잡습니다. 문장 안에서 **낱말 몇 개**가 바뀐 경우는
게이트로 막지 않고 `new_terms` 목록으로 보여 줍니다 — 교정은 원래 낱말을 바꾸는
일이라 막으면 오탐이 쏟아집니다. 실측에서 이 목록이 "기존 대비" → "동일 규모
베이스라인 대비" 같은 조용한 한정어 추가를 잡아냈습니다.

---

## 감사의 말

한국어 글의 "AI 티"를 알아보는 문제의식과 문체 분류 체계는
[epoko77-ai/im-not-ai](https://github.com/epoko77-ai/im-not-ai)에서 가져왔습니다.
코드는 가져오지 않고 새로 썼습니다. 이 서버는 "AI 티 제거"가 아니라 "읽기 쉬운 글"에
초점을 맞춘 별개의 설계입니다.

## 라이선스

MIT