fetch_reviews
Retrieve user reviews from Google Play Store or Apple App Store for app analysis and feedback monitoring.
Input Schema
TableJSON Schema
| Name | Required | Description | Default |
|---|---|---|---|
| appId | Yes | The unique identifier for the app (Android package name, iOS numeric ID or bundle ID). | |
| platform | Yes | The platform of the app ('ios' or 'android'). | |
| num | No | Number of reviews to fetch (1-1000, default 100). Note: Actual number may be less due to API limitations. For iOS, limited to 10 pages max. | |
| country | No | Two-letter country code for the App Store/Play Store region. Default 'us'. | us |
| lang | No | Language code for reviews. Default 'en'. | en |
| sort | No | Sorting order for reviews: 'newest', 'rating', 'helpfulness'. Default 'newest'. | newest |
Implementation Reference
- server.js:806-975 (registration)Registration of the 'fetch_reviews' MCP tool using server.tool(), including input schema and handler function.// Tool to fetch raw reviews without analysis server.tool( "fetch_reviews", { appId: z.string().describe("The unique identifier for the app (Android package name, iOS numeric ID or bundle ID)."), platform: z.enum(["ios", "android"]).describe("The platform of the app ('ios' or 'android')."), num: z.number().optional().default(100).describe("Number of reviews to fetch (1-1000, default 100). Note: Actual number may be less due to API limitations. For iOS, limited to 10 pages max."), country: z.string().length(2).optional().default("us").describe("Two-letter country code for the App Store/Play Store region. Default 'us'."), lang: z.string().optional().default("en").describe("Language code for reviews. Default 'en'."), sort: z.enum(["newest", "rating", "helpfulness"]).optional().default("newest").describe("Sorting order for reviews: 'newest', 'rating', 'helpfulness'. Default 'newest'.") }, async ({ appId, platform, num, country, lang, sort }) => { try { let reviews = []; // Fetch reviews from the appropriate platform if (platform === "android") { let sortType; switch (sort) { case "newest": sortType = gplay.sort.NEWEST; break; case "rating": sortType = gplay.sort.RATING; break; case "helpfulness": sortType = gplay.sort.HELPFULNESS; break; default: sortType = gplay.sort.NEWEST; } const result = await memoizedGplay.reviews({ appId, num: Math.min(num, 1000), // Limit to 1000 reviews max sort: sortType, country, lang }); reviews = result.data || []; // Android reviews have developer replies included if available return { content: [{ type: "text", text: JSON.stringify({ appId, platform, count: reviews.length, reviews: reviews.map(review => ({ id: review.id, userName: review.userName, userImage: review.userImage, score: review.score, scoreText: review.scoreText, title: review.title || "", text: review.text, date: review.date, url: review.url, version: review.version, thumbsUp: review.thumbsUp, replyDate: review.replyDate, replyText: review.replyText, hasDeveloperResponse: !!review.replyText })) }, null, 2) }] }; } else { let page = 1; let allReviews = []; let sortType; switch (sort) { case "newest": sortType = appStore.sort.RECENT; break; case "helpfulness": sortType = appStore.sort.HELPFUL; break; default: sortType = appStore.sort.RECENT; } // For iOS, we might need to fetch multiple pages while (allReviews.length < num && page <= 10) { // App Store only allows 10 pages try { // For iOS apps, we need to use id instead of appId let iosParams = {}; // Check if the appId is already a numeric ID if (/^\d+$/.test(appId)) { iosParams = { id: appId, page, sort: sortType, country }; } else { // First we need to fetch the app to get its numeric ID try { const appDetails = await memoizedAppStore.app({ appId, country }); iosParams = { id: appDetails.id.toString(), page, sort: sortType, country }; } catch (appError) { console.error(`Could not fetch app details for ${appId}:`, appError.message); break; } } const pageReviews = await memoizedAppStore.reviews(iosParams); if (!pageReviews || pageReviews.length === 0) { break; // No more reviews } allReviews = [...allReviews, ...pageReviews]; page++; } catch (err) { console.error(`Error fetching reviews page ${page}:`, err); break; } } reviews = allReviews.slice(0, num); // iOS reviews don't include developer responses in the API return { content: [{ type: "text", text: JSON.stringify({ appId, platform, count: reviews.length, reviews: reviews.map(review => ({ id: review.id, userName: review.userName, userUrl: review.userUrl, score: review.score, title: review.title, text: review.text, date: review.updated, version: review.version, url: review.url, hasDeveloperResponse: false // App Store API doesn't provide this info })) }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ error: error.message, appId, platform }, null, 2) }], isError: true }; } } );
- server.js:817-975 (handler)The core handler function that fetches raw app reviews from either Google Play Store (using google-play-scraper) or Apple App Store (using app-store-scraper), supporting pagination for iOS and including developer responses where available.async ({ appId, platform, num, country, lang, sort }) => { try { let reviews = []; // Fetch reviews from the appropriate platform if (platform === "android") { let sortType; switch (sort) { case "newest": sortType = gplay.sort.NEWEST; break; case "rating": sortType = gplay.sort.RATING; break; case "helpfulness": sortType = gplay.sort.HELPFULNESS; break; default: sortType = gplay.sort.NEWEST; } const result = await memoizedGplay.reviews({ appId, num: Math.min(num, 1000), // Limit to 1000 reviews max sort: sortType, country, lang }); reviews = result.data || []; // Android reviews have developer replies included if available return { content: [{ type: "text", text: JSON.stringify({ appId, platform, count: reviews.length, reviews: reviews.map(review => ({ id: review.id, userName: review.userName, userImage: review.userImage, score: review.score, scoreText: review.scoreText, title: review.title || "", text: review.text, date: review.date, url: review.url, version: review.version, thumbsUp: review.thumbsUp, replyDate: review.replyDate, replyText: review.replyText, hasDeveloperResponse: !!review.replyText })) }, null, 2) }] }; } else { let page = 1; let allReviews = []; let sortType; switch (sort) { case "newest": sortType = appStore.sort.RECENT; break; case "helpfulness": sortType = appStore.sort.HELPFUL; break; default: sortType = appStore.sort.RECENT; } // For iOS, we might need to fetch multiple pages while (allReviews.length < num && page <= 10) { // App Store only allows 10 pages try { // For iOS apps, we need to use id instead of appId let iosParams = {}; // Check if the appId is already a numeric ID if (/^\d+$/.test(appId)) { iosParams = { id: appId, page, sort: sortType, country }; } else { // First we need to fetch the app to get its numeric ID try { const appDetails = await memoizedAppStore.app({ appId, country }); iosParams = { id: appDetails.id.toString(), page, sort: sortType, country }; } catch (appError) { console.error(`Could not fetch app details for ${appId}:`, appError.message); break; } } const pageReviews = await memoizedAppStore.reviews(iosParams); if (!pageReviews || pageReviews.length === 0) { break; // No more reviews } allReviews = [...allReviews, ...pageReviews]; page++; } catch (err) { console.error(`Error fetching reviews page ${page}:`, err); break; } } reviews = allReviews.slice(0, num); // iOS reviews don't include developer responses in the API return { content: [{ type: "text", text: JSON.stringify({ appId, platform, count: reviews.length, reviews: reviews.map(review => ({ id: review.id, userName: review.userName, userUrl: review.userUrl, score: review.score, title: review.title, text: review.text, date: review.updated, version: review.version, url: review.url, hasDeveloperResponse: false // App Store API doesn't provide this info })) }, null, 2) }] }; } } catch (error) { return { content: [{ type: "text", text: JSON.stringify({ error: error.message, appId, platform }, null, 2) }], isError: true }; } } );
- server.js:809-816 (schema)Zod input schema defining parameters for the fetch_reviews tool: appId (required), platform (ios/android), num reviews, country, lang, and sort order.{ appId: z.string().describe("The unique identifier for the app (Android package name, iOS numeric ID or bundle ID)."), platform: z.enum(["ios", "android"]).describe("The platform of the app ('ios' or 'android')."), num: z.number().optional().default(100).describe("Number of reviews to fetch (1-1000, default 100). Note: Actual number may be less due to API limitations. For iOS, limited to 10 pages max."), country: z.string().length(2).optional().default("us").describe("Two-letter country code for the App Store/Play Store region. Default 'us'."), lang: z.string().optional().default("en").describe("Language code for reviews. Default 'en'."), sort: z.enum(["newest", "rating", "helpfulness"]).optional().default("newest").describe("Sorting order for reviews: 'newest', 'rating', 'helpfulness'. Default 'newest'.") },