# 레시피: 유료 MCP 서버

> MCP 서버 도구에 호출마다 크레딧으로 요금을 받으세요. 사람이 패키지를 한 번 사고, 호출마다 먼저 차감한 뒤 작업합니다.

에이전트가 직접 결제할 필요는 없습니다. 사람이 브라우저에서 크레딧 패키지를 사고, 이후 도구 호출마다 잔액을 확인하고, 멱등성 키로 크레딧을 차감한 뒤 작업합니다. 크레딧이 없으면 도구가 충전 링크로 답하고, 에이전트가 그 링크를 사람에게 보여 줍니다. x402도, 에이전트 안의 지갑도, 자동 청구도 없습니다.

> **참고:** 어시스턴트와 함께: 이 레시피는 Perly Pay 플러그인의 `paid-mcp-tools` 스킬이 내 프로젝트에서 실행합니다. 실행 가능한 예제는 플러그인 저장소의 `examples/paid-mcp-server`(MIT)입니다.

## 작동 방식

1. **크레딧 패키지** — [API 크레딧](https://pay.perly.io/docs/recipe-api-credits.md)과 같습니다. 도구마다 가격을 정하고 설명에 적으세요.
2. **호출하는 쪽** — 호스팅된 서버가 구독자마다 API 키를 고객의 `external_id`에 연결합니다. `PERLY_PAY_SECRET_KEY`는 내 서버에만 있습니다.
3. **먼저 차감, 그다음 작업** — 도구 이름을 `action`으로, 내 서버가 호출마다 만든 새 키로 `credits.consume`, 그다음 작업.
4. **부족하면?** — 프로토콜 오류가 아니라 `isError: true`와 충전 링크가 담긴 도구 결과를 돌려주세요: 클라이언트가 그걸 보여 줍니다.
5. **무료 도우미** — 에이전트가 상황을 설명할 수 있게 `balance`와 `top_up` 도구.

**A paid tool**

```js
server.registerTool("summarize", { description: "Costs 3 credits.", inputSchema: { text: z.string() } }, async ({ text }, extra) => {
  const refused = await charge(userId, 3, "summarize", extra.requestId); // credits.consume, or a top-up message
  if (refused) return { isError: true, content: [{ type: "text", text: refused }] };
  return { content: [{ type: "text", text: summarize(text) }] };
});
```

## 예제 실행

1. **설치** — `examples/paid-mcp-server`에서 `npm install`(Node 20.12+).
2. **키** — `.env.example`을 `.env`로 복사하고 `PERLY_PAY_SECRET_KEY`에 테스트 키(`sk_test_…`)를 넣으세요.
3. **준비** — `npm run setup`이 내 키로 상품과 가격을 만들고 `.env`용 ID를 출력합니다.
4. **실행** — README에 적힌 대로 `npm start`(또는 `npm run dev`).
5. **지갑 없이 결제** — Claude Code에 추가하고(`claude mcp add paid-tools -- npx tsx "$PWD/src/server.ts"`) `word_stats`를 실행하세요: 답은 충전 페이지입니다. 패키지를 골라 **Simulate payment**를 누르고 다시 실행하세요.

## 함정

- 다른 사람이 복사하는 MCP 설정에 `PERLY_PAY_SECRET_KEY`. 서버를 직접 호스팅하거나 내 백엔드를 부르게 하세요.
- 작업 뒤에 차감: 차감이 실패하면 무료로 일한 셈입니다.
- JSON-RPC ID로 만든 차감 키: 클라이언트가 고르는 값이라, 같은 ID면 첫 차감이 돌아오고 도구가 또 무료로 일합니다.
