Skip to main content
Glama
gavxm
by gavxm

anilist_watch_order

Read-only

Find the correct watch order for any anime franchise. Enter a title or ID to get a chronological list of sequels, prequels, and specials from first to last.

Instructions

Suggested viewing order for a franchise. Use when the user asks what order to watch a series, how to start a long franchise, or wants to know the chronological release order of sequels and prequels. Accepts any title in the franchise and traces the full chain. Returns a numbered list from first to last.

Input Schema

TableJSON Schema
NameRequiredDescriptionDefault
idNoAniList media ID of any title in the franchise
titleNoSearch by title if no ID is known
includeSpecialsNoInclude OVAs, specials, and spin-offs in the watch order

Implementation Reference

  • Registration of the anilist_watch_order tool via server.addTool(), including name, description, schema, annotations, and execute handler.
    server.addTool({
      name: "anilist_watch_order",
      description:
        "Suggested viewing order for a franchise. " +
        "Use when the user asks what order to watch a series, how to start a long franchise, " +
        "or wants to know the chronological release order of sequels and prequels. " +
        "Accepts any title in the franchise and traces the full chain. " +
        "Returns a numbered list from first to last.",
      parameters: WatchOrderInputSchema,
      annotations: {
        title: "Watch Order",
        readOnlyHint: true,
        destructiveHint: false,
        openWorldHint: true,
      },
      execute: async (args) => {
        try {
          // Resolve title to ID
          let mediaId: number;
          if (args.id) {
            mediaId = args.id;
          } else {
            const data = await anilistClient.query<MediaDetailsResponse>(
              MEDIA_DETAILS_QUERY,
              { search: args.title, type: "ANIME" },
              { cache: "media" },
            );
            mediaId = data.Media.id;
          }
    
          // BFS expansion: discover all franchise IDs via batch relation queries
          const relationsMap = new Map<number, RelationNode>();
          let frontier = [mediaId];
          const maxRounds = 5;
    
          for (let round = 0; round < maxRounds && frontier.length > 0; round++) {
            const data = await anilistClient.query<BatchRelationsResponse>(
              BATCH_RELATIONS_QUERY,
              { ids: frontier },
              { cache: "media" },
            );
    
            const nextFrontier: number[] = [];
    
            for (const media of data.Page.media) {
              if (relationsMap.has(media.id)) continue;
              relationsMap.set(media.id, media);
    
              // Only follow anime relations to stay within the anime franchise
              for (const edge of media.relations.edges) {
                if (
                  !relationsMap.has(edge.node.id) &&
                  edge.node.type === "ANIME"
                ) {
                  nextFrontier.push(edge.node.id);
                }
              }
            }
    
            frontier = nextFrontier;
          }
    
          if (relationsMap.size === 0) {
            return "Could not find franchise relations for this title.";
          }
    
          const { entries, truncated } = buildWatchOrder(
            mediaId,
            relationsMap,
            args.includeSpecials,
          );
    
          if (entries.length === 0) {
            return "No entries found in the watch order. This title may be standalone.";
          }
    
          // Get the franchise name from the root entry
          const rootTitle = entries[0].title;
          const lines: string[] = [
            `# Watch Order: ${rootTitle} franchise`,
            `${entries.length} entr${entries.length !== 1 ? "ies" : "y"}${args.includeSpecials ? " (including specials)" : ""}:`,
            "",
          ];
    
          for (let i = 0; i < entries.length; i++) {
            const e = entries[i];
            const parts = [e.format ?? "Unknown format"];
            if (e.status) parts.push(e.status.replace(/_/g, " "));
            if (e.type === "special") parts.push("special");
    
            lines.push(`${i + 1}. ${e.title} (${parts.join(" - ")})`);
          }
    
          if (truncated) {
            lines.push(
              "",
              "Note: This franchise tree was truncated at the depth limit. Some entries may be missing.",
            );
          }
    
          return lines.join("\n");
        } catch (error) {
          return throwToolError(error, "building watch order");
        }
      },
    });
  • Execute handler for anilist_watch_order: resolves title to ID, discovers franchise via BFS using BATCH_RELATIONS_QUERY, then calls buildWatchOrder to produce the ordered list.
    execute: async (args) => {
      try {
        // Resolve title to ID
        let mediaId: number;
        if (args.id) {
          mediaId = args.id;
        } else {
          const data = await anilistClient.query<MediaDetailsResponse>(
            MEDIA_DETAILS_QUERY,
            { search: args.title, type: "ANIME" },
            { cache: "media" },
          );
          mediaId = data.Media.id;
        }
    
        // BFS expansion: discover all franchise IDs via batch relation queries
        const relationsMap = new Map<number, RelationNode>();
        let frontier = [mediaId];
        const maxRounds = 5;
    
        for (let round = 0; round < maxRounds && frontier.length > 0; round++) {
          const data = await anilistClient.query<BatchRelationsResponse>(
            BATCH_RELATIONS_QUERY,
            { ids: frontier },
            { cache: "media" },
          );
    
          const nextFrontier: number[] = [];
    
          for (const media of data.Page.media) {
            if (relationsMap.has(media.id)) continue;
            relationsMap.set(media.id, media);
    
            // Only follow anime relations to stay within the anime franchise
            for (const edge of media.relations.edges) {
              if (
                !relationsMap.has(edge.node.id) &&
                edge.node.type === "ANIME"
              ) {
                nextFrontier.push(edge.node.id);
              }
            }
          }
    
          frontier = nextFrontier;
        }
    
        if (relationsMap.size === 0) {
          return "Could not find franchise relations for this title.";
        }
    
        const { entries, truncated } = buildWatchOrder(
          mediaId,
          relationsMap,
          args.includeSpecials,
        );
    
        if (entries.length === 0) {
          return "No entries found in the watch order. This title may be standalone.";
        }
    
        // Get the franchise name from the root entry
        const rootTitle = entries[0].title;
        const lines: string[] = [
          `# Watch Order: ${rootTitle} franchise`,
          `${entries.length} entr${entries.length !== 1 ? "ies" : "y"}${args.includeSpecials ? " (including specials)" : ""}:`,
          "",
        ];
    
        for (let i = 0; i < entries.length; i++) {
          const e = entries[i];
          const parts = [e.format ?? "Unknown format"];
          if (e.status) parts.push(e.status.replace(/_/g, " "));
          if (e.type === "special") parts.push("special");
    
          lines.push(`${i + 1}. ${e.title} (${parts.join(" - ")})`);
        }
    
        if (truncated) {
          lines.push(
            "",
            "Note: This franchise tree was truncated at the depth limit. Some entries may be missing.",
          );
        }
    
        return lines.join("\n");
      } catch (error) {
        return throwToolError(error, "building watch order");
      }
    },
  • Zod schema for the anilist_watch_order tool input: optional id, optional title (at least one required), and optional includeSpecials flag.
    export const WatchOrderInputSchema = z
      .object({
        id: z
          .number()
          .int()
          .positive()
          .optional()
          .describe("AniList media ID of any title in the franchise"),
        title: z.string().optional().describe("Search by title if no ID is known"),
        includeSpecials: z
          .boolean()
          .default(false)
          .describe("Include OVAs, specials, and spin-offs in the watch order"),
      })
      .refine((data) => data.id !== undefined || data.title !== undefined, {
        message: "Provide either an id or a title.",
      });
  • buildWatchOrder function: constructs the watch order by traversing sequel and side-story edges from the franchise root, respecting MAX_DEPTH.
    export function buildWatchOrder(
      startId: number,
      relationsMap: Map<number, RelationNode>,
      includeSpecials: boolean,
    ): WatchOrderResult {
      const rootId = findRoot(startId, relationsMap);
      const entries: FranchiseEntry[] = [];
      const visited = new Set<number>();
    
      // BFS through sequel and side-story edges
      const queue: number[] = [rootId];
      let depth = 0;
    
      while (queue.length > 0 && depth < MAX_DEPTH) {
        const id = queue.shift();
        if (id === undefined || visited.has(id)) continue;
        visited.add(id);
        depth++;
    
        const node = relationsMap.get(id);
        const title = node
          ? (node.title.english ?? node.title.romaji ?? "Unknown")
          : "Unknown";
        const { format, status } = resolveNodeInfo(id, relationsMap);
        const isMain = MAIN_FORMATS.has(format ?? "");
    
        if (isMain || includeSpecials) {
          entries.push({
            id,
            title,
            format,
            status,
            type: isMain ? "main" : "special",
          });
        }
    
        if (!node) continue;
    
        // Collect sequels and side stories
        const sequels: number[] = [];
        const sides: number[] = [];
    
        for (const edge of node.relations.edges) {
          if (visited.has(edge.node.id)) continue;
          if (edge.relationType === "SEQUEL") {
            sequels.push(edge.node.id);
          } else if (
            includeSpecials &&
            (edge.relationType === "SIDE_STORY" || edge.relationType === "SPIN_OFF")
          ) {
            sides.push(edge.node.id);
          }
        }
    
        // Side stories appear after their parent, before the next sequel
        queue.push(...sides, ...sequels);
      }
    
      const truncated = queue.length > 0 && depth >= MAX_DEPTH;
      return { entries, truncated };
    }
  • BATCH_RELATIONS_QUERY: GraphQL query used to fetch media relations (sequels, prequels, side stories) for franchise discovery.
    export const BATCH_RELATIONS_QUERY = `
      query BatchRelations($ids: [Int]) {
        Page(perPage: 50) {
          media(id_in: $ids) {
            id
            title { romaji english }
            format
            status
            relations {
              edges {
                relationType
                node {
                  id
                  title { romaji english }
                  format
                  status
                  type
                  season
                  seasonYear
                }
              }
            }
          }
        }
      }
    `;
Behavior4/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations already indicate readOnlyHint=true and destructiveHint=false, so the safety profile is clear. The description adds value by stating that it traces the full franchise chain and returns a numbered list, which goes beyond the annotations.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness5/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The description is three sentences, each earning its place: the purpose, the usage context, and the behavior. It is front-loaded with the core function and contains no redundant or unnecessary words.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

Given no output schema, the description explains the return format as 'a numbered list from first to last.' It covers input options (id or title) and the includeSpecials parameter. It could mention error handling for unfound franchises, but it is adequate for a simple tool.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters3/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

The input schema has 100% coverage, so the baseline is 3. The description mentions 'Accepts any title in the franchise', which loosely relates to the id and title parameters, but does not add specific format or behavior details beyond the schema.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

The description starts with 'Suggested viewing order for a franchise', which is a specific verb+resource pair. It clearly distinguishes itself from siblings like 'anilist_sequels' by focusing on the entire franchise chain rather than individual sequels.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

The description explicitly states when to use: 'when the user asks what order to watch a series, how to start a long franchise, or wants to know the chronological release order of sequels and prequels.' It also notes that it accepts any title in the franchise and traces the full chain, providing clear context without needing exclusions.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Install Server

Other Tools

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/gavxm/ani-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server