anilist_reviews
Retrieve community reviews for anime or manga, including sentiment summaries, scores, and helpfulness ratios. Understand what others think about a title.
Instructions
Get community reviews for an anime or manga. Use when the user wants to see what others think about a title. Returns sentiment summary (positive/mixed/negative), individual review scores, summaries, and helpful ratios.
Input Schema
| Name | Required | Description | Default |
|---|---|---|---|
| id | No | AniList media ID | |
| title | No | Search by title if no ID is known | |
| type | No | Media type. Defaults to ANIME. | ANIME |
| sort | No | Sort by most helpful or newest | HELPFUL |
| limit | No | Number of reviews to return (default 5, max 10) | |
| page | No | Page number for pagination (default 1) |
Implementation Reference
- src/tools/social.ts:169-239 (handler)The execute handler for the 'anilist_reviews' tool. It queries the AniList API for reviews of a media title, computes a sentiment summary (positive/mixed/negative) based on average score, formats individual review details (score, summary, body excerpt, helpfulness ratio), and returns the result with pagination footer.
execute: async (args) => { try { const variables: Record<string, unknown> = { type: args.type, page: args.page, perPage: args.limit, sort: REVIEW_SORT_MAP[args.sort], }; if (args.id) variables.id = args.id; if (args.title) variables.search = args.title; const data = await anilistClient.query<MediaReviewsResponse>( MEDIA_REVIEWS_QUERY, variables, { cache: "media" }, ); const media = data.Media; const title = getTitle(media.title); const { nodes, pageInfo } = media.reviews; if (!nodes.length) { return `No reviews found for ${title}.`; } // Sentiment summary const avgScore = Math.round( nodes.reduce((sum, r) => sum + r.score, 0) / nodes.length, ); const sentiment = avgScore >= 75 ? "Generally positive" : avgScore >= 50 ? "Mixed" : "Generally negative"; const header = `Reviews for ${title} - ${sentiment} (avg ${avgScore}/100 across ${pageInfo.total} reviews)`; const formatted = nodes.map((r, i) => { const date = new Date(r.createdAt * 1000).toLocaleDateString( "en-US", { month: "short", day: "numeric", year: "numeric" }, ); const helpful = r.ratingAmount > 0 ? `${r.rating}/${r.ratingAmount} found helpful` : "No votes"; const body = truncateDescription(r.body, 300); return [ `${i + 1}. ${r.score}/100 by ${r.user.name} (${date})`, ` ${r.summary}`, ` ${body}`, ` ${helpful}`, ].join("\n"); }); const footer = paginationFooter( args.page, args.limit, pageInfo.total, pageInfo.hasNextPage, ); return ( [header, "", ...formatted].join("\n\n") + (footer ? `\n\n${footer}` : "") ); } catch (error) { return throwToolError(error, "fetching reviews"); } }, - src/schemas.ts:878-901 (schema)ReviewsInputSchema defines the input parameters for the anilist_reviews tool: id (optional), title (optional), type (ANIME/MANGA, default ANIME), sort (HELPFUL/NEWEST, default HELPFUL), limit (1-10, default 5), and page. It requires either id or title.
export const ReviewsInputSchema = z .object({ id: z.number().int().positive().optional().describe("AniList media ID"), title: z.string().optional().describe("Search by title if no ID is known"), type: z .enum(["ANIME", "MANGA"]) .default("ANIME") .describe("Media type. Defaults to ANIME."), sort: z .enum(["HELPFUL", "NEWEST"]) .default("HELPFUL") .describe("Sort by most helpful or newest"), limit: z .number() .int() .min(1) .max(10) .default(5) .describe("Number of reviews to return (default 5, max 10)"), page: pageParam, }) .refine((data) => data.id !== undefined || data.title !== undefined, { message: "Provide either an id or a title.", }); - src/tools/social.ts:155-240 (registration)The registration of the 'anilist_reviews' tool via server.addTool, including its name, description, parameters (ReviewsInputSchema), annotations, and the execute handler.
server.addTool({ name: "anilist_reviews", description: "Get community reviews for an anime or manga. " + "Use when the user wants to see what others think about a title. " + "Returns sentiment summary (positive/mixed/negative), individual review scores, summaries, and helpful ratios.", parameters: ReviewsInputSchema, annotations: { title: "Community Reviews", readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: true, }, execute: async (args) => { try { const variables: Record<string, unknown> = { type: args.type, page: args.page, perPage: args.limit, sort: REVIEW_SORT_MAP[args.sort], }; if (args.id) variables.id = args.id; if (args.title) variables.search = args.title; const data = await anilistClient.query<MediaReviewsResponse>( MEDIA_REVIEWS_QUERY, variables, { cache: "media" }, ); const media = data.Media; const title = getTitle(media.title); const { nodes, pageInfo } = media.reviews; if (!nodes.length) { return `No reviews found for ${title}.`; } // Sentiment summary const avgScore = Math.round( nodes.reduce((sum, r) => sum + r.score, 0) / nodes.length, ); const sentiment = avgScore >= 75 ? "Generally positive" : avgScore >= 50 ? "Mixed" : "Generally negative"; const header = `Reviews for ${title} - ${sentiment} (avg ${avgScore}/100 across ${pageInfo.total} reviews)`; const formatted = nodes.map((r, i) => { const date = new Date(r.createdAt * 1000).toLocaleDateString( "en-US", { month: "short", day: "numeric", year: "numeric" }, ); const helpful = r.ratingAmount > 0 ? `${r.rating}/${r.ratingAmount} found helpful` : "No votes"; const body = truncateDescription(r.body, 300); return [ `${i + 1}. ${r.score}/100 by ${r.user.name} (${date})`, ` ${r.summary}`, ` ${body}`, ` ${helpful}`, ].join("\n"); }); const footer = paginationFooter( args.page, args.limit, pageInfo.total, pageInfo.hasNextPage, ); return ( [header, "", ...formatted].join("\n\n") + (footer ? `\n\n${footer}` : "") ); } catch (error) { return throwToolError(error, "fetching reviews"); } }, }); - src/api/queries.ts:734-754 (helper)MEDIA_REVIEWS_QUERY is the GraphQL query used to fetch reviews from the AniList API. It queries Media by id/search/type and returns review nodes with id, score, summary, body, rating, ratingAmount, createdAt, and user info.
export const MEDIA_REVIEWS_QUERY = ` query MediaReviews($id: Int, $search: String, $type: MediaType, $page: Int, $perPage: Int, $sort: [ReviewSort]) { Media(id: $id, search: $search, type: $type) { id title { romaji english native } reviews(page: $page, perPage: $perPage, sort: $sort) { pageInfo { total hasNextPage } nodes { id score summary body rating ratingAmount createdAt user { name siteUrl } } } } } `; - src/types.ts:539-560 (helper)MediaReviewsResponse TypeScript interface defines the response shape from the AniList API for the MEDIA_REVIEWS_QUERY, used by the execute handler to type the API response.
export interface MediaReviewsResponse { Media: { id: number; title: { romaji: string | null; english: string | null; native: string | null; }; reviews: { pageInfo: { total: number; hasNextPage: boolean }; nodes: Array<{ id: number; score: number; summary: string; body: string; rating: number; ratingAmount: number; createdAt: number; user: { name: string; siteUrl: string }; }>; }; };