@guildforge/mcp 1.0.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.
- package/LICENSE +21 -0
- package/README.md +223 -0
- package/dist/index.d.mts +1 -0
- package/dist/index.mjs +595 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +64 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# GuildForge
|
|
2
|
+
|
|
3
|
+
A Model Context Protocol (MCP) server that allows AI assistants to manage Discord servers through a standard interface.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Channel Management**: List, create, and delete text/voice channels
|
|
8
|
+
- **Category Management**: Create and delete channel categories
|
|
9
|
+
- **Role Management**: List, create, delete roles, and assign them to users
|
|
10
|
+
- **Permission Management**: Set permission overwrites for roles on channels and categories
|
|
11
|
+
- **Single-Guild Scoped**: Each instance manages one Discord server
|
|
12
|
+
- **100% TypeScript**: Full type safety throughout the codebase
|
|
13
|
+
- **Open Source**: Distributed under the MIT license
|
|
14
|
+
|
|
15
|
+
## Prerequisites
|
|
16
|
+
|
|
17
|
+
- Node.js >= 24
|
|
18
|
+
- A Discord Bot Token with appropriate permissions
|
|
19
|
+
- The Guild ID you want to manage
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Configuration
|
|
28
|
+
|
|
29
|
+
Copy `.env.example` to `.env` and fill in your values:
|
|
30
|
+
|
|
31
|
+
```env
|
|
32
|
+
DISCORD_TOKEN=your_bot_token_here
|
|
33
|
+
GUILD_ID=your_guild_id_here
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Discord Bot Setup
|
|
37
|
+
|
|
38
|
+
1. Go to the [Discord Developer Portal](https://discord.com/developers/applications)
|
|
39
|
+
2. Create a new application and add a Bot
|
|
40
|
+
3. Enable the following privileged intents:
|
|
41
|
+
- **Guild Members Intent** (required for role assignment)
|
|
42
|
+
4. Invite the bot to your server with these OAuth2 scopes:
|
|
43
|
+
- `bot`
|
|
44
|
+
- `applications.commands`
|
|
45
|
+
5. Grant the bot these permissions in your server:
|
|
46
|
+
- **Manage Channels**
|
|
47
|
+
- **Manage Roles**
|
|
48
|
+
|
|
49
|
+
## Usage
|
|
50
|
+
|
|
51
|
+
### Build
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
npm run build
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Start
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm start
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### Development
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
npm run dev
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### MCP Inspector (for debugging)
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
# Windows PowerShell
|
|
73
|
+
$env:DISCORD_TOKEN="your_token"
|
|
74
|
+
$env:GUILD_ID="your_guild_id"
|
|
75
|
+
npm run inspect
|
|
76
|
+
|
|
77
|
+
# macOS/Linux
|
|
78
|
+
export DISCORD_TOKEN="your_token"
|
|
79
|
+
export GUILD_ID="your_guild_id"
|
|
80
|
+
npm run inspect
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### Claude Desktop Configuration
|
|
84
|
+
|
|
85
|
+
Add this to your Claude Desktop config (`%APPDATA%\Claude\claude_desktop_config.json` on Windows, `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS):
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
{
|
|
89
|
+
"mcpServers": {
|
|
90
|
+
"guildforge": {
|
|
91
|
+
"command": "node",
|
|
92
|
+
"args": ["path/to/guildforge/dist/index.mjs"],
|
|
93
|
+
"env": {
|
|
94
|
+
"DISCORD_TOKEN": "your_bot_token",
|
|
95
|
+
"GUILD_ID": "your_guild_id"
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
## Available Tools
|
|
103
|
+
|
|
104
|
+
### Channels
|
|
105
|
+
|
|
106
|
+
| Tool | Description | Input |
|
|
107
|
+
|------|-------------|-------|
|
|
108
|
+
| `list_channels` | Lists all channels and categories | — |
|
|
109
|
+
| `create_text_channel` | Creates a text channel | `{ name: string, categoryId?: string }` |
|
|
110
|
+
| `create_voice_channel` | Creates a voice channel | `{ name: string, categoryId?: string }` |
|
|
111
|
+
| `delete_channel` | Deletes a channel | `{ id: string, confirm: true }` |
|
|
112
|
+
|
|
113
|
+
### Categories
|
|
114
|
+
|
|
115
|
+
| Tool | Description | Input |
|
|
116
|
+
|------|-------------|-------|
|
|
117
|
+
| `create_category` | Creates a category | `{ name: string }` |
|
|
118
|
+
| `delete_category` | Deletes a category | `{ id: string, confirm: true }` |
|
|
119
|
+
|
|
120
|
+
### Roles
|
|
121
|
+
|
|
122
|
+
| Tool | Description | Input |
|
|
123
|
+
|------|-------------|-------|
|
|
124
|
+
| `list_roles` | Lists all roles | — |
|
|
125
|
+
| `create_role` | Creates a role | `{ name: string, color?: string }` |
|
|
126
|
+
| `delete_role` | Deletes a role | `{ id: string, confirm: true }` |
|
|
127
|
+
| `assign_role` | Assigns/removes a role from a user | `{ userId: string, roleId: string, action: "add" \| "remove" }` |
|
|
128
|
+
|
|
129
|
+
### Permissions
|
|
130
|
+
|
|
131
|
+
| Tool | Description | Input |
|
|
132
|
+
|------|-------------|-------|
|
|
133
|
+
| `set_channel_permissions` | Sets permissions for a role on a channel | `{ channelId: string, roleId: string, allow?: string[], deny?: string[] }` |
|
|
134
|
+
| `set_category_permissions` | Sets permissions for a role on a category | `{ categoryId: string, roleId: string, allow?: string[], deny?: string[] }` |
|
|
135
|
+
|
|
136
|
+
### Utility
|
|
137
|
+
|
|
138
|
+
| Tool | Description |
|
|
139
|
+
|------|-------------|
|
|
140
|
+
| `ping` | Verifies the server is running |
|
|
141
|
+
|
|
142
|
+
### Permission Names
|
|
143
|
+
|
|
144
|
+
Use these strings when setting permissions:
|
|
145
|
+
|
|
146
|
+
- `ViewChannel`, `ManageChannels`, `ManageRoles`
|
|
147
|
+
- `SendMessages`, `SendMessagesInThreads`, `CreatePublicThreads`, `CreatePrivateThreads`
|
|
148
|
+
- `EmbedLinks`, `AttachFiles`, `AddReactions`, `UseExternalEmojis`, `UseExternalStickers`
|
|
149
|
+
- `MentionEveryone`, `ManageMessages`, `ManageThreads`, `ReadMessageHistory`
|
|
150
|
+
- `Connect`, `Speak`, `Stream`, `UseVAD`, `PrioritySpeaker`, `MuteMembers`, `DeafenMembers`, `MoveMembers`
|
|
151
|
+
- `Administrator`, `KickMembers`, `BanMembers`, `ViewAuditLog`
|
|
152
|
+
- And more (any key from `PermissionsBitField.Flags`)
|
|
153
|
+
|
|
154
|
+
## Development
|
|
155
|
+
|
|
156
|
+
### Linting
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npm run lint
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Formatting
|
|
163
|
+
|
|
164
|
+
```bash
|
|
165
|
+
npm run format
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Testing
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
npm test
|
|
172
|
+
npm run test:watch
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### Type Checking
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
npm run typecheck
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Architecture
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
LLM (Claude, Cursor, etc.)
|
|
185
|
+
|
|
|
186
|
+
v
|
|
187
|
+
MCP Protocol (stdio)
|
|
188
|
+
|
|
|
189
|
+
v
|
|
190
|
+
guildforge (this server)
|
|
191
|
+
|
|
|
192
|
+
v
|
|
193
|
+
discord.js Client
|
|
194
|
+
|
|
|
195
|
+
v
|
|
196
|
+
Discord API
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Each tool follows the Lemoncode/quickmock pattern:
|
|
200
|
+
- `index.ts` — Barrel export
|
|
201
|
+
- `<tool>.tool.ts` — Metadata (name, description, schema, execute)
|
|
202
|
+
- `<tool>.handler.ts` — Business logic
|
|
203
|
+
- `<tool>.schema.ts` — Zod validation schema (when input required)
|
|
204
|
+
- `<tool>.handler.test.ts` — Colocated unit tests
|
|
205
|
+
|
|
206
|
+
## Security
|
|
207
|
+
|
|
208
|
+
- **Single-guild scoped**: Each instance manages exactly one Discord server
|
|
209
|
+
- **Explicit confirmation**: Destructive actions (delete) require `confirm: true`
|
|
210
|
+
- **Permission validation**: Bot validates it has required permissions on startup
|
|
211
|
+
- **Type-safe inputs**: All tool inputs validated with Zod schemas
|
|
212
|
+
|
|
213
|
+
## Contributing
|
|
214
|
+
|
|
215
|
+
Contributions are welcome! Please ensure:
|
|
216
|
+
- All tests pass (`npm test`)
|
|
217
|
+
- Code is formatted (`npm run format`)
|
|
218
|
+
- Linting passes (`npm run lint`)
|
|
219
|
+
- TypeScript compiles (`npm run typecheck`)
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
[MIT](LICENSE)
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,595 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
4
|
+
import { ChannelType, Client, GatewayIntentBits, PermissionsBitField } from "discord.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
import "dotenv/config";
|
|
7
|
+
//#region src/commons/discord-client.service.ts
|
|
8
|
+
function createDiscordClientService(token, guildId) {
|
|
9
|
+
const client = new Client({ intents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers] });
|
|
10
|
+
let connected = false;
|
|
11
|
+
async function connect() {
|
|
12
|
+
if (connected) return;
|
|
13
|
+
await new Promise((resolve, reject) => {
|
|
14
|
+
const timeout = setTimeout(() => {
|
|
15
|
+
reject(/* @__PURE__ */ new Error("Discord client connection timed out after 30s"));
|
|
16
|
+
}, 3e4);
|
|
17
|
+
client.once("ready", () => {
|
|
18
|
+
clearTimeout(timeout);
|
|
19
|
+
connected = true;
|
|
20
|
+
resolve();
|
|
21
|
+
});
|
|
22
|
+
client.login(token).catch((err) => {
|
|
23
|
+
clearTimeout(timeout);
|
|
24
|
+
reject(err);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
27
|
+
const guild = client.guilds.cache.get(guildId);
|
|
28
|
+
if (!guild) throw new Error(`Guild with ID "${guildId}" not found or bot is not a member. Ensure the bot has been invited to this server.`);
|
|
29
|
+
const botMember = await guild.members.fetchMe();
|
|
30
|
+
const missing = [PermissionsBitField.Flags.ManageChannels, PermissionsBitField.Flags.ManageRoles].filter((perm) => !botMember.permissions.has(perm));
|
|
31
|
+
if (missing.length > 0) {
|
|
32
|
+
const missingNames = missing.map((perm) => {
|
|
33
|
+
return Object.entries(PermissionsBitField.Flags).find(([, value]) => value === perm)?.[0] ?? String(perm);
|
|
34
|
+
});
|
|
35
|
+
throw new Error(`Bot is missing required permissions: ${missingNames.join(", ")}. Please grant Manage Channels and Manage Roles to the bot.`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
async function disconnect() {
|
|
39
|
+
if (!connected) return;
|
|
40
|
+
await client.destroy();
|
|
41
|
+
connected = false;
|
|
42
|
+
}
|
|
43
|
+
function getGuild() {
|
|
44
|
+
if (!connected) throw new Error("Discord client is not connected. Call connect() first.");
|
|
45
|
+
const guild = client.guilds.cache.get(guildId);
|
|
46
|
+
if (!guild) throw new Error(`Guild with ID "${guildId}" is no longer available.`);
|
|
47
|
+
return guild;
|
|
48
|
+
}
|
|
49
|
+
function isConnected() {
|
|
50
|
+
return connected;
|
|
51
|
+
}
|
|
52
|
+
return {
|
|
53
|
+
connect,
|
|
54
|
+
disconnect,
|
|
55
|
+
getGuild,
|
|
56
|
+
isConnected
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
//#endregion
|
|
60
|
+
//#region src/config.ts
|
|
61
|
+
const envSchema = z.object({
|
|
62
|
+
DISCORD_TOKEN: z.string().min(1, "DISCORD_TOKEN is required"),
|
|
63
|
+
GUILD_ID: z.string().min(1, "GUILD_ID is required")
|
|
64
|
+
});
|
|
65
|
+
function parseConfig(env) {
|
|
66
|
+
const parsed = envSchema.safeParse(env);
|
|
67
|
+
if (!parsed.success) {
|
|
68
|
+
const messages = parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`);
|
|
69
|
+
throw new Error(`Invalid environment variables: ${messages.join(", ")}`);
|
|
70
|
+
}
|
|
71
|
+
return parsed.data;
|
|
72
|
+
}
|
|
73
|
+
//#endregion
|
|
74
|
+
//#region src/commons/tool-response.helpers.ts
|
|
75
|
+
function toolText(text) {
|
|
76
|
+
return { content: [{
|
|
77
|
+
type: "text",
|
|
78
|
+
text
|
|
79
|
+
}] };
|
|
80
|
+
}
|
|
81
|
+
function toolError(text) {
|
|
82
|
+
return {
|
|
83
|
+
content: [{
|
|
84
|
+
type: "text",
|
|
85
|
+
text
|
|
86
|
+
}],
|
|
87
|
+
isError: true
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/tools/assign-role/assign-role.handler.ts
|
|
92
|
+
async function assignRoleHandler(args, service) {
|
|
93
|
+
try {
|
|
94
|
+
const guild = service.getGuild();
|
|
95
|
+
const member = await guild.members.fetch(args.userId);
|
|
96
|
+
if (!member) return toolError(`User with ID "${args.userId}" not found in the server.`);
|
|
97
|
+
const role = guild.roles.cache.get(args.roleId);
|
|
98
|
+
if (!role) return toolError(`Role with ID "${args.roleId}" not found.`);
|
|
99
|
+
if (args.action === "add") await member.roles.add(role);
|
|
100
|
+
else await member.roles.remove(role);
|
|
101
|
+
return toolText(JSON.stringify({
|
|
102
|
+
userId: args.userId,
|
|
103
|
+
roleId: args.roleId
|
|
104
|
+
}));
|
|
105
|
+
} catch (err) {
|
|
106
|
+
return toolError(`Error assigning role: ${String(err)}`);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
//#region src/tools/assign-role/assign-role.tool.ts
|
|
111
|
+
const assignRole = {
|
|
112
|
+
name: "assign_role",
|
|
113
|
+
description: "Assigns or removes a role from a user in the Discord server.",
|
|
114
|
+
schema: {
|
|
115
|
+
userId: z.string().describe("ID of the user to assign/remove the role from"),
|
|
116
|
+
roleId: z.string().describe("ID of the role to assign or remove"),
|
|
117
|
+
action: z.enum(["add", "remove"]).describe("Whether to add or remove the role from the user")
|
|
118
|
+
},
|
|
119
|
+
execute: assignRoleHandler
|
|
120
|
+
};
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/tools/create-category/create-category.handler.ts
|
|
123
|
+
async function createCategoryHandler(args, service) {
|
|
124
|
+
try {
|
|
125
|
+
const category = await service.getGuild().channels.create({
|
|
126
|
+
name: args.name,
|
|
127
|
+
type: ChannelType.GuildCategory
|
|
128
|
+
});
|
|
129
|
+
return toolText(JSON.stringify({
|
|
130
|
+
id: category.id,
|
|
131
|
+
name: category.name
|
|
132
|
+
}));
|
|
133
|
+
} catch (err) {
|
|
134
|
+
return toolError(`Error creating category: ${String(err)}`);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
//#endregion
|
|
138
|
+
//#region src/tools/create-category/create-category.tool.ts
|
|
139
|
+
const createCategory = {
|
|
140
|
+
name: "create_category",
|
|
141
|
+
description: "Creates a new category in the Discord server.",
|
|
142
|
+
schema: { name: z.string().min(1).max(100).describe("Name of the category to create") },
|
|
143
|
+
execute: createCategoryHandler
|
|
144
|
+
};
|
|
145
|
+
//#endregion
|
|
146
|
+
//#region src/tools/create-role/create-role.handler.ts
|
|
147
|
+
async function createRoleHandler(args, service) {
|
|
148
|
+
try {
|
|
149
|
+
const guild = service.getGuild();
|
|
150
|
+
const colorValue = args.color ? Number.parseInt(args.color.replace("#", ""), 16) : void 0;
|
|
151
|
+
const role = await guild.roles.create({
|
|
152
|
+
name: args.name,
|
|
153
|
+
color: colorValue
|
|
154
|
+
});
|
|
155
|
+
return toolText(JSON.stringify({
|
|
156
|
+
id: role.id,
|
|
157
|
+
name: role.name
|
|
158
|
+
}));
|
|
159
|
+
} catch (err) {
|
|
160
|
+
return toolError(`Error creating role: ${String(err)}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
//#endregion
|
|
164
|
+
//#region src/tools/create-role/create-role.tool.ts
|
|
165
|
+
const createRole = {
|
|
166
|
+
name: "create_role",
|
|
167
|
+
description: "Creates a new role in the Discord server.",
|
|
168
|
+
schema: {
|
|
169
|
+
name: z.string().min(1).max(100).describe("Name of the role to create"),
|
|
170
|
+
color: z.string().regex(/^#?[0-9A-Fa-f]{6}$/).optional().describe("Optional hex color for the role (e.g., #FF0000)")
|
|
171
|
+
},
|
|
172
|
+
execute: createRoleHandler
|
|
173
|
+
};
|
|
174
|
+
//#endregion
|
|
175
|
+
//#region src/tools/create-text-channel/create-text-channel.handler.ts
|
|
176
|
+
async function createTextChannelHandler(args, service) {
|
|
177
|
+
try {
|
|
178
|
+
const channel = await service.getGuild().channels.create({
|
|
179
|
+
name: args.name,
|
|
180
|
+
type: ChannelType.GuildText,
|
|
181
|
+
parent: args.categoryId ?? void 0
|
|
182
|
+
});
|
|
183
|
+
return toolText(JSON.stringify({
|
|
184
|
+
id: channel.id,
|
|
185
|
+
name: channel.name
|
|
186
|
+
}));
|
|
187
|
+
} catch (err) {
|
|
188
|
+
return toolError(`Error creating text channel: ${String(err)}`);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/tools/create-text-channel/create-text-channel.tool.ts
|
|
193
|
+
const createTextChannel = {
|
|
194
|
+
name: "create_text_channel",
|
|
195
|
+
description: "Creates a new text channel in the Discord server.",
|
|
196
|
+
schema: {
|
|
197
|
+
name: z.string().min(1).max(100).describe("Name of the text channel to create"),
|
|
198
|
+
categoryId: z.string().optional().describe("Optional ID of the category to place the channel under")
|
|
199
|
+
},
|
|
200
|
+
execute: createTextChannelHandler
|
|
201
|
+
};
|
|
202
|
+
//#endregion
|
|
203
|
+
//#region src/tools/create-voice-channel/create-voice-channel.handler.ts
|
|
204
|
+
async function createVoiceChannelHandler(args, service) {
|
|
205
|
+
try {
|
|
206
|
+
const channel = await service.getGuild().channels.create({
|
|
207
|
+
name: args.name,
|
|
208
|
+
type: ChannelType.GuildVoice,
|
|
209
|
+
parent: args.categoryId ?? void 0
|
|
210
|
+
});
|
|
211
|
+
return toolText(JSON.stringify({
|
|
212
|
+
id: channel.id,
|
|
213
|
+
name: channel.name
|
|
214
|
+
}));
|
|
215
|
+
} catch (err) {
|
|
216
|
+
return toolError(`Error creating voice channel: ${String(err)}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/tools/create-voice-channel/create-voice-channel.tool.ts
|
|
221
|
+
const createVoiceChannel = {
|
|
222
|
+
name: "create_voice_channel",
|
|
223
|
+
description: "Creates a new voice channel in the Discord server.",
|
|
224
|
+
schema: {
|
|
225
|
+
name: z.string().min(1).max(100).describe("Name of the voice channel to create"),
|
|
226
|
+
categoryId: z.string().optional().describe("Optional ID of the category to place the channel under")
|
|
227
|
+
},
|
|
228
|
+
execute: createVoiceChannelHandler
|
|
229
|
+
};
|
|
230
|
+
//#endregion
|
|
231
|
+
//#region src/tools/delete-category/delete-category.handler.ts
|
|
232
|
+
async function deleteCategoryHandler(args, service) {
|
|
233
|
+
try {
|
|
234
|
+
const category = service.getGuild().channels.cache.get(args.id);
|
|
235
|
+
if (!category) return toolError(`Category with ID "${args.id}" not found.`);
|
|
236
|
+
if (category.type !== ChannelType.GuildCategory) return toolError(`Channel with ID "${args.id}" is not a category.`);
|
|
237
|
+
await category.delete();
|
|
238
|
+
return toolText(JSON.stringify({
|
|
239
|
+
id: category.id,
|
|
240
|
+
name: category.name
|
|
241
|
+
}));
|
|
242
|
+
} catch (err) {
|
|
243
|
+
return toolError(`Error deleting category: ${String(err)}`);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
//#endregion
|
|
247
|
+
//#region src/tools/delete-category/delete-category.tool.ts
|
|
248
|
+
const deleteCategory = {
|
|
249
|
+
name: "delete_category",
|
|
250
|
+
description: "Deletes a category from the Discord server. Requires explicit confirmation.",
|
|
251
|
+
schema: {
|
|
252
|
+
id: z.string().describe("ID of the category to delete"),
|
|
253
|
+
confirm: z.literal(true).describe("Explicit confirmation required. Must be set to true.")
|
|
254
|
+
},
|
|
255
|
+
execute: deleteCategoryHandler
|
|
256
|
+
};
|
|
257
|
+
//#endregion
|
|
258
|
+
//#region src/tools/delete-channel/delete-channel.handler.ts
|
|
259
|
+
async function deleteChannelHandler(args, service) {
|
|
260
|
+
try {
|
|
261
|
+
const channel = service.getGuild().channels.cache.get(args.id);
|
|
262
|
+
if (!channel) return toolError(`Channel with ID "${args.id}" not found.`);
|
|
263
|
+
await channel.delete();
|
|
264
|
+
return toolText(JSON.stringify({
|
|
265
|
+
id: channel.id,
|
|
266
|
+
name: channel.name
|
|
267
|
+
}));
|
|
268
|
+
} catch (err) {
|
|
269
|
+
return toolError(`Error deleting channel: ${String(err)}`);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
//#endregion
|
|
273
|
+
//#region src/tools/delete-channel/delete-channel.tool.ts
|
|
274
|
+
const deleteChannel = {
|
|
275
|
+
name: "delete_channel",
|
|
276
|
+
description: "Deletes a channel from the Discord server. Requires explicit confirmation.",
|
|
277
|
+
schema: {
|
|
278
|
+
id: z.string().describe("ID of the channel to delete"),
|
|
279
|
+
confirm: z.literal(true).describe("Explicit confirmation required. Must be set to true.")
|
|
280
|
+
},
|
|
281
|
+
execute: deleteChannelHandler
|
|
282
|
+
};
|
|
283
|
+
//#endregion
|
|
284
|
+
//#region src/tools/delete-role/delete-role.handler.ts
|
|
285
|
+
async function deleteRoleHandler(args, service) {
|
|
286
|
+
try {
|
|
287
|
+
const role = service.getGuild().roles.cache.get(args.id);
|
|
288
|
+
if (!role) return toolError(`Role with ID "${args.id}" not found.`);
|
|
289
|
+
await role.delete();
|
|
290
|
+
return toolText(JSON.stringify({
|
|
291
|
+
id: role.id,
|
|
292
|
+
name: role.name
|
|
293
|
+
}));
|
|
294
|
+
} catch (err) {
|
|
295
|
+
return toolError(`Error deleting role: ${String(err)}`);
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
//#endregion
|
|
299
|
+
//#region src/tools/delete-role/delete-role.tool.ts
|
|
300
|
+
const deleteRole = {
|
|
301
|
+
name: "delete_role",
|
|
302
|
+
description: "Deletes a role from the Discord server. Requires explicit confirmation.",
|
|
303
|
+
schema: {
|
|
304
|
+
id: z.string().describe("ID of the role to delete"),
|
|
305
|
+
confirm: z.literal(true).describe("Explicit confirmation required. Must be set to true.")
|
|
306
|
+
},
|
|
307
|
+
execute: deleteRoleHandler
|
|
308
|
+
};
|
|
309
|
+
//#endregion
|
|
310
|
+
//#region src/tools/list-channels/list-channels.handler.ts
|
|
311
|
+
function getChannelTypeName(type) {
|
|
312
|
+
return Object.entries(ChannelType).find(([, value]) => value === type)?.[0] ?? "Unknown";
|
|
313
|
+
}
|
|
314
|
+
async function listChannelsHandler(service) {
|
|
315
|
+
const channels = service.getGuild().channels.cache.map((channel) => ({
|
|
316
|
+
id: channel.id,
|
|
317
|
+
name: channel.name,
|
|
318
|
+
type: getChannelTypeName(channel.type)
|
|
319
|
+
}));
|
|
320
|
+
return toolText(JSON.stringify(channels));
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
323
|
+
//#region src/tools/list-channels/list-channels.tool.ts
|
|
324
|
+
const listChannels = {
|
|
325
|
+
name: "list_channels",
|
|
326
|
+
description: "Lists all channels and categories in the configured Discord server. Returns an array of objects with id, name, and type.",
|
|
327
|
+
execute: listChannelsHandler
|
|
328
|
+
};
|
|
329
|
+
//#endregion
|
|
330
|
+
//#region src/tools/list-channels-ordered/list-channels-ordered.handler.ts
|
|
331
|
+
async function listChannelsOrderedHandler(service) {
|
|
332
|
+
const allChannels = [...service.getGuild().channels.cache.values()];
|
|
333
|
+
const categories = allChannels.filter((ch) => ch.type === 4).sort((a, b) => a.position - b.position);
|
|
334
|
+
const uncategorized = allChannels.filter((ch) => ch.type !== 4 && !ch.parentId).sort((a, b) => a.position - b.position);
|
|
335
|
+
const result = [];
|
|
336
|
+
for (const cat of categories) {
|
|
337
|
+
const children = allChannels.filter((ch) => ch.parentId === cat.id).sort((a, b) => a.position - b.position).map((ch) => ({
|
|
338
|
+
id: ch.id,
|
|
339
|
+
name: ch.name,
|
|
340
|
+
type: String(ch.type),
|
|
341
|
+
position: ch.position
|
|
342
|
+
}));
|
|
343
|
+
result.push({
|
|
344
|
+
id: cat.id,
|
|
345
|
+
name: cat.name,
|
|
346
|
+
type: "GuildCategory",
|
|
347
|
+
position: cat.position,
|
|
348
|
+
children
|
|
349
|
+
});
|
|
350
|
+
}
|
|
351
|
+
for (const ch of uncategorized) result.push({
|
|
352
|
+
id: ch.id,
|
|
353
|
+
name: ch.name,
|
|
354
|
+
type: String(ch.type),
|
|
355
|
+
position: ch.position
|
|
356
|
+
});
|
|
357
|
+
return toolText(JSON.stringify(result, null, 2));
|
|
358
|
+
}
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/tools/list-channels-ordered/list-channels-ordered.tool.ts
|
|
361
|
+
const listChannelsOrdered = {
|
|
362
|
+
name: "list_channels_ordered",
|
|
363
|
+
description: "Lists all channels and categories in the order they appear in the Discord client (top to bottom). Categories include their nested channels.",
|
|
364
|
+
execute: listChannelsOrderedHandler
|
|
365
|
+
};
|
|
366
|
+
//#endregion
|
|
367
|
+
//#region src/tools/list-roles/list-roles.handler.ts
|
|
368
|
+
async function listRolesHandler(service) {
|
|
369
|
+
const roles = service.getGuild().roles.cache.map((role) => ({
|
|
370
|
+
id: role.id,
|
|
371
|
+
name: role.name
|
|
372
|
+
}));
|
|
373
|
+
return toolText(JSON.stringify(roles));
|
|
374
|
+
}
|
|
375
|
+
//#endregion
|
|
376
|
+
//#region src/tools/list-roles/list-roles.tool.ts
|
|
377
|
+
const listRoles = {
|
|
378
|
+
name: "list_roles",
|
|
379
|
+
description: "Lists all roles in the configured Discord server. Returns an array of objects with id and name.",
|
|
380
|
+
execute: listRolesHandler
|
|
381
|
+
};
|
|
382
|
+
//#endregion
|
|
383
|
+
//#region src/tools/ping/ping.handler.ts
|
|
384
|
+
async function pingHandler() {
|
|
385
|
+
return toolText("pong");
|
|
386
|
+
}
|
|
387
|
+
//#endregion
|
|
388
|
+
//#region src/tools/ping/ping.tool.ts
|
|
389
|
+
const ping = {
|
|
390
|
+
name: "ping",
|
|
391
|
+
description: "Responds with pong. Useful for verifying the server is running.",
|
|
392
|
+
execute: pingHandler
|
|
393
|
+
};
|
|
394
|
+
//#endregion
|
|
395
|
+
//#region src/tools/reorder-channels/reorder-channels.handler.ts
|
|
396
|
+
async function reorderChannelsHandler(args, service) {
|
|
397
|
+
try {
|
|
398
|
+
const guild = service.getGuild();
|
|
399
|
+
const results = [];
|
|
400
|
+
for (const order of args.orders) {
|
|
401
|
+
const channel = guild.channels.cache.get(order.channelId);
|
|
402
|
+
if (!channel) {
|
|
403
|
+
results.push({
|
|
404
|
+
channelId: order.channelId,
|
|
405
|
+
name: "unknown",
|
|
406
|
+
success: false
|
|
407
|
+
});
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (order.categoryId !== void 0) await channel.setParent(order.categoryId, { lockPermissions: false });
|
|
411
|
+
await channel.setPosition(order.position);
|
|
412
|
+
results.push({
|
|
413
|
+
channelId: order.channelId,
|
|
414
|
+
name: channel.name,
|
|
415
|
+
success: true
|
|
416
|
+
});
|
|
417
|
+
}
|
|
418
|
+
return toolText(JSON.stringify(results));
|
|
419
|
+
} catch (err) {
|
|
420
|
+
return toolError(`Error reordering channels: ${String(err)}`);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
//#endregion
|
|
424
|
+
//#region src/tools/reorder-channels/reorder-channels.tool.ts
|
|
425
|
+
const reorderChannels = {
|
|
426
|
+
name: "reorder_channels",
|
|
427
|
+
description: "Moves and/or reorders channels and categories. Accepts an array of operations, each specifying a channel ID, new position, and optionally a category ID to move under.",
|
|
428
|
+
schema: { orders: z.array(z.object({
|
|
429
|
+
channelId: z.string().describe("ID of the channel to move/reorder"),
|
|
430
|
+
position: z.number().int().min(0).describe("New position for the channel (0 = top)"),
|
|
431
|
+
categoryId: z.string().optional().describe("Optional category ID to move the channel under. Omit to keep current parent.")
|
|
432
|
+
})).describe("Array of channel reorder operations") },
|
|
433
|
+
execute: reorderChannelsHandler
|
|
434
|
+
};
|
|
435
|
+
//#endregion
|
|
436
|
+
//#region src/tools/set-category-permissions/set-category-permissions.handler.ts
|
|
437
|
+
function buildPermissionOptions$1(allow, deny) {
|
|
438
|
+
const options = {};
|
|
439
|
+
for (const perm of allow ?? []) {
|
|
440
|
+
if (PermissionsBitField.Flags[perm] === void 0) throw new Error(`Unknown permission: "${perm}"`);
|
|
441
|
+
options[perm] = true;
|
|
442
|
+
}
|
|
443
|
+
for (const perm of deny ?? []) {
|
|
444
|
+
if (PermissionsBitField.Flags[perm] === void 0) throw new Error(`Unknown permission: "${perm}"`);
|
|
445
|
+
options[perm] = false;
|
|
446
|
+
}
|
|
447
|
+
return options;
|
|
448
|
+
}
|
|
449
|
+
async function setCategoryPermissionsHandler(args, service) {
|
|
450
|
+
try {
|
|
451
|
+
const guild = service.getGuild();
|
|
452
|
+
const category = guild.channels.cache.get(args.categoryId);
|
|
453
|
+
if (!category) return toolError(`Category with ID "${args.categoryId}" not found.`);
|
|
454
|
+
if (category.type !== ChannelType.GuildCategory) return toolError(`Channel with ID "${args.categoryId}" is not a category.`);
|
|
455
|
+
const role = guild.roles.cache.get(args.roleId);
|
|
456
|
+
if (!role) return toolError(`Role with ID "${args.roleId}" not found.`);
|
|
457
|
+
const options = buildPermissionOptions$1(args.allow, args.deny);
|
|
458
|
+
await category.permissionOverwrites.edit(role.id, options);
|
|
459
|
+
return toolText(JSON.stringify({
|
|
460
|
+
categoryId: args.categoryId,
|
|
461
|
+
roleId: args.roleId
|
|
462
|
+
}));
|
|
463
|
+
} catch (err) {
|
|
464
|
+
return toolError(`Error setting category permissions: ${String(err)}`);
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/tools/set-category-permissions/set-category-permissions.tool.ts
|
|
469
|
+
const setCategoryPermissions = {
|
|
470
|
+
name: "set_category_permissions",
|
|
471
|
+
description: "Sets permission overwrites for a role on a specific category. Accepts arrays of permission names to allow or deny.",
|
|
472
|
+
schema: {
|
|
473
|
+
categoryId: z.string().describe("ID of the category to modify permissions for"),
|
|
474
|
+
roleId: z.string().describe("ID of the role to set permissions for"),
|
|
475
|
+
allow: z.array(z.string()).optional().describe("Optional array of permission names to allow (e.g., SendMessages, ViewChannel)"),
|
|
476
|
+
deny: z.array(z.string()).optional().describe("Optional array of permission names to deny (e.g., SendMessages, ViewChannel)")
|
|
477
|
+
},
|
|
478
|
+
execute: setCategoryPermissionsHandler
|
|
479
|
+
};
|
|
480
|
+
//#endregion
|
|
481
|
+
//#region src/tools/set-channel-permissions/set-channel-permissions.handler.ts
|
|
482
|
+
function buildPermissionOptions(allow, deny) {
|
|
483
|
+
const options = {};
|
|
484
|
+
for (const perm of allow ?? []) {
|
|
485
|
+
if (PermissionsBitField.Flags[perm] === void 0) throw new Error(`Unknown permission: "${perm}"`);
|
|
486
|
+
options[perm] = true;
|
|
487
|
+
}
|
|
488
|
+
for (const perm of deny ?? []) {
|
|
489
|
+
if (PermissionsBitField.Flags[perm] === void 0) throw new Error(`Unknown permission: "${perm}"`);
|
|
490
|
+
options[perm] = false;
|
|
491
|
+
}
|
|
492
|
+
return options;
|
|
493
|
+
}
|
|
494
|
+
async function setChannelPermissionsHandler(args, service) {
|
|
495
|
+
try {
|
|
496
|
+
const guild = service.getGuild();
|
|
497
|
+
const channel = guild.channels.cache.get(args.channelId);
|
|
498
|
+
if (!channel) return toolError(`Channel with ID "${args.channelId}" not found.`);
|
|
499
|
+
if (!("permissionOverwrites" in channel)) return toolError(`Channel with ID "${args.channelId}" does not support permission overwrites.`);
|
|
500
|
+
const role = guild.roles.cache.get(args.roleId);
|
|
501
|
+
if (!role) return toolError(`Role with ID "${args.roleId}" not found.`);
|
|
502
|
+
const options = buildPermissionOptions(args.allow, args.deny);
|
|
503
|
+
await channel.permissionOverwrites.edit(role.id, options);
|
|
504
|
+
return toolText(JSON.stringify({
|
|
505
|
+
channelId: args.channelId,
|
|
506
|
+
roleId: args.roleId
|
|
507
|
+
}));
|
|
508
|
+
} catch (err) {
|
|
509
|
+
return toolError(`Error setting channel permissions: ${String(err)}`);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
//#endregion
|
|
513
|
+
//#region src/tools/set-channel-permissions/set-channel-permissions.tool.ts
|
|
514
|
+
const setChannelPermissions = {
|
|
515
|
+
name: "set_channel_permissions",
|
|
516
|
+
description: "Sets permission overwrites for a role on a specific channel or category. Accepts arrays of permission names to allow or deny. Works on both regular channels and category channels.",
|
|
517
|
+
schema: {
|
|
518
|
+
channelId: z.string().describe("ID of the channel to modify permissions for"),
|
|
519
|
+
roleId: z.string().describe("ID of the role to set permissions for"),
|
|
520
|
+
allow: z.array(z.string()).optional().describe("Optional array of permission names to allow (e.g., SendMessages, ViewChannel)"),
|
|
521
|
+
deny: z.array(z.string()).optional().describe("Optional array of permission names to deny (e.g., SendMessages, ViewChannel)")
|
|
522
|
+
},
|
|
523
|
+
execute: setChannelPermissionsHandler
|
|
524
|
+
};
|
|
525
|
+
//#endregion
|
|
526
|
+
//#region src/index.ts
|
|
527
|
+
const config = parseConfig(process.env);
|
|
528
|
+
const discordService = createDiscordClientService(config.DISCORD_TOKEN, config.GUILD_ID);
|
|
529
|
+
const server = new McpServer({
|
|
530
|
+
name: "discord-manager-mcp",
|
|
531
|
+
version: "1.0.0"
|
|
532
|
+
});
|
|
533
|
+
server.registerTool(ping.name, { description: ping.description }, () => ping.execute());
|
|
534
|
+
server.registerTool(listChannels.name, { description: listChannels.description }, () => listChannels.execute(discordService));
|
|
535
|
+
server.registerTool(createTextChannel.name, {
|
|
536
|
+
description: createTextChannel.description,
|
|
537
|
+
inputSchema: createTextChannel.schema
|
|
538
|
+
}, (args) => createTextChannel.execute(args, discordService));
|
|
539
|
+
server.registerTool(createVoiceChannel.name, {
|
|
540
|
+
description: createVoiceChannel.description,
|
|
541
|
+
inputSchema: createVoiceChannel.schema
|
|
542
|
+
}, (args) => createVoiceChannel.execute(args, discordService));
|
|
543
|
+
server.registerTool(deleteChannel.name, {
|
|
544
|
+
description: deleteChannel.description,
|
|
545
|
+
inputSchema: deleteChannel.schema
|
|
546
|
+
}, (args) => deleteChannel.execute(args, discordService));
|
|
547
|
+
server.registerTool(createCategory.name, {
|
|
548
|
+
description: createCategory.description,
|
|
549
|
+
inputSchema: createCategory.schema
|
|
550
|
+
}, (args) => createCategory.execute(args, discordService));
|
|
551
|
+
server.registerTool(deleteCategory.name, {
|
|
552
|
+
description: deleteCategory.description,
|
|
553
|
+
inputSchema: deleteCategory.schema
|
|
554
|
+
}, (args) => deleteCategory.execute(args, discordService));
|
|
555
|
+
server.registerTool(listRoles.name, { description: listRoles.description }, () => listRoles.execute(discordService));
|
|
556
|
+
server.registerTool(createRole.name, {
|
|
557
|
+
description: createRole.description,
|
|
558
|
+
inputSchema: createRole.schema
|
|
559
|
+
}, (args) => createRole.execute(args, discordService));
|
|
560
|
+
server.registerTool(deleteRole.name, {
|
|
561
|
+
description: deleteRole.description,
|
|
562
|
+
inputSchema: deleteRole.schema
|
|
563
|
+
}, (args) => deleteRole.execute(args, discordService));
|
|
564
|
+
server.registerTool(assignRole.name, {
|
|
565
|
+
description: assignRole.description,
|
|
566
|
+
inputSchema: assignRole.schema
|
|
567
|
+
}, (args) => assignRole.execute(args, discordService));
|
|
568
|
+
server.registerTool(setChannelPermissions.name, {
|
|
569
|
+
description: setChannelPermissions.description,
|
|
570
|
+
inputSchema: setChannelPermissions.schema
|
|
571
|
+
}, (args) => setChannelPermissions.execute(args, discordService));
|
|
572
|
+
server.registerTool(setCategoryPermissions.name, {
|
|
573
|
+
description: setCategoryPermissions.description,
|
|
574
|
+
inputSchema: setCategoryPermissions.schema
|
|
575
|
+
}, (args) => setCategoryPermissions.execute(args, discordService));
|
|
576
|
+
server.registerTool(listChannelsOrdered.name, { description: listChannelsOrdered.description }, () => listChannelsOrdered.execute(discordService));
|
|
577
|
+
server.registerTool(reorderChannels.name, {
|
|
578
|
+
description: reorderChannels.description,
|
|
579
|
+
inputSchema: reorderChannels.schema
|
|
580
|
+
}, (args) => reorderChannels.execute(args, discordService));
|
|
581
|
+
async function main() {
|
|
582
|
+
console.error(`[startup] Connecting to Discord (GUILD_ID: ${config.GUILD_ID})...`);
|
|
583
|
+
await discordService.connect();
|
|
584
|
+
console.error("[startup] Discord connected and permissions verified.");
|
|
585
|
+
const transport = new StdioServerTransport();
|
|
586
|
+
await server.connect(transport);
|
|
587
|
+
}
|
|
588
|
+
main().catch((err) => {
|
|
589
|
+
console.error("fatal error:", err);
|
|
590
|
+
process.exit(1);
|
|
591
|
+
});
|
|
592
|
+
//#endregion
|
|
593
|
+
export {};
|
|
594
|
+
|
|
595
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["buildPermissionOptions"],"sources":["../src/commons/discord-client.service.ts","../src/config.ts","../src/commons/tool-response.helpers.ts","../src/tools/assign-role/assign-role.handler.ts","../src/tools/assign-role/assign-role.schema.ts","../src/tools/assign-role/assign-role.tool.ts","../src/tools/create-category/create-category.handler.ts","../src/tools/create-category/create-category.schema.ts","../src/tools/create-category/create-category.tool.ts","../src/tools/create-role/create-role.handler.ts","../src/tools/create-role/create-role.schema.ts","../src/tools/create-role/create-role.tool.ts","../src/tools/create-text-channel/create-text-channel.handler.ts","../src/tools/create-text-channel/create-text-channel.schema.ts","../src/tools/create-text-channel/create-text-channel.tool.ts","../src/tools/create-voice-channel/create-voice-channel.handler.ts","../src/tools/create-voice-channel/create-voice-channel.schema.ts","../src/tools/create-voice-channel/create-voice-channel.tool.ts","../src/tools/delete-category/delete-category.handler.ts","../src/tools/delete-category/delete-category.schema.ts","../src/tools/delete-category/delete-category.tool.ts","../src/tools/delete-channel/delete-channel.handler.ts","../src/tools/delete-channel/delete-channel.schema.ts","../src/tools/delete-channel/delete-channel.tool.ts","../src/tools/delete-role/delete-role.handler.ts","../src/tools/delete-role/delete-role.schema.ts","../src/tools/delete-role/delete-role.tool.ts","../src/tools/list-channels/list-channels.handler.ts","../src/tools/list-channels/list-channels.tool.ts","../src/tools/list-channels-ordered/list-channels-ordered.handler.ts","../src/tools/list-channels-ordered/list-channels-ordered.tool.ts","../src/tools/list-roles/list-roles.handler.ts","../src/tools/list-roles/list-roles.tool.ts","../src/tools/ping/ping.handler.ts","../src/tools/ping/ping.tool.ts","../src/tools/reorder-channels/reorder-channels.handler.ts","../src/tools/reorder-channels/reorder-channels.schema.ts","../src/tools/reorder-channels/reorder-channels.tool.ts","../src/tools/set-category-permissions/set-category-permissions.handler.ts","../src/tools/set-category-permissions/set-category-permissions.schema.ts","../src/tools/set-category-permissions/set-category-permissions.tool.ts","../src/tools/set-channel-permissions/set-channel-permissions.handler.ts","../src/tools/set-channel-permissions/set-channel-permissions.schema.ts","../src/tools/set-channel-permissions/set-channel-permissions.tool.ts","../src/index.ts"],"sourcesContent":["import type { Guild } from 'discord.js';\nimport { Client, GatewayIntentBits, PermissionsBitField } from 'discord.js';\n\nexport interface DiscordClientService {\n\tconnect(): Promise<void>;\n\tdisconnect(): Promise<void>;\n\tgetGuild(): Guild;\n\tisConnected(): boolean;\n}\n\nexport function createDiscordClientService(\n\ttoken: string,\n\tguildId: string,\n): DiscordClientService {\n\tconst client = new Client({\n\t\tintents: [GatewayIntentBits.Guilds, GatewayIntentBits.GuildMembers],\n\t});\n\n\tlet connected = false;\n\n\tasync function connect(): Promise<void> {\n\t\tif (connected) {\n\t\t\treturn;\n\t\t}\n\n\t\tawait new Promise<void>((resolve, reject) => {\n\t\t\tconst timeout = setTimeout(() => {\n\t\t\t\treject(new Error('Discord client connection timed out after 30s'));\n\t\t\t}, 30000);\n\n\t\t\tclient.once('ready', () => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\tconnected = true;\n\t\t\t\tresolve();\n\t\t\t});\n\n\t\t\tclient.login(token).catch((err) => {\n\t\t\t\tclearTimeout(timeout);\n\t\t\t\treject(err);\n\t\t\t});\n\t\t});\n\n\t\tconst guild = client.guilds.cache.get(guildId);\n\t\tif (!guild) {\n\t\t\tthrow new Error(\n\t\t\t\t`Guild with ID \"${guildId}\" not found or bot is not a member. Ensure the bot has been invited to this server.`,\n\t\t\t);\n\t\t}\n\n\t\tconst botMember = await guild.members.fetchMe();\n\t\tconst requiredPermissions = [\n\t\t\tPermissionsBitField.Flags.ManageChannels,\n\t\t\tPermissionsBitField.Flags.ManageRoles,\n\t\t];\n\n\t\tconst missing = requiredPermissions.filter(\n\t\t\t(perm) => !botMember.permissions.has(perm),\n\t\t);\n\n\t\tif (missing.length > 0) {\n\t\t\tconst missingNames = missing.map((perm) => {\n\t\t\t\tconst found = Object.entries(PermissionsBitField.Flags).find(\n\t\t\t\t\t([, value]) => value === perm,\n\t\t\t\t);\n\t\t\t\treturn found?.[0] ?? String(perm);\n\t\t\t});\n\t\t\tthrow new Error(\n\t\t\t\t`Bot is missing required permissions: ${missingNames.join(', ')}. Please grant Manage Channels and Manage Roles to the bot.`,\n\t\t\t);\n\t\t}\n\t}\n\n\tasync function disconnect(): Promise<void> {\n\t\tif (!connected) {\n\t\t\treturn;\n\t\t}\n\t\tawait client.destroy();\n\t\tconnected = false;\n\t}\n\n\tfunction getGuild(): Guild {\n\t\tif (!connected) {\n\t\t\tthrow new Error('Discord client is not connected. Call connect() first.');\n\t\t}\n\t\tconst guild = client.guilds.cache.get(guildId);\n\t\tif (!guild) {\n\t\t\tthrow new Error(`Guild with ID \"${guildId}\" is no longer available.`);\n\t\t}\n\t\treturn guild;\n\t}\n\n\tfunction isConnected(): boolean {\n\t\treturn connected;\n\t}\n\n\treturn {\n\t\tconnect,\n\t\tdisconnect,\n\t\tgetGuild,\n\t\tisConnected,\n\t};\n}\n","import { z } from 'zod';\nimport 'dotenv/config';\n\nconst envSchema = z.object({\n\tDISCORD_TOKEN: z.string().min(1, 'DISCORD_TOKEN is required'),\n\tGUILD_ID: z.string().min(1, 'GUILD_ID is required'),\n});\n\nexport type Config = z.infer<typeof envSchema>;\n\nexport function parseConfig(env: Record<string, string | undefined>): Config {\n\tconst parsed = envSchema.safeParse(env);\n\n\tif (!parsed.success) {\n\t\tconst messages = parsed.error.issues.map(\n\t\t\t(issue) => `${issue.path.join('.')}: ${issue.message}`,\n\t\t);\n\t\tthrow new Error(`Invalid environment variables: ${messages.join(', ')}`);\n\t}\n\n\treturn parsed.data;\n}\n","type TextContent = { type: 'text'; text: string };\ntype ImageContent = { type: 'image'; data: string; mimeType: string };\ntype ToolContent = TextContent | ImageContent;\n\nexport function toolText(text: string) {\n\treturn { content: [{ type: 'text' as const, text }] };\n}\n\nexport function toolImage(data: string, mimeType: string) {\n\treturn { content: [{ type: 'image' as const, data, mimeType }] };\n}\n\nexport function toolMultiContent(items: ToolContent[]) {\n\treturn { content: items };\n}\n\nexport function toolError(text: string) {\n\treturn {\n\t\tcontent: [{ type: 'text' as const, text }],\n\t\tisError: true as const,\n\t};\n}\n","import type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolError, toolText } from '#/commons/tool-response.helpers';\n\nexport async function assignRoleHandler(\n\targs: { userId: string; roleId: string; action: 'add' | 'remove' },\n\tservice: DiscordClientService,\n) {\n\ttry {\n\t\tconst guild = service.getGuild();\n\t\tconst member = await guild.members.fetch(args.userId);\n\t\tif (!member) {\n\t\t\treturn toolError(\n\t\t\t\t`User with ID \"${args.userId}\" not found in the server.`,\n\t\t\t);\n\t\t}\n\n\t\tconst role = guild.roles.cache.get(args.roleId);\n\t\tif (!role) {\n\t\t\treturn toolError(`Role with ID \"${args.roleId}\" not found.`);\n\t\t}\n\n\t\tif (args.action === 'add') {\n\t\t\tawait member.roles.add(role);\n\t\t} else {\n\t\t\tawait member.roles.remove(role);\n\t\t}\n\n\t\treturn toolText(\n\t\t\tJSON.stringify({ userId: args.userId, roleId: args.roleId }),\n\t\t);\n\t} catch (err) {\n\t\treturn toolError(`Error assigning role: ${String(err)}`);\n\t}\n}\n","import { z } from 'zod';\n\nexport const assignRoleSchema = {\n\tuserId: z.string().describe('ID of the user to assign/remove the role from'),\n\troleId: z.string().describe('ID of the role to assign or remove'),\n\taction: z\n\t\t.enum(['add', 'remove'])\n\t\t.describe('Whether to add or remove the role from the user'),\n};\n","import { assignRoleHandler } from './assign-role.handler';\nimport { assignRoleSchema } from './assign-role.schema';\n\nexport const assignRole = {\n\tname: 'assign_role' as const,\n\tdescription: 'Assigns or removes a role from a user in the Discord server.',\n\tschema: assignRoleSchema,\n\texecute: assignRoleHandler,\n};\n","import { ChannelType } from 'discord.js';\nimport type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolError, toolText } from '#/commons/tool-response.helpers';\n\nexport async function createCategoryHandler(\n\targs: { name: string },\n\tservice: DiscordClientService,\n) {\n\ttry {\n\t\tconst guild = service.getGuild();\n\t\tconst category = await guild.channels.create({\n\t\t\tname: args.name,\n\t\t\ttype: ChannelType.GuildCategory,\n\t\t});\n\t\treturn toolText(JSON.stringify({ id: category.id, name: category.name }));\n\t} catch (err) {\n\t\treturn toolError(`Error creating category: ${String(err)}`);\n\t}\n}\n","import { z } from 'zod';\n\nexport const createCategorySchema = {\n\tname: z.string().min(1).max(100).describe('Name of the category to create'),\n};\n","import { createCategoryHandler } from './create-category.handler';\nimport { createCategorySchema } from './create-category.schema';\n\nexport const createCategory = {\n\tname: 'create_category' as const,\n\tdescription: 'Creates a new category in the Discord server.',\n\tschema: createCategorySchema,\n\texecute: createCategoryHandler,\n};\n","import type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolError, toolText } from '#/commons/tool-response.helpers';\n\nexport async function createRoleHandler(\n\targs: { name: string; color?: string },\n\tservice: DiscordClientService,\n) {\n\ttry {\n\t\tconst guild = service.getGuild();\n\t\tconst colorValue = args.color\n\t\t\t? Number.parseInt(args.color.replace('#', ''), 16)\n\t\t\t: undefined;\n\t\tconst role = await guild.roles.create({\n\t\t\tname: args.name,\n\t\t\tcolor: colorValue,\n\t\t});\n\t\treturn toolText(JSON.stringify({ id: role.id, name: role.name }));\n\t} catch (err) {\n\t\treturn toolError(`Error creating role: ${String(err)}`);\n\t}\n}\n","import { z } from 'zod';\n\nexport const createRoleSchema = {\n\tname: z.string().min(1).max(100).describe('Name of the role to create'),\n\tcolor: z\n\t\t.string()\n\t\t.regex(/^#?[0-9A-Fa-f]{6}$/)\n\t\t.optional()\n\t\t.describe('Optional hex color for the role (e.g., #FF0000)'),\n};\n","import { createRoleHandler } from './create-role.handler';\nimport { createRoleSchema } from './create-role.schema';\n\nexport const createRole = {\n\tname: 'create_role' as const,\n\tdescription: 'Creates a new role in the Discord server.',\n\tschema: createRoleSchema,\n\texecute: createRoleHandler,\n};\n","import { ChannelType } from 'discord.js';\nimport type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolError, toolText } from '#/commons/tool-response.helpers';\n\nexport async function createTextChannelHandler(\n\targs: { name: string; categoryId?: string },\n\tservice: DiscordClientService,\n) {\n\ttry {\n\t\tconst guild = service.getGuild();\n\t\tconst channel = await guild.channels.create({\n\t\t\tname: args.name,\n\t\t\ttype: ChannelType.GuildText,\n\t\t\tparent: args.categoryId ?? undefined,\n\t\t});\n\t\treturn toolText(JSON.stringify({ id: channel.id, name: channel.name }));\n\t} catch (err) {\n\t\treturn toolError(`Error creating text channel: ${String(err)}`);\n\t}\n}\n","import { z } from 'zod';\n\nexport const createTextChannelSchema = {\n\tname: z\n\t\t.string()\n\t\t.min(1)\n\t\t.max(100)\n\t\t.describe('Name of the text channel to create'),\n\tcategoryId: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe('Optional ID of the category to place the channel under'),\n};\n","import { createTextChannelHandler } from './create-text-channel.handler';\nimport { createTextChannelSchema } from './create-text-channel.schema';\n\nexport const createTextChannel = {\n\tname: 'create_text_channel' as const,\n\tdescription: 'Creates a new text channel in the Discord server.',\n\tschema: createTextChannelSchema,\n\texecute: createTextChannelHandler,\n};\n","import { ChannelType } from 'discord.js';\nimport type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolError, toolText } from '#/commons/tool-response.helpers';\n\nexport async function createVoiceChannelHandler(\n\targs: { name: string; categoryId?: string },\n\tservice: DiscordClientService,\n) {\n\ttry {\n\t\tconst guild = service.getGuild();\n\t\tconst channel = await guild.channels.create({\n\t\t\tname: args.name,\n\t\t\ttype: ChannelType.GuildVoice,\n\t\t\tparent: args.categoryId ?? undefined,\n\t\t});\n\t\treturn toolText(JSON.stringify({ id: channel.id, name: channel.name }));\n\t} catch (err) {\n\t\treturn toolError(`Error creating voice channel: ${String(err)}`);\n\t}\n}\n","import { z } from 'zod';\n\nexport const createVoiceChannelSchema = {\n\tname: z\n\t\t.string()\n\t\t.min(1)\n\t\t.max(100)\n\t\t.describe('Name of the voice channel to create'),\n\tcategoryId: z\n\t\t.string()\n\t\t.optional()\n\t\t.describe('Optional ID of the category to place the channel under'),\n};\n","import { createVoiceChannelHandler } from './create-voice-channel.handler';\nimport { createVoiceChannelSchema } from './create-voice-channel.schema';\n\nexport const createVoiceChannel = {\n\tname: 'create_voice_channel' as const,\n\tdescription: 'Creates a new voice channel in the Discord server.',\n\tschema: createVoiceChannelSchema,\n\texecute: createVoiceChannelHandler,\n};\n","import { ChannelType } from 'discord.js';\nimport type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolError, toolText } from '#/commons/tool-response.helpers';\n\nexport async function deleteCategoryHandler(\n\targs: { id: string; confirm: true },\n\tservice: DiscordClientService,\n) {\n\ttry {\n\t\tconst guild = service.getGuild();\n\t\tconst category = guild.channels.cache.get(args.id);\n\t\tif (!category) {\n\t\t\treturn toolError(`Category with ID \"${args.id}\" not found.`);\n\t\t}\n\t\tif (category.type !== ChannelType.GuildCategory) {\n\t\t\treturn toolError(`Channel with ID \"${args.id}\" is not a category.`);\n\t\t}\n\t\tawait category.delete();\n\t\treturn toolText(JSON.stringify({ id: category.id, name: category.name }));\n\t} catch (err) {\n\t\treturn toolError(`Error deleting category: ${String(err)}`);\n\t}\n}\n","import { z } from 'zod';\n\nexport const deleteCategorySchema = {\n\tid: z.string().describe('ID of the category to delete'),\n\tconfirm: z\n\t\t.literal(true)\n\t\t.describe('Explicit confirmation required. Must be set to true.'),\n};\n","import { deleteCategoryHandler } from './delete-category.handler';\nimport { deleteCategorySchema } from './delete-category.schema';\n\nexport const deleteCategory = {\n\tname: 'delete_category' as const,\n\tdescription:\n\t\t'Deletes a category from the Discord server. Requires explicit confirmation.',\n\tschema: deleteCategorySchema,\n\texecute: deleteCategoryHandler,\n};\n","import type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolError, toolText } from '#/commons/tool-response.helpers';\n\nexport async function deleteChannelHandler(\n\targs: { id: string; confirm: true },\n\tservice: DiscordClientService,\n) {\n\ttry {\n\t\tconst guild = service.getGuild();\n\t\tconst channel = guild.channels.cache.get(args.id);\n\t\tif (!channel) {\n\t\t\treturn toolError(`Channel with ID \"${args.id}\" not found.`);\n\t\t}\n\t\tawait channel.delete();\n\t\treturn toolText(JSON.stringify({ id: channel.id, name: channel.name }));\n\t} catch (err) {\n\t\treturn toolError(`Error deleting channel: ${String(err)}`);\n\t}\n}\n","import { z } from 'zod';\n\nexport const deleteChannelSchema = {\n\tid: z.string().describe('ID of the channel to delete'),\n\tconfirm: z\n\t\t.literal(true)\n\t\t.describe('Explicit confirmation required. Must be set to true.'),\n};\n","import { deleteChannelHandler } from './delete-channel.handler';\nimport { deleteChannelSchema } from './delete-channel.schema';\n\nexport const deleteChannel = {\n\tname: 'delete_channel' as const,\n\tdescription:\n\t\t'Deletes a channel from the Discord server. Requires explicit confirmation.',\n\tschema: deleteChannelSchema,\n\texecute: deleteChannelHandler,\n};\n","import type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolError, toolText } from '#/commons/tool-response.helpers';\n\nexport async function deleteRoleHandler(\n\targs: { id: string; confirm: true },\n\tservice: DiscordClientService,\n) {\n\ttry {\n\t\tconst guild = service.getGuild();\n\t\tconst role = guild.roles.cache.get(args.id);\n\t\tif (!role) {\n\t\t\treturn toolError(`Role with ID \"${args.id}\" not found.`);\n\t\t}\n\t\tawait role.delete();\n\t\treturn toolText(JSON.stringify({ id: role.id, name: role.name }));\n\t} catch (err) {\n\t\treturn toolError(`Error deleting role: ${String(err)}`);\n\t}\n}\n","import { z } from 'zod';\n\nexport const deleteRoleSchema = {\n\tid: z.string().describe('ID of the role to delete'),\n\tconfirm: z\n\t\t.literal(true)\n\t\t.describe('Explicit confirmation required. Must be set to true.'),\n};\n","import { deleteRoleHandler } from './delete-role.handler';\nimport { deleteRoleSchema } from './delete-role.schema';\n\nexport const deleteRole = {\n\tname: 'delete_role' as const,\n\tdescription:\n\t\t'Deletes a role from the Discord server. Requires explicit confirmation.',\n\tschema: deleteRoleSchema,\n\texecute: deleteRoleHandler,\n};\n","import { ChannelType } from 'discord.js';\nimport type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolText } from '#/commons/tool-response.helpers';\n\nfunction getChannelTypeName(type: ChannelType): string {\n\tconst entry = Object.entries(ChannelType).find(([, value]) => value === type);\n\treturn entry?.[0] ?? 'Unknown';\n}\n\nexport async function listChannelsHandler(service: DiscordClientService) {\n\tconst guild = service.getGuild();\n\tconst channels = guild.channels.cache.map((channel) => ({\n\t\tid: channel.id,\n\t\tname: channel.name,\n\t\ttype: getChannelTypeName(channel.type),\n\t}));\n\treturn toolText(JSON.stringify(channels));\n}\n","import { listChannelsHandler } from './list-channels.handler';\n\nexport const listChannels = {\n\tname: 'list_channels' as const,\n\tdescription:\n\t\t'Lists all channels and categories in the configured Discord server. Returns an array of objects with id, name, and type.',\n\texecute: listChannelsHandler,\n};\n","import type { GuildChannel } from 'discord.js';\nimport type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolText } from '#/commons/tool-response.helpers';\n\ninterface OrderedChannel {\n\tid: string;\n\tname: string;\n\ttype: string;\n\tposition: number;\n\tchildren?: OrderedChannel[];\n}\n\nexport async function listChannelsOrderedHandler(\n\tservice: DiscordClientService,\n) {\n\tconst guild = service.getGuild();\n\tconst allChannels = [...guild.channels.cache.values()] as GuildChannel[];\n\n\tconst categories = allChannels\n\t\t.filter((ch) => ch.type === 4)\n\t\t.sort((a, b) => a.position - b.position);\n\n\tconst uncategorized = allChannels\n\t\t.filter((ch) => ch.type !== 4 && !ch.parentId)\n\t\t.sort((a, b) => a.position - b.position);\n\n\tconst result: OrderedChannel[] = [];\n\n\tfor (const cat of categories) {\n\t\tconst children = allChannels\n\t\t\t.filter((ch) => ch.parentId === cat.id)\n\t\t\t.sort((a, b) => a.position - b.position)\n\t\t\t.map((ch) => ({\n\t\t\t\tid: ch.id,\n\t\t\t\tname: ch.name,\n\t\t\t\ttype: String(ch.type),\n\t\t\t\tposition: ch.position,\n\t\t\t}));\n\n\t\tresult.push({\n\t\t\tid: cat.id,\n\t\t\tname: cat.name,\n\t\t\ttype: 'GuildCategory',\n\t\t\tposition: cat.position,\n\t\t\tchildren,\n\t\t});\n\t}\n\n\tfor (const ch of uncategorized) {\n\t\tresult.push({\n\t\t\tid: ch.id,\n\t\t\tname: ch.name,\n\t\t\ttype: String(ch.type),\n\t\t\tposition: ch.position,\n\t\t});\n\t}\n\n\treturn toolText(JSON.stringify(result, null, 2));\n}\n","import { listChannelsOrderedHandler } from './list-channels-ordered.handler';\n\nexport const listChannelsOrdered = {\n\tname: 'list_channels_ordered' as const,\n\tdescription:\n\t\t'Lists all channels and categories in the order they appear in the Discord client (top to bottom). Categories include their nested channels.',\n\texecute: listChannelsOrderedHandler,\n};\n","import type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolText } from '#/commons/tool-response.helpers';\n\nexport async function listRolesHandler(service: DiscordClientService) {\n\tconst guild = service.getGuild();\n\tconst roles = guild.roles.cache.map((role) => ({\n\t\tid: role.id,\n\t\tname: role.name,\n\t}));\n\treturn toolText(JSON.stringify(roles));\n}\n","import { listRolesHandler } from './list-roles.handler';\n\nexport const listRoles = {\n\tname: 'list_roles' as const,\n\tdescription:\n\t\t'Lists all roles in the configured Discord server. Returns an array of objects with id and name.',\n\texecute: listRolesHandler,\n};\n","import { toolText } from '#/commons/tool-response.helpers';\n\nexport async function pingHandler() {\n\treturn toolText('pong');\n}\n","import { pingHandler } from './ping.handler';\n\nexport const ping = {\n\tname: 'ping' as const,\n\tdescription:\n\t\t'Responds with pong. Useful for verifying the server is running.',\n\texecute: pingHandler,\n};\n","import type { GuildChannel } from 'discord.js';\nimport type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolError, toolText } from '#/commons/tool-response.helpers';\n\nexport async function reorderChannelsHandler(\n\targs: {\n\t\torders: Array<{\n\t\t\tchannelId: string;\n\t\t\tposition: number;\n\t\t\tcategoryId?: string;\n\t\t}>;\n\t},\n\tservice: DiscordClientService,\n) {\n\ttry {\n\t\tconst guild = service.getGuild();\n\t\tconst results: Array<{\n\t\t\tchannelId: string;\n\t\t\tname: string;\n\t\t\tsuccess: boolean;\n\t\t}> = [];\n\n\t\tfor (const order of args.orders) {\n\t\t\tconst channel = guild.channels.cache.get(order.channelId) as\n\t\t\t\t| GuildChannel\n\t\t\t\t| undefined;\n\t\t\tif (!channel) {\n\t\t\t\tresults.push({\n\t\t\t\t\tchannelId: order.channelId,\n\t\t\t\t\tname: 'unknown',\n\t\t\t\t\tsuccess: false,\n\t\t\t\t});\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (order.categoryId !== undefined) {\n\t\t\t\tawait channel.setParent(order.categoryId, {\n\t\t\t\t\tlockPermissions: false,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tawait channel.setPosition(order.position);\n\n\t\t\tresults.push({\n\t\t\t\tchannelId: order.channelId,\n\t\t\t\tname: channel.name,\n\t\t\t\tsuccess: true,\n\t\t\t});\n\t\t}\n\n\t\treturn toolText(JSON.stringify(results));\n\t} catch (err) {\n\t\treturn toolError(`Error reordering channels: ${String(err)}`);\n\t}\n}\n","import { z } from 'zod';\n\nexport const reorderChannelsSchema = {\n\torders: z\n\t\t.array(\n\t\t\tz.object({\n\t\t\t\tchannelId: z.string().describe('ID of the channel to move/reorder'),\n\t\t\t\tposition: z\n\t\t\t\t\t.number()\n\t\t\t\t\t.int()\n\t\t\t\t\t.min(0)\n\t\t\t\t\t.describe('New position for the channel (0 = top)'),\n\t\t\t\tcategoryId: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.optional()\n\t\t\t\t\t.describe(\n\t\t\t\t\t\t'Optional category ID to move the channel under. Omit to keep current parent.',\n\t\t\t\t\t),\n\t\t\t}),\n\t\t)\n\t\t.describe('Array of channel reorder operations'),\n};\n","import { reorderChannelsHandler } from './reorder-channels.handler';\nimport { reorderChannelsSchema } from './reorder-channels.schema';\n\nexport const reorderChannels = {\n\tname: 'reorder_channels' as const,\n\tdescription:\n\t\t'Moves and/or reorders channels and categories. Accepts an array of operations, each specifying a channel ID, new position, and optionally a category ID to move under.',\n\tschema: reorderChannelsSchema,\n\texecute: reorderChannelsHandler,\n};\n","import { ChannelType, PermissionsBitField } from 'discord.js';\nimport type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolError, toolText } from '#/commons/tool-response.helpers';\n\ntype PermissionOverwriteOptions = Partial<\n\tRecord<keyof typeof PermissionsBitField.Flags, boolean | null>\n>;\n\nfunction buildPermissionOptions(\n\tallow: string[] | undefined,\n\tdeny: string[] | undefined,\n): PermissionOverwriteOptions {\n\tconst options: PermissionOverwriteOptions = {};\n\n\tfor (const perm of allow ?? []) {\n\t\tif (\n\t\t\tPermissionsBitField.Flags[\n\t\t\t\tperm as keyof typeof PermissionsBitField.Flags\n\t\t\t] === undefined\n\t\t) {\n\t\t\tthrow new Error(`Unknown permission: \"${perm}\"`);\n\t\t}\n\t\toptions[perm as keyof typeof PermissionsBitField.Flags] = true;\n\t}\n\n\tfor (const perm of deny ?? []) {\n\t\tif (\n\t\t\tPermissionsBitField.Flags[\n\t\t\t\tperm as keyof typeof PermissionsBitField.Flags\n\t\t\t] === undefined\n\t\t) {\n\t\t\tthrow new Error(`Unknown permission: \"${perm}\"`);\n\t\t}\n\t\toptions[perm as keyof typeof PermissionsBitField.Flags] = false;\n\t}\n\n\treturn options;\n}\n\nexport async function setCategoryPermissionsHandler(\n\targs: {\n\t\tcategoryId: string;\n\t\troleId: string;\n\t\tallow?: string[];\n\t\tdeny?: string[];\n\t},\n\tservice: DiscordClientService,\n) {\n\ttry {\n\t\tconst guild = service.getGuild();\n\t\tconst category = guild.channels.cache.get(args.categoryId);\n\t\tif (!category) {\n\t\t\treturn toolError(`Category with ID \"${args.categoryId}\" not found.`);\n\t\t}\n\n\t\tif (category.type !== ChannelType.GuildCategory) {\n\t\t\treturn toolError(\n\t\t\t\t`Channel with ID \"${args.categoryId}\" is not a category.`,\n\t\t\t);\n\t\t}\n\n\t\tconst role = guild.roles.cache.get(args.roleId);\n\t\tif (!role) {\n\t\t\treturn toolError(`Role with ID \"${args.roleId}\" not found.`);\n\t\t}\n\n\t\tconst options = buildPermissionOptions(args.allow, args.deny);\n\n\t\tawait category.permissionOverwrites.edit(role.id, options);\n\n\t\treturn toolText(\n\t\t\tJSON.stringify({ categoryId: args.categoryId, roleId: args.roleId }),\n\t\t);\n\t} catch (err) {\n\t\treturn toolError(`Error setting category permissions: ${String(err)}`);\n\t}\n}\n","import { z } from 'zod';\n\nexport const setCategoryPermissionsSchema = {\n\tcategoryId: z\n\t\t.string()\n\t\t.describe('ID of the category to modify permissions for'),\n\troleId: z.string().describe('ID of the role to set permissions for'),\n\tallow: z\n\t\t.array(z.string())\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Optional array of permission names to allow (e.g., SendMessages, ViewChannel)',\n\t\t),\n\tdeny: z\n\t\t.array(z.string())\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Optional array of permission names to deny (e.g., SendMessages, ViewChannel)',\n\t\t),\n};\n","import { setCategoryPermissionsHandler } from './set-category-permissions.handler';\nimport { setCategoryPermissionsSchema } from './set-category-permissions.schema';\n\nexport const setCategoryPermissions = {\n\tname: 'set_category_permissions' as const,\n\tdescription:\n\t\t'Sets permission overwrites for a role on a specific category. Accepts arrays of permission names to allow or deny.',\n\tschema: setCategoryPermissionsSchema,\n\texecute: setCategoryPermissionsHandler,\n};\n","import { PermissionsBitField } from 'discord.js';\nimport type { DiscordClientService } from '#/commons/discord-client.service';\nimport { toolError, toolText } from '#/commons/tool-response.helpers';\n\ntype PermissionOverwriteOptions = Partial<\n\tRecord<keyof typeof PermissionsBitField.Flags, boolean | null>\n>;\n\nfunction buildPermissionOptions(\n\tallow: string[] | undefined,\n\tdeny: string[] | undefined,\n): PermissionOverwriteOptions {\n\tconst options: PermissionOverwriteOptions = {};\n\n\tfor (const perm of allow ?? []) {\n\t\tif (\n\t\t\tPermissionsBitField.Flags[\n\t\t\t\tperm as keyof typeof PermissionsBitField.Flags\n\t\t\t] === undefined\n\t\t) {\n\t\t\tthrow new Error(`Unknown permission: \"${perm}\"`);\n\t\t}\n\t\toptions[perm as keyof typeof PermissionsBitField.Flags] = true;\n\t}\n\n\tfor (const perm of deny ?? []) {\n\t\tif (\n\t\t\tPermissionsBitField.Flags[\n\t\t\t\tperm as keyof typeof PermissionsBitField.Flags\n\t\t\t] === undefined\n\t\t) {\n\t\t\tthrow new Error(`Unknown permission: \"${perm}\"`);\n\t\t}\n\t\toptions[perm as keyof typeof PermissionsBitField.Flags] = false;\n\t}\n\n\treturn options;\n}\n\nexport async function setChannelPermissionsHandler(\n\targs: {\n\t\tchannelId: string;\n\t\troleId: string;\n\t\tallow?: string[];\n\t\tdeny?: string[];\n\t},\n\tservice: DiscordClientService,\n) {\n\ttry {\n\t\tconst guild = service.getGuild();\n\t\tconst channel = guild.channels.cache.get(args.channelId);\n\t\tif (!channel) {\n\t\t\treturn toolError(`Channel with ID \"${args.channelId}\" not found.`);\n\t\t}\n\n\t\tif (!('permissionOverwrites' in channel)) {\n\t\t\treturn toolError(\n\t\t\t\t`Channel with ID \"${args.channelId}\" does not support permission overwrites.`,\n\t\t\t);\n\t\t}\n\n\t\tconst role = guild.roles.cache.get(args.roleId);\n\t\tif (!role) {\n\t\t\treturn toolError(`Role with ID \"${args.roleId}\" not found.`);\n\t\t}\n\n\t\tconst options = buildPermissionOptions(args.allow, args.deny);\n\n\t\tawait (\n\t\t\tchannel as Extract<typeof channel, { permissionOverwrites: unknown }>\n\t\t).permissionOverwrites.edit(role.id, options);\n\n\t\treturn toolText(\n\t\t\tJSON.stringify({ channelId: args.channelId, roleId: args.roleId }),\n\t\t);\n\t} catch (err) {\n\t\treturn toolError(`Error setting channel permissions: ${String(err)}`);\n\t}\n}\n","import { z } from 'zod';\n\nexport const setChannelPermissionsSchema = {\n\tchannelId: z.string().describe('ID of the channel to modify permissions for'),\n\troleId: z.string().describe('ID of the role to set permissions for'),\n\tallow: z\n\t\t.array(z.string())\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Optional array of permission names to allow (e.g., SendMessages, ViewChannel)',\n\t\t),\n\tdeny: z\n\t\t.array(z.string())\n\t\t.optional()\n\t\t.describe(\n\t\t\t'Optional array of permission names to deny (e.g., SendMessages, ViewChannel)',\n\t\t),\n};\n","import { setChannelPermissionsHandler } from './set-channel-permissions.handler';\nimport { setChannelPermissionsSchema } from './set-channel-permissions.schema';\n\nexport const setChannelPermissions = {\n\tname: 'set_channel_permissions' as const,\n\tdescription:\n\t\t'Sets permission overwrites for a role on a specific channel or category. Accepts arrays of permission names to allow or deny. Works on both regular channels and category channels.',\n\tschema: setChannelPermissionsSchema,\n\texecute: setChannelPermissionsHandler,\n};\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { createDiscordClientService } from './commons/discord-client.service';\nimport { parseConfig } from './config';\nimport { assignRole } from './tools/assign-role';\nimport { createCategory } from './tools/create-category';\nimport { createRole } from './tools/create-role';\nimport { createTextChannel } from './tools/create-text-channel';\nimport { createVoiceChannel } from './tools/create-voice-channel';\nimport { deleteCategory } from './tools/delete-category';\nimport { deleteChannel } from './tools/delete-channel';\nimport { deleteRole } from './tools/delete-role';\nimport { listChannels } from './tools/list-channels';\nimport { listChannelsOrdered } from './tools/list-channels-ordered';\nimport { listRoles } from './tools/list-roles';\nimport { ping } from './tools/ping';\nimport { reorderChannels } from './tools/reorder-channels';\nimport { setCategoryPermissions } from './tools/set-category-permissions';\nimport { setChannelPermissions } from './tools/set-channel-permissions';\n\nconst config = parseConfig(process.env);\nconst discordService = createDiscordClientService(\n\tconfig.DISCORD_TOKEN,\n\tconfig.GUILD_ID,\n);\n\nconst server = new McpServer({\n\tname: 'discord-manager-mcp',\n\tversion: '1.0.0',\n});\n\nserver.registerTool(ping.name, { description: ping.description }, () =>\n\tping.execute(),\n);\n\nserver.registerTool(\n\tlistChannels.name,\n\t{ description: listChannels.description },\n\t() => listChannels.execute(discordService),\n);\n\nserver.registerTool(\n\tcreateTextChannel.name,\n\t{\n\t\tdescription: createTextChannel.description,\n\t\tinputSchema: createTextChannel.schema,\n\t},\n\t(args) => createTextChannel.execute(args, discordService),\n);\n\nserver.registerTool(\n\tcreateVoiceChannel.name,\n\t{\n\t\tdescription: createVoiceChannel.description,\n\t\tinputSchema: createVoiceChannel.schema,\n\t},\n\t(args) => createVoiceChannel.execute(args, discordService),\n);\n\nserver.registerTool(\n\tdeleteChannel.name,\n\t{\n\t\tdescription: deleteChannel.description,\n\t\tinputSchema: deleteChannel.schema,\n\t},\n\t(args) => deleteChannel.execute(args, discordService),\n);\n\nserver.registerTool(\n\tcreateCategory.name,\n\t{\n\t\tdescription: createCategory.description,\n\t\tinputSchema: createCategory.schema,\n\t},\n\t(args) => createCategory.execute(args, discordService),\n);\n\nserver.registerTool(\n\tdeleteCategory.name,\n\t{\n\t\tdescription: deleteCategory.description,\n\t\tinputSchema: deleteCategory.schema,\n\t},\n\t(args) => deleteCategory.execute(args, discordService),\n);\n\nserver.registerTool(\n\tlistRoles.name,\n\t{ description: listRoles.description },\n\t() => listRoles.execute(discordService),\n);\n\nserver.registerTool(\n\tcreateRole.name,\n\t{\n\t\tdescription: createRole.description,\n\t\tinputSchema: createRole.schema,\n\t},\n\t(args) => createRole.execute(args, discordService),\n);\n\nserver.registerTool(\n\tdeleteRole.name,\n\t{\n\t\tdescription: deleteRole.description,\n\t\tinputSchema: deleteRole.schema,\n\t},\n\t(args) => deleteRole.execute(args, discordService),\n);\n\nserver.registerTool(\n\tassignRole.name,\n\t{\n\t\tdescription: assignRole.description,\n\t\tinputSchema: assignRole.schema,\n\t},\n\t(args) => assignRole.execute(args, discordService),\n);\n\nserver.registerTool(\n\tsetChannelPermissions.name,\n\t{\n\t\tdescription: setChannelPermissions.description,\n\t\tinputSchema: setChannelPermissions.schema,\n\t},\n\t(args) => setChannelPermissions.execute(args, discordService),\n);\n\nserver.registerTool(\n\tsetCategoryPermissions.name,\n\t{\n\t\tdescription: setCategoryPermissions.description,\n\t\tinputSchema: setCategoryPermissions.schema,\n\t},\n\t(args) => setCategoryPermissions.execute(args, discordService),\n);\n\nserver.registerTool(\n\tlistChannelsOrdered.name,\n\t{ description: listChannelsOrdered.description },\n\t() => listChannelsOrdered.execute(discordService),\n);\n\nserver.registerTool(\n\treorderChannels.name,\n\t{\n\t\tdescription: reorderChannels.description,\n\t\tinputSchema: reorderChannels.schema,\n\t},\n\t(args) => reorderChannels.execute(args, discordService),\n);\n\nasync function main() {\n\tconsole.error(\n\t\t`[startup] Connecting to Discord (GUILD_ID: ${config.GUILD_ID})...`,\n\t);\n\tawait discordService.connect();\n\tconsole.error('[startup] Discord connected and permissions verified.');\n\n\tconst transport = new StdioServerTransport();\n\tawait server.connect(transport);\n}\n\nmain().catch((err) => {\n\tconsole.error('fatal error:', err);\n\tprocess.exit(1);\n});\n"],"mappings":";;;;;;;AAUA,SAAgB,2BACf,OACA,SACuB;CACvB,MAAM,SAAS,IAAI,OAAO,EACzB,SAAS,CAAC,kBAAkB,QAAQ,kBAAkB,YAAY,EACnE,CAAC;CAED,IAAI,YAAY;CAEhB,eAAe,UAAyB;EACvC,IAAI,WACH;EAGD,MAAM,IAAI,SAAe,SAAS,WAAW;GAC5C,MAAM,UAAU,iBAAiB;IAChC,uBAAO,IAAI,MAAM,+CAA+C,CAAC;GAClE,GAAG,GAAK;GAER,OAAO,KAAK,eAAe;IAC1B,aAAa,OAAO;IACpB,YAAY;IACZ,QAAQ;GACT,CAAC;GAED,OAAO,MAAM,KAAK,EAAE,OAAO,QAAQ;IAClC,aAAa,OAAO;IACpB,OAAO,GAAG;GACX,CAAC;EACF,CAAC;EAED,MAAM,QAAQ,OAAO,OAAO,MAAM,IAAI,OAAO;EAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MACT,kBAAkB,QAAQ,oFAC3B;EAGD,MAAM,YAAY,MAAM,MAAM,QAAQ,QAAQ;EAM9C,MAAM,UAAU,CAJf,oBAAoB,MAAM,gBAC1B,oBAAoB,MAAM,WAGO,EAAE,QAClC,SAAS,CAAC,UAAU,YAAY,IAAI,IAAI,CAC1C;EAEA,IAAI,QAAQ,SAAS,GAAG;GACvB,MAAM,eAAe,QAAQ,KAAK,SAAS;IAI1C,OAHc,OAAO,QAAQ,oBAAoB,KAAK,EAAE,MACtD,GAAG,WAAW,UAAU,IAEf,IAAI,MAAM,OAAO,IAAI;GACjC,CAAC;GACD,MAAM,IAAI,MACT,wCAAwC,aAAa,KAAK,IAAI,EAAE,4DACjE;EACD;CACD;CAEA,eAAe,aAA4B;EAC1C,IAAI,CAAC,WACJ;EAED,MAAM,OAAO,QAAQ;EACrB,YAAY;CACb;CAEA,SAAS,WAAkB;EAC1B,IAAI,CAAC,WACJ,MAAM,IAAI,MAAM,wDAAwD;EAEzE,MAAM,QAAQ,OAAO,OAAO,MAAM,IAAI,OAAO;EAC7C,IAAI,CAAC,OACJ,MAAM,IAAI,MAAM,kBAAkB,QAAQ,0BAA0B;EAErE,OAAO;CACR;CAEA,SAAS,cAAuB;EAC/B,OAAO;CACR;CAEA,OAAO;EACN;EACA;EACA;EACA;CACD;AACD;;;AClGA,MAAM,YAAY,EAAE,OAAO;CAC1B,eAAe,EAAE,OAAO,EAAE,IAAI,GAAG,2BAA2B;CAC5D,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,sBAAsB;AACnD,CAAC;AAID,SAAgB,YAAY,KAAiD;CAC5E,MAAM,SAAS,UAAU,UAAU,GAAG;CAEtC,IAAI,CAAC,OAAO,SAAS;EACpB,MAAM,WAAW,OAAO,MAAM,OAAO,KACnC,UAAU,GAAG,MAAM,KAAK,KAAK,GAAG,EAAE,IAAI,MAAM,SAC9C;EACA,MAAM,IAAI,MAAM,kCAAkC,SAAS,KAAK,IAAI,GAAG;CACxE;CAEA,OAAO,OAAO;AACf;;;ACjBA,SAAgB,SAAS,MAAc;CACtC,OAAO,EAAE,SAAS,CAAC;EAAE,MAAM;EAAiB;CAAK,CAAC,EAAE;AACrD;AAUA,SAAgB,UAAU,MAAc;CACvC,OAAO;EACN,SAAS,CAAC;GAAE,MAAM;GAAiB;EAAK,CAAC;EACzC,SAAS;CACV;AACD;;;AClBA,eAAsB,kBACrB,MACA,SACC;CACD,IAAI;EACH,MAAM,QAAQ,QAAQ,SAAS;EAC/B,MAAM,SAAS,MAAM,MAAM,QAAQ,MAAM,KAAK,MAAM;EACpD,IAAI,CAAC,QACJ,OAAO,UACN,iBAAiB,KAAK,OAAO,2BAC9B;EAGD,MAAM,OAAO,MAAM,MAAM,MAAM,IAAI,KAAK,MAAM;EAC9C,IAAI,CAAC,MACJ,OAAO,UAAU,iBAAiB,KAAK,OAAO,aAAa;EAG5D,IAAI,KAAK,WAAW,OACnB,MAAM,OAAO,MAAM,IAAI,IAAI;OAE3B,MAAM,OAAO,MAAM,OAAO,IAAI;EAG/B,OAAO,SACN,KAAK,UAAU;GAAE,QAAQ,KAAK;GAAQ,QAAQ,KAAK;EAAO,CAAC,CAC5D;CACD,SAAS,KAAK;EACb,OAAO,UAAU,yBAAyB,OAAO,GAAG,GAAG;CACxD;AACD;;;AE9BA,MAAa,aAAa;CACzB,MAAM;CACN,aAAa;CACb,QAAQ;EDHR,QAAQ,EAAE,OAAO,EAAE,SAAS,+CAA+C;EAC3E,QAAQ,EAAE,OAAO,EAAE,SAAS,oCAAoC;EAChE,QAAQ,EACN,KAAK,CAAC,OAAO,QAAQ,CAAC,EACtB,SAAS,iDAAiD;CCDpD;CACR,SAAS;AACV;;;ACJA,eAAsB,sBACrB,MACA,SACC;CACD,IAAI;EAEH,MAAM,WAAW,MADH,QAAQ,SACK,EAAE,SAAS,OAAO;GAC5C,MAAM,KAAK;GACX,MAAM,YAAY;EACnB,CAAC;EACD,OAAO,SAAS,KAAK,UAAU;GAAE,IAAI,SAAS;GAAI,MAAM,SAAS;EAAK,CAAC,CAAC;CACzE,SAAS,KAAK;EACb,OAAO,UAAU,4BAA4B,OAAO,GAAG,GAAG;CAC3D;AACD;;;AEfA,MAAa,iBAAiB;CAC7B,MAAM;CACN,aAAa;CACb,QAAQ,EDHR,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,gCAAgC,ECGlE;CACR,SAAS;AACV;;;ACLA,eAAsB,kBACrB,MACA,SACC;CACD,IAAI;EACH,MAAM,QAAQ,QAAQ,SAAS;EAC/B,MAAM,aAAa,KAAK,QACrB,OAAO,SAAS,KAAK,MAAM,QAAQ,KAAK,EAAE,GAAG,EAAE,IAC/C,KAAA;EACH,MAAM,OAAO,MAAM,MAAM,MAAM,OAAO;GACrC,MAAM,KAAK;GACX,OAAO;EACR,CAAC;EACD,OAAO,SAAS,KAAK,UAAU;GAAE,IAAI,KAAK;GAAI,MAAM,KAAK;EAAK,CAAC,CAAC;CACjE,SAAS,KAAK;EACb,OAAO,UAAU,wBAAwB,OAAO,GAAG,GAAG;CACvD;AACD;;;AEjBA,MAAa,aAAa;CACzB,MAAM;CACN,aAAa;CACb,QAAQ;EDHR,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS,4BAA4B;EACtE,OAAO,EACL,OAAO,EACP,MAAM,oBAAoB,EAC1B,SAAS,EACT,SAAS,iDAAiD;CCFpD;CACR,SAAS;AACV;;;ACJA,eAAsB,yBACrB,MACA,SACC;CACD,IAAI;EAEH,MAAM,UAAU,MADF,QAAQ,SACI,EAAE,SAAS,OAAO;GAC3C,MAAM,KAAK;GACX,MAAM,YAAY;GAClB,QAAQ,KAAK,cAAc,KAAA;EAC5B,CAAC;EACD,OAAO,SAAS,KAAK,UAAU;GAAE,IAAI,QAAQ;GAAI,MAAM,QAAQ;EAAK,CAAC,CAAC;CACvE,SAAS,KAAK;EACb,OAAO,UAAU,gCAAgC,OAAO,GAAG,GAAG;CAC/D;AACD;;;AEhBA,MAAa,oBAAoB;CAChC,MAAM;CACN,aAAa;CACb,QAAQ;EDHR,MAAM,EACJ,OAAO,EACP,IAAI,CAAC,EACL,IAAI,GAAG,EACP,SAAS,oCAAoC;EAC/C,YAAY,EACV,OAAO,EACP,SAAS,EACT,SAAS,wDAAwD;CCL3D;CACR,SAAS;AACV;;;ACJA,eAAsB,0BACrB,MACA,SACC;CACD,IAAI;EAEH,MAAM,UAAU,MADF,QAAQ,SACI,EAAE,SAAS,OAAO;GAC3C,MAAM,KAAK;GACX,MAAM,YAAY;GAClB,QAAQ,KAAK,cAAc,KAAA;EAC5B,CAAC;EACD,OAAO,SAAS,KAAK,UAAU;GAAE,IAAI,QAAQ;GAAI,MAAM,QAAQ;EAAK,CAAC,CAAC;CACvE,SAAS,KAAK;EACb,OAAO,UAAU,iCAAiC,OAAO,GAAG,GAAG;CAChE;AACD;;;AEhBA,MAAa,qBAAqB;CACjC,MAAM;CACN,aAAa;CACb,QAAQ;EDHR,MAAM,EACJ,OAAO,EACP,IAAI,CAAC,EACL,IAAI,GAAG,EACP,SAAS,qCAAqC;EAChD,YAAY,EACV,OAAO,EACP,SAAS,EACT,SAAS,wDAAwD;CCL3D;CACR,SAAS;AACV;;;ACJA,eAAsB,sBACrB,MACA,SACC;CACD,IAAI;EAEH,MAAM,WADQ,QAAQ,SACD,EAAE,SAAS,MAAM,IAAI,KAAK,EAAE;EACjD,IAAI,CAAC,UACJ,OAAO,UAAU,qBAAqB,KAAK,GAAG,aAAa;EAE5D,IAAI,SAAS,SAAS,YAAY,eACjC,OAAO,UAAU,oBAAoB,KAAK,GAAG,qBAAqB;EAEnE,MAAM,SAAS,OAAO;EACtB,OAAO,SAAS,KAAK,UAAU;GAAE,IAAI,SAAS;GAAI,MAAM,SAAS;EAAK,CAAC,CAAC;CACzE,SAAS,KAAK;EACb,OAAO,UAAU,4BAA4B,OAAO,GAAG,GAAG;CAC3D;AACD;;;AEnBA,MAAa,iBAAiB;CAC7B,MAAM;CACN,aACC;CACD,QAAQ;EDJR,IAAI,EAAE,OAAO,EAAE,SAAS,8BAA8B;EACtD,SAAS,EACP,QAAQ,IAAI,EACZ,SAAS,sDAAsD;CCCzD;CACR,SAAS;AACV;;;ACNA,eAAsB,qBACrB,MACA,SACC;CACD,IAAI;EAEH,MAAM,UADQ,QAAQ,SACF,EAAE,SAAS,MAAM,IAAI,KAAK,EAAE;EAChD,IAAI,CAAC,SACJ,OAAO,UAAU,oBAAoB,KAAK,GAAG,aAAa;EAE3D,MAAM,QAAQ,OAAO;EACrB,OAAO,SAAS,KAAK,UAAU;GAAE,IAAI,QAAQ;GAAI,MAAM,QAAQ;EAAK,CAAC,CAAC;CACvE,SAAS,KAAK;EACb,OAAO,UAAU,2BAA2B,OAAO,GAAG,GAAG;CAC1D;AACD;;;AEfA,MAAa,gBAAgB;CAC5B,MAAM;CACN,aACC;CACD,QAAQ;EDJR,IAAI,EAAE,OAAO,EAAE,SAAS,6BAA6B;EACrD,SAAS,EACP,QAAQ,IAAI,EACZ,SAAS,sDAAsD;CCCzD;CACR,SAAS;AACV;;;ACNA,eAAsB,kBACrB,MACA,SACC;CACD,IAAI;EAEH,MAAM,OADQ,QAAQ,SACL,EAAE,MAAM,MAAM,IAAI,KAAK,EAAE;EAC1C,IAAI,CAAC,MACJ,OAAO,UAAU,iBAAiB,KAAK,GAAG,aAAa;EAExD,MAAM,KAAK,OAAO;EAClB,OAAO,SAAS,KAAK,UAAU;GAAE,IAAI,KAAK;GAAI,MAAM,KAAK;EAAK,CAAC,CAAC;CACjE,SAAS,KAAK;EACb,OAAO,UAAU,wBAAwB,OAAO,GAAG,GAAG;CACvD;AACD;;;AEfA,MAAa,aAAa;CACzB,MAAM;CACN,aACC;CACD,QAAQ;EDJR,IAAI,EAAE,OAAO,EAAE,SAAS,0BAA0B;EAClD,SAAS,EACP,QAAQ,IAAI,EACZ,SAAS,sDAAsD;CCCzD;CACR,SAAS;AACV;;;ACLA,SAAS,mBAAmB,MAA2B;CAEtD,OADc,OAAO,QAAQ,WAAW,EAAE,MAAM,GAAG,WAAW,UAAU,IAC7D,IAAI,MAAM;AACtB;AAEA,eAAsB,oBAAoB,SAA+B;CAExE,MAAM,WADQ,QAAQ,SACD,EAAE,SAAS,MAAM,KAAK,aAAa;EACvD,IAAI,QAAQ;EACZ,MAAM,QAAQ;EACd,MAAM,mBAAmB,QAAQ,IAAI;CACtC,EAAE;CACF,OAAO,SAAS,KAAK,UAAU,QAAQ,CAAC;AACzC;;;ACfA,MAAa,eAAe;CAC3B,MAAM;CACN,aACC;CACD,SAAS;AACV;;;ACKA,eAAsB,2BACrB,SACC;CAED,MAAM,cAAc,CAAC,GADP,QAAQ,SACM,EAAE,SAAS,MAAM,OAAO,CAAC;CAErD,MAAM,aAAa,YACjB,QAAQ,OAAO,GAAG,SAAS,CAAC,EAC5B,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CAExC,MAAM,gBAAgB,YACpB,QAAQ,OAAO,GAAG,SAAS,KAAK,CAAC,GAAG,QAAQ,EAC5C,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ;CAExC,MAAM,SAA2B,CAAC;CAElC,KAAK,MAAM,OAAO,YAAY;EAC7B,MAAM,WAAW,YACf,QAAQ,OAAO,GAAG,aAAa,IAAI,EAAE,EACrC,MAAM,GAAG,MAAM,EAAE,WAAW,EAAE,QAAQ,EACtC,KAAK,QAAQ;GACb,IAAI,GAAG;GACP,MAAM,GAAG;GACT,MAAM,OAAO,GAAG,IAAI;GACpB,UAAU,GAAG;EACd,EAAE;EAEH,OAAO,KAAK;GACX,IAAI,IAAI;GACR,MAAM,IAAI;GACV,MAAM;GACN,UAAU,IAAI;GACd;EACD,CAAC;CACF;CAEA,KAAK,MAAM,MAAM,eAChB,OAAO,KAAK;EACX,IAAI,GAAG;EACP,MAAM,GAAG;EACT,MAAM,OAAO,GAAG,IAAI;EACpB,UAAU,GAAG;CACd,CAAC;CAGF,OAAO,SAAS,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAChD;;;ACxDA,MAAa,sBAAsB;CAClC,MAAM;CACN,aACC;CACD,SAAS;AACV;;;ACJA,eAAsB,iBAAiB,SAA+B;CAErE,MAAM,QADQ,QAAQ,SACJ,EAAE,MAAM,MAAM,KAAK,UAAU;EAC9C,IAAI,KAAK;EACT,MAAM,KAAK;CACZ,EAAE;CACF,OAAO,SAAS,KAAK,UAAU,KAAK,CAAC;AACtC;;;ACRA,MAAa,YAAY;CACxB,MAAM;CACN,aACC;CACD,SAAS;AACV;;;ACLA,eAAsB,cAAc;CACnC,OAAO,SAAS,MAAM;AACvB;;;ACFA,MAAa,OAAO;CACnB,MAAM;CACN,aACC;CACD,SAAS;AACV;;;ACHA,eAAsB,uBACrB,MAOA,SACC;CACD,IAAI;EACH,MAAM,QAAQ,QAAQ,SAAS;EAC/B,MAAM,UAID,CAAC;EAEN,KAAK,MAAM,SAAS,KAAK,QAAQ;GAChC,MAAM,UAAU,MAAM,SAAS,MAAM,IAAI,MAAM,SAAS;GAGxD,IAAI,CAAC,SAAS;IACb,QAAQ,KAAK;KACZ,WAAW,MAAM;KACjB,MAAM;KACN,SAAS;IACV,CAAC;IACD;GACD;GAEA,IAAI,MAAM,eAAe,KAAA,GACxB,MAAM,QAAQ,UAAU,MAAM,YAAY,EACzC,iBAAiB,MAClB,CAAC;GAGF,MAAM,QAAQ,YAAY,MAAM,QAAQ;GAExC,QAAQ,KAAK;IACZ,WAAW,MAAM;IACjB,MAAM,QAAQ;IACd,SAAS;GACV,CAAC;EACF;EAEA,OAAO,SAAS,KAAK,UAAU,OAAO,CAAC;CACxC,SAAS,KAAK;EACb,OAAO,UAAU,8BAA8B,OAAO,GAAG,GAAG;CAC7D;AACD;;;AEnDA,MAAa,kBAAkB;CAC9B,MAAM;CACN,aACC;CACD,QAAQ,EDJR,QAAQ,EACN,MACA,EAAE,OAAO;EACR,WAAW,EAAE,OAAO,EAAE,SAAS,mCAAmC;EAClE,UAAU,EACR,OAAO,EACP,IAAI,EACJ,IAAI,CAAC,EACL,SAAS,wCAAwC;EACnD,YAAY,EACV,OAAO,EACP,SAAS,EACT,SACA,8EACD;CACF,CAAC,CACF,EACC,SAAS,qCAAqC,ECbxC;CACR,SAAS;AACV;;;ACDA,SAASA,yBACR,OACA,MAC6B;CAC7B,MAAM,UAAsC,CAAC;CAE7C,KAAK,MAAM,QAAQ,SAAS,CAAC,GAAG;EAC/B,IACC,oBAAoB,MACnB,UACK,KAAA,GAEN,MAAM,IAAI,MAAM,wBAAwB,KAAK,EAAE;EAEhD,QAAQ,QAAkD;CAC3D;CAEA,KAAK,MAAM,QAAQ,QAAQ,CAAC,GAAG;EAC9B,IACC,oBAAoB,MACnB,UACK,KAAA,GAEN,MAAM,IAAI,MAAM,wBAAwB,KAAK,EAAE;EAEhD,QAAQ,QAAkD;CAC3D;CAEA,OAAO;AACR;AAEA,eAAsB,8BACrB,MAMA,SACC;CACD,IAAI;EACH,MAAM,QAAQ,QAAQ,SAAS;EAC/B,MAAM,WAAW,MAAM,SAAS,MAAM,IAAI,KAAK,UAAU;EACzD,IAAI,CAAC,UACJ,OAAO,UAAU,qBAAqB,KAAK,WAAW,aAAa;EAGpE,IAAI,SAAS,SAAS,YAAY,eACjC,OAAO,UACN,oBAAoB,KAAK,WAAW,qBACrC;EAGD,MAAM,OAAO,MAAM,MAAM,MAAM,IAAI,KAAK,MAAM;EAC9C,IAAI,CAAC,MACJ,OAAO,UAAU,iBAAiB,KAAK,OAAO,aAAa;EAG5D,MAAM,UAAUA,yBAAuB,KAAK,OAAO,KAAK,IAAI;EAE5D,MAAM,SAAS,qBAAqB,KAAK,KAAK,IAAI,OAAO;EAEzD,OAAO,SACN,KAAK,UAAU;GAAE,YAAY,KAAK;GAAY,QAAQ,KAAK;EAAO,CAAC,CACpE;CACD,SAAS,KAAK;EACb,OAAO,UAAU,uCAAuC,OAAO,GAAG,GAAG;CACtE;AACD;;;AEzEA,MAAa,yBAAyB;CACrC,MAAM;CACN,aACC;CACD,QAAQ;EDJR,YAAY,EACV,OAAO,EACP,SAAS,8CAA8C;EACzD,QAAQ,EAAE,OAAO,EAAE,SAAS,uCAAuC;EACnE,OAAO,EACL,MAAM,EAAE,OAAO,CAAC,EAChB,SAAS,EACT,SACA,+EACD;EACD,MAAM,EACJ,MAAM,EAAE,OAAO,CAAC,EAChB,SAAS,EACT,SACA,8EACD;CCXO;CACR,SAAS;AACV;;;ACDA,SAAS,uBACR,OACA,MAC6B;CAC7B,MAAM,UAAsC,CAAC;CAE7C,KAAK,MAAM,QAAQ,SAAS,CAAC,GAAG;EAC/B,IACC,oBAAoB,MACnB,UACK,KAAA,GAEN,MAAM,IAAI,MAAM,wBAAwB,KAAK,EAAE;EAEhD,QAAQ,QAAkD;CAC3D;CAEA,KAAK,MAAM,QAAQ,QAAQ,CAAC,GAAG;EAC9B,IACC,oBAAoB,MACnB,UACK,KAAA,GAEN,MAAM,IAAI,MAAM,wBAAwB,KAAK,EAAE;EAEhD,QAAQ,QAAkD;CAC3D;CAEA,OAAO;AACR;AAEA,eAAsB,6BACrB,MAMA,SACC;CACD,IAAI;EACH,MAAM,QAAQ,QAAQ,SAAS;EAC/B,MAAM,UAAU,MAAM,SAAS,MAAM,IAAI,KAAK,SAAS;EACvD,IAAI,CAAC,SACJ,OAAO,UAAU,oBAAoB,KAAK,UAAU,aAAa;EAGlE,IAAI,EAAE,0BAA0B,UAC/B,OAAO,UACN,oBAAoB,KAAK,UAAU,0CACpC;EAGD,MAAM,OAAO,MAAM,MAAM,MAAM,IAAI,KAAK,MAAM;EAC9C,IAAI,CAAC,MACJ,OAAO,UAAU,iBAAiB,KAAK,OAAO,aAAa;EAG5D,MAAM,UAAU,uBAAuB,KAAK,OAAO,KAAK,IAAI;EAE5D,MACC,QACC,qBAAqB,KAAK,KAAK,IAAI,OAAO;EAE5C,OAAO,SACN,KAAK,UAAU;GAAE,WAAW,KAAK;GAAW,QAAQ,KAAK;EAAO,CAAC,CAClE;CACD,SAAS,KAAK;EACb,OAAO,UAAU,sCAAsC,OAAO,GAAG,GAAG;CACrE;AACD;;;AE3EA,MAAa,wBAAwB;CACpC,MAAM;CACN,aACC;CACD,QAAQ;EDJR,WAAW,EAAE,OAAO,EAAE,SAAS,6CAA6C;EAC5E,QAAQ,EAAE,OAAO,EAAE,SAAS,uCAAuC;EACnE,OAAO,EACL,MAAM,EAAE,OAAO,CAAC,EAChB,SAAS,EACT,SACA,+EACD;EACD,MAAM,EACJ,MAAM,EAAE,OAAO,CAAC,EAChB,SAAS,EACT,SACA,8EACD;CCTO;CACR,SAAS;AACV;;;ACWA,MAAM,SAAS,YAAY,QAAQ,GAAG;AACtC,MAAM,iBAAiB,2BACtB,OAAO,eACP,OAAO,QACR;AAEA,MAAM,SAAS,IAAI,UAAU;CAC5B,MAAM;CACN,SAAS;AACV,CAAC;AAED,OAAO,aAAa,KAAK,MAAM,EAAE,aAAa,KAAK,YAAY,SAC9D,KAAK,QAAQ,CACd;AAEA,OAAO,aACN,aAAa,MACb,EAAE,aAAa,aAAa,YAAY,SAClC,aAAa,QAAQ,cAAc,CAC1C;AAEA,OAAO,aACN,kBAAkB,MAClB;CACC,aAAa,kBAAkB;CAC/B,aAAa,kBAAkB;AAChC,IACC,SAAS,kBAAkB,QAAQ,MAAM,cAAc,CACzD;AAEA,OAAO,aACN,mBAAmB,MACnB;CACC,aAAa,mBAAmB;CAChC,aAAa,mBAAmB;AACjC,IACC,SAAS,mBAAmB,QAAQ,MAAM,cAAc,CAC1D;AAEA,OAAO,aACN,cAAc,MACd;CACC,aAAa,cAAc;CAC3B,aAAa,cAAc;AAC5B,IACC,SAAS,cAAc,QAAQ,MAAM,cAAc,CACrD;AAEA,OAAO,aACN,eAAe,MACf;CACC,aAAa,eAAe;CAC5B,aAAa,eAAe;AAC7B,IACC,SAAS,eAAe,QAAQ,MAAM,cAAc,CACtD;AAEA,OAAO,aACN,eAAe,MACf;CACC,aAAa,eAAe;CAC5B,aAAa,eAAe;AAC7B,IACC,SAAS,eAAe,QAAQ,MAAM,cAAc,CACtD;AAEA,OAAO,aACN,UAAU,MACV,EAAE,aAAa,UAAU,YAAY,SAC/B,UAAU,QAAQ,cAAc,CACvC;AAEA,OAAO,aACN,WAAW,MACX;CACC,aAAa,WAAW;CACxB,aAAa,WAAW;AACzB,IACC,SAAS,WAAW,QAAQ,MAAM,cAAc,CAClD;AAEA,OAAO,aACN,WAAW,MACX;CACC,aAAa,WAAW;CACxB,aAAa,WAAW;AACzB,IACC,SAAS,WAAW,QAAQ,MAAM,cAAc,CAClD;AAEA,OAAO,aACN,WAAW,MACX;CACC,aAAa,WAAW;CACxB,aAAa,WAAW;AACzB,IACC,SAAS,WAAW,QAAQ,MAAM,cAAc,CAClD;AAEA,OAAO,aACN,sBAAsB,MACtB;CACC,aAAa,sBAAsB;CACnC,aAAa,sBAAsB;AACpC,IACC,SAAS,sBAAsB,QAAQ,MAAM,cAAc,CAC7D;AAEA,OAAO,aACN,uBAAuB,MACvB;CACC,aAAa,uBAAuB;CACpC,aAAa,uBAAuB;AACrC,IACC,SAAS,uBAAuB,QAAQ,MAAM,cAAc,CAC9D;AAEA,OAAO,aACN,oBAAoB,MACpB,EAAE,aAAa,oBAAoB,YAAY,SACzC,oBAAoB,QAAQ,cAAc,CACjD;AAEA,OAAO,aACN,gBAAgB,MAChB;CACC,aAAa,gBAAgB;CAC7B,aAAa,gBAAgB;AAC9B,IACC,SAAS,gBAAgB,QAAQ,MAAM,cAAc,CACvD;AAEA,eAAe,OAAO;CACrB,QAAQ,MACP,8CAA8C,OAAO,SAAS,KAC/D;CACA,MAAM,eAAe,QAAQ;CAC7B,QAAQ,MAAM,uDAAuD;CAErE,MAAM,YAAY,IAAI,qBAAqB;CAC3C,MAAM,OAAO,QAAQ,SAAS;AAC/B;AAEA,KAAK,EAAE,OAAO,QAAQ;CACrB,QAAQ,MAAM,gBAAgB,GAAG;CACjC,QAAQ,KAAK,CAAC;AACf,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@guildforge/mcp",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "MCP Server to manage Discord channels, categories, roles and permissions",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"types": "./dist/index.d.mts",
|
|
9
|
+
"default": "./dist/index.mjs"
|
|
10
|
+
},
|
|
11
|
+
"./package.json": "./package.json"
|
|
12
|
+
},
|
|
13
|
+
"imports": {
|
|
14
|
+
"#*": "./src/*"
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist"
|
|
18
|
+
],
|
|
19
|
+
"bin": {
|
|
20
|
+
"guildforge": "./dist/index.mjs"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"mcp",
|
|
24
|
+
"discord",
|
|
25
|
+
"bot",
|
|
26
|
+
"manager"
|
|
27
|
+
],
|
|
28
|
+
"author": "",
|
|
29
|
+
"license": "MIT",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/guildforge/guildforge.git",
|
|
33
|
+
"directory": "packages/mcp"
|
|
34
|
+
},
|
|
35
|
+
"dependencies": {
|
|
36
|
+
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
37
|
+
"discord.js": "^14.26.4",
|
|
38
|
+
"dotenv": "^17.4.2",
|
|
39
|
+
"zod": "^4.4.3"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"@types/node": "^25.9.1",
|
|
43
|
+
"tsdown": "^0.22.1",
|
|
44
|
+
"typescript": "^6.0.3",
|
|
45
|
+
"vitest": "^4.1.7",
|
|
46
|
+
"@guildforge/tsdown-config": "0.0.0",
|
|
47
|
+
"@guildforge/typescript-config": "0.0.0",
|
|
48
|
+
"@guildforge/vitest-config": "0.0.0"
|
|
49
|
+
},
|
|
50
|
+
"publishConfig": {
|
|
51
|
+
"access": "public"
|
|
52
|
+
},
|
|
53
|
+
"engines": {
|
|
54
|
+
"node": ">=24.0.0"
|
|
55
|
+
},
|
|
56
|
+
"scripts": {
|
|
57
|
+
"build": "tsdown",
|
|
58
|
+
"typecheck": "tsc --noEmit",
|
|
59
|
+
"test": "vitest run",
|
|
60
|
+
"test:watch": "vitest",
|
|
61
|
+
"test:coverage": "vitest run --coverage",
|
|
62
|
+
"inspect": "npm run build && npx @modelcontextprotocol/inspector node dist/index.mjs"
|
|
63
|
+
}
|
|
64
|
+
}
|