@unclick/bgg-mcp 0.1.0
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/LICENSE +15 -0
- package/README.md +37 -0
- package/dist/bgg-tool.js +249 -0
- package/dist/index.js +105 -0
- package/package.json +45 -0
- package/server.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
Apache License 2.0
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 UnClick / malamutemayhem
|
|
4
|
+
|
|
5
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
you may not use this file except in compliance with the License.
|
|
7
|
+
You may obtain a copy of the License at
|
|
8
|
+
|
|
9
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
|
|
11
|
+
Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
See the License for the specific language governing permissions and
|
|
15
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# BoardGameGeek MCP by UnClick
|
|
2
|
+
|
|
3
|
+
BoardGameGeek search, game details, rankings, and collections.
|
|
4
|
+
|
|
5
|
+
> By UnClick. 180+ tools plus persistent agent memory in one install: https://unclick.world
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
Installs straight from GitHub, no npm account needed.
|
|
10
|
+
|
|
11
|
+
```json
|
|
12
|
+
{
|
|
13
|
+
"mcpServers": {
|
|
14
|
+
"bgg": {
|
|
15
|
+
"command": "npx",
|
|
16
|
+
"args": ["-y", "https://github.com/malamutemayhem/unclick/releases/download/standalone-mcps-latest/bgg.tgz"]
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Tools
|
|
23
|
+
|
|
24
|
+
- `bgg_search`
|
|
25
|
+
- `bgg_game_details`
|
|
26
|
+
- `bgg_user_collection`
|
|
27
|
+
- `bgg_top_games`
|
|
28
|
+
- `bgg_game_reviews`
|
|
29
|
+
|
|
30
|
+
## Want the rest?
|
|
31
|
+
|
|
32
|
+
This is one connector. [UnClick](https://unclick.world) bundles 180+ tools plus
|
|
33
|
+
persistent cross-session agent memory in a single install.
|
|
34
|
+
|
|
35
|
+
## License
|
|
36
|
+
|
|
37
|
+
Apache-2.0
|
package/dist/bgg-tool.js
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
// BoardGameGeek XML API2 integration.
|
|
2
|
+
// Docs: https://boardgamegeek.com/wiki/page/BGG_XML_API2
|
|
3
|
+
// No authentication required.
|
|
4
|
+
// Base URL: https://boardgamegeek.com/xmlapi2
|
|
5
|
+
import { XMLParser } from "fast-xml-parser";
|
|
6
|
+
const BGG_BASE = "https://boardgamegeek.com/xmlapi2";
|
|
7
|
+
const parser = new XMLParser({
|
|
8
|
+
ignoreAttributes: false,
|
|
9
|
+
attributeNamePrefix: "@_",
|
|
10
|
+
});
|
|
11
|
+
// ─── helpers ──────────────────────────────────────────────────────────────────
|
|
12
|
+
async function bggFetch(path) {
|
|
13
|
+
const res = await fetch(`${BGG_BASE}${path}`, {
|
|
14
|
+
headers: { "User-Agent": "UnClickMCP/1.0 (https://unclick.io)" },
|
|
15
|
+
});
|
|
16
|
+
if (!res.ok)
|
|
17
|
+
throw new Error(`BGG API HTTP ${res.status}`);
|
|
18
|
+
const xml = await res.text();
|
|
19
|
+
return parser.parse(xml);
|
|
20
|
+
}
|
|
21
|
+
/** BGG collection endpoints return 202 while the data is being prepared. Retry. */
|
|
22
|
+
async function bggFetchWithRetry(path, maxRetries = 5, delayMs = 2000) {
|
|
23
|
+
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
24
|
+
const res = await fetch(`${BGG_BASE}${path}`, {
|
|
25
|
+
headers: { "User-Agent": "UnClickMCP/1.0 (https://unclick.io)" },
|
|
26
|
+
});
|
|
27
|
+
if (res.status === 202) {
|
|
28
|
+
if (attempt < maxRetries - 1) {
|
|
29
|
+
await new Promise((r) => setTimeout(r, delayMs));
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
throw new Error("BGG collection is still being prepared. Please try again in a few seconds.");
|
|
33
|
+
}
|
|
34
|
+
if (!res.ok)
|
|
35
|
+
throw new Error(`BGG API HTTP ${res.status}`);
|
|
36
|
+
const xml = await res.text();
|
|
37
|
+
return parser.parse(xml);
|
|
38
|
+
}
|
|
39
|
+
throw new Error("BGG collection request timed out after retries.");
|
|
40
|
+
}
|
|
41
|
+
function attr(obj, key) {
|
|
42
|
+
if (obj && typeof obj === "object") {
|
|
43
|
+
return String(obj[`@_${key}`] ?? "");
|
|
44
|
+
}
|
|
45
|
+
return "";
|
|
46
|
+
}
|
|
47
|
+
function val(obj) {
|
|
48
|
+
return attr(obj, "value");
|
|
49
|
+
}
|
|
50
|
+
function primaryName(names) {
|
|
51
|
+
if (!Array.isArray(names)) {
|
|
52
|
+
return val(names);
|
|
53
|
+
}
|
|
54
|
+
const primary = names.find((n) => attr(n, "type") === "primary");
|
|
55
|
+
return val(primary ?? names[0]);
|
|
56
|
+
}
|
|
57
|
+
// ─── bgg_search ───────────────────────────────────────────────────────────────
|
|
58
|
+
// GET /search?query=<name>&type=boardgame[,boardgameexpansion]
|
|
59
|
+
export async function bggSearch(args) {
|
|
60
|
+
const query = String(args.query ?? "").trim();
|
|
61
|
+
if (!query)
|
|
62
|
+
return { error: "query is required." };
|
|
63
|
+
const type = String(args.type ?? "boardgame").trim();
|
|
64
|
+
const allowed = ["boardgame", "boardgameexpansion"];
|
|
65
|
+
const safeType = allowed.includes(type) ? type : "boardgame";
|
|
66
|
+
const data = await bggFetch(`/search?query=${encodeURIComponent(query)}&type=${encodeURIComponent(safeType)}`);
|
|
67
|
+
const root = (data.items ?? {});
|
|
68
|
+
const items = root.item ?? [];
|
|
69
|
+
return {
|
|
70
|
+
total: Number(attr(root, "total") ?? items.length),
|
|
71
|
+
results: items.slice(0, 20).map((item) => ({
|
|
72
|
+
id: attr(item, "id"),
|
|
73
|
+
type: attr(item, "type"),
|
|
74
|
+
name: primaryName(item.name),
|
|
75
|
+
year_published: val(item.yearpublished) || null,
|
|
76
|
+
})),
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
// ─── bgg_game_details ─────────────────────────────────────────────────────────
|
|
80
|
+
// GET /thing?id=<id>&stats=1
|
|
81
|
+
export async function bggGameDetails(args) {
|
|
82
|
+
const gameId = String(args.gameId ?? "").trim();
|
|
83
|
+
if (!gameId)
|
|
84
|
+
return { error: "gameId is required." };
|
|
85
|
+
const data = await bggFetch(`/thing?id=${encodeURIComponent(gameId)}&stats=1`);
|
|
86
|
+
const root = (data.items ?? {});
|
|
87
|
+
const items = root.item ?? [];
|
|
88
|
+
const item = items[0];
|
|
89
|
+
if (!item)
|
|
90
|
+
return { error: `No game found with id ${gameId}` };
|
|
91
|
+
const stats = item.statistics ?? {};
|
|
92
|
+
const ratings = stats.ratings ?? {};
|
|
93
|
+
const links = (item.link ?? []);
|
|
94
|
+
const categories = links
|
|
95
|
+
.filter((l) => attr(l, "type") === "boardgamecategory")
|
|
96
|
+
.map((l) => val(l));
|
|
97
|
+
const mechanics = links
|
|
98
|
+
.filter((l) => attr(l, "type") === "boardgamemechanic")
|
|
99
|
+
.map((l) => val(l));
|
|
100
|
+
const designers = links
|
|
101
|
+
.filter((l) => attr(l, "type") === "boardgamedesigner")
|
|
102
|
+
.map((l) => val(l));
|
|
103
|
+
const publishers = links
|
|
104
|
+
.filter((l) => attr(l, "type") === "boardgamepublisher")
|
|
105
|
+
.map((l) => val(l));
|
|
106
|
+
// Strip HTML tags from description
|
|
107
|
+
const rawDesc = String(item.description ?? "");
|
|
108
|
+
const description = rawDesc.replace(/ /g, "\n").replace(/<[^>]+>/g, "").trim();
|
|
109
|
+
return {
|
|
110
|
+
id: attr(item, "id"),
|
|
111
|
+
type: attr(item, "type"),
|
|
112
|
+
name: primaryName(item.name),
|
|
113
|
+
year_published: val(item.yearpublished) || null,
|
|
114
|
+
description: description.slice(0, 1000) + (description.length > 1000 ? "…" : ""),
|
|
115
|
+
image: String(item.image ?? "").trim() || null,
|
|
116
|
+
min_players: val(item.minplayers) || null,
|
|
117
|
+
max_players: val(item.maxplayers) || null,
|
|
118
|
+
playing_time: val(item.playingtime) || null,
|
|
119
|
+
min_play_time: val(item.minplaytime) || null,
|
|
120
|
+
max_play_time: val(item.maxplaytime) || null,
|
|
121
|
+
min_age: val(item.minage) || null,
|
|
122
|
+
complexity_weight: val(ratings.averageweight) || null,
|
|
123
|
+
average_rating: val(ratings.average) || null,
|
|
124
|
+
bayes_average: val(ratings.bayesaverage) || null,
|
|
125
|
+
rank: (() => {
|
|
126
|
+
const ranks = ratings.ranks ?? {};
|
|
127
|
+
const rankItems = Array.isArray(ranks.rank) ? ranks.rank : ranks.rank ? [ranks.rank] : [];
|
|
128
|
+
const overall = rankItems.find((r) => attr(r, "name") === "boardgame");
|
|
129
|
+
return overall ? val(overall) : null;
|
|
130
|
+
})(),
|
|
131
|
+
num_ratings: val(ratings.usersrated) || null,
|
|
132
|
+
num_owned: val(ratings.owned) || null,
|
|
133
|
+
categories,
|
|
134
|
+
mechanics,
|
|
135
|
+
designers,
|
|
136
|
+
publishers: publishers.slice(0, 5),
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// ─── bgg_user_collection ──────────────────────────────────────────────────────
|
|
140
|
+
// GET /collection?username=<username>&own=1 (or wishlist=1, played=1)
|
|
141
|
+
export async function bggUserCollection(args) {
|
|
142
|
+
const username = String(args.username ?? "").trim();
|
|
143
|
+
if (!username)
|
|
144
|
+
return { error: "username is required." };
|
|
145
|
+
const status = String(args.status ?? "owned").trim();
|
|
146
|
+
const statusMap = {
|
|
147
|
+
owned: "own=1",
|
|
148
|
+
wishlist: "wishlist=1",
|
|
149
|
+
played: "played=1",
|
|
150
|
+
};
|
|
151
|
+
const statusParam = statusMap[status] ?? "own=1";
|
|
152
|
+
const data = await bggFetchWithRetry(`/collection?username=${encodeURIComponent(username)}&${statusParam}&stats=1`);
|
|
153
|
+
const root = (data.items ?? {});
|
|
154
|
+
const rawItems = root.item;
|
|
155
|
+
const items = Array.isArray(rawItems)
|
|
156
|
+
? rawItems
|
|
157
|
+
: rawItems
|
|
158
|
+
? [rawItems]
|
|
159
|
+
: [];
|
|
160
|
+
return {
|
|
161
|
+
username,
|
|
162
|
+
status,
|
|
163
|
+
total: items.length,
|
|
164
|
+
games: items.slice(0, 50).map((item) => {
|
|
165
|
+
const stats = item.stats ?? {};
|
|
166
|
+
const rating = (item.stats
|
|
167
|
+
? stats.rating ?? {}
|
|
168
|
+
: {});
|
|
169
|
+
return {
|
|
170
|
+
id: attr(item, "objectid"),
|
|
171
|
+
name: String(item.name?.["#text"] ?? ""),
|
|
172
|
+
year_published: attr(item.yearpublished ?? {}, ""),
|
|
173
|
+
image: String(item.image ?? "").trim() || null,
|
|
174
|
+
num_plays: attr(item, "numplays") || String(item.numplays ?? ""),
|
|
175
|
+
user_rating: val(rating) || null,
|
|
176
|
+
average_rating: val(stats.rating?.average ?? {}) || null,
|
|
177
|
+
rank: (() => {
|
|
178
|
+
const r = stats.ranks;
|
|
179
|
+
if (!r)
|
|
180
|
+
return null;
|
|
181
|
+
const rankItems = Array.isArray(r.rank) ? r.rank : r.rank ? [r.rank] : [];
|
|
182
|
+
const overall = rankItems.find((x) => attr(x, "name") === "boardgame");
|
|
183
|
+
return overall ? attr(overall, "value") : null;
|
|
184
|
+
})(),
|
|
185
|
+
};
|
|
186
|
+
}),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
// ─── bgg_top_games ────────────────────────────────────────────────────────────
|
|
190
|
+
// GET /hot?type=boardgame - returns BGG's "Hotness" list (top 50)
|
|
191
|
+
export async function bggTopGames(args) {
|
|
192
|
+
const limit = Math.min(Number(args.limit ?? 20), 50);
|
|
193
|
+
const data = await bggFetch("/hot?type=boardgame");
|
|
194
|
+
const root = (data.items ?? {});
|
|
195
|
+
const rawItems = root.item;
|
|
196
|
+
const items = Array.isArray(rawItems)
|
|
197
|
+
? rawItems
|
|
198
|
+
: rawItems
|
|
199
|
+
? [rawItems]
|
|
200
|
+
: [];
|
|
201
|
+
return {
|
|
202
|
+
list: "BGG Hotness (most discussed/active games right now)",
|
|
203
|
+
total: items.length,
|
|
204
|
+
games: items.slice(0, limit).map((item, i) => ({
|
|
205
|
+
rank: i + 1,
|
|
206
|
+
id: attr(item, "id"),
|
|
207
|
+
name: val(item.name),
|
|
208
|
+
year_published: val(item.yearpublished) || null,
|
|
209
|
+
thumbnail: val(item.thumbnail) || null,
|
|
210
|
+
})),
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
// ─── bgg_game_reviews ─────────────────────────────────────────────────────────
|
|
214
|
+
// GET /thing?id=<id>&comments=1&page=<page>
|
|
215
|
+
export async function bggGameReviews(args) {
|
|
216
|
+
const gameId = String(args.gameId ?? "").trim();
|
|
217
|
+
if (!gameId)
|
|
218
|
+
return { error: "gameId is required." };
|
|
219
|
+
const page = Math.max(1, Number(args.page ?? 1));
|
|
220
|
+
const data = await bggFetch(`/thing?id=${encodeURIComponent(gameId)}&comments=1&page=${page}&pagesize=25`);
|
|
221
|
+
const root = (data.items ?? {});
|
|
222
|
+
const rawItems = root.item;
|
|
223
|
+
const items = Array.isArray(rawItems)
|
|
224
|
+
? rawItems
|
|
225
|
+
: rawItems
|
|
226
|
+
? [rawItems]
|
|
227
|
+
: [];
|
|
228
|
+
const item = items[0];
|
|
229
|
+
if (!item)
|
|
230
|
+
return { error: `No game found with id ${gameId}` };
|
|
231
|
+
const comments = item.comments ?? {};
|
|
232
|
+
const rawComments = comments.comment;
|
|
233
|
+
const commentList = Array.isArray(rawComments)
|
|
234
|
+
? rawComments
|
|
235
|
+
: rawComments
|
|
236
|
+
? [rawComments]
|
|
237
|
+
: [];
|
|
238
|
+
return {
|
|
239
|
+
game_id: gameId,
|
|
240
|
+
game_name: primaryName(item.name),
|
|
241
|
+
page,
|
|
242
|
+
total_comments: Number(attr(comments, "totalitems") ?? 0),
|
|
243
|
+
comments: commentList.map((c) => ({
|
|
244
|
+
username: attr(c, "username"),
|
|
245
|
+
rating: attr(c, "rating") || null,
|
|
246
|
+
comment: String(c["#text"] ?? attr(c, "value") ?? "").trim(),
|
|
247
|
+
})),
|
|
248
|
+
};
|
|
249
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// BoardGameGeek MCP. Standalone MCP server by UnClick.
|
|
3
|
+
// By UnClick. 180+ tools plus persistent agent memory in one install: https://unclick.world
|
|
4
|
+
//
|
|
5
|
+
// Generated from the UnClick connector by scripts/generate-standalone-mcp.mjs.
|
|
6
|
+
// Edit the connector in the UnClick monorepo, not here.
|
|
7
|
+
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
8
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
|
+
import { CallToolRequestSchema, ListToolsRequestSchema, } from "@modelcontextprotocol/sdk/types.js";
|
|
10
|
+
import { bggSearch, bggGameDetails, bggUserCollection, bggTopGames, bggGameReviews, } from "./bgg-tool.js";
|
|
11
|
+
const TOOLS = [
|
|
12
|
+
{
|
|
13
|
+
name: "bgg_search",
|
|
14
|
+
description: "Search BoardGameGeek for board games by name.",
|
|
15
|
+
inputSchema: {
|
|
16
|
+
type: "object",
|
|
17
|
+
additionalProperties: false,
|
|
18
|
+
properties: {
|
|
19
|
+
query: { type: "string", description: "Game name to search for" },
|
|
20
|
+
type: { type: "string", enum: ["boardgame", "boardgameexpansion"], description: "Type of item to search for (default: boardgame)" },
|
|
21
|
+
},
|
|
22
|
+
required: ["query"],
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
name: "bgg_game_details",
|
|
27
|
+
description: "Get full details for a board game by its BGG ID - name, year, rating, players, playtime, description, categories, and mechanics.",
|
|
28
|
+
inputSchema: {
|
|
29
|
+
type: "object",
|
|
30
|
+
additionalProperties: false,
|
|
31
|
+
properties: {
|
|
32
|
+
gameId: { type: "string", description: "BoardGameGeek game ID" },
|
|
33
|
+
},
|
|
34
|
+
required: ["gameId"],
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
name: "bgg_user_collection",
|
|
39
|
+
description: "Get a BGG user's game collection filtered by status (owned, wishlist, or played).",
|
|
40
|
+
inputSchema: {
|
|
41
|
+
type: "object",
|
|
42
|
+
additionalProperties: false,
|
|
43
|
+
properties: {
|
|
44
|
+
username: { type: "string", description: "BGG username" },
|
|
45
|
+
status: { type: "string", enum: ["owned", "wishlist", "played"], description: "Collection filter (default: owned)" },
|
|
46
|
+
},
|
|
47
|
+
required: ["username"],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
name: "bgg_top_games",
|
|
52
|
+
description: "Get the BGG Hotness list - the most discussed and active board games right now.",
|
|
53
|
+
inputSchema: {
|
|
54
|
+
type: "object",
|
|
55
|
+
additionalProperties: false,
|
|
56
|
+
properties: {
|
|
57
|
+
limit: { type: "number", description: "Number of games to return (max 50, default 20)" },
|
|
58
|
+
},
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
name: "bgg_game_reviews",
|
|
63
|
+
description: "Get user comments and ratings for a board game on BGG.",
|
|
64
|
+
inputSchema: {
|
|
65
|
+
type: "object",
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
properties: {
|
|
68
|
+
gameId: { type: "string", description: "BoardGameGeek game ID" },
|
|
69
|
+
page: { type: "number", description: "Page number (default 1, 25 comments per page)" },
|
|
70
|
+
},
|
|
71
|
+
required: ["gameId"],
|
|
72
|
+
},
|
|
73
|
+
}
|
|
74
|
+
];
|
|
75
|
+
const HANDLERS = {
|
|
76
|
+
bgg_search: (args) => bggSearch(args),
|
|
77
|
+
bgg_game_details: (args) => bggGameDetails(args),
|
|
78
|
+
bgg_user_collection: (args) => bggUserCollection(args),
|
|
79
|
+
bgg_top_games: (args) => bggTopGames(args),
|
|
80
|
+
bgg_game_reviews: (args) => bggGameReviews(args),
|
|
81
|
+
};
|
|
82
|
+
const server = new Server({ name: "io.github.malamutemayhem/bgg", version: "0.1.0" }, { capabilities: { tools: {} } });
|
|
83
|
+
server.setRequestHandler(ListToolsRequestSchema, async () => ({ tools: TOOLS }));
|
|
84
|
+
server.setRequestHandler(CallToolRequestSchema, async (req) => {
|
|
85
|
+
const handler = HANDLERS[req.params.name];
|
|
86
|
+
if (!handler) {
|
|
87
|
+
return { content: [{ type: "text", text: `Unknown tool: ${req.params.name}` }], isError: true };
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
const result = await handler((req.params.arguments ?? {}));
|
|
91
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
95
|
+
return { content: [{ type: "text", text: message }], isError: true };
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
async function main() {
|
|
99
|
+
const transport = new StdioServerTransport();
|
|
100
|
+
await server.connect(transport);
|
|
101
|
+
}
|
|
102
|
+
main().catch((err) => {
|
|
103
|
+
process.stderr.write(`[bgg-mcp] fatal: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
104
|
+
process.exit(1);
|
|
105
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@unclick/bgg-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"mcpName": "io.github.malamutemayhem/bgg",
|
|
5
|
+
"description": "BoardGameGeek search, game details, rankings, and collections. By UnClick (https://unclick.world).",
|
|
6
|
+
"keywords": [
|
|
7
|
+
"mcp",
|
|
8
|
+
"model-context-protocol",
|
|
9
|
+
"unclick",
|
|
10
|
+
"boardgamegeek",
|
|
11
|
+
"bgg",
|
|
12
|
+
"board-games"
|
|
13
|
+
],
|
|
14
|
+
"author": "UnClick (https://unclick.world)",
|
|
15
|
+
"type": "module",
|
|
16
|
+
"bin": {
|
|
17
|
+
"bgg-mcp": "./dist/index.js"
|
|
18
|
+
},
|
|
19
|
+
"main": "./dist/index.js",
|
|
20
|
+
"files": [
|
|
21
|
+
"dist",
|
|
22
|
+
"README.md",
|
|
23
|
+
"server.json"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "tsc",
|
|
27
|
+
"start": "node dist/index.js",
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@modelcontextprotocol/sdk": "^1.15.1",
|
|
32
|
+
"fast-xml-parser": "^5.8.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"typescript": "^5.6.0",
|
|
36
|
+
"@types/node": "^22.0.0"
|
|
37
|
+
},
|
|
38
|
+
"license": "Apache-2.0",
|
|
39
|
+
"repository": {
|
|
40
|
+
"type": "git",
|
|
41
|
+
"url": "https://github.com/malamutemayhem/unclick.git",
|
|
42
|
+
"directory": "packages/standalone/bgg-mcp"
|
|
43
|
+
},
|
|
44
|
+
"homepage": "https://unclick.world"
|
|
45
|
+
}
|
package/server.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
|
+
"name": "io.github.malamutemayhem/bgg",
|
|
4
|
+
"title": "BoardGameGeek MCP by UnClick",
|
|
5
|
+
"description": "BoardGameGeek search, game details, rankings, and collections. By UnClick.",
|
|
6
|
+
"version": "0.1.0",
|
|
7
|
+
"websiteUrl": "https://unclick.world",
|
|
8
|
+
"icons": [
|
|
9
|
+
{
|
|
10
|
+
"src": "https://unclick.world/favicon.png",
|
|
11
|
+
"mimeType": "image/png",
|
|
12
|
+
"sizes": [
|
|
13
|
+
"512x512"
|
|
14
|
+
]
|
|
15
|
+
}
|
|
16
|
+
],
|
|
17
|
+
"repository": {
|
|
18
|
+
"url": "https://github.com/malamutemayhem/unclick.git",
|
|
19
|
+
"source": "github",
|
|
20
|
+
"subfolder": "packages/standalone/bgg-mcp"
|
|
21
|
+
},
|
|
22
|
+
"packages": [
|
|
23
|
+
{
|
|
24
|
+
"registryType": "npm",
|
|
25
|
+
"identifier": "@unclick/bgg-mcp",
|
|
26
|
+
"version": "0.1.0",
|
|
27
|
+
"runtimeHint": "npx",
|
|
28
|
+
"transport": {
|
|
29
|
+
"type": "stdio"
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
}
|