ani-mcp 0.6.0 → 0.6.2

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.
@@ -35,7 +35,7 @@ export function rankSimilar(source, candidates, recRatings) {
35
35
  const rating = recRatings.get(candidate.id) ?? 0;
36
36
  const recBoost = rating > 0 ? rating / maxRating : 0;
37
37
  if (rating > 0) {
38
- reasons.push(`Recommended by community (${rating > 0 ? "+" : ""}${rating})`);
38
+ reasons.push(`Recommended by community (+${rating})`);
39
39
  }
40
40
  const score = genreOverlap * GENRE_WEIGHT +
41
41
  tagOverlap * TAG_WEIGHT +
package/dist/index.js CHANGED
@@ -20,7 +20,7 @@ if (!process.env.ANILIST_TOKEN) {
20
20
  }
21
21
  const server = new FastMCP({
22
22
  name: "ani-mcp",
23
- version: "0.6.0",
23
+ version: "0.6.2",
24
24
  });
25
25
  registerSearchTools(server);
26
26
  registerListTools(server);
package/dist/resources.js CHANGED
@@ -14,9 +14,16 @@ export function registerResources(server) {
14
14
  description: "AniList profile with bio, anime/manga stats, and favourites.",
15
15
  mimeType: "text/plain",
16
16
  async load() {
17
- const username = getDefaultUsername();
18
- const data = await anilistClient.query(USER_PROFILE_QUERY, { name: username }, { cache: "stats" });
19
- return { text: formatProfile(data.User) };
17
+ try {
18
+ const username = getDefaultUsername();
19
+ const data = await anilistClient.query(USER_PROFILE_QUERY, { name: username }, { cache: "stats" });
20
+ return { text: formatProfile(data.User) };
21
+ }
22
+ catch (err) {
23
+ return {
24
+ text: `Error loading profile: ${err instanceof Error ? err.message : String(err)}`,
25
+ };
26
+ }
20
27
  },
21
28
  });
22
29
  // === Taste Profile ===
@@ -33,11 +40,18 @@ export function registerResources(server) {
33
40
  },
34
41
  ],
35
42
  async load({ type }) {
36
- const username = getDefaultUsername();
37
- const mediaType = String(type).toUpperCase();
38
- const entries = await anilistClient.fetchList(username, mediaType, "COMPLETED");
39
- const profile = buildTasteProfile(entries);
40
- return { text: formatTasteProfile(profile, username) };
43
+ try {
44
+ const username = getDefaultUsername();
45
+ const mediaType = String(type).toUpperCase();
46
+ const entries = await anilistClient.fetchList(username, mediaType, "COMPLETED");
47
+ const profile = buildTasteProfile(entries);
48
+ return { text: formatTasteProfile(profile, username) };
49
+ }
50
+ catch (err) {
51
+ return {
52
+ text: `Error loading taste profile: ${err instanceof Error ? err.message : String(err)}`,
53
+ };
54
+ }
41
55
  },
42
56
  });
43
57
  // === Current List ===
@@ -54,23 +68,30 @@ export function registerResources(server) {
54
68
  },
55
69
  ],
56
70
  async load({ type }) {
57
- const username = getDefaultUsername();
58
- const mediaType = String(type).toUpperCase();
59
- const [entries, scoreFormat] = await Promise.all([
60
- anilistClient.fetchList(username, mediaType, "CURRENT"),
61
- detectScoreFormat(async () => {
62
- const data = await anilistClient.query(USER_STATS_QUERY, { name: username }, { cache: "stats" });
63
- return data.User.mediaListOptions.scoreFormat;
64
- }),
65
- ]);
66
- if (!entries.length) {
71
+ try {
72
+ const username = getDefaultUsername();
73
+ const mediaType = String(type).toUpperCase();
74
+ const [entries, scoreFormat] = await Promise.all([
75
+ anilistClient.fetchList(username, mediaType, "CURRENT"),
76
+ detectScoreFormat(async () => {
77
+ const data = await anilistClient.query(USER_STATS_QUERY, { name: username }, { cache: "stats" });
78
+ return data.User.mediaListOptions.scoreFormat;
79
+ }),
80
+ ]);
81
+ if (!entries.length) {
82
+ return {
83
+ text: `${username} has no current ${mediaType.toLowerCase()} entries.`,
84
+ };
85
+ }
86
+ const header = `${username}'s current ${mediaType.toLowerCase()} - ${entries.length} entries`;
87
+ const formatted = entries.map((entry, i) => formatListEntry(entry, i + 1, scoreFormat));
88
+ return { text: [header, "", ...formatted].join("\n\n") };
89
+ }
90
+ catch (err) {
67
91
  return {
68
- text: `${username} has no current ${mediaType.toLowerCase()} entries.`,
92
+ text: `Error loading list: ${err instanceof Error ? err.message : String(err)}`,
69
93
  };
70
94
  }
71
- const header = `${username}'s current ${mediaType.toLowerCase()} - ${entries.length} entries`;
72
- const formatted = entries.map((entry, i) => formatListEntry(entry, i + 1, scoreFormat));
73
- return { text: [header, "", ...formatted].join("\n\n") };
74
95
  },
75
96
  });
76
97
  }
