pay-per-call-mcp 0.5.0
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/LICENSE +21 -0
- package/README.md +191 -0
- package/dist/index.js +903 -0
- package/package.json +63 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Evid AI / LemonCake
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
# 🍋 LemonCake — AI Agent Wallet & USDC Pay-per-call MCP Server
|
|
2
|
+
|
|
3
|
+
> 🎮 **Try instantly — no signup needed.** Click **"Try in Browser"** above → leave **BOTH** env var fields **EMPTY** → click **Start Inspector**. Demo Mode hits real Wikipedia / httpbin / live FX APIs. No account, no card, no API key.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/pay-per-call-mcp)
|
|
6
|
+
[](https://www.npmjs.com/package/pay-per-call-mcp)
|
|
7
|
+
[](https://modelcontextprotocol.io)
|
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
|
9
|
+
[](https://glama.ai/mcp/servers/evidai/lemon-cake)
|
|
10
|
+
[](https://nodejs.org)
|
|
11
|
+
|
|
12
|
+
> 📦 **v0.5.0 rename:** the npm package is now **`pay-per-call-mcp`** (was `lemon-cake-mcp`). The old name still works as a thin wrapper, but new configs should use `npx -y pay-per-call-mcp`.
|
|
13
|
+
|
|
14
|
+
> **Give your AI agent a wallet.** Pay-per-call USDC payments for any HTTP API — straight from Claude Desktop, Cursor, Cline, or any MCP client. No human in the loop, no per-API signups, no API key juggling.
|
|
15
|
+
|
|
16
|
+
LemonCake の MCP サーバーで、Claude Desktop / Cursor / Cline などの MCP 互換クライアントから、人間の介在なしに USDC で有料 API を呼び出せるようになります。
|
|
17
|
+
|
|
18
|
+
**English ↓** [Quickstart](#-3分で始める) · [Tools](#%EF%B8%8F-提供ツール) · [Use Cases](#-use-cases) · [Compatibility](#-tested-clients)
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 🚀 3分で始める
|
|
23
|
+
|
|
24
|
+
MCP サーバーの利用には LemonCake アカウントと USDC 残高が必要です。
|
|
25
|
+
|
|
26
|
+
1. **[無料アカウント作成](https://lemoncake.xyz/register?utm_source=mcp-server&utm_medium=npm-readme&utm_campaign=onboard)** — メール1つで完了
|
|
27
|
+
2. **残高チャージ** — 最低 $5 USDC または JPYC([Billing](https://lemoncake.xyz/dashboard/billing?utm_source=mcp-server&utm_medium=npm-readme&utm_campaign=topup))
|
|
28
|
+
3. **Buyer JWT をコピー** — [Dashboard → API Keys](https://lemoncake.xyz/dashboard?utm_source=mcp-server&utm_medium=npm-readme) から
|
|
29
|
+
4. 下記の `claude_desktop_config.json` に設定
|
|
30
|
+
|
|
31
|
+
> 📚 詳細: [クイックスタート ドキュメント](https://lemoncake.xyz/docs/quickstart?utm_source=mcp-server&utm_medium=npm-readme)
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## 📦 インストール
|
|
36
|
+
|
|
37
|
+
### Claude Desktop の場合
|
|
38
|
+
|
|
39
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json`(macOS)または
|
|
40
|
+
`%APPDATA%\Claude\claude_desktop_config.json`(Windows)に追加:
|
|
41
|
+
|
|
42
|
+
```json
|
|
43
|
+
{
|
|
44
|
+
"mcpServers": {
|
|
45
|
+
"pay-per-call": {
|
|
46
|
+
"command": "npx",
|
|
47
|
+
"args": ["-y", "pay-per-call-mcp"],
|
|
48
|
+
"env": {
|
|
49
|
+
"LEMON_CAKE_BUYER_JWT": "eyJhbGci..."
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Claude Desktop を再起動すれば、🔨 ツールアイコンに LemonCake のツールが表示されます。
|
|
57
|
+
|
|
58
|
+
### Cursor / Cline / その他 MCP クライアント
|
|
59
|
+
|
|
60
|
+
同様に、サーバー起動コマンドを `npx -y pay-per-call-mcp` / 環境変数に `LEMON_CAKE_BUYER_JWT` を設定してください。
|
|
61
|
+
|
|
62
|
+
**Node.js 要件**: v20 以上
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## 🛠️ 提供ツール
|
|
67
|
+
|
|
68
|
+
| ツール名 | 用途 | 主なパラメータ |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| `setup` | 初回セットアップガイド(アカウント作成・チャージ方法を返す) | — |
|
|
71
|
+
| `list_services` | LemonCake マーケットプレイスで利用可能な有料 API 一覧 | `limit?` (1–100) |
|
|
72
|
+
| `call_service` | 指定サービスへ Pay Token 経由で課金付き呼び出し | `serviceId`, `path?`, `method?`, `body?`, `idempotencyKey?` |
|
|
73
|
+
| `check_balance` | 現在の USDC 残高と KYA 上限を取得 | — |
|
|
74
|
+
| `check_tax` | 国税庁 API で適格請求書発行事業者番号を検証 | `registrationNumber` (T+13桁), `description?`, `amountJpy?` |
|
|
75
|
+
| `get_service_stats` | サービス別の利用統計・課金履歴集計 | — |
|
|
76
|
+
|
|
77
|
+
すべての引数スキーマは MCP Inspector または `tools/list` で取得可能です。
|
|
78
|
+
|
|
79
|
+
---
|
|
80
|
+
|
|
81
|
+
## 🎮 Demo Mode(認証情報なしで試せる)
|
|
82
|
+
|
|
83
|
+
`LEMON_CAKE_BUYER_JWT` / `LEMON_CAKE_PAY_TOKEN` を**何も設定せずに**起動すると、自動で **DEMO MODE** になります。サインアップなしで以下が動きます:
|
|
84
|
+
|
|
85
|
+
- `list_services` → 実マーケット + `demo_search` (Wikipedia) / `demo_echo` (httpbin) / `demo_fx` (open.er-api) の 3 デモが先頭に
|
|
86
|
+
- `call_service` → `demo_*` サービスは**実フリー API を叩いて生データ**を返却(課金なし、認証なし)
|
|
87
|
+
- `check_balance` → `$1.00` のモック残高を返却(`mode: "demo"`)
|
|
88
|
+
- `check_tax` / `get_service_stats` → 通常通り(元から認証不要)
|
|
89
|
+
|
|
90
|
+
→ Glama Inspector や [npm の試用環境](https://www.npmjs.com/package/pay-per-call-mcp) でも、何も設定せず実装挙動を確認できます。本番の有料 API を叩きたくなったら `LEMON_CAKE_PAY_TOKEN` を設定してください。
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 💡 Try these prompts(MCP プロンプト)
|
|
95
|
+
|
|
96
|
+
このサーバーは MCP の **prompts capability** に対応しています。Glama Inspector / Claude Desktop / Cursor の "prompt picker" に下記のプリセットが表示され、ワンクリックで実行できます:
|
|
97
|
+
|
|
98
|
+
| Prompt 名 | 内容 | 認証 |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| 🎮 `explore-demo` | デモモードで setup → list_services → demo_search → demo_fx を一気通貫 | 不要 |
|
|
101
|
+
| 🛍 `discover-marketplace` | 実マーケットの全サービスを listing して、用途別に top 3 を推薦 | 不要 |
|
|
102
|
+
| 🇯🇵 `japan-tax-check` | 適格請求書発行事業者番号を国税庁で検証+源泉徴収判定 | 不要 |
|
|
103
|
+
| 💰 `spend-with-budget` | check_balance → call_service → check_balance のパターンで予算管理を実演 | demo OK / 実 service には PAY_TOKEN |
|
|
104
|
+
| 🔄 `real-vs-demo` | 同じクエリを demo_search と実 Serper で叩いて比較 | demo OK / 比較に PAY_TOKEN |
|
|
105
|
+
| 🏯 `japan-finance-bundle` | gBizINFO + 国税庁 + e-Gov を組み合わせた日本企業リサーチ | PAY_TOKEN(demo 部分は不要) |
|
|
106
|
+
|
|
107
|
+
→ Glama / Claude Desktop で `pay-per-call` MCP(旧 `lemon-cake`)を有効にすると、上記が自動で候補に出ます。
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 💡 使い方の例
|
|
112
|
+
|
|
113
|
+
Claude Desktop で:
|
|
114
|
+
|
|
115
|
+
> 「LemonCake で `demo_agent_search_api` を 0.50 USDC で呼び出して、"AI agent payments" を検索して」
|
|
116
|
+
|
|
117
|
+
Claude は自動で:
|
|
118
|
+
1. `setup` でセットアップ状況を確認(初回のみ)
|
|
119
|
+
2. `call_service(serviceId="demo_agent_search_api", limitUsdc="0.50", body={query:"AI agent payments"})`
|
|
120
|
+
3. 結果を要約して返答
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## 🎯 Use Cases
|
|
125
|
+
|
|
126
|
+
- **Autonomous research agents** — Let your agent pay-per-call for premium search, scraping, or data APIs without giving it your credit card.
|
|
127
|
+
- **Multi-API workflows** — One JWT, one balance, dozens of upstream APIs. No per-vendor signup or rotating keys.
|
|
128
|
+
- **Compliance-aware spending** — KYA (Know-Your-Agent) limits cap how much an agent can spend per session/day.
|
|
129
|
+
- **Japanese tax automation** — `check_tax` validates 適格請求書 numbers against 国税庁 API for invoice compliance.
|
|
130
|
+
- **Idempotent retries** — `idempotencyKey` makes call_service safe to retry without double-charging.
|
|
131
|
+
|
|
132
|
+
---
|
|
133
|
+
|
|
134
|
+
## ✅ Tested Clients
|
|
135
|
+
|
|
136
|
+
| Client | Status | Notes |
|
|
137
|
+
|---|:---:|---|
|
|
138
|
+
| Claude Desktop (macOS / Windows) | ✅ | Primary target |
|
|
139
|
+
| Cursor | ✅ | stdio transport |
|
|
140
|
+
| Cline (VS Code) | ✅ | stdio transport |
|
|
141
|
+
| Claude Code CLI | ✅ | stdio transport |
|
|
142
|
+
| Continue.dev | ✅ | MCP support since v0.9 |
|
|
143
|
+
| Custom MCP clients | ✅ | Any client speaking MCP 1.10+ over stdio |
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## 🔐 環境変数
|
|
148
|
+
|
|
149
|
+
| 変数名 | 必須 | 説明 |
|
|
150
|
+
|---|:---:|---|
|
|
151
|
+
| `LEMON_CAKE_BUYER_JWT` | ✅ | Buyer JWT(ダッシュボードの Settings → API Keys から取得)|
|
|
152
|
+
| `LEMON_CAKE_PAY_TOKEN` | — | Pay Token JWT(`call_service` で必要、未設定なら demo_* サービスのみ呼べる)|
|
|
153
|
+
| `LEMON_CAKE_API_URL` | — | API エンドポイント(デフォルト: `https://api.lemoncake.xyz`)|
|
|
154
|
+
|
|
155
|
+
---
|
|
156
|
+
|
|
157
|
+
## 🏃 ローカル開発
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
git clone https://github.com/evidai/lemon-cake.git
|
|
161
|
+
cd lemon-cake/mcp-server
|
|
162
|
+
npm install
|
|
163
|
+
npm run build
|
|
164
|
+
npm start
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
### Docker
|
|
168
|
+
|
|
169
|
+
```bash
|
|
170
|
+
docker build -t pay-per-call-mcp .
|
|
171
|
+
docker run --rm -i -e LEMON_CAKE_BUYER_JWT=eyJhbGci... pay-per-call-mcp
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
イメージは Glama Inspector のブラウザ内プレビューにも利用されます。
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 🔗 関連リンク
|
|
179
|
+
|
|
180
|
+
- [LemonCake ダッシュボード](https://lemoncake.xyz/dashboard?utm_source=mcp-server&utm_medium=npm-readme)
|
|
181
|
+
- [API ドキュメント](https://lemoncake.xyz/docs?utm_source=mcp-server&utm_medium=npm-readme)
|
|
182
|
+
- [Glama MCP listing](https://glama.ai/mcp/servers/evidai/lemon-cake)
|
|
183
|
+
- [Model Context Protocol](https://modelcontextprotocol.io)
|
|
184
|
+
- [GitHub](https://github.com/evidai/lemon-cake)
|
|
185
|
+
- [Security Policy](https://github.com/evidai/lemon-cake/blob/main/SECURITY.md) · [Contributing](https://github.com/evidai/lemon-cake/blob/main/CONTRIBUTING.md) · [Code of Conduct](https://github.com/evidai/lemon-cake/blob/main/CODE_OF_CONDUCT.md)
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 📄 ライセンス
|
|
190
|
+
|
|
191
|
+
MIT © [LemonCake](https://lemoncake.xyz)
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,903 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* LemonCake MCP Server v0.2.0
|
|
4
|
+
*
|
|
5
|
+
* AIエージェントにLemonCakeの決済インフラを提供するMCPサーバー。
|
|
6
|
+
*
|
|
7
|
+
* Tools:
|
|
8
|
+
* - setup : 初回セットアップガイド(認証不要)
|
|
9
|
+
* - list_services : マーケットプレイスの公開サービス一覧(認証不要)
|
|
10
|
+
* - get_service_stats : サービスの利用統計(認証不要)
|
|
11
|
+
* - check_tax : 日本の税務コンプライアンス確認(認証不要)
|
|
12
|
+
* - check_balance : USDC残高確認(LEMON_CAKE_BUYER_JWT 必須)
|
|
13
|
+
* - call_service : Pay-per-call APIプロキシ(LEMON_CAKE_PAY_TOKEN 必須)
|
|
14
|
+
*
|
|
15
|
+
* 環境変数:
|
|
16
|
+
* LEMON_CAKE_PAY_TOKEN : Pay Token JWT(ダッシュボードで発行)※ call_service に必須
|
|
17
|
+
* LEMON_CAKE_BUYER_JWT : Buyer JWT(ログイン時に取得) ※ check_balance に必須
|
|
18
|
+
* LEMON_CAKE_API_URL : APIベースURL(省略可)
|
|
19
|
+
*/
|
|
20
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
21
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
22
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
23
|
+
// ── 設定 ──────────────────────────────────────────────────────────────────────
|
|
24
|
+
const API_URL = (process.env.LEMON_CAKE_API_URL ?? "https://api.lemoncake.xyz").replace(/\/$/, "");
|
|
25
|
+
const PAY_TOKEN = process.env.LEMON_CAKE_PAY_TOKEN ?? "";
|
|
26
|
+
const BUYER_JWT = process.env.LEMON_CAKE_BUYER_JWT ?? "";
|
|
27
|
+
// ── バージョン・ユーザーエージェント ─────────────────────────────────────
|
|
28
|
+
// 単一ソース化: package.json の version を読む。ESM の import attributes を
|
|
29
|
+
// 使わずに createRequire 経由にすることで、バンドラなしで Node 22 でも動く。
|
|
30
|
+
import { createRequire } from "node:module";
|
|
31
|
+
const requireFromHere = createRequire(import.meta.url);
|
|
32
|
+
const MCP_VERSION = requireFromHere("../package.json").version;
|
|
33
|
+
const USER_AGENT = `pay-per-call-mcp/${MCP_VERSION} (node/${process.versions.node}; ${process.platform} ${process.arch})`;
|
|
34
|
+
// ── デモモード(認証情報なしで Glama Inspector / 新規ユーザーが試せるように) ──
|
|
35
|
+
const DEMO_MODE = !PAY_TOKEN && !BUYER_JWT;
|
|
36
|
+
/**
|
|
37
|
+
* Fire-and-forget fetch with a hard timeout. Returns null on any failure
|
|
38
|
+
* so demo handlers can fall back to canned data without blocking the user.
|
|
39
|
+
*/
|
|
40
|
+
async function tryFetch(url, init, timeoutMs = 4000) {
|
|
41
|
+
const ctrl = new AbortController();
|
|
42
|
+
const timer = setTimeout(() => ctrl.abort(), timeoutMs);
|
|
43
|
+
try {
|
|
44
|
+
const res = await fetch(url, { ...init, signal: ctrl.signal, headers: { "User-Agent": USER_AGENT, ...(init?.headers ?? {}) } });
|
|
45
|
+
if (!res.ok)
|
|
46
|
+
return null;
|
|
47
|
+
const ct = res.headers.get("content-type") ?? "";
|
|
48
|
+
return ct.includes("application/json") ? await res.json() : await res.text();
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
finally {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const DEMO_SERVICES = [
|
|
58
|
+
{
|
|
59
|
+
id: "demo_search",
|
|
60
|
+
name: "Demo Search (Wikipedia, free)",
|
|
61
|
+
provider: "LemonCake DEMO → Wikipedia",
|
|
62
|
+
type: "API",
|
|
63
|
+
pricePerCall: "0.00 USDC (free demo, real upstream)",
|
|
64
|
+
description: "Searches English Wikipedia via opensearch API. Returns up to 5 matching titles with snippets and URLs for any query. No auth required.",
|
|
65
|
+
example: { path: "/search", method: "POST", body: { q: "Model Context Protocol" } },
|
|
66
|
+
handler: async (_path, body) => {
|
|
67
|
+
const q = body?.q ?? "Model Context Protocol";
|
|
68
|
+
const data = await tryFetch(`https://en.wikipedia.org/w/api.php?action=opensearch&search=${encodeURIComponent(q)}&limit=5&format=json&namespace=0&origin=*`);
|
|
69
|
+
// opensearch returns [query, titles[], descriptions[], urls[]]
|
|
70
|
+
if (Array.isArray(data) && data.length === 4 && Array.isArray(data[1])) {
|
|
71
|
+
const titles = data[1];
|
|
72
|
+
const descriptions = data[2] ?? [];
|
|
73
|
+
const urls = data[3] ?? [];
|
|
74
|
+
return {
|
|
75
|
+
query: q,
|
|
76
|
+
results: titles.map((title, i) => ({ title, snippet: descriptions[i] ?? "", url: urls[i] ?? "" })),
|
|
77
|
+
upstream: "en.wikipedia.org/opensearch (real)",
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
// Fallback if upstream is down
|
|
81
|
+
return {
|
|
82
|
+
query: q,
|
|
83
|
+
results: [
|
|
84
|
+
{ title: "LemonCake — Give your AI agent a wallet", url: "https://lemoncake.xyz", snippet: "Pay-per-call USDC payments for any HTTP API." },
|
|
85
|
+
{ title: "Model Context Protocol", url: "https://modelcontextprotocol.io", snippet: "Open standard for connecting AI agents to tools." },
|
|
86
|
+
],
|
|
87
|
+
upstream: "canned (Wikipedia unreachable)",
|
|
88
|
+
};
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
id: "demo_echo",
|
|
93
|
+
name: "Demo Echo (httpbin.org, free)",
|
|
94
|
+
provider: "LemonCake DEMO → httpbin.org",
|
|
95
|
+
type: "API",
|
|
96
|
+
pricePerCall: "0.00 USDC (free demo, real upstream)",
|
|
97
|
+
description: "Echoes your request via httpbin.org/anything. Returns headers, query params, and body — useful to verify your call_service request shape against a real HTTP server.",
|
|
98
|
+
example: { path: "/anything", method: "POST", body: { hello: "world" } },
|
|
99
|
+
handler: async (path, body) => {
|
|
100
|
+
const data = await tryFetch(`https://httpbin.org/anything${path}`, {
|
|
101
|
+
method: body ? "POST" : "GET",
|
|
102
|
+
headers: { "Content-Type": "application/json" },
|
|
103
|
+
body: body ? JSON.stringify(body) : undefined,
|
|
104
|
+
});
|
|
105
|
+
if (data)
|
|
106
|
+
return { ...data, upstream: "httpbin.org (real)" };
|
|
107
|
+
return { receivedPath: path, receivedBody: body ?? null, timestamp: new Date().toISOString(), upstream: "canned (httpbin unreachable)" };
|
|
108
|
+
},
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
id: "demo_fx",
|
|
112
|
+
name: "Demo FX rates (open.er-api.com, free)",
|
|
113
|
+
provider: "LemonCake DEMO → open.er-api.com",
|
|
114
|
+
type: "API",
|
|
115
|
+
pricePerCall: "0.00 USDC (free demo, real upstream)",
|
|
116
|
+
description: "Real USD-base FX rates from open.er-api.com (free, no auth). Returns 160+ currencies updated daily.",
|
|
117
|
+
example: { path: "/latest", method: "GET" },
|
|
118
|
+
handler: async () => {
|
|
119
|
+
const data = await tryFetch("https://open.er-api.com/v6/latest/USD");
|
|
120
|
+
if (data && data.rates) {
|
|
121
|
+
return {
|
|
122
|
+
base: data.base_code ?? "USD",
|
|
123
|
+
rates: { JPY: data.rates.JPY, EUR: data.rates.EUR, GBP: data.rates.GBP, CNY: data.rates.CNY, KRW: data.rates.KRW },
|
|
124
|
+
asOf: data.time_last_update_utc ?? null,
|
|
125
|
+
upstream: "open.er-api.com (real)",
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
return { base: "USD", rates: { JPY: 150.42, EUR: 0.92, GBP: 0.79, CNY: 7.12 }, asOf: new Date().toISOString().slice(0, 10), upstream: "canned (er-api unreachable)" };
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
];
|
|
132
|
+
function findDemoService(id) {
|
|
133
|
+
return DEMO_SERVICES.find((s) => s.id === id);
|
|
134
|
+
}
|
|
135
|
+
const DEMO_NOTICE = "🎮 DEMO MODE — no real charge, no real upstream. Set LEMON_CAKE_PAY_TOKEN / LEMON_CAKE_BUYER_JWT to call real services. Run the `setup` tool for instructions.";
|
|
136
|
+
// ── 登録/入金/ダッシュボード URL(UTM 付きで経由クライアントを区別) ──
|
|
137
|
+
const UTM = "utm_source=mcp-server&utm_medium=cli";
|
|
138
|
+
const REGISTER_URL = `https://lemoncake.xyz/register?${UTM}&utm_campaign=credential-missing`;
|
|
139
|
+
const DASHBOARD_URL = `https://lemoncake.xyz/dashboard?${UTM}`;
|
|
140
|
+
const BILLING_URL = `https://lemoncake.xyz/dashboard/billing?${UTM}&utm_campaign=topup`;
|
|
141
|
+
const DOCS_URL = `https://lemoncake.xyz/docs/quickstart?${UTM}`;
|
|
142
|
+
// ── 起動時: 認証状態を stderr に出力(MCP クライアントのログに残る)───────────
|
|
143
|
+
const hasPayToken = PAY_TOKEN.length > 0;
|
|
144
|
+
const hasBuyerJwt = BUYER_JWT.length > 0;
|
|
145
|
+
console.error("[LemonCake MCP] Starting...");
|
|
146
|
+
console.error(`[LemonCake MCP] API URL : ${API_URL}`);
|
|
147
|
+
console.error(`[LemonCake MCP] PAY_TOKEN : ${hasPayToken ? "✓ set" : "✗ NOT SET — call_service will be unavailable"}`);
|
|
148
|
+
console.error(`[LemonCake MCP] BUYER_JWT : ${hasBuyerJwt ? "✓ set" : "✗ NOT SET — check_balance will be unavailable"}`);
|
|
149
|
+
console.error(`[LemonCake MCP] MODE : ${DEMO_MODE ? "🎮 DEMO (try-without-signup; demo_* services + mock balance)" : "LIVE"}`);
|
|
150
|
+
if (DEMO_MODE) {
|
|
151
|
+
console.error("[LemonCake MCP]");
|
|
152
|
+
console.error("[LemonCake MCP] 🎮 Demo mode active — you can try these without any signup:");
|
|
153
|
+
console.error("[LemonCake MCP] • list_services → see real marketplace + 3 demo services");
|
|
154
|
+
console.error("[LemonCake MCP] • call_service → demo_search (DuckDuckGo) / demo_echo (httpbin) / demo_fx (open.er-api) — real upstreams, no auth");
|
|
155
|
+
console.error("[LemonCake MCP] • check_balance → returns a mock $1.00 demo balance");
|
|
156
|
+
console.error("[LemonCake MCP] • check_tax / get_service_stats → real (no auth needed)");
|
|
157
|
+
console.error("[LemonCake MCP]");
|
|
158
|
+
console.error(`[LemonCake MCP] For real calls, create a free account → ${REGISTER_URL}`);
|
|
159
|
+
}
|
|
160
|
+
else if (!hasPayToken || !hasBuyerJwt) {
|
|
161
|
+
console.error("[LemonCake MCP]");
|
|
162
|
+
console.error("[LemonCake MCP] 🚀 Get started in 3 minutes:");
|
|
163
|
+
console.error(`[LemonCake MCP] 1. Create a free account → ${REGISTER_URL}`);
|
|
164
|
+
console.error("[LemonCake MCP] 2. Top up balance ($5 USDC / JPYC supported)");
|
|
165
|
+
console.error("[LemonCake MCP] 3. Issue a Pay Token or copy Buyer JWT from Dashboard");
|
|
166
|
+
console.error("[LemonCake MCP]");
|
|
167
|
+
console.error("[LemonCake MCP] Or call the `setup` tool from your MCP client for interactive guidance.");
|
|
168
|
+
}
|
|
169
|
+
// ── ヘルパー ──────────────────────────────────────────────────────────────────
|
|
170
|
+
async function apiGet(path, auth) {
|
|
171
|
+
const res = await fetch(`${API_URL}${path}`, {
|
|
172
|
+
headers: {
|
|
173
|
+
"Content-Type": "application/json",
|
|
174
|
+
"User-Agent": USER_AGENT,
|
|
175
|
+
"X-LemonCake-Client": USER_AGENT,
|
|
176
|
+
...(auth ? { Authorization: `Bearer ${auth}` } : {}),
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
const body = await res.json();
|
|
180
|
+
if (!res.ok)
|
|
181
|
+
throw new Error(`API ${res.status}: ${JSON.stringify(body)}`);
|
|
182
|
+
return body;
|
|
183
|
+
}
|
|
184
|
+
async function apiPost(path, data, auth, extraHeaders) {
|
|
185
|
+
const res = await fetch(`${API_URL}${path}`, {
|
|
186
|
+
method: "POST",
|
|
187
|
+
headers: {
|
|
188
|
+
"Content-Type": "application/json",
|
|
189
|
+
"User-Agent": USER_AGENT,
|
|
190
|
+
"X-LemonCake-Client": USER_AGENT,
|
|
191
|
+
...(auth ? { Authorization: `Bearer ${auth}` } : {}),
|
|
192
|
+
...(extraHeaders ?? {}),
|
|
193
|
+
},
|
|
194
|
+
body: JSON.stringify(data),
|
|
195
|
+
});
|
|
196
|
+
const body = await res.json();
|
|
197
|
+
if (!res.ok)
|
|
198
|
+
throw new Error(`API ${res.status}: ${JSON.stringify(body)}`);
|
|
199
|
+
return body;
|
|
200
|
+
}
|
|
201
|
+
function json(data) {
|
|
202
|
+
return { content: [{ type: "text", text: JSON.stringify(data, null, 2) }] };
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* サービス名から呼び方ヒントを返す。
|
|
206
|
+
* エージェントが call_service で正しい path/method/body を即座に推測できるよう
|
|
207
|
+
* 既知の人気サービスについて推奨呼び出しを示す。
|
|
208
|
+
*/
|
|
209
|
+
function usageHintFor(name) {
|
|
210
|
+
const n = name.toLowerCase();
|
|
211
|
+
if (n.includes("serper"))
|
|
212
|
+
return { path: "/search", method: "POST", body: { q: "<query>", num: 5 }, description: "Google検索 (organic, news, images, knowledge graph)" };
|
|
213
|
+
if (n.includes("hunter"))
|
|
214
|
+
return { path: "/domain-search?domain=<domain>&limit=5", method: "GET", description: "ドメインから企業の連絡先メール一覧 (役職・信頼度付き)" };
|
|
215
|
+
if (n.includes("jina"))
|
|
216
|
+
return { path: "/?url=<url>", method: "GET", description: "Webページを LLM-ready Markdown に変換" };
|
|
217
|
+
if (n.includes("firecrawl"))
|
|
218
|
+
return { path: "/scrape", method: "POST", body: { url: "<url>" }, description: "JS実行ありのWebスクレイピング → Markdown" };
|
|
219
|
+
if (n.includes("ipinfo"))
|
|
220
|
+
return { path: "/<ip>", method: "GET", description: "IP geolocation・ISP・ASN・risk score" };
|
|
221
|
+
if (n.includes("exchange") || n.includes("為替"))
|
|
222
|
+
return { path: "/latest.json", method: "GET", description: "USD基準の170+通貨レート (1日1回更新)" };
|
|
223
|
+
if (n.includes("slack"))
|
|
224
|
+
return { path: "/chat.postMessage", method: "POST", body: { channel: "<id>", text: "<msg>" }, description: "Slackメッセージ送信、人間の判断仰ぎに最適" };
|
|
225
|
+
if (n.includes("gbiz") || n.includes("法人"))
|
|
226
|
+
return { path: "/hojin/<corporate_number>", method: "GET", description: "経産省 gBizINFO 法人情報 (法人番号13桁)" };
|
|
227
|
+
if (n.includes("インボイス") || n.includes("invoice"))
|
|
228
|
+
return { path: "/check?id=T<13桁>", method: "GET", description: "国税庁 適格請求書発行事業者番号の照合" };
|
|
229
|
+
if (n.includes("e-gov") || n.includes("法令"))
|
|
230
|
+
return { path: "/keyword?keyword=<語>", method: "GET", description: "日本の法律・政令・省令を全文検索" };
|
|
231
|
+
if (n.includes("vat") || n.includes("abstract"))
|
|
232
|
+
return { path: "/validate?vat_number=<vat>", method: "GET", description: "EU VAT番号有効性検証" };
|
|
233
|
+
if (n.includes("coze") || n.includes("test"))
|
|
234
|
+
return { path: "/anything", method: "POST", body: { test: "any" }, description: "Echoサーバ (リクエストをそのまま200で返す、デバッグ用)" };
|
|
235
|
+
return undefined;
|
|
236
|
+
}
|
|
237
|
+
/** 未設定の認証情報に対して、取得方法を含む分かりやすいエラーを返す */
|
|
238
|
+
function credentialError(envVar, toolName) {
|
|
239
|
+
return {
|
|
240
|
+
content: [{
|
|
241
|
+
type: "text",
|
|
242
|
+
text: JSON.stringify({
|
|
243
|
+
error: `${envVar} is not configured.`,
|
|
244
|
+
code: "CREDENTIAL_MISSING",
|
|
245
|
+
howToFix: [
|
|
246
|
+
`1. Create a free account → ${REGISTER_URL}`,
|
|
247
|
+
`2. Top up balance ($5 USDC or JPYC) → ${BILLING_URL}`,
|
|
248
|
+
envVar === "LEMON_CAKE_PAY_TOKEN"
|
|
249
|
+
? `3. Dashboard → Tokens → Issue Pay Token (set your spending limit) → ${DASHBOARD_URL}`
|
|
250
|
+
: `3. Dashboard → Settings → Copy your Buyer JWT → ${DASHBOARD_URL}`,
|
|
251
|
+
`4. Add to your MCP client config:`,
|
|
252
|
+
` "env": { "${envVar}": "<paste token here>" }`,
|
|
253
|
+
`5. Restart your MCP client`,
|
|
254
|
+
],
|
|
255
|
+
docs: DOCS_URL,
|
|
256
|
+
tip: `You can also call the 'setup' tool to see full setup instructions.`,
|
|
257
|
+
}, null, 2),
|
|
258
|
+
}],
|
|
259
|
+
isError: true,
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
// ── サーバー初期化 ────────────────────────────────────────────────────────────
|
|
263
|
+
const SERVER_INSTRUCTIONS = DEMO_MODE
|
|
264
|
+
? [
|
|
265
|
+
"🎮 DEMO MODE ACTIVE — no signup, no API key, no card required.",
|
|
266
|
+
"",
|
|
267
|
+
"You are connected with no credentials, so this server is in Demo Mode:",
|
|
268
|
+
" • list_services → marketplace listing + 3 free demo services",
|
|
269
|
+
" • call_service(demo_search) → real Wikipedia search",
|
|
270
|
+
" • call_service(demo_fx) → real live FX rates (open.er-api.com)",
|
|
271
|
+
" • call_service(demo_echo) → httpbin.org echo",
|
|
272
|
+
" • check_balance → mock $1.00 balance",
|
|
273
|
+
" • check_tax / get_service_stats → real (no auth)",
|
|
274
|
+
"",
|
|
275
|
+
"👉 Quick start: try the `explore-demo` prompt above, or call `setup` for the full guide.",
|
|
276
|
+
"",
|
|
277
|
+
"To unlock paid services (Serper, Hunter.io, gBizINFO, NTA invoice check, etc.),",
|
|
278
|
+
"set LEMON_CAKE_PAY_TOKEN. Free signup at https://lemoncake.xyz/register.",
|
|
279
|
+
].join("\n")
|
|
280
|
+
: [
|
|
281
|
+
"LemonCake MCP — Pay-per-call USDC payments for any HTTP API.",
|
|
282
|
+
"",
|
|
283
|
+
"Call `setup` first to verify credentials and list available paid services.",
|
|
284
|
+
"Use `list_services` to browse the marketplace, then `call_service` to invoke.",
|
|
285
|
+
].join("\n");
|
|
286
|
+
const server = new Server({ name: "pay-per-call-mcp", version: MCP_VERSION }, {
|
|
287
|
+
capabilities: { tools: {}, prompts: {}, logging: {} },
|
|
288
|
+
instructions: SERVER_INSTRUCTIONS,
|
|
289
|
+
});
|
|
290
|
+
// ── ツール定義 ────────────────────────────────────────────────────────────────
|
|
291
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({
|
|
292
|
+
tools: [
|
|
293
|
+
// ─── setup(認証不要) ────────────────────────────────────────────────
|
|
294
|
+
{
|
|
295
|
+
name: "setup",
|
|
296
|
+
description: [
|
|
297
|
+
"Show the LemonCake MCP first-run setup guide. No authentication required.",
|
|
298
|
+
"Call this tool FIRST to learn what credentials are missing and how to obtain them.",
|
|
299
|
+
"",
|
|
300
|
+
"If no auth env vars are set, the server is in DEMO MODE: list_services returns",
|
|
301
|
+
"three demo services (demo_search / demo_echo / demo_fx) and call_service / check_balance",
|
|
302
|
+
"respond with mock data so you can verify integration before signing up.",
|
|
303
|
+
"",
|
|
304
|
+
"Returns the current credential status (Pay Token / Buyer JWT), demo-mode flag, and",
|
|
305
|
+
"step-by-step instructions to obtain anything that is missing, including a sample MCP",
|
|
306
|
+
"client config snippet ready to paste.",
|
|
307
|
+
"",
|
|
308
|
+
"Returns: { version, apiUrl, mode, credentials, availableTools, setupSteps, register, dashboard, docs }",
|
|
309
|
+
"Errors: none — this tool always succeeds.",
|
|
310
|
+
].join("\n"),
|
|
311
|
+
annotations: {
|
|
312
|
+
title: "Setup guide",
|
|
313
|
+
readOnlyHint: true,
|
|
314
|
+
destructiveHint: false,
|
|
315
|
+
idempotentHint: true,
|
|
316
|
+
openWorldHint: false,
|
|
317
|
+
},
|
|
318
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
319
|
+
},
|
|
320
|
+
// ─── list_services(認証不要) ───────────────────────────────────────
|
|
321
|
+
{
|
|
322
|
+
name: "list_services",
|
|
323
|
+
description: [
|
|
324
|
+
"List approved API services available on the LemonCake marketplace. No authentication required.",
|
|
325
|
+
"",
|
|
326
|
+
"Use this BEFORE call_service to discover serviceId values and per-call USDC pricing.",
|
|
327
|
+
"",
|
|
328
|
+
"When LEMON_CAKE_PAY_TOKEN is missing, three demo services are prepended",
|
|
329
|
+
"(demo_search → Wikipedia, demo_echo → httpbin, demo_fx → open.er-api) so you",
|
|
330
|
+
"can try call_service without signing up. Live users (PAY_TOKEN set) see only",
|
|
331
|
+
"real marketplace entries; demo_* IDs remain callable directly.",
|
|
332
|
+
"",
|
|
333
|
+
"Each item: { id, name, provider, type ('API' | 'MCP'), pricePerCall, [usage], [mode] }.",
|
|
334
|
+
"Errors: HTTP-level errors are returned as `Error: API <status>: <body>`.",
|
|
335
|
+
].join("\n"),
|
|
336
|
+
annotations: {
|
|
337
|
+
title: "List marketplace services",
|
|
338
|
+
readOnlyHint: true,
|
|
339
|
+
destructiveHint: false,
|
|
340
|
+
idempotentHint: true,
|
|
341
|
+
openWorldHint: true,
|
|
342
|
+
},
|
|
343
|
+
inputSchema: {
|
|
344
|
+
type: "object",
|
|
345
|
+
additionalProperties: false,
|
|
346
|
+
properties: {
|
|
347
|
+
limit: {
|
|
348
|
+
type: "integer",
|
|
349
|
+
minimum: 1,
|
|
350
|
+
maximum: 100,
|
|
351
|
+
default: 50,
|
|
352
|
+
description: "Maximum number of services to return (default 50, max 100).",
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
},
|
|
356
|
+
},
|
|
357
|
+
// ─── call_service(PAY_TOKEN 必須 / demo_* は不要) ─────────────────
|
|
358
|
+
{
|
|
359
|
+
name: "call_service",
|
|
360
|
+
description: [
|
|
361
|
+
"Invoke an upstream API service through LemonCake's pay-per-call proxy.",
|
|
362
|
+
"Each successful call automatically charges USDC against your configured Pay Token.",
|
|
363
|
+
"",
|
|
364
|
+
"PRECONDITIONS:",
|
|
365
|
+
" • LEMON_CAKE_PAY_TOKEN env var must be set for real services. If missing, the tool",
|
|
366
|
+
" returns a structured CREDENTIAL_MISSING error with how-to-fix steps.",
|
|
367
|
+
" • DEMO MODE: serviceId values starting with `demo_` (demo_search / demo_echo / demo_fx)",
|
|
368
|
+
" work WITHOUT any auth and return canned responses — useful for Glama Inspector",
|
|
369
|
+
" or new-user trial. They are clearly marked with `mode: \"demo\"` and incur no charge.",
|
|
370
|
+
" • serviceId must come from list_services.",
|
|
371
|
+
"",
|
|
372
|
+
"BEHAVIOR:",
|
|
373
|
+
" • Returns the upstream response body verbatim (JSON or text), plus the X-Charge-Id",
|
|
374
|
+
" and X-Amount-Usdc headers reported by the proxy.",
|
|
375
|
+
" • HTTP 402 Payment Required is returned as a normal result (NOT thrown) so the",
|
|
376
|
+
" agent can autonomously stop spending when the Pay Token's limitUsdc is exhausted.",
|
|
377
|
+
" • Pass the same idempotencyKey to retry safely without double-charging.",
|
|
378
|
+
" • This tool spends real money and contacts an external service — it is",
|
|
379
|
+
" non-idempotent by default and has external side effects.",
|
|
380
|
+
"",
|
|
381
|
+
"Returns: { status, chargeId, amountUsdc, response }",
|
|
382
|
+
].join("\n"),
|
|
383
|
+
annotations: {
|
|
384
|
+
title: "Call a paid service (charges USDC)",
|
|
385
|
+
readOnlyHint: false,
|
|
386
|
+
destructiveHint: false,
|
|
387
|
+
idempotentHint: false,
|
|
388
|
+
openWorldHint: true,
|
|
389
|
+
},
|
|
390
|
+
inputSchema: {
|
|
391
|
+
type: "object",
|
|
392
|
+
required: ["serviceId"],
|
|
393
|
+
additionalProperties: false,
|
|
394
|
+
properties: {
|
|
395
|
+
serviceId: {
|
|
396
|
+
type: "string",
|
|
397
|
+
description: "ID of the service to call (obtain from list_services).",
|
|
398
|
+
minLength: 1,
|
|
399
|
+
},
|
|
400
|
+
path: {
|
|
401
|
+
type: "string",
|
|
402
|
+
description: "Sub-path on the service (e.g. \"/search\", \"/v1/completions\"). Defaults to \"/\".",
|
|
403
|
+
default: "/",
|
|
404
|
+
pattern: "^/.*",
|
|
405
|
+
},
|
|
406
|
+
method: {
|
|
407
|
+
type: "string",
|
|
408
|
+
enum: ["GET", "POST", "PUT", "PATCH", "DELETE"],
|
|
409
|
+
description: "HTTP method to use against the service. Defaults to GET.",
|
|
410
|
+
default: "GET",
|
|
411
|
+
},
|
|
412
|
+
body: {
|
|
413
|
+
type: "object",
|
|
414
|
+
description: "JSON request body (only used for POST/PUT/PATCH).",
|
|
415
|
+
additionalProperties: true,
|
|
416
|
+
},
|
|
417
|
+
idempotencyKey: {
|
|
418
|
+
type: "string",
|
|
419
|
+
description: "Optional idempotency key (UUID recommended). Identical keys within the proxy's retention window return the cached result without re-charging.",
|
|
420
|
+
format: "uuid",
|
|
421
|
+
},
|
|
422
|
+
},
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
// ─── check_balance(BUYER_JWT 必須 / DEMO_MODE 時は mock) ───────────
|
|
426
|
+
{
|
|
427
|
+
name: "check_balance",
|
|
428
|
+
description: [
|
|
429
|
+
"Check the current USDC balance, KYC tier, and account info of the configured buyer.",
|
|
430
|
+
"",
|
|
431
|
+
"PRECONDITIONS:",
|
|
432
|
+
" • LEMON_CAKE_BUYER_JWT env var must be set. If missing AND no PAY_TOKEN is set,",
|
|
433
|
+
" DEMO MODE returns a canned $1.00 demo balance with kycTier=\"DEMO\" so trial users",
|
|
434
|
+
" see something instead of an error. With PAY_TOKEN set but no BUYER_JWT, returns",
|
|
435
|
+
" a structured CREDENTIAL_MISSING error.",
|
|
436
|
+
"",
|
|
437
|
+
"Use BEFORE call_service to confirm sufficient funds, especially before a long batch.",
|
|
438
|
+
"",
|
|
439
|
+
"Returns: { balanceUsdc, kycTier, email, name, [mode], [note] }",
|
|
440
|
+
].join("\n"),
|
|
441
|
+
annotations: {
|
|
442
|
+
title: "Check USDC balance",
|
|
443
|
+
readOnlyHint: true,
|
|
444
|
+
destructiveHint: false,
|
|
445
|
+
idempotentHint: true,
|
|
446
|
+
openWorldHint: true,
|
|
447
|
+
},
|
|
448
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
449
|
+
},
|
|
450
|
+
// ─── check_tax(認証不要) ───────────────────────────────────────────
|
|
451
|
+
{
|
|
452
|
+
name: "check_tax",
|
|
453
|
+
description: [
|
|
454
|
+
"Run a Japanese tax compliance check on a single transaction. No authentication required.",
|
|
455
|
+
"",
|
|
456
|
+
"Performs three checks in one call:",
|
|
457
|
+
" 1. Validates the qualified-invoice registration number (T-number) against the NTA registry.",
|
|
458
|
+
" 2. Determines whether source-withholding (源泉徴収) applies based on the service description.",
|
|
459
|
+
" 3. If withholding applies, computes the withholding amount and net payable.",
|
|
460
|
+
"",
|
|
461
|
+
"Intended for Japanese corporations that pay AI / API services and need to file",
|
|
462
|
+
"withholding correctly under the qualified-invoice (インボイス制度) regime.",
|
|
463
|
+
"",
|
|
464
|
+
"Returns: { invoice: { valid, name, ... }, withholding: { required, rate, amount, net } }",
|
|
465
|
+
"Errors: invalid registrationNumber returns invoice.valid = false (not an exception).",
|
|
466
|
+
].join("\n"),
|
|
467
|
+
annotations: {
|
|
468
|
+
title: "Japanese tax compliance check",
|
|
469
|
+
readOnlyHint: true,
|
|
470
|
+
destructiveHint: false,
|
|
471
|
+
idempotentHint: true,
|
|
472
|
+
openWorldHint: true,
|
|
473
|
+
},
|
|
474
|
+
inputSchema: {
|
|
475
|
+
type: "object",
|
|
476
|
+
required: ["registrationNumber", "serviceDescription", "grossAmountJpy"],
|
|
477
|
+
additionalProperties: false,
|
|
478
|
+
properties: {
|
|
479
|
+
registrationNumber: {
|
|
480
|
+
type: "string",
|
|
481
|
+
description: "Qualified-invoice registration number issued by the Japanese NTA (e.g. \"T1234567890123\").",
|
|
482
|
+
pattern: "^T?\\d{13}$",
|
|
483
|
+
},
|
|
484
|
+
serviceDescription: {
|
|
485
|
+
type: "string",
|
|
486
|
+
description: "Plain-text description of what was purchased. Used to classify whether source-withholding applies.",
|
|
487
|
+
minLength: 1,
|
|
488
|
+
},
|
|
489
|
+
grossAmountJpy: {
|
|
490
|
+
type: "number",
|
|
491
|
+
description: "Gross transaction amount in JPY, tax inclusive.",
|
|
492
|
+
exclusiveMinimum: 0,
|
|
493
|
+
},
|
|
494
|
+
},
|
|
495
|
+
},
|
|
496
|
+
},
|
|
497
|
+
// ─── get_service_stats(認証不要) ───────────────────────────────────
|
|
498
|
+
{
|
|
499
|
+
name: "get_service_stats",
|
|
500
|
+
description: [
|
|
501
|
+
"Return public usage statistics for every approved service on the marketplace. No authentication required.",
|
|
502
|
+
"",
|
|
503
|
+
"Use this AFTER list_services and BEFORE call_service to pick a service based on",
|
|
504
|
+
"real-world traction (call counts, USDC revenue, last-used timestamp).",
|
|
505
|
+
"",
|
|
506
|
+
"Returns: an array of { serviceId, callCount, totalRevenueUsdc, lastCalledAt }.",
|
|
507
|
+
].join("\n"),
|
|
508
|
+
annotations: {
|
|
509
|
+
title: "Marketplace usage stats",
|
|
510
|
+
readOnlyHint: true,
|
|
511
|
+
destructiveHint: false,
|
|
512
|
+
idempotentHint: true,
|
|
513
|
+
openWorldHint: true,
|
|
514
|
+
},
|
|
515
|
+
inputSchema: { type: "object", properties: {}, additionalProperties: false },
|
|
516
|
+
},
|
|
517
|
+
],
|
|
518
|
+
}));
|
|
519
|
+
// ── プロンプト定義 ────────────────────────────────────────────────────────────
|
|
520
|
+
// MCP Prompts: Glama Inspector / Claude Desktop / Cursor の prompt-picker に
|
|
521
|
+
// 「Try this」ボタンとして表示される pre-written conversation starters。
|
|
522
|
+
// Demo Mode(auth 不要)でもすぐ動くものを優先しているので、新規ユーザーが
|
|
523
|
+
// click 1 つで実プロダクトの挙動を確認できる。
|
|
524
|
+
const PROMPTS = [
|
|
525
|
+
{
|
|
526
|
+
name: "explore-demo",
|
|
527
|
+
title: "🎮 Try the demo (no signup)",
|
|
528
|
+
description: "Walk through demo_search → demo_fx → demo_echo with no auth required.",
|
|
529
|
+
template: [
|
|
530
|
+
"Use the LemonCake MCP server (pay-per-call-mcp) in demo mode (no auth needed) to:",
|
|
531
|
+
"1. Run `setup` to confirm we're in demo mode.",
|
|
532
|
+
"2. Run `list_services` and tell me which entries are demos.",
|
|
533
|
+
"3. Call `call_service` with serviceId='demo_search' and body={\"q\":\"Model Context Protocol\"} — show me the Wikipedia results.",
|
|
534
|
+
"4. Call `call_service` with serviceId='demo_fx' and report current USD/JPY.",
|
|
535
|
+
"Then summarize what LemonCake is, in 2 sentences.",
|
|
536
|
+
].join("\n"),
|
|
537
|
+
},
|
|
538
|
+
{
|
|
539
|
+
name: "discover-marketplace",
|
|
540
|
+
title: "🛍 Discover marketplace services",
|
|
541
|
+
description: "List approved services and pick one that matches a use case.",
|
|
542
|
+
template: [
|
|
543
|
+
"Using this server's `list_services` tool, list every approved service with its category and price.",
|
|
544
|
+
"Then recommend the top 3 for an AI agent that needs to: (a) find recent news, (b) verify a Japanese invoice number, (c) translate text.",
|
|
545
|
+
"For each recommendation, show the exact `call_service` arguments to invoke it.",
|
|
546
|
+
].join("\n"),
|
|
547
|
+
},
|
|
548
|
+
{
|
|
549
|
+
name: "japan-tax-check",
|
|
550
|
+
title: "🇯🇵 Validate a Japanese invoice number",
|
|
551
|
+
description: "Use the check_tax tool to verify a 適格請求書発行事業者番号 against the NTA registry.",
|
|
552
|
+
arguments: [
|
|
553
|
+
{ name: "registrationNumber", description: "T + 13 digit number. Leave empty to use a sample (T1234567890123).", required: false },
|
|
554
|
+
{ name: "amountJpy", description: "Gross amount in JPY. Defaults to 110000.", required: false },
|
|
555
|
+
],
|
|
556
|
+
template: (args) => [
|
|
557
|
+
`Use the \`check_tax\` tool to validate registration number ${args.registrationNumber ?? "T1234567890123"}.`,
|
|
558
|
+
args.amountJpy ? `The transaction amount is ${args.amountJpy} JPY (tax-inclusive).` : "Use a sample amount of 110000 JPY.",
|
|
559
|
+
"Report: (1) is the number valid? (2) registered name and address. (3) does source-withholding (源泉徴収) apply, and how much?",
|
|
560
|
+
].join("\n"),
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
name: "spend-with-budget",
|
|
564
|
+
title: "💰 Spend with a strict budget cap",
|
|
565
|
+
description: "Pattern: check_balance → call_service → check_balance again, demonstrating KYA/Pay-Token spending limits.",
|
|
566
|
+
arguments: [
|
|
567
|
+
{ name: "serviceId", description: "Marketplace service ID (omit for demo_search)", required: false },
|
|
568
|
+
{ name: "query", description: "Search query (for search/data services)", required: false },
|
|
569
|
+
],
|
|
570
|
+
template: (args) => {
|
|
571
|
+
const sid = args.serviceId ?? "demo_search";
|
|
572
|
+
const q = args.query ?? "AI agent payments 2026";
|
|
573
|
+
return [
|
|
574
|
+
`Demonstrate the LemonCake spending pattern with serviceId=\`${sid}\`:`,
|
|
575
|
+
"1. Call `check_balance` and report current USDC balance + KYA tier.",
|
|
576
|
+
`2. Call \`call_service\` with serviceId='${sid}', method='POST', path='/search', body={\"q\":\"${q}\"}.`,
|
|
577
|
+
"3. Call `check_balance` again and report the delta. If we're in demo mode, note that no real charge happened.",
|
|
578
|
+
"Throughout, mention any 4xx hints the proxy returns so I learn the failure modes.",
|
|
579
|
+
].join("\n");
|
|
580
|
+
},
|
|
581
|
+
},
|
|
582
|
+
{
|
|
583
|
+
name: "real-vs-demo",
|
|
584
|
+
title: "🔄 Compare demo vs real upstream",
|
|
585
|
+
description: "Hit the same logical query against demo_search (Wikipedia) and a real marketplace search service to see the difference.",
|
|
586
|
+
template: [
|
|
587
|
+
"Compare LemonCake's demo vs real search:",
|
|
588
|
+
"1. Call `call_service` with serviceId='demo_search', body={\"q\":\"Model Context Protocol\"}. Note the Wikipedia results.",
|
|
589
|
+
"2. From `list_services`, find a real Serper / search service (category='検索') and call it with the same query.",
|
|
590
|
+
"3. Tabulate: result count, top result title, latency feel (you'll see chargeId only for the real one).",
|
|
591
|
+
"If LEMON_CAKE_PAY_TOKEN isn't set, gracefully skip step 2 and explain how to set it.",
|
|
592
|
+
].join("\n"),
|
|
593
|
+
},
|
|
594
|
+
{
|
|
595
|
+
name: "japan-finance-bundle",
|
|
596
|
+
title: "🏯 Japan finance research bundle",
|
|
597
|
+
description: "Combine gBizINFO 法人情報 + 国税庁 invoice check + e-Gov 法令 in one workflow.",
|
|
598
|
+
arguments: [
|
|
599
|
+
{ name: "corporateNumber", description: "13-digit corporate number (法人番号)", required: false },
|
|
600
|
+
],
|
|
601
|
+
template: (args) => {
|
|
602
|
+
const cn = args.corporateNumber ?? "3010001088782";
|
|
603
|
+
return [
|
|
604
|
+
`Run a Japan finance research workflow with 法人番号 ${cn}:`,
|
|
605
|
+
"1. `list_services` — confirm gBizINFO / 国税庁インボイス / e-Gov are available (category='日本特化').",
|
|
606
|
+
`2. Call gBizINFO with path='/hojin/${cn}'. Report company name, address, capital, representative.`,
|
|
607
|
+
`3. Call 国税庁インボイス with path='/check?id=T${cn}'. Verify if this corp is a registered invoice issuer.`,
|
|
608
|
+
"4. Call e-Gov with keyword='インボイス制度'. Find 1-2 relevant law articles.",
|
|
609
|
+
"Summarize all findings in a single executive briefing.",
|
|
610
|
+
].join("\n");
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
];
|
|
614
|
+
server.setRequestHandler(ListPromptsRequestSchema, async () => ({
|
|
615
|
+
prompts: PROMPTS.map((p) => ({
|
|
616
|
+
name: p.name,
|
|
617
|
+
title: p.title,
|
|
618
|
+
description: p.description,
|
|
619
|
+
arguments: "arguments" in p ? p.arguments : undefined,
|
|
620
|
+
})),
|
|
621
|
+
}));
|
|
622
|
+
server.setRequestHandler(GetPromptRequestSchema, async (req) => {
|
|
623
|
+
const name = req.params.name;
|
|
624
|
+
const args = (req.params.arguments ?? {});
|
|
625
|
+
const prompt = PROMPTS.find((p) => p.name === name);
|
|
626
|
+
if (!prompt) {
|
|
627
|
+
throw new Error(`Unknown prompt: ${name}`);
|
|
628
|
+
}
|
|
629
|
+
const text = typeof prompt.template === "function"
|
|
630
|
+
? prompt.template(args)
|
|
631
|
+
: prompt.template;
|
|
632
|
+
return {
|
|
633
|
+
description: prompt.description,
|
|
634
|
+
messages: [
|
|
635
|
+
{
|
|
636
|
+
role: "user",
|
|
637
|
+
content: { type: "text", text },
|
|
638
|
+
},
|
|
639
|
+
],
|
|
640
|
+
};
|
|
641
|
+
});
|
|
642
|
+
// ── ツール実装 ────────────────────────────────────────────────────────────────
|
|
643
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
644
|
+
const { name, arguments: args = {} } = req.params;
|
|
645
|
+
try {
|
|
646
|
+
switch (name) {
|
|
647
|
+
// ─── setup ──────────────────────────────────────────────────────────
|
|
648
|
+
case "setup": {
|
|
649
|
+
const status = {
|
|
650
|
+
payToken: hasPayToken ? "✓ 設定済み" : "✗ 未設定(call_service が使えません)",
|
|
651
|
+
buyerJwt: hasBuyerJwt ? "✓ 設定済み" : "✗ 未設定(check_balance が使えません)",
|
|
652
|
+
};
|
|
653
|
+
const steps = [];
|
|
654
|
+
if (!hasPayToken || !hasBuyerJwt) {
|
|
655
|
+
steps.push("=== セットアップ手順 ===");
|
|
656
|
+
steps.push("");
|
|
657
|
+
steps.push("1. 無料アカウント作成(3分で完了)");
|
|
658
|
+
steps.push(` → ${REGISTER_URL}`);
|
|
659
|
+
steps.push("");
|
|
660
|
+
steps.push("2. USDC残高をチャージ($5〜)");
|
|
661
|
+
steps.push(` → ${BILLING_URL}`);
|
|
662
|
+
steps.push(" JPYCまたはUSDCで入金可能");
|
|
663
|
+
steps.push("");
|
|
664
|
+
}
|
|
665
|
+
if (!hasPayToken) {
|
|
666
|
+
steps.push("3. Pay Tokenを発行(call_service に必要)");
|
|
667
|
+
steps.push(" Dashboard → Tokens → 「Pay Tokenを発行」");
|
|
668
|
+
steps.push(" ・serviceId: 使いたいサービスのIDを選択");
|
|
669
|
+
steps.push(" ・limitUsdc: このトークンで使える上限額(例: 5.00)");
|
|
670
|
+
steps.push(" → 発行されたJWTをコピー");
|
|
671
|
+
steps.push("");
|
|
672
|
+
steps.push("4. MCP設定ファイルに追加:");
|
|
673
|
+
steps.push(' "LEMON_CAKE_PAY_TOKEN": "<コピーしたJWT>"');
|
|
674
|
+
steps.push("");
|
|
675
|
+
}
|
|
676
|
+
if (!hasBuyerJwt) {
|
|
677
|
+
steps.push(!hasPayToken ? "5." : "3." + " Buyer JWTを取得(check_balance に必要)");
|
|
678
|
+
steps.push(" Dashboard → Settings → 「Buyer JWTをコピー」");
|
|
679
|
+
steps.push(' "LEMON_CAKE_BUYER_JWT": "<コピーしたJWT>"');
|
|
680
|
+
steps.push("");
|
|
681
|
+
}
|
|
682
|
+
steps.push("=== MCP設定ファイルのサンプル ===");
|
|
683
|
+
steps.push("");
|
|
684
|
+
steps.push(JSON.stringify({
|
|
685
|
+
mcpServers: {
|
|
686
|
+
"pay-per-call": {
|
|
687
|
+
command: "npx",
|
|
688
|
+
args: ["-y", "pay-per-call-mcp"],
|
|
689
|
+
env: {
|
|
690
|
+
LEMON_CAKE_PAY_TOKEN: hasPayToken ? "(設定済み)" : "<ダッシュボードで発行したPay Token JWT>",
|
|
691
|
+
LEMON_CAKE_BUYER_JWT: hasBuyerJwt ? "(設定済み)" : "<ダッシュボードのSettingsからコピーしたJWT>",
|
|
692
|
+
},
|
|
693
|
+
},
|
|
694
|
+
},
|
|
695
|
+
}, null, 2));
|
|
696
|
+
steps.push("");
|
|
697
|
+
steps.push("※ 旧パッケージ名 `lemon-cake-mcp` も同じく動作します(ラッパーとして維持中)。");
|
|
698
|
+
return json({
|
|
699
|
+
version: MCP_VERSION,
|
|
700
|
+
apiUrl: API_URL,
|
|
701
|
+
mode: DEMO_MODE ? "demo" : "live",
|
|
702
|
+
credentials: status,
|
|
703
|
+
availableTools: DEMO_MODE
|
|
704
|
+
? {
|
|
705
|
+
noAuth: ["setup", "list_services", "get_service_stats", "check_tax", "check_balance (mock)", "call_service (demo_* only)"],
|
|
706
|
+
demoServices: DEMO_SERVICES.map((d) => d.id),
|
|
707
|
+
upgradeHint: "Set LEMON_CAKE_PAY_TOKEN to call real services; set LEMON_CAKE_BUYER_JWT for real balance.",
|
|
708
|
+
}
|
|
709
|
+
: {
|
|
710
|
+
noAuth: ["setup", "list_services", "get_service_stats", "check_tax"],
|
|
711
|
+
needPayToken: ["call_service"],
|
|
712
|
+
needBuyerJwt: ["check_balance"],
|
|
713
|
+
},
|
|
714
|
+
setupSteps: steps.length > 0 ? steps.join("\n") : "✅ 全ての認証情報が設定されています。",
|
|
715
|
+
register: REGISTER_URL,
|
|
716
|
+
dashboard: DASHBOARD_URL,
|
|
717
|
+
docs: "https://github.com/evidai/lemon-cake",
|
|
718
|
+
});
|
|
719
|
+
}
|
|
720
|
+
// ─── list_services ───────────────────────────────────────────────────
|
|
721
|
+
case "list_services": {
|
|
722
|
+
const limit = args.limit ?? 50;
|
|
723
|
+
const services = await apiGet(`/api/services?reviewStatus=APPROVED&limit=${limit}`);
|
|
724
|
+
const real = services
|
|
725
|
+
.filter((s) => s.verified)
|
|
726
|
+
.map((s) => ({
|
|
727
|
+
id: s.id,
|
|
728
|
+
name: s.name,
|
|
729
|
+
provider: s.providerName,
|
|
730
|
+
type: s.type,
|
|
731
|
+
pricePerCall: `${s.pricePerCallUsdc} USDC`,
|
|
732
|
+
usage: usageHintFor(s.name),
|
|
733
|
+
}));
|
|
734
|
+
// Surface demo services whenever PAY_TOKEN is missing (covers full
|
|
735
|
+
// DEMO_MODE and the partial-auth case where only BUYER_JWT is set).
|
|
736
|
+
// Live users with PAY_TOKEN see only real services to avoid clutter,
|
|
737
|
+
// but can still call demo_* serviceIds directly.
|
|
738
|
+
if (!PAY_TOKEN) {
|
|
739
|
+
const demos = DEMO_SERVICES.map((d) => ({
|
|
740
|
+
id: d.id,
|
|
741
|
+
name: d.name,
|
|
742
|
+
provider: d.provider,
|
|
743
|
+
type: d.type,
|
|
744
|
+
pricePerCall: d.pricePerCall,
|
|
745
|
+
description: d.description,
|
|
746
|
+
usage: d.example,
|
|
747
|
+
mode: "demo",
|
|
748
|
+
}));
|
|
749
|
+
return json([...demos, ...real]);
|
|
750
|
+
}
|
|
751
|
+
return json(real);
|
|
752
|
+
}
|
|
753
|
+
// ─── call_service ────────────────────────────────────────────────────
|
|
754
|
+
case "call_service": {
|
|
755
|
+
const serviceId = args.serviceId;
|
|
756
|
+
const subPath = args.path ?? "/";
|
|
757
|
+
const method = args.method ?? "GET";
|
|
758
|
+
const body = args.body;
|
|
759
|
+
const idempotencyKey = args.idempotencyKey;
|
|
760
|
+
const normalizedPath = subPath.startsWith("/") ? subPath : `/${subPath}`;
|
|
761
|
+
// Demo mode: handle demo_* services without auth so Glama Inspector
|
|
762
|
+
// and new users can verify the call shape before signing up.
|
|
763
|
+
const demoSvc = findDemoService(serviceId);
|
|
764
|
+
if (demoSvc) {
|
|
765
|
+
return json({
|
|
766
|
+
status: 200,
|
|
767
|
+
chargeId: `demo_${Date.now().toString(36)}`,
|
|
768
|
+
amountUsdc: demoSvc.pricePerCall.split(" ")[0],
|
|
769
|
+
response: await demoSvc.handler(normalizedPath, body),
|
|
770
|
+
mode: "demo",
|
|
771
|
+
note: DEMO_NOTICE,
|
|
772
|
+
});
|
|
773
|
+
}
|
|
774
|
+
if (!PAY_TOKEN)
|
|
775
|
+
return credentialError("LEMON_CAKE_PAY_TOKEN", "call_service");
|
|
776
|
+
const url = `${API_URL}/api/proxy/${serviceId}${normalizedPath}`;
|
|
777
|
+
const headers = {
|
|
778
|
+
"Content-Type": "application/json",
|
|
779
|
+
"Authorization": `Bearer ${PAY_TOKEN}`,
|
|
780
|
+
"User-Agent": USER_AGENT,
|
|
781
|
+
"X-LemonCake-Client": USER_AGENT,
|
|
782
|
+
};
|
|
783
|
+
if (idempotencyKey)
|
|
784
|
+
headers["Idempotency-Key"] = idempotencyKey;
|
|
785
|
+
const fetchOptions = { method, headers };
|
|
786
|
+
if (body && ["POST", "PUT", "PATCH"].includes(method)) {
|
|
787
|
+
fetchOptions.body = JSON.stringify(body);
|
|
788
|
+
}
|
|
789
|
+
const res = await fetch(url, fetchOptions);
|
|
790
|
+
const chargeId = res.headers.get("X-Charge-Id");
|
|
791
|
+
const amountUsdc = res.headers.get("X-Amount-Usdc");
|
|
792
|
+
let responseBody;
|
|
793
|
+
if ((res.headers.get("content-type") ?? "").includes("application/json")) {
|
|
794
|
+
responseBody = await res.json();
|
|
795
|
+
}
|
|
796
|
+
else {
|
|
797
|
+
responseBody = await res.text();
|
|
798
|
+
}
|
|
799
|
+
// 402 の場合もエラーではなく構造化レスポンスとして返す
|
|
800
|
+
// (エージェントが自律的に停止判断できるように)
|
|
801
|
+
const result = { status: res.status, chargeId, amountUsdc, response: responseBody };
|
|
802
|
+
// よくある 4xx に対するエージェント向けヒントを付与
|
|
803
|
+
if (res.status >= 400) {
|
|
804
|
+
let hint;
|
|
805
|
+
const bodyStr = typeof responseBody === "string" ? responseBody : JSON.stringify(responseBody ?? "");
|
|
806
|
+
if (res.status === 401)
|
|
807
|
+
hint = "Upstream authentication failed. The service's API key may be invalid or expired. Try a different service.";
|
|
808
|
+
else if (res.status === 402)
|
|
809
|
+
hint = "Pay Token limit exhausted or buyer balance insufficient. Stop further calls and notify the user to top up.";
|
|
810
|
+
else if (res.status === 403)
|
|
811
|
+
hint = "Forbidden. Token scope may not match this serviceId, or service is not approved.";
|
|
812
|
+
else if (res.status === 404)
|
|
813
|
+
hint = "Path not found on upstream. Re-check the `path` argument — common shapes vary by service.";
|
|
814
|
+
else if (res.status === 422 && bodyStr.includes("service_uneconomical"))
|
|
815
|
+
hint = "This service is below the platform's minimum revenue floor. Cannot be called regardless of buyer balance.";
|
|
816
|
+
else if (res.status === 429)
|
|
817
|
+
hint = "Upstream rate-limited. Retry after backoff or pick a different service.";
|
|
818
|
+
else if (res.status === 501)
|
|
819
|
+
hint = "Service has no proxy endpoint configured. Pick a different service from list_services.";
|
|
820
|
+
else if (res.status >= 500)
|
|
821
|
+
hint = "Upstream server error. Retry once with the same idempotencyKey, then escalate or switch service.";
|
|
822
|
+
if (hint)
|
|
823
|
+
result.hint = hint;
|
|
824
|
+
}
|
|
825
|
+
return json(result);
|
|
826
|
+
}
|
|
827
|
+
// ─── check_balance ───────────────────────────────────────────────────
|
|
828
|
+
case "check_balance": {
|
|
829
|
+
if (!BUYER_JWT) {
|
|
830
|
+
if (DEMO_MODE)
|
|
831
|
+
return json({
|
|
832
|
+
balanceUsdc: "1.00",
|
|
833
|
+
kycTier: "DEMO",
|
|
834
|
+
email: "demo@lemoncake.xyz",
|
|
835
|
+
name: "Demo Buyer",
|
|
836
|
+
mode: "demo",
|
|
837
|
+
note: DEMO_NOTICE,
|
|
838
|
+
});
|
|
839
|
+
return credentialError("LEMON_CAKE_BUYER_JWT", "check_balance");
|
|
840
|
+
}
|
|
841
|
+
const me = await apiGet("/api/auth/me", BUYER_JWT);
|
|
842
|
+
return json({
|
|
843
|
+
balanceUsdc: me.buyer?.balanceUsdc ?? me.balanceUsdc,
|
|
844
|
+
kycTier: me.buyer?.kycTier ?? me.kycTier,
|
|
845
|
+
email: me.email,
|
|
846
|
+
name: me.name,
|
|
847
|
+
});
|
|
848
|
+
}
|
|
849
|
+
// ─── check_tax ───────────────────────────────────────────────────────
|
|
850
|
+
case "check_tax": {
|
|
851
|
+
const result = await apiPost("/api/tax/full-check", {
|
|
852
|
+
registrationNumber: args.registrationNumber,
|
|
853
|
+
serviceDescription: args.serviceDescription,
|
|
854
|
+
grossAmountJpy: args.grossAmountJpy,
|
|
855
|
+
});
|
|
856
|
+
return json(result);
|
|
857
|
+
}
|
|
858
|
+
// ─── get_service_stats ───────────────────────────────────────────────
|
|
859
|
+
case "get_service_stats": {
|
|
860
|
+
const stats = await apiGet("/api/services/stats");
|
|
861
|
+
return json(stats);
|
|
862
|
+
}
|
|
863
|
+
default:
|
|
864
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
catch (err) {
|
|
868
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
869
|
+
return {
|
|
870
|
+
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
871
|
+
isError: true,
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
});
|
|
875
|
+
// ── 起動 ──────────────────────────────────────────────────────────────────────
|
|
876
|
+
async function main() {
|
|
877
|
+
const transport = new StdioServerTransport();
|
|
878
|
+
await server.connect(transport);
|
|
879
|
+
console.error("[LemonCake MCP] Ready.");
|
|
880
|
+
// 接続直後に Inspector の Request Log にバナーを出す。
|
|
881
|
+
// Glama UI の env-var ダイアログを抜けて Inspector まで来た人に
|
|
882
|
+
// 「空のまま動いてる、Demo Mode だよ」を即座に伝えるのが目的。
|
|
883
|
+
try {
|
|
884
|
+
await server.notification({
|
|
885
|
+
method: "notifications/message",
|
|
886
|
+
params: {
|
|
887
|
+
level: "info",
|
|
888
|
+
logger: "pay-per-call-mcp",
|
|
889
|
+
data: DEMO_MODE
|
|
890
|
+
? "🎮 DEMO MODE — connected without credentials. Try the `explore-demo` prompt or call `setup` for guided onboarding. No signup needed."
|
|
891
|
+
: `LemonCake MCP v${MCP_VERSION} ready. Run \`setup\` to verify credentials and \`list_services\` to browse paid APIs.`,
|
|
892
|
+
},
|
|
893
|
+
});
|
|
894
|
+
}
|
|
895
|
+
catch {
|
|
896
|
+
// Older clients may not accept notifications before initialize completes.
|
|
897
|
+
// The console.error banners above already cover stdio-only environments.
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
main().catch((err) => {
|
|
901
|
+
console.error("[LemonCake MCP] Fatal:", err);
|
|
902
|
+
process.exit(1);
|
|
903
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pay-per-call-mcp",
|
|
3
|
+
"version": "0.5.0",
|
|
4
|
+
"description": "Pay-per-call USDC payments for any HTTP API. Give your AI agent a wallet — M2M finance, agent payments, Japan tax invoice check. (formerly lemon-cake-mcp)",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"pay-per-call-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE"
|
|
14
|
+
],
|
|
15
|
+
"keywords": [
|
|
16
|
+
"mcp",
|
|
17
|
+
"model-context-protocol",
|
|
18
|
+
"ai-agent",
|
|
19
|
+
"agent-wallet",
|
|
20
|
+
"payment",
|
|
21
|
+
"pay-per-call",
|
|
22
|
+
"usdc",
|
|
23
|
+
"stablecoin",
|
|
24
|
+
"crypto",
|
|
25
|
+
"wallet",
|
|
26
|
+
"m2m",
|
|
27
|
+
"api-proxy",
|
|
28
|
+
"api-gateway",
|
|
29
|
+
"finance",
|
|
30
|
+
"japan-tax",
|
|
31
|
+
"invoice",
|
|
32
|
+
"autonomous-agent",
|
|
33
|
+
"llm",
|
|
34
|
+
"claude",
|
|
35
|
+
"cursor"
|
|
36
|
+
],
|
|
37
|
+
"repository": {
|
|
38
|
+
"type": "git",
|
|
39
|
+
"url": "https://github.com/evidai/lemon-cake.git"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://lemoncake.xyz",
|
|
42
|
+
"bugs": {
|
|
43
|
+
"url": "https://github.com/evidai/lemon-cake/issues"
|
|
44
|
+
},
|
|
45
|
+
"license": "MIT",
|
|
46
|
+
"scripts": {
|
|
47
|
+
"build": "tsc",
|
|
48
|
+
"dev": "tsx src/index.ts",
|
|
49
|
+
"start": "node dist/index.js",
|
|
50
|
+
"prepublishOnly": "npm run build"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@modelcontextprotocol/sdk": "^1.10.2"
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@types/node": "^22.0.0",
|
|
60
|
+
"tsx": "^4.19.0",
|
|
61
|
+
"typescript": "^5.7.0"
|
|
62
|
+
}
|
|
63
|
+
}
|