@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.
@@ -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
+ }
@@ -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
@@ -0,0 +1,6 @@
1
+ // Type definitions for Discord MCP Server
2
+ export var ResponseFormat;
3
+ (function (ResponseFormat) {
4
+ ResponseFormat["JSON"] = "json";
5
+ ResponseFormat["MARKDOWN"] = "markdown";
6
+ })(ResponseFormat || (ResponseFormat = {}));
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
+ }