@xiedada/nodemw-mcp-server 0.0.12 → 0.1.1

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.
Files changed (2) hide show
  1. package/dist/index.js +74 -29
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -37,7 +37,7 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
37
37
  // package.json
38
38
  var package_default = {
39
39
  name: "@xiedada/nodemw-mcp-server",
40
- version: "0.0.12",
40
+ version: "0.1.1",
41
41
  description: "MCP server for nodemw - MediaWiki API client",
42
42
  type: "module",
43
43
  main: "dist/index.js",
@@ -334,7 +334,7 @@ async function handleGetArticleTool(title, followRedirect, redirectInfo, revisio
334
334
  const [content, redirect] = result;
335
335
  if (content == null) {
336
336
  return {
337
- content: [{ type: "text", text: `Page "${title}" not found or has no content.` }],
337
+ content: [{ type: "text", text: `Page "${title}" not found.` }],
338
338
  isError: true
339
339
  };
340
340
  }
@@ -344,7 +344,7 @@ ${content}
344
344
 
345
345
  Redirect Information:
346
346
 
347
- ${JSON.stringify(redirect, null, 2)}` : content;
347
+ ${JSON.stringify(redirect, null, 2)}` : content === "" ? "(empty page)" : content;
348
348
  await recordReadState(title);
349
349
  return {
350
350
  content: [{ type: "text", text: responseText }]
@@ -358,13 +358,13 @@ ${JSON.stringify(redirect, null, 2)}` : content;
358
358
  );
359
359
  if (result == null) {
360
360
  return {
361
- content: [{ type: "text", text: `Page "${title}" not found or has no content.` }],
361
+ content: [{ type: "text", text: `Page "${title}" not found.` }],
362
362
  isError: true
363
363
  };
364
364
  }
365
365
  await recordReadState(title);
366
366
  return {
367
- content: [{ type: "text", text: result }]
367
+ content: [{ type: "text", text: result === "" ? "(empty page)" : result }]
368
368
  };
369
369
  }
370
370
  } catch (error) {
@@ -809,7 +809,7 @@ async function handleGetArticlePropertiesTool(title) {
809
809
  );
810
810
  return jsonResult({
811
811
  title,
812
- properties
812
+ properties: properties || {}
813
813
  });
814
814
  } catch (error) {
815
815
  return errorResult("Failed to get article properties", error);
@@ -866,41 +866,79 @@ import { z as z14 } from "zod";
866
866
  function getUserContribsTool(server) {
867
867
  const tool = server.tool(
868
868
  "get-user-contribs",
869
- "Get contributions made by a specific user",
869
+ "Get contributions made by a specific user. Pagination: the response includes total (matching edits found) and displayed (returned in this batch). If displayed < total, more results exist \u2014 use the timestamp of the LAST returned contribution as the start parameter for the next page. Repeat until displayed < limit to get all results.",
870
870
  {
871
871
  username: z14.string().describe("Username to get contributions for"),
872
872
  namespace: z14.number().optional().describe("Filter contributions by namespace"),
873
- limit: z14.number().optional().default(50).describe("Maximum number of contributions to return")
873
+ limit: z14.number().optional().default(50).describe("Maximum number of contributions to return"),
874
+ start: z14.string().optional().describe(
875
+ 'Timestamp to start listing from \u2014 only return edits before this time (not inclusive). Accepts ISO 8601 (e.g. "2026-05-10T22:54:37Z"), MediaWiki format "YYYYMMDDHHMMSS", or unix timestamp. All times are UTC \u2014 MW ignores timezone offsets. To paginate: pass the timestamp of the LAST item from the previous page as start. The returned contributions are guaranteed to be strictly older than this timestamp.'
876
+ )
874
877
  },
875
878
  {
876
879
  title: "Get user contributions",
877
880
  readOnlyHint: true,
878
881
  destructiveHint: false
879
882
  },
880
- async ({ username, namespace, limit }) => handleGetUserContribsTool(username, namespace, limit)
883
+ async ({ username, namespace, limit, start }) => handleGetUserContribsTool(username, namespace, limit, start)
881
884
  );
882
- tool.update({ outputSchema: { username: z14.string(), namespace: z14.number().optional(), limit: z14.number(), total: z14.number(), displayed: z14.number(), contributions: z14.array(z14.record(z14.unknown())) } });
885
+ tool.update({ outputSchema: { username: z14.string(), namespace: z14.number().optional(), limit: z14.number(), start: z14.string().optional(), total: z14.number(), displayed: z14.number(), contributions: z14.array(z14.record(z14.unknown())) } });
883
886
  return tool;
884
887
  }
885
- async function handleGetUserContribsTool(username, namespace, limit = 50) {
888
+ async function handleGetUserContribsTool(username, namespace, limit = 50, start) {
886
889
  try {
887
890
  const bot = await getBot();
888
- const options = {
889
- user: username,
890
- ...namespace !== void 0 && { namespace }
891
- };
892
- const callbackArgs = await promisifyBotMethod(
891
+ const userInfo = await promisifyBotMethod(
893
892
  bot,
894
- "getUserContribs",
895
- options
893
+ "whois",
894
+ username
896
895
  );
897
- const contribs = Array.isArray(callbackArgs[1]) ? callbackArgs[1] : [];
898
- const limitedContribs = contribs.slice(0, limit);
896
+ if (userInfo.missing !== void 0) {
897
+ return errorResult(`User "${username}" not found.`);
898
+ }
899
+ const allContribs = [];
900
+ const perPage = Math.min(limit, 500);
901
+ const baseParams = {
902
+ action: "query",
903
+ list: "usercontribs",
904
+ ucuser: username,
905
+ uclimit: perPage,
906
+ ucprop: "ids|title|timestamp|comment|size|flags",
907
+ ...namespace !== void 0 && { ucnamespace: namespace },
908
+ ...start && { ucstart: start }
909
+ };
910
+ let continueParams;
911
+ do {
912
+ const params = { ...baseParams, ...continueParams || {} };
913
+ const rawData = await new Promise((resolve, reject) => {
914
+ bot.api.call(params, (_err, _info, _next, raw) => {
915
+ if (_err) reject(_err);
916
+ else resolve(raw);
917
+ });
918
+ });
919
+ const usercontribs = rawData.query?.usercontribs;
920
+ if (usercontribs) {
921
+ allContribs.push(...usercontribs);
922
+ }
923
+ if (allContribs.length >= limit) {
924
+ break;
925
+ }
926
+ if (rawData.continue) {
927
+ continueParams = rawData.continue;
928
+ } else if (rawData["query-continue"]) {
929
+ const qc = rawData["query-continue"];
930
+ continueParams = qc.usercontribs || qc[Object.keys(qc)[0]];
931
+ } else {
932
+ continueParams = void 0;
933
+ }
934
+ } while (continueParams);
935
+ const limitedContribs = allContribs.slice(0, limit);
899
936
  return jsonResult({
900
937
  username,
901
938
  namespace,
902
939
  limit,
903
- total: contribs.length,
940
+ start,
941
+ total: allContribs.length,
904
942
  displayed: limitedContribs.length,
905
943
  contributions: limitedContribs
906
944
  });
@@ -1002,7 +1040,10 @@ async function handleWhoareTool(params) {
1002
1040
  "whoare",
1003
1041
  params.usernames
1004
1042
  );
1005
- return jsonResult({ users, count: users.length });
1043
+ const normalized = users.map(
1044
+ (u) => u && u.missing !== void 0 ? { ...u, missing: true } : u
1045
+ );
1046
+ return jsonResult({ users: normalized, count: normalized.length });
1006
1047
  } catch (error) {
1007
1048
  return errorResult("Failed to get user information", error);
1008
1049
  }
@@ -1171,10 +1212,12 @@ import { z as z22 } from "zod";
1171
1212
  function getLogTool(server) {
1172
1213
  const tool = server.tool(
1173
1214
  "get-log",
1174
- "Get log entries of a specific type",
1215
+ "Get log entries of a specific type (e.g. delete, block, move). Pagination: the response includes total (matching entries found) and displayed (returned in this batch). If displayed < total, more results exist \u2014 use the timestamp of the LAST returned entry as the start parameter for the next page.",
1175
1216
  {
1176
1217
  type: z22.string().describe("Log type (e.g. delete, block, move)"),
1177
- start: z22.string().optional().default("").describe("Start timestamp (YYYYMMDDHHMMSS format)"),
1218
+ start: z22.string().optional().describe(
1219
+ 'Timestamp to start listing from \u2014 only return entries before this time. Accepts ISO 8601 (e.g. "2026-05-10T22:54:37Z"), MediaWiki format "YYYYMMDDHHMMSS", or unix timestamp. All times are UTC \u2014 MW ignores timezone offsets. To paginate: pass the timestamp of the LAST item from the previous page as start.'
1220
+ ),
1178
1221
  limit: z22.number().optional().default(50).describe("Maximum number of entries to return")
1179
1222
  },
1180
1223
  {
@@ -1184,14 +1227,14 @@ function getLogTool(server) {
1184
1227
  },
1185
1228
  async ({ type, start, limit }) => handleGetLogTool(type, start, limit)
1186
1229
  );
1187
- tool.update({ outputSchema: { type: z22.string(), start: z22.string(), limit: z22.number(), total: z22.number(), displayed: z22.number(), entries: z22.array(z22.record(z22.unknown())) } });
1230
+ tool.update({ outputSchema: { type: z22.string(), start: z22.string().optional(), limit: z22.number(), total: z22.number(), displayed: z22.number(), entries: z22.array(z22.record(z22.unknown())) } });
1188
1231
  return tool;
1189
1232
  }
1190
- async function handleGetLogTool(type, start, limit) {
1233
+ async function handleGetLogTool(type, start, limit = 50) {
1191
1234
  try {
1192
1235
  const bot = await getBot();
1193
1236
  const entries = await new Promise((resolve, reject) => {
1194
- bot.getLog(type, start, (err, ...args) => {
1237
+ bot.getLog(type, start || "", (err, ...args) => {
1195
1238
  if (err) {
1196
1239
  reject(err);
1197
1240
  } else {
@@ -1310,9 +1353,11 @@ import { z as z25 } from "zod";
1310
1353
  function getRecentChangesTool(server) {
1311
1354
  const tool = server.tool(
1312
1355
  "get-recent-changes",
1313
- "Get recent changes on the wiki",
1356
+ "Get recent changes on the wiki. Pagination: the response includes total (matching changes found) and displayed (returned in this batch). If displayed < total, more results exist \u2014 use the timestamp of the LAST returned change as the start parameter for the next page.",
1314
1357
  {
1315
- start: z25.string().optional().describe("Start timestamp (YYYYMMDDHHMMSS format)"),
1358
+ start: z25.string().optional().describe(
1359
+ 'Timestamp to start listing from \u2014 only return changes before this time. Accepts ISO 8601 (e.g. "2026-05-10T22:54:37Z"), MediaWiki format "YYYYMMDDHHMMSS", or unix timestamp. All times are UTC \u2014 MW ignores timezone offsets. To paginate: pass the timestamp of the LAST item from the previous page as start.'
1360
+ ),
1316
1361
  limit: z25.number().optional().default(50).describe("Maximum number of changes to return")
1317
1362
  },
1318
1363
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiedada/nodemw-mcp-server",
3
- "version": "0.0.12",
3
+ "version": "0.1.1",
4
4
  "description": "MCP server for nodemw - MediaWiki API client",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",