@xiedada/nodemw-mcp-server 0.1.0 → 0.1.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/index.js +385 -106
- package/package.json +55 -47
package/dist/index.js
CHANGED
|
@@ -37,8 +37,16 @@ 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.1.
|
|
40
|
+
version: "0.1.2",
|
|
41
41
|
description: "MCP server for nodemw - MediaWiki API client",
|
|
42
|
+
repository: {
|
|
43
|
+
type: "git",
|
|
44
|
+
url: "git+https://github.com/TimXiedada/nodemw-mcp.git"
|
|
45
|
+
},
|
|
46
|
+
bugs: {
|
|
47
|
+
url: "https://github.com/TimXiedada/nodemw-mcp/issues"
|
|
48
|
+
},
|
|
49
|
+
homepage: "https://github.com/TimXiedada/nodemw-mcp#readme",
|
|
42
50
|
type: "module",
|
|
43
51
|
main: "dist/index.js",
|
|
44
52
|
bin: {
|
|
@@ -239,13 +247,13 @@ async function requireRead(title) {
|
|
|
239
247
|
}
|
|
240
248
|
|
|
241
249
|
// src/tools/ro/get-article.ts
|
|
242
|
-
async function recordReadState(
|
|
250
|
+
async function recordReadState(identifier) {
|
|
243
251
|
try {
|
|
244
252
|
const bot = await getBot();
|
|
245
253
|
const pages = await promisifyBotMethod(
|
|
246
254
|
bot,
|
|
247
255
|
"getArticleInfo",
|
|
248
|
-
|
|
256
|
+
identifier,
|
|
249
257
|
{ prop: "info" }
|
|
250
258
|
);
|
|
251
259
|
const page = Array.isArray(pages) ? pages[0] : null;
|
|
@@ -267,8 +275,9 @@ function getArticleTool(server) {
|
|
|
267
275
|
"get-article",
|
|
268
276
|
"Retrieve the content of a wiki article",
|
|
269
277
|
{
|
|
270
|
-
title: z.string().describe(
|
|
271
|
-
|
|
278
|
+
title: z.string().optional().describe('Article title (required if "id" is not provided)'),
|
|
279
|
+
id: z.number().optional().describe('Page ID (required if "title" is not provided)'),
|
|
280
|
+
followRedirect: z.boolean().optional().default(true).describe('Follow redirects (only applies when using "title")'),
|
|
272
281
|
redirectInfo: z.boolean().optional().default(false).describe("Include information about redirects"),
|
|
273
282
|
revision: z.number().optional().describe("Specific revision ID to fetch. If omitted, returns the latest version.")
|
|
274
283
|
},
|
|
@@ -277,21 +286,35 @@ function getArticleTool(server) {
|
|
|
277
286
|
readOnlyHint: true,
|
|
278
287
|
destructiveHint: false
|
|
279
288
|
},
|
|
280
|
-
async ({ title, followRedirect, redirectInfo, revision }) => handleGetArticleTool(title, followRedirect, redirectInfo, revision)
|
|
289
|
+
async ({ title, id, followRedirect, redirectInfo, revision }) => handleGetArticleTool(title, id, followRedirect, redirectInfo, revision)
|
|
281
290
|
);
|
|
282
291
|
}
|
|
283
|
-
async function handleGetArticleTool(title, followRedirect, redirectInfo, revision) {
|
|
292
|
+
async function handleGetArticleTool(title, id, followRedirect, redirectInfo, revision) {
|
|
284
293
|
try {
|
|
285
294
|
const bot = await getBot();
|
|
286
|
-
if (
|
|
295
|
+
if (!title && id == null) {
|
|
296
|
+
return {
|
|
297
|
+
content: [{ type: "text", text: 'Either "title" or "id" must be provided.' }],
|
|
298
|
+
isError: true
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
if (title && id != null) {
|
|
302
|
+
return {
|
|
303
|
+
content: [{ type: "text", text: 'Provide either "title" or "id", not both.' }],
|
|
304
|
+
isError: true
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
const useDirectApi = revision !== void 0 || id !== void 0;
|
|
308
|
+
if (useDirectApi) {
|
|
287
309
|
const params = {
|
|
288
310
|
action: "query",
|
|
289
311
|
prop: "revisions",
|
|
290
312
|
rvprop: "content",
|
|
291
|
-
rvstartid: revision,
|
|
292
313
|
rvlimit: 1,
|
|
293
|
-
titles: title,
|
|
294
|
-
...
|
|
314
|
+
...id !== void 0 ? { pageids: id } : { titles: title },
|
|
315
|
+
...revision !== void 0 && { rvstartid: revision },
|
|
316
|
+
// redirects param is ignored by MW API when pageids is used
|
|
317
|
+
...id === void 0 && followRedirect && { redirects: "" }
|
|
295
318
|
};
|
|
296
319
|
const info = await new Promise((resolve, reject) => {
|
|
297
320
|
bot.api.call(params, (err, info2) => {
|
|
@@ -302,22 +325,38 @@ async function handleGetArticleTool(title, followRedirect, redirectInfo, revisio
|
|
|
302
325
|
const pages = info.pages;
|
|
303
326
|
const page = getFirstItem(pages);
|
|
304
327
|
if (!page || page.missing !== void 0) {
|
|
328
|
+
const identifier = title ?? `id ${id}`;
|
|
305
329
|
return {
|
|
306
|
-
content: [{ type: "text", text: `Page "${
|
|
330
|
+
content: [{ type: "text", text: `Page "${identifier}" not found.` }],
|
|
307
331
|
isError: true
|
|
308
332
|
};
|
|
309
333
|
}
|
|
334
|
+
if (revision !== void 0) {
|
|
335
|
+
const revisions2 = page.revisions;
|
|
336
|
+
const rev = revisions2?.[0];
|
|
337
|
+
if (!rev || rev["*"] == null) {
|
|
338
|
+
const identifier = title ?? `id ${id}`;
|
|
339
|
+
return {
|
|
340
|
+
content: [{ type: "text", text: `Revision ${revision} not found for page "${identifier}".` }],
|
|
341
|
+
isError: true
|
|
342
|
+
};
|
|
343
|
+
}
|
|
344
|
+
await recordReadState(id ?? title);
|
|
345
|
+
return {
|
|
346
|
+
content: [{ type: "text", text: rev["*"] }]
|
|
347
|
+
};
|
|
348
|
+
}
|
|
310
349
|
const revisions = page.revisions;
|
|
311
|
-
const
|
|
312
|
-
if (
|
|
350
|
+
const content = revisions?.[0]?.["*"];
|
|
351
|
+
if (content == null) {
|
|
313
352
|
return {
|
|
314
|
-
content: [{ type: "text", text: `
|
|
315
|
-
isError:
|
|
353
|
+
content: [{ type: "text", text: page.title ? `Page "${page.title}" is empty.` : `Page ID ${id} is empty.` }],
|
|
354
|
+
isError: false
|
|
316
355
|
};
|
|
317
356
|
}
|
|
318
|
-
await recordReadState(title);
|
|
357
|
+
await recordReadState(id ?? title);
|
|
319
358
|
return {
|
|
320
|
-
content: [{ type: "text", text:
|
|
359
|
+
content: [{ type: "text", text: content === "" ? "(empty page)" : content }]
|
|
321
360
|
};
|
|
322
361
|
}
|
|
323
362
|
if (redirectInfo) {
|
|
@@ -712,21 +751,73 @@ function getArticleRevisionsTool(server) {
|
|
|
712
751
|
"get-article-revisions",
|
|
713
752
|
"Get all revisions of a wiki article",
|
|
714
753
|
{
|
|
715
|
-
title: z10.
|
|
754
|
+
title: z10.string().optional().describe('Article title (required if "id" is not provided)'),
|
|
755
|
+
id: z10.number().optional().describe('Page ID (required if "title" is not provided)')
|
|
716
756
|
},
|
|
717
757
|
{
|
|
718
758
|
title: "Get article revisions",
|
|
719
759
|
readOnlyHint: true,
|
|
720
760
|
destructiveHint: false
|
|
721
761
|
},
|
|
722
|
-
async ({ title }) => handleGetArticleRevisionsTool(title)
|
|
762
|
+
async ({ title, id }) => handleGetArticleRevisionsTool(title, id)
|
|
723
763
|
);
|
|
724
|
-
tool.update({ outputSchema: {
|
|
764
|
+
tool.update({ outputSchema: { identifier: z10.union([z10.string(), z10.number()]), revisions: z10.array(z10.record(z10.unknown())), count: z10.number() } });
|
|
725
765
|
return tool;
|
|
726
766
|
}
|
|
727
|
-
async function
|
|
767
|
+
async function apiCall(bot, params) {
|
|
768
|
+
return new Promise((resolve, reject) => {
|
|
769
|
+
bot.api.call(
|
|
770
|
+
params,
|
|
771
|
+
(err, data) => {
|
|
772
|
+
if (err) reject(err);
|
|
773
|
+
else resolve(data);
|
|
774
|
+
},
|
|
775
|
+
"GET"
|
|
776
|
+
);
|
|
777
|
+
});
|
|
778
|
+
}
|
|
779
|
+
async function handleGetArticleRevisionsTool(title, id) {
|
|
728
780
|
try {
|
|
781
|
+
if (!title && id == null) {
|
|
782
|
+
return errorResult('Either "title" or "id" must be provided');
|
|
783
|
+
}
|
|
784
|
+
if (title && id != null) {
|
|
785
|
+
return errorResult('Provide either "title" or "id", not both');
|
|
786
|
+
}
|
|
729
787
|
const bot = await getBot();
|
|
788
|
+
if (id !== void 0) {
|
|
789
|
+
const allRevisions2 = [];
|
|
790
|
+
let rvcontinue;
|
|
791
|
+
do {
|
|
792
|
+
const params = {
|
|
793
|
+
action: "query",
|
|
794
|
+
prop: "revisions",
|
|
795
|
+
pageids: id,
|
|
796
|
+
rvprop: "ids|timestamp|user|comment|size",
|
|
797
|
+
rvlimit: 500,
|
|
798
|
+
rvdir: "older"
|
|
799
|
+
};
|
|
800
|
+
if (rvcontinue) {
|
|
801
|
+
params.rvcontinue = rvcontinue;
|
|
802
|
+
}
|
|
803
|
+
const result = await apiCall(bot, params);
|
|
804
|
+
const pages = result.pages;
|
|
805
|
+
if (!pages) break;
|
|
806
|
+
const pageId = String(id);
|
|
807
|
+
const page = pages[pageId];
|
|
808
|
+
if (!page || page.missing !== void 0) break;
|
|
809
|
+
const revs = page.revisions;
|
|
810
|
+
if (revs) {
|
|
811
|
+
allRevisions2.push(...revs);
|
|
812
|
+
}
|
|
813
|
+
rvcontinue = result.continue?.rvcontinue;
|
|
814
|
+
} while (rvcontinue && allRevisions2.length < 1e4);
|
|
815
|
+
return jsonResult({
|
|
816
|
+
identifier: id,
|
|
817
|
+
revisions: allRevisions2,
|
|
818
|
+
count: allRevisions2.length
|
|
819
|
+
});
|
|
820
|
+
}
|
|
730
821
|
const allRevisions = await promisifyBotMethod(
|
|
731
822
|
bot,
|
|
732
823
|
"getArticleRevisions",
|
|
@@ -734,7 +825,7 @@ async function handleGetArticleRevisionsTool(title) {
|
|
|
734
825
|
);
|
|
735
826
|
const revisions = allRevisions.flat().filter((rev) => rev != null);
|
|
736
827
|
return jsonResult({
|
|
737
|
-
title,
|
|
828
|
+
identifier: title,
|
|
738
829
|
revisions,
|
|
739
830
|
count: revisions.length
|
|
740
831
|
});
|
|
@@ -750,21 +841,59 @@ function getArticleCategoriesTool(server) {
|
|
|
750
841
|
"get-article-categories",
|
|
751
842
|
"Get all categories that an article belongs to",
|
|
752
843
|
{
|
|
753
|
-
title: z11.
|
|
844
|
+
title: z11.string().optional().describe('Article title (required if "id" is not provided)'),
|
|
845
|
+
id: z11.number().optional().describe('Page ID (required if "title" is not provided)')
|
|
754
846
|
},
|
|
755
847
|
{
|
|
756
848
|
title: "Get article categories",
|
|
757
849
|
readOnlyHint: true,
|
|
758
850
|
destructiveHint: false
|
|
759
851
|
},
|
|
760
|
-
async ({ title }) => handleGetArticleCategoriesTool(title)
|
|
852
|
+
async ({ title, id }) => handleGetArticleCategoriesTool(title, id)
|
|
761
853
|
);
|
|
762
|
-
tool.update({ outputSchema: { title: z11.string(), categories: z11.array(z11.string()), count: z11.number() } });
|
|
854
|
+
tool.update({ outputSchema: { title: z11.union([z11.string(), z11.number()]), categories: z11.array(z11.string()), count: z11.number() } });
|
|
763
855
|
return tool;
|
|
764
856
|
}
|
|
765
|
-
|
|
857
|
+
function getFirstItem2(obj) {
|
|
858
|
+
if (!obj) return null;
|
|
859
|
+
for (const key in obj) {
|
|
860
|
+
return obj[key];
|
|
861
|
+
}
|
|
862
|
+
return null;
|
|
863
|
+
}
|
|
864
|
+
async function handleGetArticleCategoriesTool(title, id) {
|
|
766
865
|
try {
|
|
866
|
+
if (!title && id == null) {
|
|
867
|
+
return errorResult('Either "title" or "id" must be provided');
|
|
868
|
+
}
|
|
869
|
+
if (title && id != null) {
|
|
870
|
+
return errorResult('Provide either "title" or "id", not both');
|
|
871
|
+
}
|
|
767
872
|
const bot = await getBot();
|
|
873
|
+
if (id !== void 0) {
|
|
874
|
+
const result = await new Promise((resolve, reject) => {
|
|
875
|
+
bot.api.call(
|
|
876
|
+
{ action: "query", prop: "categories", pageids: id, cllimit: "max" },
|
|
877
|
+
(err, data) => {
|
|
878
|
+
if (err) reject(err);
|
|
879
|
+
else resolve(data);
|
|
880
|
+
},
|
|
881
|
+
"GET"
|
|
882
|
+
);
|
|
883
|
+
});
|
|
884
|
+
const pages = result.pages;
|
|
885
|
+
const page = getFirstItem2(pages);
|
|
886
|
+
if (!page || page.missing !== void 0) {
|
|
887
|
+
return errorResult(`Page with ID ${id} not found`);
|
|
888
|
+
}
|
|
889
|
+
const rawCategories = page.categories;
|
|
890
|
+
const categories2 = (rawCategories || []).map((c) => c.title);
|
|
891
|
+
return jsonResult({
|
|
892
|
+
title: page.title ?? id,
|
|
893
|
+
categories: categories2,
|
|
894
|
+
count: categories2.length
|
|
895
|
+
});
|
|
896
|
+
}
|
|
768
897
|
const categories = await promisifyBotMethod(
|
|
769
898
|
bot,
|
|
770
899
|
"getArticleCategories",
|
|
@@ -787,21 +916,56 @@ function getArticlePropertiesTool(server) {
|
|
|
787
916
|
"get-article-properties",
|
|
788
917
|
"Get page properties (page_props table data) for a wiki article",
|
|
789
918
|
{
|
|
790
|
-
title: z12.string().describe(
|
|
919
|
+
title: z12.string().optional().describe('Article title (required if "id" is not provided)'),
|
|
920
|
+
id: z12.number().optional().describe('Page ID (required if "title" is not provided)')
|
|
791
921
|
},
|
|
792
922
|
{
|
|
793
923
|
title: "Get article properties",
|
|
794
924
|
readOnlyHint: true,
|
|
795
925
|
destructiveHint: false
|
|
796
926
|
},
|
|
797
|
-
async ({ title }) => handleGetArticlePropertiesTool(title)
|
|
927
|
+
async ({ title, id }) => handleGetArticlePropertiesTool(title, id)
|
|
798
928
|
);
|
|
799
|
-
tool.update({ outputSchema: { title: z12.string(), properties: z12.record(z12.unknown()) } });
|
|
929
|
+
tool.update({ outputSchema: { title: z12.union([z12.string(), z12.number()]), properties: z12.record(z12.unknown()) } });
|
|
800
930
|
return tool;
|
|
801
931
|
}
|
|
802
|
-
|
|
932
|
+
function getFirstItem3(obj) {
|
|
933
|
+
if (!obj) return null;
|
|
934
|
+
for (const key in obj) {
|
|
935
|
+
return obj[key];
|
|
936
|
+
}
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
async function handleGetArticlePropertiesTool(title, id) {
|
|
803
940
|
try {
|
|
941
|
+
if (!title && id == null) {
|
|
942
|
+
return errorResult('Either "title" or "id" must be provided');
|
|
943
|
+
}
|
|
944
|
+
if (title && id != null) {
|
|
945
|
+
return errorResult('Provide either "title" or "id", not both');
|
|
946
|
+
}
|
|
804
947
|
const bot = await getBot();
|
|
948
|
+
if (id !== void 0) {
|
|
949
|
+
const result = await new Promise((resolve, reject) => {
|
|
950
|
+
bot.api.call(
|
|
951
|
+
{ action: "query", prop: "pageprops", pageids: id },
|
|
952
|
+
(err, data) => {
|
|
953
|
+
if (err) reject(err);
|
|
954
|
+
else resolve(data);
|
|
955
|
+
},
|
|
956
|
+
"GET"
|
|
957
|
+
);
|
|
958
|
+
});
|
|
959
|
+
const pages = result.pages;
|
|
960
|
+
const page = getFirstItem3(pages);
|
|
961
|
+
if (!page || page.missing !== void 0) {
|
|
962
|
+
return errorResult(`Page with ID ${id} not found`);
|
|
963
|
+
}
|
|
964
|
+
return jsonResult({
|
|
965
|
+
title: page.title ?? id,
|
|
966
|
+
properties: page.pageprops || {}
|
|
967
|
+
});
|
|
968
|
+
}
|
|
805
969
|
const properties = await promisifyBotMethod(
|
|
806
970
|
bot,
|
|
807
971
|
"getArticleProperties",
|
|
@@ -825,9 +989,12 @@ function getArticleInfoTool(server) {
|
|
|
825
989
|
{
|
|
826
990
|
title: z13.union([
|
|
827
991
|
z13.string(),
|
|
992
|
+
z13.array(z13.string())
|
|
993
|
+
]).optional().describe('Article title or array of titles (required if "id" is not provided)'),
|
|
994
|
+
id: z13.union([
|
|
828
995
|
z13.number(),
|
|
829
|
-
z13.array(z13.
|
|
830
|
-
]).describe(
|
|
996
|
+
z13.array(z13.number())
|
|
997
|
+
]).optional().describe('Page ID or array of page IDs (required if "title" is not provided)'),
|
|
831
998
|
properties: z13.array(z13.string()).optional().describe("Specific properties to retrieve (e.g. protection, talkid, url)")
|
|
832
999
|
},
|
|
833
1000
|
{
|
|
@@ -835,24 +1002,58 @@ function getArticleInfoTool(server) {
|
|
|
835
1002
|
readOnlyHint: true,
|
|
836
1003
|
destructiveHint: false
|
|
837
1004
|
},
|
|
838
|
-
async ({ title, properties }) => handleGetArticleInfoTool(title, properties)
|
|
1005
|
+
async ({ title, id, properties }) => handleGetArticleInfoTool(title, id, properties)
|
|
839
1006
|
);
|
|
840
|
-
tool.update({ outputSchema: {
|
|
1007
|
+
tool.update({ outputSchema: { identifier: z13.union([z13.string(), z13.number(), z13.array(z13.unknown())]), results: z13.array(z13.record(z13.unknown())), count: z13.number() } });
|
|
841
1008
|
return tool;
|
|
842
1009
|
}
|
|
843
|
-
async function handleGetArticleInfoTool(title, properties) {
|
|
1010
|
+
async function handleGetArticleInfoTool(title, id, properties) {
|
|
844
1011
|
try {
|
|
1012
|
+
const hasTitle = typeof title === "string" ? title.length > 0 : Array.isArray(title) ? title.length > 0 : false;
|
|
1013
|
+
const hasId = typeof id === "number" ? id > 0 : Array.isArray(id) ? id.length > 0 : false;
|
|
1014
|
+
if (!hasTitle && !hasId) {
|
|
1015
|
+
return errorResult('Either "title" or "id" must be provided');
|
|
1016
|
+
}
|
|
1017
|
+
if (hasTitle && hasId) {
|
|
1018
|
+
return errorResult('Provide either "title" or "id", not both');
|
|
1019
|
+
}
|
|
845
1020
|
const bot = await getBot();
|
|
846
|
-
|
|
1021
|
+
if (hasId) {
|
|
1022
|
+
const ids = Array.isArray(id) ? id : [id];
|
|
1023
|
+
const pageids = ids.join("|");
|
|
1024
|
+
const apiParams = {
|
|
1025
|
+
action: "query",
|
|
1026
|
+
prop: "info",
|
|
1027
|
+
pageids,
|
|
1028
|
+
inprop: properties?.join("|") || "protection|talkid|url"
|
|
1029
|
+
};
|
|
1030
|
+
const result = await new Promise((resolve, reject) => {
|
|
1031
|
+
bot.api.call(
|
|
1032
|
+
apiParams,
|
|
1033
|
+
(err, data) => {
|
|
1034
|
+
if (err) reject(err);
|
|
1035
|
+
else resolve(data);
|
|
1036
|
+
},
|
|
1037
|
+
"GET"
|
|
1038
|
+
);
|
|
1039
|
+
});
|
|
1040
|
+
const pages = result.pages;
|
|
1041
|
+
const results2 = pages ? Object.values(pages).filter((p) => p.missing === void 0) : [];
|
|
1042
|
+
return jsonResult({
|
|
1043
|
+
identifier: Array.isArray(id) ? id : id,
|
|
1044
|
+
results: results2,
|
|
1045
|
+
count: results2.length
|
|
1046
|
+
});
|
|
1047
|
+
}
|
|
847
1048
|
const info = await promisifyBotMethod(
|
|
848
1049
|
bot,
|
|
849
1050
|
"getArticleInfo",
|
|
850
1051
|
title,
|
|
851
|
-
|
|
1052
|
+
{}
|
|
852
1053
|
);
|
|
853
1054
|
const results = Array.isArray(info) ? info : [info];
|
|
854
1055
|
return jsonResult({
|
|
855
|
-
title,
|
|
1056
|
+
identifier: title,
|
|
856
1057
|
results,
|
|
857
1058
|
count: results.length
|
|
858
1059
|
});
|
|
@@ -1212,7 +1413,8 @@ import { z as z22 } from "zod";
|
|
|
1212
1413
|
function getLogTool(server) {
|
|
1213
1414
|
const tool = server.tool(
|
|
1214
1415
|
"get-log",
|
|
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."
|
|
1416
|
+
"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.",
|
|
1417
|
+
{
|
|
1216
1418
|
type: z22.string().describe("Log type (e.g. delete, block, move)"),
|
|
1217
1419
|
start: z22.string().optional().describe(
|
|
1218
1420
|
'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.'
|
|
@@ -1233,7 +1435,7 @@ async function handleGetLogTool(type, start, limit = 50) {
|
|
|
1233
1435
|
try {
|
|
1234
1436
|
const bot = await getBot();
|
|
1235
1437
|
const entries = await new Promise((resolve, reject) => {
|
|
1236
|
-
bot.getLog(type, start, (err, ...args) => {
|
|
1438
|
+
bot.getLog(type, start || "", (err, ...args) => {
|
|
1237
1439
|
if (err) {
|
|
1238
1440
|
reject(err);
|
|
1239
1441
|
} else {
|
|
@@ -1352,7 +1554,8 @@ import { z as z25 } from "zod";
|
|
|
1352
1554
|
function getRecentChangesTool(server) {
|
|
1353
1555
|
const tool = server.tool(
|
|
1354
1556
|
"get-recent-changes",
|
|
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."
|
|
1557
|
+
"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.",
|
|
1558
|
+
{
|
|
1356
1559
|
start: z25.string().optional().describe(
|
|
1357
1560
|
'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
1561
|
),
|
|
@@ -1597,20 +1800,95 @@ async function handleGetBacklinksTool(title) {
|
|
|
1597
1800
|
}
|
|
1598
1801
|
}
|
|
1599
1802
|
|
|
1600
|
-
// src/tools/
|
|
1803
|
+
// src/tools/ro/get-article-by-revision.ts
|
|
1601
1804
|
import { z as z32 } from "zod";
|
|
1805
|
+
function getArticleByRevisionTool(server) {
|
|
1806
|
+
return server.tool(
|
|
1807
|
+
"get-article-by-revision",
|
|
1808
|
+
"Retrieve the content of a wiki article by a specific revision ID, without needing the page title or ID",
|
|
1809
|
+
{
|
|
1810
|
+
revision: z32.number().describe("Revision ID to fetch")
|
|
1811
|
+
},
|
|
1812
|
+
{
|
|
1813
|
+
title: "Get article by revision",
|
|
1814
|
+
readOnlyHint: true,
|
|
1815
|
+
destructiveHint: false
|
|
1816
|
+
},
|
|
1817
|
+
async ({ revision }) => handleGetArticleByRevisionTool(revision)
|
|
1818
|
+
);
|
|
1819
|
+
}
|
|
1820
|
+
async function handleGetArticleByRevisionTool(revision) {
|
|
1821
|
+
try {
|
|
1822
|
+
const bot = await getBot();
|
|
1823
|
+
const result = await new Promise((resolve, reject) => {
|
|
1824
|
+
bot.api.call(
|
|
1825
|
+
{
|
|
1826
|
+
action: "query",
|
|
1827
|
+
prop: "revisions",
|
|
1828
|
+
rvprop: "content",
|
|
1829
|
+
revids: revision
|
|
1830
|
+
},
|
|
1831
|
+
(err, data) => {
|
|
1832
|
+
if (err) reject(err);
|
|
1833
|
+
else resolve(data);
|
|
1834
|
+
},
|
|
1835
|
+
"GET"
|
|
1836
|
+
);
|
|
1837
|
+
});
|
|
1838
|
+
const pages = result.pages;
|
|
1839
|
+
if (!pages) {
|
|
1840
|
+
return {
|
|
1841
|
+
content: [{ type: "text", text: `Revision ${revision} not found.` }],
|
|
1842
|
+
isError: true
|
|
1843
|
+
};
|
|
1844
|
+
}
|
|
1845
|
+
const pageIds = Object.keys(pages);
|
|
1846
|
+
if (pageIds.length === 0) {
|
|
1847
|
+
return {
|
|
1848
|
+
content: [{ type: "text", text: `Revision ${revision} not found.` }],
|
|
1849
|
+
isError: true
|
|
1850
|
+
};
|
|
1851
|
+
}
|
|
1852
|
+
const page = pages[pageIds[0]];
|
|
1853
|
+
if (!page || page.missing !== void 0) {
|
|
1854
|
+
return {
|
|
1855
|
+
content: [{ type: "text", text: `Revision ${revision} not found.` }],
|
|
1856
|
+
isError: true
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
const revisions = page.revisions;
|
|
1860
|
+
const rev = revisions?.[0];
|
|
1861
|
+
if (!rev || rev["*"] == null) {
|
|
1862
|
+
return {
|
|
1863
|
+
content: [{ type: "text", text: `Revision ${revision} not found or has no content.` }],
|
|
1864
|
+
isError: true
|
|
1865
|
+
};
|
|
1866
|
+
}
|
|
1867
|
+
return {
|
|
1868
|
+
content: [{ type: "text", text: rev["*"] }]
|
|
1869
|
+
};
|
|
1870
|
+
} catch (error) {
|
|
1871
|
+
return {
|
|
1872
|
+
content: [{ type: "text", text: `Error: ${error.message}` }],
|
|
1873
|
+
isError: true
|
|
1874
|
+
};
|
|
1875
|
+
}
|
|
1876
|
+
}
|
|
1877
|
+
|
|
1878
|
+
// src/tools/editing/edit.ts
|
|
1879
|
+
import { z as z33 } from "zod";
|
|
1602
1880
|
function editTool(server) {
|
|
1603
1881
|
const tool = server.tool(
|
|
1604
1882
|
"edit",
|
|
1605
1883
|
"Replace the ENTIRE content of a wiki page (requires authentication). CRITICAL: This is a FULL replacement \u2014 content you provide becomes the complete page, not an addition. There is NO undelete/undo tool \u2014 any damage you cause must be manually reverted by a human. To add a category or make a small change, you MUST first call get-article to retrieve the current content, modify it as needed, then pass the FULL modified content here. For appending or prepending without fetching the full page first, use the append/prepend tools instead.",
|
|
1606
1884
|
{
|
|
1607
|
-
title:
|
|
1608
|
-
content:
|
|
1609
|
-
intent:
|
|
1885
|
+
title: z33.string().describe("Page title to edit"),
|
|
1886
|
+
content: z33.string().describe("COMPLETE new wikitext for the ENTIRE page \u2014 not a snippet, not a prefix, not an appendage. This replaces everything. Always fetch the current content with get-article first, then modify and resubmit the full text."),
|
|
1887
|
+
intent: z33.enum(["add", "revise", "delete"]).describe(
|
|
1610
1888
|
'Your editing intent: "add" = adding content (page should grow), "revise" = modifying content (small net change, must keep \u22653/4 of existing bytes), "delete" = removing significant content (page should shrink significantly)'
|
|
1611
1889
|
),
|
|
1612
|
-
summary:
|
|
1613
|
-
minor:
|
|
1890
|
+
summary: z33.string().describe("Edit summary describing what was changed and why"),
|
|
1891
|
+
minor: z33.boolean().optional().default(false).describe("Mark as minor edit")
|
|
1614
1892
|
},
|
|
1615
1893
|
{
|
|
1616
1894
|
title: "Edit page",
|
|
@@ -1619,7 +1897,7 @@ function editTool(server) {
|
|
|
1619
1897
|
},
|
|
1620
1898
|
async (params) => handleEditTool(params)
|
|
1621
1899
|
);
|
|
1622
|
-
tool.update({ outputSchema: { result:
|
|
1900
|
+
tool.update({ outputSchema: { result: z33.string(), pageid: z33.number(), title: z33.string(), contentmodel: z33.string().optional(), newrevid: z33.number(), newtimestamp: z33.string().optional(), oldrevid: z33.number().optional() } });
|
|
1623
1901
|
return tool;
|
|
1624
1902
|
}
|
|
1625
1903
|
async function handleEditTool(params) {
|
|
@@ -1673,15 +1951,15 @@ async function handleEditTool(params) {
|
|
|
1673
1951
|
}
|
|
1674
1952
|
|
|
1675
1953
|
// src/tools/editing/append.ts
|
|
1676
|
-
import { z as
|
|
1954
|
+
import { z as z34 } from "zod";
|
|
1677
1955
|
function appendTool(server) {
|
|
1678
1956
|
const tool = server.tool(
|
|
1679
1957
|
"append",
|
|
1680
1958
|
"Append content to the END of a wiki page without changing existing content (requires authentication). Safe for adding categories, interwiki links, or any content that belongs at the bottom of a page.",
|
|
1681
1959
|
{
|
|
1682
|
-
title:
|
|
1683
|
-
content:
|
|
1684
|
-
summary:
|
|
1960
|
+
title: z34.string().describe("Page title"),
|
|
1961
|
+
content: z34.string().describe('Content to append to the end of the page (e.g., "\\n[[Category:MyCategory]]")'),
|
|
1962
|
+
summary: z34.string().describe("Edit summary")
|
|
1685
1963
|
},
|
|
1686
1964
|
{
|
|
1687
1965
|
title: "Append to page",
|
|
@@ -1690,7 +1968,7 @@ function appendTool(server) {
|
|
|
1690
1968
|
},
|
|
1691
1969
|
async (params) => handleAppendTool(params)
|
|
1692
1970
|
);
|
|
1693
|
-
tool.update({ outputSchema: { success:
|
|
1971
|
+
tool.update({ outputSchema: { success: z34.boolean(), title: z34.string() } });
|
|
1694
1972
|
return tool;
|
|
1695
1973
|
}
|
|
1696
1974
|
async function handleAppendTool(params) {
|
|
@@ -1712,15 +1990,15 @@ async function handleAppendTool(params) {
|
|
|
1712
1990
|
}
|
|
1713
1991
|
|
|
1714
1992
|
// src/tools/editing/prepend.ts
|
|
1715
|
-
import { z as
|
|
1993
|
+
import { z as z35 } from "zod";
|
|
1716
1994
|
function prependTool(server) {
|
|
1717
1995
|
const tool = server.tool(
|
|
1718
1996
|
"prepend",
|
|
1719
1997
|
"Prepend content to the TOP of a wiki page without changing existing content (requires authentication). Useful for adding notices, templates, or cleanup tags that belong at the top of a page.",
|
|
1720
1998
|
{
|
|
1721
|
-
title:
|
|
1722
|
-
content:
|
|
1723
|
-
summary:
|
|
1999
|
+
title: z35.string().describe("Page title to prepend to"),
|
|
2000
|
+
content: z35.string().describe('Content to prepend to the top of the page (e.g., "{{Cleanup}}\\n")'),
|
|
2001
|
+
summary: z35.string().describe("Edit summary")
|
|
1724
2002
|
},
|
|
1725
2003
|
{
|
|
1726
2004
|
title: "Prepend to page",
|
|
@@ -1729,7 +2007,7 @@ function prependTool(server) {
|
|
|
1729
2007
|
},
|
|
1730
2008
|
async (params) => handlePrependTool(params)
|
|
1731
2009
|
);
|
|
1732
|
-
tool.update({ outputSchema: { result:
|
|
2010
|
+
tool.update({ outputSchema: { result: z35.string(), pageid: z35.number(), title: z35.string(), contentmodel: z35.string().optional(), newrevid: z35.number(), newtimestamp: z35.string().optional(), oldrevid: z35.number().optional() } });
|
|
1733
2011
|
return tool;
|
|
1734
2012
|
}
|
|
1735
2013
|
async function handlePrependTool(params) {
|
|
@@ -1751,15 +2029,15 @@ async function handlePrependTool(params) {
|
|
|
1751
2029
|
}
|
|
1752
2030
|
|
|
1753
2031
|
// src/tools/editing/move.ts
|
|
1754
|
-
import { z as
|
|
2032
|
+
import { z as z36 } from "zod";
|
|
1755
2033
|
function moveTool(server) {
|
|
1756
2034
|
const tool = server.tool(
|
|
1757
2035
|
"move",
|
|
1758
2036
|
"Move (rename) a wiki page \u2014 changes the page title and creates a redirect from the old name (requires authentication). The old page title becomes a redirect to the new title. All page history moves with the page.",
|
|
1759
2037
|
{
|
|
1760
|
-
from:
|
|
1761
|
-
to:
|
|
1762
|
-
summary:
|
|
2038
|
+
from: z36.string().describe("Current/existing page title to rename"),
|
|
2039
|
+
to: z36.string().describe("New target page title \u2014 must not already exist (unless moving to overwrite)"),
|
|
2040
|
+
summary: z36.string().describe("Reason for the move (visible in move log)")
|
|
1763
2041
|
},
|
|
1764
2042
|
{
|
|
1765
2043
|
title: "Move page",
|
|
@@ -1768,7 +2046,7 @@ function moveTool(server) {
|
|
|
1768
2046
|
},
|
|
1769
2047
|
async (params) => handleMoveTool(params)
|
|
1770
2048
|
);
|
|
1771
|
-
tool.update({ outputSchema: { from:
|
|
2049
|
+
tool.update({ outputSchema: { from: z36.string(), to: z36.string(), reason: z36.string(), redirectcreated: z36.boolean().optional() } });
|
|
1772
2050
|
return tool;
|
|
1773
2051
|
}
|
|
1774
2052
|
async function handleMoveTool(params) {
|
|
@@ -1790,14 +2068,14 @@ async function handleMoveTool(params) {
|
|
|
1790
2068
|
}
|
|
1791
2069
|
|
|
1792
2070
|
// src/tools/editing/delete.ts
|
|
1793
|
-
import { z as
|
|
2071
|
+
import { z as z37 } from "zod";
|
|
1794
2072
|
function deleteTool(server) {
|
|
1795
2073
|
const tool = server.tool(
|
|
1796
2074
|
"delete",
|
|
1797
2075
|
"PERMANENTLY delete a wiki page (requires authentication). CRITICAL: This action is IRREVERSIBLE \u2014 there is NO undelete/undo tool available. Any deletion must be manually restored by a human administrator. Only delete a page when the user explicitly asks for it. Always verify the title is correct before proceeding.",
|
|
1798
2076
|
{
|
|
1799
|
-
title:
|
|
1800
|
-
reason:
|
|
2077
|
+
title: z37.string().describe("Exact page title to permanently delete \u2014 double-check this is correct"),
|
|
2078
|
+
reason: z37.string().describe("Detailed reason for deletion (visible in deletion log)")
|
|
1801
2079
|
},
|
|
1802
2080
|
{
|
|
1803
2081
|
title: "Delete page",
|
|
@@ -1806,7 +2084,7 @@ function deleteTool(server) {
|
|
|
1806
2084
|
},
|
|
1807
2085
|
async (params) => handleDeleteTool(params)
|
|
1808
2086
|
);
|
|
1809
|
-
tool.update({ outputSchema: { title:
|
|
2087
|
+
tool.update({ outputSchema: { title: z37.string(), reason: z37.string(), logid: z37.number().optional() } });
|
|
1810
2088
|
return tool;
|
|
1811
2089
|
}
|
|
1812
2090
|
async function handleDeleteTool(params) {
|
|
@@ -1827,24 +2105,24 @@ async function handleDeleteTool(params) {
|
|
|
1827
2105
|
}
|
|
1828
2106
|
|
|
1829
2107
|
// src/tools/editing/protect.ts
|
|
1830
|
-
import { z as
|
|
2108
|
+
import { z as z38 } from "zod";
|
|
1831
2109
|
function protectTool(server) {
|
|
1832
2110
|
const tool = server.tool(
|
|
1833
2111
|
"protect",
|
|
1834
2112
|
'Protect or unprotect a wiki page to restrict editing/moving (requires authentication). CRITICAL: Protection can lock out legitimate editors \u2014 only protect pages when there is a clear need (ongoing vandalism, edit war, high-risk template, policy page). To remove protection, set level to "all". Available levels: "all" (anyone), "autoconfirmed" (trusted users), "sysop" (admins only).',
|
|
1835
2113
|
{
|
|
1836
|
-
title:
|
|
1837
|
-
protections:
|
|
1838
|
-
|
|
1839
|
-
type:
|
|
1840
|
-
level:
|
|
2114
|
+
title: z38.string().describe("Page title to protect or unprotect"),
|
|
2115
|
+
protections: z38.array(
|
|
2116
|
+
z38.object({
|
|
2117
|
+
type: z38.enum(["edit", "move"]).describe('Action to restrict: "edit" or "move"'),
|
|
2118
|
+
level: z38.enum(["all", "autoconfirmed", "sysop"]).optional().default("all").describe(
|
|
1841
2119
|
'Who can perform this action: "all" = no restriction, "autoconfirmed" = trusted users only, "sysop" = admins only'
|
|
1842
2120
|
),
|
|
1843
|
-
expiry:
|
|
2121
|
+
expiry: z38.string().optional().describe('How long protection lasts (e.g. "1 day", "1 week", "infinite"). Default is indefinite.')
|
|
1844
2122
|
})
|
|
1845
2123
|
).describe('Protection rules \u2014 typically one entry for "edit" and optionally one for "move". Example: [{type:"edit",level:"sysop",expiry:"1 week"}]'),
|
|
1846
|
-
reason:
|
|
1847
|
-
cascade:
|
|
2124
|
+
reason: z38.string().optional().describe("Reason for changing protection, visible in the page log"),
|
|
2125
|
+
cascade: z38.boolean().optional().default(false).describe("If true, transcluded templates/pages inherit this protection. Only works with full sysop protection on edit. Use with caution.")
|
|
1848
2126
|
},
|
|
1849
2127
|
{
|
|
1850
2128
|
title: "Protect page",
|
|
@@ -1853,7 +2131,7 @@ function protectTool(server) {
|
|
|
1853
2131
|
},
|
|
1854
2132
|
async (params) => handleProtectTool(params)
|
|
1855
2133
|
);
|
|
1856
|
-
tool.update({ outputSchema: { title:
|
|
2134
|
+
tool.update({ outputSchema: { title: z38.string(), reason: z38.string().optional(), protections: z38.array(z38.record(z38.unknown())), cascade: z38.boolean().optional() } });
|
|
1857
2135
|
return tool;
|
|
1858
2136
|
}
|
|
1859
2137
|
async function handleProtectTool(params) {
|
|
@@ -1881,13 +2159,13 @@ async function handleProtectTool(params) {
|
|
|
1881
2159
|
}
|
|
1882
2160
|
|
|
1883
2161
|
// src/tools/editing/purge.ts
|
|
1884
|
-
import { z as
|
|
2162
|
+
import { z as z39 } from "zod";
|
|
1885
2163
|
function purgeTool(server) {
|
|
1886
2164
|
const tool = server.tool(
|
|
1887
2165
|
"purge",
|
|
1888
2166
|
"Purge the server-side cache for one or more wiki pages (requires authentication). Forces MediaWiki to regenerate the page from current wikitext. This is a safe, non-destructive action.",
|
|
1889
2167
|
{
|
|
1890
|
-
titles:
|
|
2168
|
+
titles: z39.union([z39.string(), z39.array(z39.string())]).describe("Page title(s) or category name to purge")
|
|
1891
2169
|
},
|
|
1892
2170
|
{
|
|
1893
2171
|
title: "Purge pages",
|
|
@@ -1896,7 +2174,7 @@ function purgeTool(server) {
|
|
|
1896
2174
|
},
|
|
1897
2175
|
async (params) => handlePurgeTool(params)
|
|
1898
2176
|
);
|
|
1899
|
-
tool.update({ outputSchema: { pages:
|
|
2177
|
+
tool.update({ outputSchema: { pages: z39.array(z39.record(z39.unknown())) } });
|
|
1900
2178
|
return tool;
|
|
1901
2179
|
}
|
|
1902
2180
|
async function handlePurgeTool(params) {
|
|
@@ -1914,15 +2192,15 @@ async function handlePurgeTool(params) {
|
|
|
1914
2192
|
}
|
|
1915
2193
|
|
|
1916
2194
|
// src/tools/editing/send-email.ts
|
|
1917
|
-
import { z as
|
|
2195
|
+
import { z as z40 } from "zod";
|
|
1918
2196
|
function sendEmailTool(server) {
|
|
1919
2197
|
const tool = server.tool(
|
|
1920
2198
|
"send-email",
|
|
1921
2199
|
"Send an ACTUAL email to a wiki user via the wiki's built-in email system (requires authentication). CRITICAL: This sends a real email to the user's registered address \u2014 it is NOT a simulation. The recipient will see it came from the authenticated bot operator's wiki account. Abuse (spam, harassment, unsolicited messages) WILL result in the bot account being blocked. ONLY use this when the human user has explicitly asked you to send an email.",
|
|
1922
2200
|
{
|
|
1923
|
-
username:
|
|
1924
|
-
subject:
|
|
1925
|
-
text:
|
|
2201
|
+
username: z40.string().describe("Target wiki username \u2014 email goes to their registered email address"),
|
|
2202
|
+
subject: z40.string().describe("Email subject line \u2014 be clear and professional, no misleading subjects"),
|
|
2203
|
+
text: z40.string().describe("Plain text email body \u2014 will be delivered as-is to the recipient's inbox")
|
|
1926
2204
|
},
|
|
1927
2205
|
{
|
|
1928
2206
|
title: "Send email",
|
|
@@ -1931,7 +2209,7 @@ function sendEmailTool(server) {
|
|
|
1931
2209
|
},
|
|
1932
2210
|
async (params) => handleSendEmailTool(params)
|
|
1933
2211
|
);
|
|
1934
|
-
tool.update({ outputSchema: { result:
|
|
2212
|
+
tool.update({ outputSchema: { result: z40.string(), message: z40.string().optional() } });
|
|
1935
2213
|
return tool;
|
|
1936
2214
|
}
|
|
1937
2215
|
async function handleSendEmailTool(params) {
|
|
@@ -1951,15 +2229,15 @@ async function handleSendEmailTool(params) {
|
|
|
1951
2229
|
}
|
|
1952
2230
|
|
|
1953
2231
|
// src/tools/editing/upload.ts
|
|
1954
|
-
import { z as
|
|
2232
|
+
import { z as z41 } from "zod";
|
|
1955
2233
|
function uploadTool(server) {
|
|
1956
2234
|
const tool = server.tool(
|
|
1957
2235
|
"upload",
|
|
1958
2236
|
"Upload a file to the wiki (requires authentication). CRITICAL: If a file with the same name already exists, it WILL BE OVERWRITTEN. Ensure you have the right to upload the content. Use only when explicitly requested.",
|
|
1959
2237
|
{
|
|
1960
|
-
filename:
|
|
1961
|
-
content:
|
|
1962
|
-
comment:
|
|
2238
|
+
filename: z41.string().describe('Destination filename on wiki (e.g., "MyImage.png") \u2014 existing file will be overwritten!'),
|
|
2239
|
+
content: z41.string().describe("File content encoded as base64 string"),
|
|
2240
|
+
comment: z41.string().optional().describe("Upload comment describing the file")
|
|
1963
2241
|
},
|
|
1964
2242
|
{
|
|
1965
2243
|
title: "Upload file",
|
|
@@ -1968,7 +2246,7 @@ function uploadTool(server) {
|
|
|
1968
2246
|
},
|
|
1969
2247
|
async (params) => handleUploadTool(params)
|
|
1970
2248
|
);
|
|
1971
|
-
tool.update({ outputSchema: { result:
|
|
2249
|
+
tool.update({ outputSchema: { result: z41.string(), filename: z41.string(), imageinfo: z41.record(z41.unknown()).optional() } });
|
|
1972
2250
|
return tool;
|
|
1973
2251
|
}
|
|
1974
2252
|
async function handleUploadTool(params) {
|
|
@@ -1990,15 +2268,15 @@ async function handleUploadTool(params) {
|
|
|
1990
2268
|
}
|
|
1991
2269
|
|
|
1992
2270
|
// src/tools/editing/upload-by-url.ts
|
|
1993
|
-
import { z as
|
|
2271
|
+
import { z as z42 } from "zod";
|
|
1994
2272
|
function uploadByUrlTool(server) {
|
|
1995
2273
|
const tool = server.tool(
|
|
1996
2274
|
"upload-by-url",
|
|
1997
2275
|
"Upload a file to the wiki by downloading it from a URL (requires authentication). CRITICAL: If a file with the same name already exists, it WILL BE OVERWRITTEN. Ensure you have the right to upload the content from the source URL.",
|
|
1998
2276
|
{
|
|
1999
|
-
filename:
|
|
2000
|
-
url:
|
|
2001
|
-
summary:
|
|
2277
|
+
filename: z42.string().describe('Destination filename on wiki (e.g., "Diagram.png") \u2014 existing file will be overwritten!'),
|
|
2278
|
+
url: z42.string().url().describe("Source URL to download the file from \u2014 must be publicly accessible"),
|
|
2279
|
+
summary: z42.string().optional().describe("Upload summary")
|
|
2002
2280
|
},
|
|
2003
2281
|
{
|
|
2004
2282
|
title: "Upload file by URL",
|
|
@@ -2007,7 +2285,7 @@ function uploadByUrlTool(server) {
|
|
|
2007
2285
|
},
|
|
2008
2286
|
async (params) => handleUploadByUrlTool(params)
|
|
2009
2287
|
);
|
|
2010
|
-
tool.update({ outputSchema: { result:
|
|
2288
|
+
tool.update({ outputSchema: { result: z42.string(), filename: z42.string(), imageinfo: z42.record(z42.unknown()).optional() } });
|
|
2011
2289
|
return tool;
|
|
2012
2290
|
}
|
|
2013
2291
|
async function handleUploadByUrlTool(params) {
|
|
@@ -2028,15 +2306,15 @@ async function handleUploadByUrlTool(params) {
|
|
|
2028
2306
|
}
|
|
2029
2307
|
|
|
2030
2308
|
// src/tools/editing/add-flow-topic.ts
|
|
2031
|
-
import { z as
|
|
2309
|
+
import { z as z43 } from "zod";
|
|
2032
2310
|
function addFlowTopicTool(server) {
|
|
2033
2311
|
const tool = server.tool(
|
|
2034
2312
|
"add-flow-topic",
|
|
2035
2313
|
"Add a new Flow/Structured Discussions topic to a wiki talk page (requires authentication). Creates a publicly visible discussion thread on the wiki. Ensure the content is appropriate and relevant.",
|
|
2036
2314
|
{
|
|
2037
|
-
title:
|
|
2038
|
-
subject:
|
|
2039
|
-
content:
|
|
2315
|
+
title: z43.string().describe('Talk page title to add the topic to (e.g., "Talk:Main Page")'),
|
|
2316
|
+
subject: z43.string().describe("Topic title/heading \u2014 should summarize the discussion topic"),
|
|
2317
|
+
content: z43.string().describe("Topic body content in wikitext format")
|
|
2040
2318
|
},
|
|
2041
2319
|
{
|
|
2042
2320
|
title: "Add Flow topic",
|
|
@@ -2045,7 +2323,7 @@ function addFlowTopicTool(server) {
|
|
|
2045
2323
|
},
|
|
2046
2324
|
async (params) => handleAddFlowTopicTool(params)
|
|
2047
2325
|
);
|
|
2048
|
-
tool.update({ outputSchema: { "new-topic":
|
|
2326
|
+
tool.update({ outputSchema: { "new-topic": z43.record(z43.unknown()) } });
|
|
2049
2327
|
return tool;
|
|
2050
2328
|
}
|
|
2051
2329
|
async function handleAddFlowTopicTool(params) {
|
|
@@ -2065,14 +2343,14 @@ async function handleAddFlowTopicTool(params) {
|
|
|
2065
2343
|
}
|
|
2066
2344
|
|
|
2067
2345
|
// src/tools/editing/create-account.ts
|
|
2068
|
-
import { z as
|
|
2346
|
+
import { z as z44 } from "zod";
|
|
2069
2347
|
function createAccountTool(server) {
|
|
2070
2348
|
const tool = server.tool(
|
|
2071
2349
|
"create-account",
|
|
2072
2350
|
"Create a NEW user account on the wiki (requires authentication). CRITICAL: This creates a real user account. Do NOT create accounts for yourself or without explicit user request. The account will be permanently registered on the wiki.",
|
|
2073
2351
|
{
|
|
2074
|
-
username:
|
|
2075
|
-
password:
|
|
2352
|
+
username: z44.string().describe("Desired username for the new account \u2014 must follow wiki username rules"),
|
|
2353
|
+
password: z44.string().describe("Password for the new account \u2014 use a strong, unique password")
|
|
2076
2354
|
},
|
|
2077
2355
|
{
|
|
2078
2356
|
title: "Create user account",
|
|
@@ -2081,7 +2359,7 @@ function createAccountTool(server) {
|
|
|
2081
2359
|
},
|
|
2082
2360
|
async (params) => handleCreateAccountTool(params)
|
|
2083
2361
|
);
|
|
2084
|
-
tool.update({ outputSchema: { account:
|
|
2362
|
+
tool.update({ outputSchema: { account: z44.record(z44.unknown()) } });
|
|
2085
2363
|
return tool;
|
|
2086
2364
|
}
|
|
2087
2365
|
async function handleCreateAccountTool(params) {
|
|
@@ -2131,7 +2409,8 @@ var readToolRegistrars = [
|
|
|
2131
2409
|
getMediaWikiVersionTool,
|
|
2132
2410
|
getQueryPageTool,
|
|
2133
2411
|
getExternalLinksTool,
|
|
2134
|
-
getBacklinksTool
|
|
2412
|
+
getBacklinksTool,
|
|
2413
|
+
getArticleByRevisionTool
|
|
2135
2414
|
];
|
|
2136
2415
|
var writeToolRegistrars = [
|
|
2137
2416
|
editTool,
|
package/package.json
CHANGED
|
@@ -1,47 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@xiedada/nodemw-mcp-server",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "MCP server for nodemw - MediaWiki API client",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"
|
|
17
|
-
},
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
"
|
|
27
|
-
"
|
|
28
|
-
"
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
-
"
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
"
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
"
|
|
46
|
-
|
|
47
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@xiedada/nodemw-mcp-server",
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "MCP server for nodemw - MediaWiki API client",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/TimXiedada/nodemw-mcp.git"
|
|
8
|
+
},
|
|
9
|
+
"bugs": {
|
|
10
|
+
"url": "https://github.com/TimXiedada/nodemw-mcp/issues"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/TimXiedada/nodemw-mcp#readme",
|
|
13
|
+
"type": "module",
|
|
14
|
+
"main": "dist/index.js",
|
|
15
|
+
"bin": {
|
|
16
|
+
"nodemw-mcp-server": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"scripts": {
|
|
19
|
+
"build": "node scripts/build.js",
|
|
20
|
+
"dev": "node scripts/build.js --watch",
|
|
21
|
+
"lint": "eslint src/**/*.ts",
|
|
22
|
+
"test": "vitest",
|
|
23
|
+
"prepare": "npm run build",
|
|
24
|
+
"clean": "rimraf dist"
|
|
25
|
+
},
|
|
26
|
+
"keywords": [
|
|
27
|
+
"mcp",
|
|
28
|
+
"mediawiki",
|
|
29
|
+
"nodemw",
|
|
30
|
+
"wiki"
|
|
31
|
+
],
|
|
32
|
+
"author": "",
|
|
33
|
+
"license": "BSD-2-Clause",
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
36
|
+
"nodemw": "^0.26.0",
|
|
37
|
+
"zod": "^3.23.8"
|
|
38
|
+
},
|
|
39
|
+
"devDependencies": {
|
|
40
|
+
"@types/node": "^22.0.0",
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^8.0.0",
|
|
42
|
+
"@typescript-eslint/parser": "^8.0.0",
|
|
43
|
+
"esbuild": "^0.24.0",
|
|
44
|
+
"eslint": "^9.0.0",
|
|
45
|
+
"rimraf": "^5.0.5",
|
|
46
|
+
"typescript": "^5.5.0",
|
|
47
|
+
"vitest": "^2.0.0"
|
|
48
|
+
},
|
|
49
|
+
"files": [
|
|
50
|
+
"dist"
|
|
51
|
+
],
|
|
52
|
+
"engines": {
|
|
53
|
+
"node": ">=18.0.0"
|
|
54
|
+
}
|
|
55
|
+
}
|