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.
- package/dist/engine/similar.js +1 -1
- package/dist/index.js +1 -1
- package/dist/resources.js +43 -22
- package/dist/tools/recommend.js +3 -0
- package/dist/tools/social.js +9 -5
- package/dist/utils.js +3 -3
- package/manifest.json +26 -2
- package/package.json +1 -1
- package/server.json +2 -2
package/dist/engine/similar.js
CHANGED
|
@@ -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 (
|
|
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
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
|
-
|
|
18
|
-
|
|
19
|
-
|
|
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
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
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:
|
|
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
|
}
|
package/dist/tools/recommend.js
CHANGED
|
@@ -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 () => {
|
package/dist/tools/social.js
CHANGED
|
@@ -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", {
|
|
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.
|
|
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": "
|
|
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.
|
|
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.
|
|
9
|
+
"version": "0.6.2",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"identifier": "ani-mcp",
|
|
14
|
-
"version": "0.6.
|
|
14
|
+
"version": "0.6.2",
|
|
15
15
|
"transport": {
|
|
16
16
|
"type": "stdio"
|
|
17
17
|
},
|