@mahesvara/discord-mcpserver 1.0.7
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 +21 -0
- package/README.md +466 -0
- package/dist/constants.d.ts +4 -0
- package/dist/constants.js +5 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2874 -0
- package/dist/schemas/index.d.ts +855 -0
- package/dist/schemas/index.js +617 -0
- package/dist/services/discord.d.ts +51 -0
- package/dist/services/discord.js +211 -0
- package/dist/types.d.ts +57 -0
- package/dist/types.js +6 -0
- package/package.json +55 -0
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { Client, GatewayIntentBits, ChannelType } from "discord.js";
|
|
2
|
+
import { ResponseFormat } from "../types.js";
|
|
3
|
+
import { CHARACTER_LIMIT } from "../constants.js";
|
|
4
|
+
let client = null;
|
|
5
|
+
let isReady = false;
|
|
6
|
+
/**
|
|
7
|
+
* Initialize the Discord client with bot token
|
|
8
|
+
*/
|
|
9
|
+
export async function initializeClient() {
|
|
10
|
+
if (client && isReady) {
|
|
11
|
+
return client;
|
|
12
|
+
}
|
|
13
|
+
const token = process.env.DISCORD_BOT_TOKEN;
|
|
14
|
+
if (!token) {
|
|
15
|
+
throw new Error("DISCORD_BOT_TOKEN environment variable is required. " +
|
|
16
|
+
"Set it before starting the server: export DISCORD_BOT_TOKEN=your_token_here");
|
|
17
|
+
}
|
|
18
|
+
client = new Client({
|
|
19
|
+
intents: [
|
|
20
|
+
GatewayIntentBits.Guilds,
|
|
21
|
+
GatewayIntentBits.GuildMembers,
|
|
22
|
+
GatewayIntentBits.GuildMessages,
|
|
23
|
+
GatewayIntentBits.MessageContent,
|
|
24
|
+
GatewayIntentBits.GuildModeration,
|
|
25
|
+
],
|
|
26
|
+
});
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
client.once("ready", () => {
|
|
29
|
+
isReady = true;
|
|
30
|
+
console.error(`Discord bot logged in as ${client.user?.tag}`);
|
|
31
|
+
resolve(client);
|
|
32
|
+
});
|
|
33
|
+
client.once("error", (error) => {
|
|
34
|
+
reject(new Error(`Discord client error: ${error.message}`));
|
|
35
|
+
});
|
|
36
|
+
client.login(token).catch(reject);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the initialized Discord client
|
|
41
|
+
*/
|
|
42
|
+
export async function getClient() {
|
|
43
|
+
if (!client || !isReady) {
|
|
44
|
+
return initializeClient();
|
|
45
|
+
}
|
|
46
|
+
return client;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Format a guild for response
|
|
50
|
+
*/
|
|
51
|
+
export function formatGuild(guild) {
|
|
52
|
+
return {
|
|
53
|
+
id: guild.id,
|
|
54
|
+
name: guild.name,
|
|
55
|
+
memberCount: guild.memberCount,
|
|
56
|
+
icon: guild.iconURL() ?? undefined,
|
|
57
|
+
ownerId: guild.ownerId,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Format a channel for response
|
|
62
|
+
*/
|
|
63
|
+
export function formatChannel(channel) {
|
|
64
|
+
const baseChannel = {
|
|
65
|
+
id: channel.id,
|
|
66
|
+
name: channel.name,
|
|
67
|
+
type: ChannelType[channel.type],
|
|
68
|
+
position: channel.position,
|
|
69
|
+
};
|
|
70
|
+
if (channel.parent) {
|
|
71
|
+
baseChannel.parentId = channel.parent.id;
|
|
72
|
+
baseChannel.parentName = channel.parent.name;
|
|
73
|
+
}
|
|
74
|
+
if (channel.type === ChannelType.GuildText && 'topic' in channel) {
|
|
75
|
+
baseChannel.topic = channel.topic ?? undefined;
|
|
76
|
+
}
|
|
77
|
+
return baseChannel;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Format a member for response
|
|
81
|
+
*/
|
|
82
|
+
export function formatMember(member) {
|
|
83
|
+
return {
|
|
84
|
+
id: member.id,
|
|
85
|
+
username: member.user.username,
|
|
86
|
+
displayName: member.displayName,
|
|
87
|
+
nickname: member.nickname ?? undefined,
|
|
88
|
+
roles: member.roles.cache.map(r => r.name).filter(n => n !== "@everyone"),
|
|
89
|
+
joinedAt: member.joinedAt?.toISOString(),
|
|
90
|
+
isBot: member.user.bot,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Format a role for response
|
|
95
|
+
*/
|
|
96
|
+
export function formatRole(role) {
|
|
97
|
+
return {
|
|
98
|
+
id: role.id,
|
|
99
|
+
name: role.name,
|
|
100
|
+
color: role.color,
|
|
101
|
+
position: role.position,
|
|
102
|
+
permissions: role.permissions.bitfield.toString(),
|
|
103
|
+
mentionable: role.mentionable,
|
|
104
|
+
hoist: role.hoist,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Format a message for response
|
|
109
|
+
*/
|
|
110
|
+
export function formatMessage(message) {
|
|
111
|
+
return {
|
|
112
|
+
id: message.id,
|
|
113
|
+
content: message.content,
|
|
114
|
+
authorId: message.author.id,
|
|
115
|
+
authorName: message.author.username,
|
|
116
|
+
channelId: message.channelId,
|
|
117
|
+
timestamp: message.createdAt.toISOString(),
|
|
118
|
+
editedTimestamp: message.editedAt?.toISOString(),
|
|
119
|
+
attachments: message.attachments.map(a => a.url),
|
|
120
|
+
embeds: message.embeds.length,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Convert channel type string to Discord.js ChannelType
|
|
125
|
+
*/
|
|
126
|
+
export function getChannelType(type) {
|
|
127
|
+
switch (type) {
|
|
128
|
+
case "text": return ChannelType.GuildText;
|
|
129
|
+
case "voice": return ChannelType.GuildVoice;
|
|
130
|
+
case "category": return ChannelType.GuildCategory;
|
|
131
|
+
default: return null;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Format data as markdown
|
|
136
|
+
*/
|
|
137
|
+
export function toMarkdown(data, formatter) {
|
|
138
|
+
if (Array.isArray(data)) {
|
|
139
|
+
if (data.length === 0)
|
|
140
|
+
return "No results found.";
|
|
141
|
+
return data.map(formatter).join("\n\n---\n\n");
|
|
142
|
+
}
|
|
143
|
+
return formatter(data);
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Truncate text if too long
|
|
147
|
+
*/
|
|
148
|
+
export function truncateIfNeeded(text, limit = CHARACTER_LIMIT) {
|
|
149
|
+
if (text.length <= limit)
|
|
150
|
+
return text;
|
|
151
|
+
return text.slice(0, limit - 100) + "\n\n... [Output truncated due to length]";
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Format response based on format preference
|
|
155
|
+
*/
|
|
156
|
+
export function formatResponse(data, format, markdownFormatter) {
|
|
157
|
+
if (format === ResponseFormat.JSON) {
|
|
158
|
+
return JSON.stringify(data, null, 2);
|
|
159
|
+
}
|
|
160
|
+
return toMarkdown(data, markdownFormatter);
|
|
161
|
+
}
|
|
162
|
+
// Markdown formatters for different types
|
|
163
|
+
export function guildToMarkdown(guild) {
|
|
164
|
+
return `## ${guild.name}
|
|
165
|
+
- **ID**: ${guild.id}
|
|
166
|
+
- **Members**: ${guild.memberCount}
|
|
167
|
+
- **Owner ID**: ${guild.ownerId}`;
|
|
168
|
+
}
|
|
169
|
+
export function channelToMarkdown(channel) {
|
|
170
|
+
let md = `### #${channel.name}
|
|
171
|
+
- **ID**: ${channel.id}
|
|
172
|
+
- **Type**: ${channel.type}`;
|
|
173
|
+
if (channel.topic)
|
|
174
|
+
md += `\n- **Topic**: ${channel.topic}`;
|
|
175
|
+
if (channel.parentName)
|
|
176
|
+
md += `\n- **Category**: ${channel.parentName}`;
|
|
177
|
+
return md;
|
|
178
|
+
}
|
|
179
|
+
export function memberToMarkdown(member) {
|
|
180
|
+
let md = `### ${member.displayName}
|
|
181
|
+
- **Username**: ${member.username}
|
|
182
|
+
- **ID**: ${member.id}`;
|
|
183
|
+
if (member.nickname)
|
|
184
|
+
md += `\n- **Nickname**: ${member.nickname}`;
|
|
185
|
+
if (member.roles.length > 0)
|
|
186
|
+
md += `\n- **Roles**: ${member.roles.join(", ")}`;
|
|
187
|
+
if (member.joinedAt)
|
|
188
|
+
md += `\n- **Joined**: ${new Date(member.joinedAt).toLocaleDateString()}`;
|
|
189
|
+
if (member.isBot)
|
|
190
|
+
md += `\n- **Bot**: Yes`;
|
|
191
|
+
return md;
|
|
192
|
+
}
|
|
193
|
+
export function roleToMarkdown(role) {
|
|
194
|
+
return `### ${role.name}
|
|
195
|
+
- **ID**: ${role.id}
|
|
196
|
+
- **Color**: #${role.color.toString(16).padStart(6, "0")}
|
|
197
|
+
- **Position**: ${role.position}
|
|
198
|
+
- **Mentionable**: ${role.mentionable ? "Yes" : "No"}
|
|
199
|
+
- **Hoisted**: ${role.hoist ? "Yes" : "No"}`;
|
|
200
|
+
}
|
|
201
|
+
export function messageToMarkdown(message) {
|
|
202
|
+
let md = `**${message.authorName}** (${new Date(message.timestamp).toLocaleString()})
|
|
203
|
+
${message.content || "[No text content]"}`;
|
|
204
|
+
if (message.attachments.length > 0) {
|
|
205
|
+
md += `\nš ${message.attachments.length} attachment(s)`;
|
|
206
|
+
}
|
|
207
|
+
if (message.embeds > 0) {
|
|
208
|
+
md += `\nš ${message.embeds} embed(s)`;
|
|
209
|
+
}
|
|
210
|
+
return md;
|
|
211
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
export interface DiscordChannel {
|
|
2
|
+
id: string;
|
|
3
|
+
name: string;
|
|
4
|
+
type: string;
|
|
5
|
+
topic?: string;
|
|
6
|
+
position?: number;
|
|
7
|
+
parentId?: string;
|
|
8
|
+
parentName?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface DiscordGuild {
|
|
11
|
+
id: string;
|
|
12
|
+
name: string;
|
|
13
|
+
memberCount: number;
|
|
14
|
+
icon?: string;
|
|
15
|
+
ownerId: string;
|
|
16
|
+
}
|
|
17
|
+
export interface DiscordMember {
|
|
18
|
+
id: string;
|
|
19
|
+
username: string;
|
|
20
|
+
displayName: string;
|
|
21
|
+
nickname?: string;
|
|
22
|
+
roles: string[];
|
|
23
|
+
joinedAt?: string;
|
|
24
|
+
isBot: boolean;
|
|
25
|
+
}
|
|
26
|
+
export interface DiscordRole {
|
|
27
|
+
id: string;
|
|
28
|
+
name: string;
|
|
29
|
+
color: number;
|
|
30
|
+
position: number;
|
|
31
|
+
permissions: string;
|
|
32
|
+
mentionable: boolean;
|
|
33
|
+
hoist: boolean;
|
|
34
|
+
}
|
|
35
|
+
export interface DiscordMessage {
|
|
36
|
+
id: string;
|
|
37
|
+
content: string;
|
|
38
|
+
authorId: string;
|
|
39
|
+
authorName: string;
|
|
40
|
+
channelId: string;
|
|
41
|
+
timestamp: string;
|
|
42
|
+
editedTimestamp?: string;
|
|
43
|
+
attachments: string[];
|
|
44
|
+
embeds: number;
|
|
45
|
+
}
|
|
46
|
+
export interface PaginatedResponse<T> {
|
|
47
|
+
items: T[];
|
|
48
|
+
total: number;
|
|
49
|
+
count: number;
|
|
50
|
+
offset: number;
|
|
51
|
+
has_more: boolean;
|
|
52
|
+
next_offset?: number;
|
|
53
|
+
}
|
|
54
|
+
export declare enum ResponseFormat {
|
|
55
|
+
JSON = "json",
|
|
56
|
+
MARKDOWN = "markdown"
|
|
57
|
+
}
|
package/dist/types.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mahesvara/discord-mcpserver",
|
|
3
|
+
"version": "1.0.7",
|
|
4
|
+
"description": "MCP server for controlling Discord servers via bot token",
|
|
5
|
+
"author": "Mahesvara",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/Oratorian/discord-node-mcp.git"
|
|
9
|
+
},
|
|
10
|
+
"mcpName": "io.github.Oratorian/discord-node-mcp",
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"mcp",
|
|
14
|
+
"model-context-protocol",
|
|
15
|
+
"discord",
|
|
16
|
+
"discord-bot",
|
|
17
|
+
"discord-api",
|
|
18
|
+
"discord-management",
|
|
19
|
+
"claude",
|
|
20
|
+
"llm",
|
|
21
|
+
"ai",
|
|
22
|
+
"automation",
|
|
23
|
+
"chatbot"
|
|
24
|
+
],
|
|
25
|
+
"publishConfig": {
|
|
26
|
+
"access": "public"
|
|
27
|
+
},
|
|
28
|
+
"main": "dist/index.js",
|
|
29
|
+
"bin": {
|
|
30
|
+
"discord-mcp-server": "dist/index.js"
|
|
31
|
+
},
|
|
32
|
+
"files": [
|
|
33
|
+
"dist",
|
|
34
|
+
"README.md"
|
|
35
|
+
],
|
|
36
|
+
"type": "module",
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "tsc",
|
|
39
|
+
"start": "node dist/index.js",
|
|
40
|
+
"dev": "tsc --watch",
|
|
41
|
+
"prepublishOnly": "npm run build"
|
|
42
|
+
},
|
|
43
|
+
"dependencies": {
|
|
44
|
+
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
45
|
+
"discord.js": "^14.14.1",
|
|
46
|
+
"dotenv": "^17.2.3",
|
|
47
|
+
"express": "^4.18.2",
|
|
48
|
+
"zod": "^3.22.4"
|
|
49
|
+
},
|
|
50
|
+
"devDependencies": {
|
|
51
|
+
"@types/express": "^4.17.21",
|
|
52
|
+
"@types/node": "^20.10.0",
|
|
53
|
+
"typescript": "^5.3.2"
|
|
54
|
+
}
|
|
55
|
+
}
|