# レシピ：有料 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 はクライアントが選び、同じ ID は最初の消費を返すため、ツールが再び無料で動きます。