@@ -128,6 +128,9 @@ export function registerRecommendTools(server) {
128
128
  let candidatePromise;
129
129
  let sourceLabel;
130
130
  if (source === "SEASONAL") {
131
+ if (args.type === "MANGA") {
132
+ return "SEASONAL source only works with anime. Use PLANNING or DISCOVER for manga recommendations.";
133
+ }
131
134
  const { season, year } = resolveSeasonYear(args.season, args.year);
132
135
  sourceLabel = `${season} ${year} seasonal anime`;
133
136
  candidatePromise = (async () => {
@@ -16,6 +16,7 @@ export function registerSocialTools(server) {
16
16
  title: "Activity Feed",
17
17
  readOnlyHint: true,
18
18
  destructiveHint: false,
19
+ idempotentHint: true,
19
20
  openWorldHint: true,
20
21
  },
21
22
  execute: async (args) => {
@@ -56,6 +57,7 @@ export function registerSocialTools(server) {
56
57
  title: "User Profile",
57
58
  readOnlyHint: true,
58
59
  destructiveHint: false,
60
+ idempotentHint: true,
59
61
  openWorldHint: true,
60
62
  },
61
63
  execute: async (args) => {
@@ -84,6 +86,7 @@ export function registerSocialTools(server) {
84
86
  title: "Community Reviews",
85
87
  readOnlyHint: true,
86
88
  destructiveHint: false,
89
+ idempotentHint: true,
87
90
  openWorldHint: true,
88
91
  },
89
92
  execute: async (args) => {
@@ -169,15 +172,16 @@ export function formatProfile(user) {
169
172
  fav.characters.nodes.map((n) => n.name.full).join(", "));
170
173
  }
171
174
  if (fav.staff.nodes.length) {
172
- lines.push("Favourite Staff: " +
173
- fav.staff.nodes.map((n) => n.name.full).join(", "));
175
+ lines.push("Favourite Staff: " + fav.staff.nodes.map((n) => n.name.full).join(", "));
174
176
  }
175
177
  if (fav.studios.nodes.length) {
176
- lines.push("Favourite Studios: " +
177
- fav.studios.nodes.map((n) => n.name).join(", "));
178
+ lines.push("Favourite Studios: " + fav.studios.nodes.map((n) => n.name).join(", "));
178
179
  }
179
180
  // Account age
180
- const created = new Date(user.createdAt * 1000).toLocaleDateString("en-US", { month: "short", year: "numeric" });
181
+ const created = new Date(user.createdAt * 1000).toLocaleDateString("en-US", {
182
+ month: "short",
183
+ year: "numeric",
184
+ });
181
185
  lines.push("", `Member since ${created}`);
182
186
  return lines.join("\n");
183
187
  }
package/dist/utils.js CHANGED
@@ -170,10 +170,10 @@ export function formatScore(score10, format) {
170
170
  }
171
171
  case "POINT_3": {
172
172
  if (score10 >= 7)
173
- return "🙂";
173
+ return ":)";
174
174
  if (score10 >= 4)
175
- return "😐";
176
- return "🙁";
175
+ return ":|";
176
+ return ":(";
177
177
  }
178
178
  default:
179
179
  return `${score10}/10`;
package/manifest.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "manifest_version": "0.3",
3
3
  "name": "ani-mcp",
4
- "version": "0.6.0",
4
+ "version": "0.6.2",
5
5
  "display_name": "AniList MCP",
6
6
  "description": "A smart MCP server for AniList that gets your anime/manga taste - not just API calls.",
7
7
  "author": {
@@ -72,12 +72,24 @@
72
72
  {
73
73
  "name": "anilist_genres"
74
74
  },
75
+ {
76
+ "name": "anilist_genre_list"
77
+ },
75
78
  {
76
79
  "name": "anilist_taste"
77
80
  },
78
81
  {
79
82
  "name": "anilist_pick"
80
83
  },
84
+ {
85
+ "name": "anilist_session"
86
+ },
87
+ {
88
+ "name": "anilist_sequels"
89
+ },
90
+ {
91
+ "name": "anilist_watch_order"
92
+ },
81
93
  {
82
94
  "name": "anilist_compare"
83
95
  },
@@ -109,7 +121,19 @@
109
121
  "name": "anilist_whoami"
110
122
  },
111
123
  {
112
- "name": "anilist_genre_list"
124
+ "name": "anilist_profile"
125
+ },
126
+ {
127
+ "name": "anilist_feed"
128
+ },
129
+ {
130
+ "name": "anilist_reviews"
131
+ },
132
+ {
133
+ "name": "anilist_favourite"
134
+ },
135
+ {
136
+ "name": "anilist_activity"
113
137
  },
114
138
  {
115
139
  "name": "anilist_update_progress"
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "ani-mcp",
3
3
  "mcpName": "io.github.gavxm/ani-mcp",
4
- "version": "0.6.0",
4
+ "version": "0.6.2",
5
5
  "description": "A smart [MCP](https://modelcontextprotocol.io) server for [AniList](https://anilist.co) that gets your anime/manga taste - not just API calls.",
6
6
  "type": "module",
7
7
  "main": "dist/index.js",
package/server.json CHANGED
@@ -6,12 +6,12 @@
6
6
  "url": "https://github.com/gavxm/ani-mcp",
7
7
  "source": "github"
8
8
  },
9
- "version": "0.6.0",
9
+ "version": "0.6.2",
10
10
  "packages": [
11
11
  {
12
12
  "registryType": "npm",
13
13
  "identifier": "ani-mcp",
14
- "version": "0.6.0",
14
+ "version": "0.6.2",
15
15
  "transport": {
16
16
  "type": "stdio"
17
17
  },