rakuten-mcp 0.1.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/README.md +57 -0
- package/dist/index.js +529 -0
- package/package.json +36 -0
package/README.md
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# @japan-mcp/rakuten
|
|
2
|
+
|
|
3
|
+
MCP server for [Rakuten Web Service APIs](https://webservice.rakuten.co.jp/).
|
|
4
|
+
|
|
5
|
+
Search Japan's largest e-commerce marketplace, plus Rakuten Books and Rakuten Travel.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
1. Register at [Rakuten Web Service](https://webservice.rakuten.co.jp/) (free)
|
|
10
|
+
2. Create an application to get an **Application ID** and **Access Key**
|
|
11
|
+
3. Set the environment variables:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
export RAKUTEN_APP_ID="your-app-id-here"
|
|
15
|
+
export RAKUTEN_ACCESS_KEY="your-access-key-here"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Tools
|
|
19
|
+
|
|
20
|
+
| Tool | Description |
|
|
21
|
+
|------|-------------|
|
|
22
|
+
| `search_products` | Full-text product search with price filters, sorting, and pagination |
|
|
23
|
+
| `get_genre_ranking` | Bestseller rankings (overall or by category) |
|
|
24
|
+
| `search_genres` | Browse product category hierarchy |
|
|
25
|
+
| `search_books` | Search Rakuten Books by title, author, ISBN |
|
|
26
|
+
| `search_travel` | Search hotels on Rakuten Travel by keyword |
|
|
27
|
+
| `search_travel_vacancy` | Search available hotel rooms with date/price/location filters |
|
|
28
|
+
| `get_product_reviews` | Read product reviews with rating and date sorting |
|
|
29
|
+
|
|
30
|
+
## Usage with Claude Desktop
|
|
31
|
+
|
|
32
|
+
```json
|
|
33
|
+
{
|
|
34
|
+
"mcpServers": {
|
|
35
|
+
"rakuten": {
|
|
36
|
+
"command": "node",
|
|
37
|
+
"args": ["path/to/servers/rakuten-mcp/dist/index.js"],
|
|
38
|
+
"env": {
|
|
39
|
+
"RAKUTEN_APP_ID": "your-app-id-here",
|
|
40
|
+
"RAKUTEN_ACCESS_KEY": "your-access-key-here"
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Example Prompts
|
|
48
|
+
|
|
49
|
+
- "Find wireless earphones under 10,000 yen with good reviews"
|
|
50
|
+
- "What are the top sellers on Rakuten right now?"
|
|
51
|
+
- "Search for hotels in Kyoto on Rakuten Travel"
|
|
52
|
+
- "Find available rooms near Tokyo Station for April 15-17 under 15,000 yen"
|
|
53
|
+
- "Find books by Haruki Murakami on Rakuten Books"
|
|
54
|
+
|
|
55
|
+
## License
|
|
56
|
+
|
|
57
|
+
MIT
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,529 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/index.ts
|
|
4
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
|
+
import { z } from "zod";
|
|
7
|
+
var RAKUTEN_API_BASE = "https://app.rakuten.co.jp/services/api";
|
|
8
|
+
var RAKUTEN_TRAVEL_API_BASE = "https://openapi.rakuten.co.jp/engine/api";
|
|
9
|
+
function getAppId() {
|
|
10
|
+
const appId = process.env.RAKUTEN_APP_ID;
|
|
11
|
+
if (!appId) {
|
|
12
|
+
throw new Error(
|
|
13
|
+
"RAKUTEN_APP_ID environment variable is required. Get one at https://webservice.rakuten.co.jp/"
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
return appId;
|
|
17
|
+
}
|
|
18
|
+
function getAccessKey() {
|
|
19
|
+
const key = process.env.RAKUTEN_ACCESS_KEY;
|
|
20
|
+
if (!key) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
"RAKUTEN_ACCESS_KEY environment variable is required. Get one at https://webservice.rakuten.co.jp/"
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
return key;
|
|
26
|
+
}
|
|
27
|
+
async function rakutenRequest(endpoint, params = {}, baseUrl = RAKUTEN_API_BASE) {
|
|
28
|
+
const appId = getAppId();
|
|
29
|
+
const accessKey = getAccessKey();
|
|
30
|
+
const searchParams = new URLSearchParams({
|
|
31
|
+
applicationId: appId,
|
|
32
|
+
accessKey,
|
|
33
|
+
format: "json",
|
|
34
|
+
...params
|
|
35
|
+
});
|
|
36
|
+
const url = `${baseUrl}${endpoint}?${searchParams}`;
|
|
37
|
+
const res = await fetch(url);
|
|
38
|
+
if (!res.ok) {
|
|
39
|
+
const status = res.status;
|
|
40
|
+
await res.text();
|
|
41
|
+
throw new Error(`Rakuten API error (HTTP ${status}) on ${endpoint}`);
|
|
42
|
+
}
|
|
43
|
+
const text = await res.text();
|
|
44
|
+
if (!text) return { success: true };
|
|
45
|
+
try {
|
|
46
|
+
return JSON.parse(text);
|
|
47
|
+
} catch {
|
|
48
|
+
throw new Error(`Rakuten API returned malformed JSON on ${endpoint}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
var server = new McpServer({
|
|
52
|
+
name: "rakuten-mcp",
|
|
53
|
+
version: "0.1.0"
|
|
54
|
+
});
|
|
55
|
+
server.tool(
|
|
56
|
+
"search_products",
|
|
57
|
+
"Search for products on Rakuten Ichiba (Japan's largest e-commerce marketplace)",
|
|
58
|
+
{
|
|
59
|
+
keyword: z.string().describe("Search keyword (Japanese or English)"),
|
|
60
|
+
hits: z.number().min(1).max(30).default(10).describe("Number of results (1-30)"),
|
|
61
|
+
page: z.number().min(1).default(1).describe("Page number"),
|
|
62
|
+
sort: z.enum([
|
|
63
|
+
"standard",
|
|
64
|
+
"+affiliateRate",
|
|
65
|
+
"-affiliateRate",
|
|
66
|
+
"+reviewCount",
|
|
67
|
+
"-reviewCount",
|
|
68
|
+
"+reviewAverage",
|
|
69
|
+
"-reviewAverage",
|
|
70
|
+
"+itemPrice",
|
|
71
|
+
"-itemPrice",
|
|
72
|
+
"+updateTimestamp",
|
|
73
|
+
"-updateTimestamp"
|
|
74
|
+
]).default("standard").describe("Sort order (prefix + for ascending, - for descending)"),
|
|
75
|
+
minPrice: z.number().optional().describe("Minimum price in yen"),
|
|
76
|
+
maxPrice: z.number().optional().describe("Maximum price in yen")
|
|
77
|
+
},
|
|
78
|
+
async ({ keyword, hits, page, sort, minPrice, maxPrice }) => {
|
|
79
|
+
const params = {
|
|
80
|
+
keyword,
|
|
81
|
+
hits: String(hits),
|
|
82
|
+
page: String(page),
|
|
83
|
+
sort
|
|
84
|
+
};
|
|
85
|
+
if (minPrice !== void 0) params.minPrice = String(minPrice);
|
|
86
|
+
if (maxPrice !== void 0) params.maxPrice = String(maxPrice);
|
|
87
|
+
const data = await rakutenRequest(
|
|
88
|
+
"/IchibaItem/Search/20220601",
|
|
89
|
+
params
|
|
90
|
+
);
|
|
91
|
+
const items = data.Items?.map((i) => ({
|
|
92
|
+
name: i.Item.itemName,
|
|
93
|
+
price: i.Item.itemPrice,
|
|
94
|
+
url: i.Item.itemUrl,
|
|
95
|
+
shop: i.Item.shopName,
|
|
96
|
+
reviewAverage: i.Item.reviewAverage,
|
|
97
|
+
reviewCount: i.Item.reviewCount,
|
|
98
|
+
imageUrl: i.Item.mediumImageUrls?.[0]?.imageUrl
|
|
99
|
+
})) ?? [];
|
|
100
|
+
return {
|
|
101
|
+
content: [
|
|
102
|
+
{
|
|
103
|
+
type: "text",
|
|
104
|
+
text: JSON.stringify(
|
|
105
|
+
{ totalCount: data.count, items },
|
|
106
|
+
null,
|
|
107
|
+
2
|
|
108
|
+
)
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
server.tool(
|
|
115
|
+
"get_genre_ranking",
|
|
116
|
+
"Get the Rakuten Ichiba ranking (bestsellers) \u2014 overall or by genre",
|
|
117
|
+
{
|
|
118
|
+
genreId: z.string().default("0").describe("Genre ID (0 for overall ranking)")
|
|
119
|
+
},
|
|
120
|
+
async ({ genreId }) => {
|
|
121
|
+
const data = await rakutenRequest("/IchibaItem/Ranking/20220601", {
|
|
122
|
+
genreId
|
|
123
|
+
});
|
|
124
|
+
const items = data.Items?.map((i) => ({
|
|
125
|
+
rank: i.Item.rank,
|
|
126
|
+
name: i.Item.itemName,
|
|
127
|
+
price: i.Item.itemPrice,
|
|
128
|
+
url: i.Item.itemUrl,
|
|
129
|
+
shop: i.Item.shopName
|
|
130
|
+
})) ?? [];
|
|
131
|
+
return {
|
|
132
|
+
content: [{ type: "text", text: JSON.stringify(items, null, 2) }]
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
);
|
|
136
|
+
server.tool(
|
|
137
|
+
"search_genres",
|
|
138
|
+
"Browse Rakuten Ichiba product categories/genres",
|
|
139
|
+
{
|
|
140
|
+
genreId: z.string().default("0").describe("Parent genre ID (0 for top-level)")
|
|
141
|
+
},
|
|
142
|
+
async ({ genreId }) => {
|
|
143
|
+
const data = await rakutenRequest("/IchibaGenre/Search/20140222", {
|
|
144
|
+
genreId
|
|
145
|
+
});
|
|
146
|
+
return {
|
|
147
|
+
content: [
|
|
148
|
+
{
|
|
149
|
+
type: "text",
|
|
150
|
+
text: JSON.stringify(
|
|
151
|
+
{
|
|
152
|
+
current: data.current,
|
|
153
|
+
children: data.children?.map((c) => c.child)
|
|
154
|
+
},
|
|
155
|
+
null,
|
|
156
|
+
2
|
|
157
|
+
)
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
);
|
|
163
|
+
server.tool(
|
|
164
|
+
"search_books",
|
|
165
|
+
"Search for books on Rakuten Books by title, author, or ISBN. For general keyword searches across all book categories, use keyword (routes to BooksTotal).",
|
|
166
|
+
{
|
|
167
|
+
title: z.string().optional().describe("Book title"),
|
|
168
|
+
author: z.string().optional().describe("Author name"),
|
|
169
|
+
isbn: z.string().optional().describe("ISBN code"),
|
|
170
|
+
keyword: z.string().optional().describe("General keyword (uses cross-category search)"),
|
|
171
|
+
hits: z.number().min(1).max(30).default(10).describe("Number of results")
|
|
172
|
+
},
|
|
173
|
+
async ({ title, author, isbn, keyword, hits }) => {
|
|
174
|
+
if (!title && !author && !isbn && !keyword) {
|
|
175
|
+
return {
|
|
176
|
+
content: [
|
|
177
|
+
{
|
|
178
|
+
type: "text",
|
|
179
|
+
text: "Error: At least one search field is required (title, author, isbn, or keyword)."
|
|
180
|
+
}
|
|
181
|
+
]
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
const params = { hits: String(hits) };
|
|
185
|
+
const useTotal = !title && !author && !isbn && !!keyword;
|
|
186
|
+
if (useTotal) {
|
|
187
|
+
params.keyword = keyword;
|
|
188
|
+
} else {
|
|
189
|
+
if (title) params.title = title;
|
|
190
|
+
if (author) params.author = author;
|
|
191
|
+
if (isbn) params.isbn = isbn;
|
|
192
|
+
}
|
|
193
|
+
const endpoint = useTotal ? "/BooksTotal/Search/20170404" : "/BooksBook/Search/20170404";
|
|
194
|
+
const data = await rakutenRequest(endpoint, params);
|
|
195
|
+
const items = data.Items?.map((i) => ({
|
|
196
|
+
title: i.Item.title,
|
|
197
|
+
author: i.Item.author,
|
|
198
|
+
publisher: i.Item.publisherName,
|
|
199
|
+
price: i.Item.itemPrice,
|
|
200
|
+
isbn: i.Item.isbn,
|
|
201
|
+
url: i.Item.itemUrl,
|
|
202
|
+
imageUrl: i.Item.largeImageUrl,
|
|
203
|
+
salesDate: i.Item.salesDate,
|
|
204
|
+
reviewAverage: i.Item.reviewAverage
|
|
205
|
+
})) ?? [];
|
|
206
|
+
return {
|
|
207
|
+
content: [{ type: "text", text: JSON.stringify(items, null, 2) }]
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
);
|
|
211
|
+
server.tool(
|
|
212
|
+
"search_travel",
|
|
213
|
+
"Search for hotels on Rakuten Travel by keyword. For availability/date/price search, use search_travel_vacancy instead.",
|
|
214
|
+
{
|
|
215
|
+
keyword: z.string().describe("Search keyword (e.g., hotel name, area)"),
|
|
216
|
+
hits: z.number().min(1).max(30).default(10).describe("Number of results"),
|
|
217
|
+
page: z.number().min(1).default(1).describe("Page number")
|
|
218
|
+
},
|
|
219
|
+
async ({ keyword, hits, page }) => {
|
|
220
|
+
const data = await rakutenRequest(
|
|
221
|
+
"/Travel/KeywordHotelSearch/20170426",
|
|
222
|
+
{ keyword, hits: String(hits), page: String(page) },
|
|
223
|
+
RAKUTEN_TRAVEL_API_BASE
|
|
224
|
+
);
|
|
225
|
+
const hotels = data.hotels?.map((h) => {
|
|
226
|
+
const info = h.hotel[0]?.hotelBasicInfo ?? {};
|
|
227
|
+
return {
|
|
228
|
+
name: info.hotelName,
|
|
229
|
+
address: `${info.address1 ?? ""}${info.address2 ?? ""}`,
|
|
230
|
+
price: info.hotelMinCharge,
|
|
231
|
+
rating: info.reviewAverage,
|
|
232
|
+
url: info.hotelInformationUrl,
|
|
233
|
+
imageUrl: info.hotelImageUrl
|
|
234
|
+
};
|
|
235
|
+
}) ?? [];
|
|
236
|
+
return {
|
|
237
|
+
content: [{ type: "text", text: JSON.stringify(hotels, null, 2) }]
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
);
|
|
241
|
+
server.tool(
|
|
242
|
+
"search_travel_vacancy",
|
|
243
|
+
"Search for available hotel rooms on Rakuten Travel by location, date, and price. Requires coordinates (lat/lng) or a hotel number for location.",
|
|
244
|
+
{
|
|
245
|
+
checkinDate: z.string().describe("Check-in date (YYYY-MM-DD)"),
|
|
246
|
+
checkoutDate: z.string().describe("Check-out date (YYYY-MM-DD)"),
|
|
247
|
+
latitude: z.number().optional().describe("Latitude (WGS84 decimal degrees, e.g., 35.6812)"),
|
|
248
|
+
longitude: z.number().optional().describe("Longitude (WGS84 decimal degrees, e.g., 139.7671)"),
|
|
249
|
+
searchRadius: z.number().min(0.1).max(3).optional().describe("Search radius in km (0.1-3, requires lat/lng)"),
|
|
250
|
+
hotelNo: z.number().optional().describe("Specific Rakuten hotel number (alternative to coordinates)"),
|
|
251
|
+
maxCharge: z.number().optional().describe("Maximum price per night in yen"),
|
|
252
|
+
adultNum: z.number().min(1).max(10).default(1).describe("Number of adults (1-10)"),
|
|
253
|
+
hits: z.number().min(1).max(30).default(10).describe("Number of results")
|
|
254
|
+
},
|
|
255
|
+
async ({ checkinDate, checkoutDate, latitude, longitude, searchRadius, hotelNo, maxCharge, adultNum, hits }) => {
|
|
256
|
+
const hasCoords = latitude !== void 0 && longitude !== void 0;
|
|
257
|
+
if (!hasCoords && hotelNo === void 0) {
|
|
258
|
+
return {
|
|
259
|
+
content: [
|
|
260
|
+
{
|
|
261
|
+
type: "text",
|
|
262
|
+
text: "Error: A location is required. Provide either latitude+longitude or hotelNo."
|
|
263
|
+
}
|
|
264
|
+
]
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
if (hasCoords && hotelNo !== void 0) {
|
|
268
|
+
return {
|
|
269
|
+
content: [
|
|
270
|
+
{
|
|
271
|
+
type: "text",
|
|
272
|
+
text: "Error: Provide either latitude+longitude or hotelNo, not both."
|
|
273
|
+
}
|
|
274
|
+
]
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
const params = {
|
|
278
|
+
checkinDate,
|
|
279
|
+
checkoutDate,
|
|
280
|
+
adultNum: String(adultNum),
|
|
281
|
+
hits: String(hits)
|
|
282
|
+
};
|
|
283
|
+
if (hasCoords) {
|
|
284
|
+
params.latitude = String(latitude);
|
|
285
|
+
params.longitude = String(longitude);
|
|
286
|
+
params.datumType = "1";
|
|
287
|
+
}
|
|
288
|
+
if (hasCoords && searchRadius !== void 0) params.searchRadius = String(searchRadius);
|
|
289
|
+
if (hotelNo !== void 0) params.hotelNo = String(hotelNo);
|
|
290
|
+
if (maxCharge !== void 0) params.maxCharge = String(maxCharge);
|
|
291
|
+
const data = await rakutenRequest(
|
|
292
|
+
"/Travel/VacantHotelSearch/20170426",
|
|
293
|
+
params,
|
|
294
|
+
RAKUTEN_TRAVEL_API_BASE
|
|
295
|
+
);
|
|
296
|
+
const hotels = data.hotels?.map((h) => {
|
|
297
|
+
const basic = h.hotel.find((entry) => entry.hotelBasicInfo)?.hotelBasicInfo ?? {};
|
|
298
|
+
const room = h.hotel.find((entry) => entry.roomInfo)?.roomInfo?.[0];
|
|
299
|
+
return {
|
|
300
|
+
name: basic.hotelName,
|
|
301
|
+
address: `${basic.address1 ?? ""}${basic.address2 ?? ""}`,
|
|
302
|
+
price: room?.dailyCharge?.total ?? basic.hotelMinCharge,
|
|
303
|
+
rating: basic.reviewAverage,
|
|
304
|
+
url: basic.hotelInformationUrl,
|
|
305
|
+
imageUrl: basic.hotelImageUrl,
|
|
306
|
+
roomName: room?.roomBasicInfo?.roomName
|
|
307
|
+
};
|
|
308
|
+
}) ?? [];
|
|
309
|
+
return {
|
|
310
|
+
content: [{ type: "text", text: JSON.stringify(hotels, null, 2) }]
|
|
311
|
+
};
|
|
312
|
+
}
|
|
313
|
+
);
|
|
314
|
+
server.tool(
|
|
315
|
+
"get_product_reviews",
|
|
316
|
+
"Get reviews for a specific Rakuten product",
|
|
317
|
+
{
|
|
318
|
+
itemCode: z.string().describe("Rakuten item code (shop:itemId format)"),
|
|
319
|
+
hits: z.number().min(1).max(30).default(10).describe("Number of reviews"),
|
|
320
|
+
sort: z.enum(["+reviewDate", "-reviewDate", "+reviewPoint", "-reviewPoint"]).default("-reviewDate").describe("Sort order")
|
|
321
|
+
},
|
|
322
|
+
async ({ itemCode, hits, sort }) => {
|
|
323
|
+
const data = await rakutenRequest(
|
|
324
|
+
"/IchibaItem/Review/20220601",
|
|
325
|
+
{
|
|
326
|
+
itemCode,
|
|
327
|
+
hits: String(hits),
|
|
328
|
+
sort
|
|
329
|
+
}
|
|
330
|
+
);
|
|
331
|
+
const reviews = data.reviews?.map((r) => ({
|
|
332
|
+
rating: r.review.reviewPoint,
|
|
333
|
+
title: r.review.reviewTitle,
|
|
334
|
+
comment: r.review.reviewComment,
|
|
335
|
+
date: r.review.reviewDate
|
|
336
|
+
})) ?? [];
|
|
337
|
+
return {
|
|
338
|
+
content: [{ type: "text", text: JSON.stringify(reviews, null, 2) }]
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
);
|
|
342
|
+
server.prompt(
|
|
343
|
+
"search_products",
|
|
344
|
+
"Search for products on Rakuten Ichiba with optional price filters",
|
|
345
|
+
{
|
|
346
|
+
query: z.string().describe("What to search for"),
|
|
347
|
+
maxPrice: z.string().optional().describe("Maximum price in yen")
|
|
348
|
+
},
|
|
349
|
+
({ query, maxPrice }) => ({
|
|
350
|
+
messages: [
|
|
351
|
+
{
|
|
352
|
+
role: "user",
|
|
353
|
+
content: {
|
|
354
|
+
type: "text",
|
|
355
|
+
text: maxPrice ? `Search Rakuten for "${query}" under \xA5${maxPrice}` : `Search Rakuten for "${query}"`
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
]
|
|
359
|
+
})
|
|
360
|
+
);
|
|
361
|
+
server.prompt(
|
|
362
|
+
"compare_products",
|
|
363
|
+
"Compare products on Rakuten by searching and sorting by reviews or price",
|
|
364
|
+
{
|
|
365
|
+
query: z.string().describe("Product type to compare"),
|
|
366
|
+
sortBy: z.enum(["reviews", "price_low", "price_high"]).describe("How to sort results")
|
|
367
|
+
},
|
|
368
|
+
({ query, sortBy }) => {
|
|
369
|
+
const sortMap = { reviews: "-reviewCount", price_low: "+itemPrice", price_high: "-itemPrice" };
|
|
370
|
+
return {
|
|
371
|
+
messages: [
|
|
372
|
+
{
|
|
373
|
+
role: "user",
|
|
374
|
+
content: {
|
|
375
|
+
type: "text",
|
|
376
|
+
text: `Search Rakuten for "${query}" sorted by ${sortBy === "reviews" ? "most reviews" : sortBy === "price_low" ? "lowest price" : "highest price"} and compare the top results`
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
]
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
);
|
|
383
|
+
server.prompt(
|
|
384
|
+
"category_bestsellers",
|
|
385
|
+
"Get the current bestseller ranking for a Rakuten product category",
|
|
386
|
+
{
|
|
387
|
+
category: z.string().describe("Product category (e.g., electronics, fashion, food)")
|
|
388
|
+
},
|
|
389
|
+
({ category }) => ({
|
|
390
|
+
messages: [
|
|
391
|
+
{
|
|
392
|
+
role: "user",
|
|
393
|
+
content: {
|
|
394
|
+
type: "text",
|
|
395
|
+
text: `Show me the current Rakuten bestseller ranking for ${category}`
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
]
|
|
399
|
+
})
|
|
400
|
+
);
|
|
401
|
+
server.prompt(
|
|
402
|
+
"product_reviews",
|
|
403
|
+
"Read reviews for a specific Rakuten product",
|
|
404
|
+
{
|
|
405
|
+
itemCode: z.string().describe("Rakuten item code (shop:itemId format)")
|
|
406
|
+
},
|
|
407
|
+
({ itemCode }) => ({
|
|
408
|
+
messages: [
|
|
409
|
+
{
|
|
410
|
+
role: "user",
|
|
411
|
+
content: {
|
|
412
|
+
type: "text",
|
|
413
|
+
text: `Get reviews for Rakuten product ${itemCode} and summarize the overall sentiment`
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
]
|
|
417
|
+
})
|
|
418
|
+
);
|
|
419
|
+
server.prompt(
|
|
420
|
+
"find_hotel",
|
|
421
|
+
"Find available hotels on Rakuten Travel for specific dates",
|
|
422
|
+
{
|
|
423
|
+
location: z.string().describe("City or area name"),
|
|
424
|
+
checkin: z.string().describe("Check-in date (YYYY-MM-DD)"),
|
|
425
|
+
checkout: z.string().describe("Check-out date (YYYY-MM-DD)")
|
|
426
|
+
},
|
|
427
|
+
({ location, checkin, checkout }) => ({
|
|
428
|
+
messages: [
|
|
429
|
+
{
|
|
430
|
+
role: "user",
|
|
431
|
+
content: {
|
|
432
|
+
type: "text",
|
|
433
|
+
text: `Find available hotels in ${location} on Rakuten Travel from ${checkin} to ${checkout}`
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
]
|
|
437
|
+
})
|
|
438
|
+
);
|
|
439
|
+
server.prompt(
|
|
440
|
+
"budget_hotel",
|
|
441
|
+
"Find cheap hotels on Rakuten Travel within a budget",
|
|
442
|
+
{
|
|
443
|
+
location: z.string().describe("City or area name"),
|
|
444
|
+
checkin: z.string().describe("Check-in date (YYYY-MM-DD)"),
|
|
445
|
+
checkout: z.string().describe("Check-out date (YYYY-MM-DD)"),
|
|
446
|
+
maxPrice: z.string().describe("Maximum price per night in yen")
|
|
447
|
+
},
|
|
448
|
+
({ location, checkin, checkout, maxPrice }) => ({
|
|
449
|
+
messages: [
|
|
450
|
+
{
|
|
451
|
+
role: "user",
|
|
452
|
+
content: {
|
|
453
|
+
type: "text",
|
|
454
|
+
text: `Find hotels in ${location} on Rakuten Travel from ${checkin} to ${checkout} under \xA5${maxPrice} per night`
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
]
|
|
458
|
+
})
|
|
459
|
+
);
|
|
460
|
+
server.prompt(
|
|
461
|
+
"find_book",
|
|
462
|
+
"Search for a book on Rakuten Books",
|
|
463
|
+
{
|
|
464
|
+
query: z.string().describe("Book title, author, or ISBN")
|
|
465
|
+
},
|
|
466
|
+
({ query }) => ({
|
|
467
|
+
messages: [
|
|
468
|
+
{
|
|
469
|
+
role: "user",
|
|
470
|
+
content: {
|
|
471
|
+
type: "text",
|
|
472
|
+
text: `Search Rakuten Books for "${query}"`
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
]
|
|
476
|
+
})
|
|
477
|
+
);
|
|
478
|
+
server.prompt(
|
|
479
|
+
"books_by_author",
|
|
480
|
+
"Find all books by a specific author on Rakuten Books",
|
|
481
|
+
{
|
|
482
|
+
author: z.string().describe("Author name")
|
|
483
|
+
},
|
|
484
|
+
({ author }) => ({
|
|
485
|
+
messages: [
|
|
486
|
+
{
|
|
487
|
+
role: "user",
|
|
488
|
+
content: {
|
|
489
|
+
type: "text",
|
|
490
|
+
text: `Find all books by ${author} on Rakuten Books`
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
]
|
|
494
|
+
})
|
|
495
|
+
);
|
|
496
|
+
server.resource(
|
|
497
|
+
"supported-genres",
|
|
498
|
+
"rakuten://genres",
|
|
499
|
+
{ description: "Top-level Rakuten Ichiba product categories", mimeType: "application/json" },
|
|
500
|
+
async () => ({
|
|
501
|
+
contents: [
|
|
502
|
+
{
|
|
503
|
+
uri: "rakuten://genres",
|
|
504
|
+
mimeType: "application/json",
|
|
505
|
+
text: JSON.stringify({
|
|
506
|
+
note: "Use search_genres tool with genreId '0' to get the full live category tree. Common top-level genres:",
|
|
507
|
+
genres: [
|
|
508
|
+
{ id: "100371", name: "\u30D1\u30BD\u30B3\u30F3\u30FB\u5468\u8FBA\u6A5F\u5668 (Computers)" },
|
|
509
|
+
{ id: "100026", name: "\u672C\u30FB\u96D1\u8A8C\u30FB\u30B3\u30DF\u30C3\u30AF (Books)" },
|
|
510
|
+
{ id: "100227", name: "\u98DF\u54C1 (Food)" },
|
|
511
|
+
{ id: "558885", name: "\u5BB6\u96FB (Electronics)" },
|
|
512
|
+
{ id: "100433", name: "\u30D5\u30A1\u30C3\u30B7\u30E7\u30F3 (Fashion)" },
|
|
513
|
+
{ id: "101070", name: "\u30A4\u30F3\u30C6\u30EA\u30A2\u30FB\u5BDD\u5177 (Home & Living)" },
|
|
514
|
+
{ id: "100533", name: "\u30B9\u30DD\u30FC\u30C4\u30FB\u30A2\u30A6\u30C8\u30C9\u30A2 (Sports)" }
|
|
515
|
+
]
|
|
516
|
+
}, null, 2)
|
|
517
|
+
}
|
|
518
|
+
]
|
|
519
|
+
})
|
|
520
|
+
);
|
|
521
|
+
async function main() {
|
|
522
|
+
const transport = new StdioServerTransport();
|
|
523
|
+
await server.connect(transport);
|
|
524
|
+
console.error("Rakuten MCP server running on stdio");
|
|
525
|
+
}
|
|
526
|
+
main().catch((err) => {
|
|
527
|
+
console.error("Fatal error:", err);
|
|
528
|
+
process.exit(1);
|
|
529
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "rakuten-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "MCP server for Rakuten APIs — search products, books, hotels, and rankings through AI assistants",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"rakuten-mcp": "./dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": ["dist"],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18.0.0"
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsup --config ../../tsup.config.js",
|
|
16
|
+
"start": "node dist/index.js",
|
|
17
|
+
"dev": "tsx src/index.ts"
|
|
18
|
+
},
|
|
19
|
+
"keywords": ["mcp", "rakuten", "japan", "ecommerce", "ai-agents"],
|
|
20
|
+
"author": "Marsel Bait",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"repository": {
|
|
23
|
+
"type": "git",
|
|
24
|
+
"url": "https://github.com/mrslbt/japan-mcp-servers",
|
|
25
|
+
"directory": "servers/rakuten-mcp"
|
|
26
|
+
},
|
|
27
|
+
"dependencies": {
|
|
28
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
29
|
+
"zod": "^3.25.0"
|
|
30
|
+
},
|
|
31
|
+
"devDependencies": {
|
|
32
|
+
"typescript": "^5.7.0",
|
|
33
|
+
"tsx": "^4.0.0",
|
|
34
|
+
"@types/node": "^22.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|