@xiedada/nodemw-mcp-server 0.0.11 → 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.
Files changed (2) hide show
  1. package/dist/index.js +79 -77
  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.11",
40
+ version: "0.1.0",
41
41
  description: "MCP server for nodemw - MediaWiki API client",
42
42
  type: "module",
43
43
  main: "dist/index.js",
@@ -301,7 +301,7 @@ async function handleGetArticleTool(title, followRedirect, redirectInfo, revisio
301
301
  });
302
302
  const pages = info.pages;
303
303
  const page = getFirstItem(pages);
304
- if (!page || page.missing) {
304
+ if (!page || page.missing !== void 0) {
305
305
  return {
306
306
  content: [{ type: "text", text: `Page "${title}" not found.` }],
307
307
  isError: true
@@ -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
  });
@@ -923,17 +961,7 @@ function whoamiTool(server) {
923
961
  },
924
962
  async () => handleWhoamiTool()
925
963
  );
926
- tool.update({ outputSchema: { user: z15.object({
927
- name: z15.string(),
928
- id: z15.number(),
929
- groups: z15.array(z15.string()),
930
- rights: z15.array(z15.string()),
931
- ratelimits: z15.union([z15.record(z15.unknown()), z15.array(z15.unknown())]),
932
- editcount: z15.number(),
933
- realname: z15.string().optional(),
934
- email: z15.string().optional(),
935
- emailauthenticated: z15.string().optional()
936
- }) } });
964
+ tool.update({ outputSchema: { user: z15.record(z15.unknown()) } });
937
965
  return tool;
938
966
  }
939
967
  async function handleWhoamiTool() {
@@ -965,18 +993,7 @@ function whoisTool(server) {
965
993
  },
966
994
  async ({ username }) => handleWhoisTool(username)
967
995
  );
968
- tool.update({ outputSchema: { user: z16.object({
969
- userid: z16.number(),
970
- name: z16.string(),
971
- groups: z16.array(z16.string()),
972
- implicitgroups: z16.array(z16.string()).optional(),
973
- rights: z16.array(z16.string()),
974
- editcount: z16.number(),
975
- registration: z16.string().optional(),
976
- emailable: z16.string().optional(),
977
- gender: z16.string().optional(),
978
- blockinfo: z16.record(z16.unknown()).optional()
979
- }) } });
996
+ tool.update({ outputSchema: { user: z16.record(z16.unknown()) } });
980
997
  return tool;
981
998
  }
982
999
  async function handleWhoisTool(username) {
@@ -987,7 +1004,7 @@ async function handleWhoisTool(username) {
987
1004
  "whois",
988
1005
  username
989
1006
  );
990
- if (userInfo.missing) {
1007
+ if (userInfo.missing !== void 0) {
991
1008
  return errorResult(`User "${username}" not found.`);
992
1009
  }
993
1010
  return jsonResult({ user: userInfo });
@@ -1012,18 +1029,7 @@ function whoareTool(server) {
1012
1029
  },
1013
1030
  async (params) => handleWhoareTool(params)
1014
1031
  );
1015
- tool.update({ outputSchema: { users: z17.array(z17.object({
1016
- userid: z17.number(),
1017
- name: z17.string(),
1018
- groups: z17.array(z17.string()),
1019
- implicitgroups: z17.array(z17.string()).optional(),
1020
- rights: z17.array(z17.string()),
1021
- editcount: z17.number(),
1022
- registration: z17.string().optional(),
1023
- emailable: z17.string().optional(),
1024
- gender: z17.string().optional(),
1025
- blockinfo: z17.record(z17.unknown()).optional()
1026
- })), count: z17.number() } });
1032
+ tool.update({ outputSchema: { users: z17.array(z17.record(z17.unknown())), count: z17.number() } });
1027
1033
  return tool;
1028
1034
  }
1029
1035
  async function handleWhoareTool(params) {
@@ -1034,7 +1040,10 @@ async function handleWhoareTool(params) {
1034
1040
  "whoare",
1035
1041
  params.usernames
1036
1042
  );
1037
- 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 });
1038
1047
  } catch (error) {
1039
1048
  return errorResult("Failed to get user information", error);
1040
1049
  }
@@ -1203,10 +1212,11 @@ import { z as z22 } from "zod";
1203
1212
  function getLogTool(server) {
1204
1213
  const tool = server.tool(
1205
1214
  "get-log",
1206
- "Get log entries of a specific type",
1207
- {
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." + {
1208
1216
  type: z22.string().describe("Log type (e.g. delete, block, move)"),
1209
- start: z22.string().optional().default("").describe("Start timestamp (YYYYMMDDHHMMSS format)"),
1217
+ start: z22.string().optional().describe(
1218
+ '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.'
1219
+ ),
1210
1220
  limit: z22.number().optional().default(50).describe("Maximum number of entries to return")
1211
1221
  },
1212
1222
  {
@@ -1216,10 +1226,10 @@ function getLogTool(server) {
1216
1226
  },
1217
1227
  async ({ type, start, limit }) => handleGetLogTool(type, start, limit)
1218
1228
  );
1219
- 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())) } });
1229
+ 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())) } });
1220
1230
  return tool;
1221
1231
  }
1222
- async function handleGetLogTool(type, start, limit) {
1232
+ async function handleGetLogTool(type, start, limit = 50) {
1223
1233
  try {
1224
1234
  const bot = await getBot();
1225
1235
  const entries = await new Promise((resolve, reject) => {
@@ -1342,9 +1352,10 @@ import { z as z25 } from "zod";
1342
1352
  function getRecentChangesTool(server) {
1343
1353
  const tool = server.tool(
1344
1354
  "get-recent-changes",
1345
- "Get recent changes on the wiki",
1346
- {
1347
- start: z25.string().optional().describe("Start timestamp (YYYYMMDDHHMMSS format)"),
1355
+ "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." + {
1356
+ start: z25.string().optional().describe(
1357
+ '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.'
1358
+ ),
1348
1359
  limit: z25.number().optional().default(50).describe("Maximum number of changes to return")
1349
1360
  },
1350
1361
  {
@@ -1429,16 +1440,7 @@ function getSiteStatsTool(server) {
1429
1440
  },
1430
1441
  async () => handleGetSiteStatsTool()
1431
1442
  );
1432
- tool.update({ outputSchema: { statistics: z27.object({
1433
- pages: z27.number(),
1434
- articles: z27.number(),
1435
- edits: z27.number(),
1436
- images: z27.number(),
1437
- users: z27.number(),
1438
- activeusers: z27.number().optional(),
1439
- admins: z27.number().optional(),
1440
- jobs: z27.number().optional()
1441
- }) } });
1443
+ tool.update({ outputSchema: { statistics: z27.record(z27.unknown()) } });
1442
1444
  return tool;
1443
1445
  }
1444
1446
  async function handleGetSiteStatsTool() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xiedada/nodemw-mcp-server",
3
- "version": "0.0.11",
3
+ "version": "0.1.0",
4
4
  "description": "MCP server for nodemw - MediaWiki API client",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",