discord-selfbot-mcp 1.2.7 → 1.3.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/README.md +37 -2
- package/SKILL.md +5 -2
- package/package.json +1 -1
- package/scripts/daemon.py +109 -9
- package/scripts/dcli.py +96 -4
- package/server.json +3 -3
package/README.md
CHANGED
|
@@ -186,6 +186,7 @@ powered by the robust `discord.py-self` library.
|
|
|
186
186
|
| **system** | 2 | get_user_info, list_guilds |
|
|
187
187
|
| **messages** | 6 | send_message, read_messages, search_messages, edit_message, delete_message, get_message_attachments |
|
|
188
188
|
| **channels** | 3 | create_channel, delete_channel, list_channels |
|
|
189
|
+
| **dms** | 1 | list_dm_channels |
|
|
189
190
|
| **voice** | 2 | join_voice_channel, leave_voice_channel |
|
|
190
191
|
| **relationships** | 4 | list_friends, send_friend_request, add_friend, remove_friend |
|
|
191
192
|
| **presence** | 2 | set_status, set_activity |
|
|
@@ -197,11 +198,42 @@ powered by the robust `discord.py-self` library.
|
|
|
197
198
|
| **reactions** | 2 | add_reaction, remove_reaction |
|
|
198
199
|
| **discrawl** | 7 | run_discrawl, discrawl_doctor, discrawl_status, discrawl_sync, discrawl_search, discrawl_messages, discrawl_mentions |
|
|
199
200
|
|
|
201
|
+
### direct messages
|
|
202
|
+
|
|
203
|
+
`list_dm_channels` enumerates your open 1:1 and group DM channels so you can
|
|
204
|
+
discover a `channel_id` instead of needing to know it in advance. Each row is
|
|
205
|
+
`<channel_id> - [dm|group] <recipient display> (id=<user_id>) [BOT]`. Optional
|
|
206
|
+
args: `include_groups` (default `true`) and `name_contains` (case-insensitive
|
|
207
|
+
filter on recipient name/handle), e.g. find the DM with a person by name and
|
|
208
|
+
feed the id straight into `read_messages` / `send_message`.
|
|
209
|
+
|
|
210
|
+
### slash commands
|
|
211
|
+
|
|
212
|
+
`send_slash_command` invokes an application command in a channel or DM. Pass
|
|
213
|
+
`application_id` (the bot's user/application ID) for reliable resolution; in a DM
|
|
214
|
+
with a bot it is inferred automatically. The command and any options are
|
|
215
|
+
resolved from the application's registered command list
|
|
216
|
+
(`GET /applications/{id}/commands`), which works for ordinary guild-installed
|
|
217
|
+
bots whose commands the per-channel `/` search index does not return. Subcommands
|
|
218
|
+
are space-separated in `command_name` (e.g. `"config set"`). Example:
|
|
219
|
+
|
|
220
|
+
```json
|
|
221
|
+
{
|
|
222
|
+
"channel_id": "123456789012345678",
|
|
223
|
+
"command_name": "remind",
|
|
224
|
+
"application_id": "987654321098765432",
|
|
225
|
+
"options": { "text": "stand up", "when": "in 10 minutes" }
|
|
226
|
+
}
|
|
227
|
+
```
|
|
228
|
+
|
|
200
229
|
### discrawl integration
|
|
201
230
|
|
|
202
231
|
Use `run_discrawl` to execute local `discrawl` commands directly from MCP.
|
|
203
232
|
Use typed tools for common operations (`discrawl_sync`, `discrawl_search`, `discrawl_messages`, etc.) when you want structured params.
|
|
204
|
-
|
|
233
|
+
By default this MCP uses the Microck fork at `../discrawl-self/bin/discrawl`:
|
|
234
|
+
`https://github.com/Microck/discrawl-self`. It does not silently fall back to a
|
|
235
|
+
global `discrawl` from `PATH`; set `DISCRAWL_BIN` or the tool `binary` argument
|
|
236
|
+
when you intentionally want another executable.
|
|
205
237
|
|
|
206
238
|
Example tool call payload:
|
|
207
239
|
|
|
@@ -229,7 +261,7 @@ Typed tool payload example:
|
|
|
229
261
|
|
|
230
262
|
Optional env var:
|
|
231
263
|
|
|
232
|
-
- `DISCRAWL_BIN` - custom path to discrawl executable. This overrides the default fork
|
|
264
|
+
- `DISCRAWL_BIN` - custom path to discrawl executable. This overrides the default Microck fork lookup.
|
|
233
265
|
|
|
234
266
|
### attachment access
|
|
235
267
|
|
|
@@ -336,6 +368,7 @@ discord_py_self_mcp/
|
|
|
336
368
|
│ └── solver.py
|
|
337
369
|
└── tools/
|
|
338
370
|
├── channels.py
|
|
371
|
+
├── dms.py
|
|
339
372
|
├── discrawl.py
|
|
340
373
|
├── embed.py
|
|
341
374
|
├── guilds.py
|
|
@@ -377,6 +410,8 @@ python3 scripts/dcli.py daemon status # check daemon status
|
|
|
377
410
|
python3 scripts/dcli.py send-message --channel CHANNEL_ID --content "Hello"
|
|
378
411
|
python3 scripts/dcli.py list-guilds
|
|
379
412
|
python3 scripts/dcli.py read-messages --channel CHANNEL_ID --limit 20
|
|
413
|
+
python3 scripts/dcli.py get-message-attachments --channel CHANNEL_ID --message MESSAGE_ID
|
|
414
|
+
python3 scripts/dcli.py get-message-attachments --channel CHANNEL_ID --message MESSAGE_ID --download --output-dir ./attachments
|
|
380
415
|
```
|
|
381
416
|
|
|
382
417
|
**when to use skill mode**:
|
package/SKILL.md
CHANGED
|
@@ -66,8 +66,11 @@ This server also exposes dedicated MCP tools to run local `discrawl` commands:
|
|
|
66
66
|
- `discrawl_messages`
|
|
67
67
|
- `discrawl_mentions`
|
|
68
68
|
|
|
69
|
-
By default this MCP
|
|
70
|
-
|
|
69
|
+
By default this MCP uses the Microck fork build at `../discrawl-self/bin/discrawl`
|
|
70
|
+
from `https://github.com/Microck/discrawl-self`. It does not silently fall back
|
|
71
|
+
to a global `discrawl` from `PATH`.
|
|
72
|
+
|
|
73
|
+
Use `DISCRAWL_BIN` if you want to intentionally override that:
|
|
71
74
|
|
|
72
75
|
```bash
|
|
73
76
|
DISCRAWL_BIN=/absolute/path/to/discrawl
|
package/package.json
CHANGED
package/scripts/daemon.py
CHANGED
|
@@ -5,6 +5,7 @@ Auto-restart on code change: Enabled
|
|
|
5
5
|
"""
|
|
6
6
|
|
|
7
7
|
import asyncio
|
|
8
|
+
import base64
|
|
8
9
|
import hashlib
|
|
9
10
|
import json
|
|
10
11
|
import os
|
|
@@ -17,8 +18,13 @@ from datetime import datetime, timedelta, timezone
|
|
|
17
18
|
from pathlib import Path
|
|
18
19
|
from secrets import compare_digest
|
|
19
20
|
|
|
21
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
22
|
+
PROJECT_ROOT = SCRIPT_DIR.parent
|
|
23
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
24
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
25
|
+
|
|
20
26
|
env_paths = [
|
|
21
|
-
|
|
27
|
+
PROJECT_ROOT / ".env",
|
|
22
28
|
Path.cwd() / ".env",
|
|
23
29
|
]
|
|
24
30
|
|
|
@@ -29,11 +35,6 @@ for env_path in env_paths:
|
|
|
29
35
|
load_dotenv(env_path)
|
|
30
36
|
break
|
|
31
37
|
|
|
32
|
-
TOKEN = os.getenv("DISCORD_TOKEN")
|
|
33
|
-
if not TOKEN:
|
|
34
|
-
print("Error: DISCORD_TOKEN not found in .env file")
|
|
35
|
-
sys.exit(1)
|
|
36
|
-
|
|
37
38
|
import discord
|
|
38
39
|
|
|
39
40
|
from discord_py_self_mcp.cli_runtime import (
|
|
@@ -46,11 +47,11 @@ from discord_py_self_mcp.cli_runtime import (
|
|
|
46
47
|
)
|
|
47
48
|
from discord_py_self_mcp.logging_utils import log_to_stderr
|
|
48
49
|
from discord_py_self_mcp.tool_utils import NON_MESSAGEABLE_TEXT, validate_message_content
|
|
49
|
-
from discord_py_self_mcp.tools.embed import serialize_message
|
|
50
|
+
from discord_py_self_mcp.tools.embed import serialize_attachment, serialize_message
|
|
50
51
|
|
|
51
|
-
SCRIPT_DIR = Path(__file__).parent
|
|
52
52
|
DAEMON_SCRIPT = SCRIPT_DIR / "daemon.py"
|
|
53
53
|
CHECK_INTERVAL = 2
|
|
54
|
+
MAX_ATTACHMENT_BYTES_DEFAULT = 10 * 1024 * 1024
|
|
54
55
|
|
|
55
56
|
|
|
56
57
|
def _safe_unlink(path: Path) -> None:
|
|
@@ -74,6 +75,15 @@ def _load_or_create_auth_token() -> str:
|
|
|
74
75
|
return token
|
|
75
76
|
|
|
76
77
|
|
|
78
|
+
def _get_token_or_exit() -> str:
|
|
79
|
+
token = os.getenv("DISCORD_TOKEN")
|
|
80
|
+
if token:
|
|
81
|
+
return token
|
|
82
|
+
|
|
83
|
+
print("Error: DISCORD_TOKEN not found in .env file")
|
|
84
|
+
raise SystemExit(1)
|
|
85
|
+
|
|
86
|
+
|
|
77
87
|
class DiscordDaemon:
|
|
78
88
|
def __init__(self):
|
|
79
89
|
self.client = discord.Client()
|
|
@@ -163,6 +173,7 @@ class DiscordDaemon:
|
|
|
163
173
|
|
|
164
174
|
async def connect(self):
|
|
165
175
|
"""Connect to Discord and wait for the initial ready signal."""
|
|
176
|
+
token = _get_token_or_exit()
|
|
166
177
|
|
|
167
178
|
@self.client.event
|
|
168
179
|
async def on_ready():
|
|
@@ -173,7 +184,7 @@ class DiscordDaemon:
|
|
|
173
184
|
async def on_disconnect():
|
|
174
185
|
log_to_stderr(f"[{datetime.now()}] Disconnected from Discord")
|
|
175
186
|
|
|
176
|
-
asyncio.create_task(self.client.start(
|
|
187
|
+
asyncio.create_task(self.client.start(token))
|
|
177
188
|
await asyncio.wait_for(self._connected.wait(), timeout=30)
|
|
178
189
|
|
|
179
190
|
async def handle_command(self, command_data):
|
|
@@ -194,6 +205,14 @@ class DiscordDaemon:
|
|
|
194
205
|
return await self._send_message(
|
|
195
206
|
args.get("channel_id"), args.get("content")
|
|
196
207
|
)
|
|
208
|
+
if cmd == "get_message_attachments":
|
|
209
|
+
return await self._get_message_attachments(
|
|
210
|
+
args.get("channel_id"),
|
|
211
|
+
args.get("message_id"),
|
|
212
|
+
args.get("attachment_index"),
|
|
213
|
+
args.get("download_content", False),
|
|
214
|
+
args.get("max_bytes", MAX_ATTACHMENT_BYTES_DEFAULT),
|
|
215
|
+
)
|
|
197
216
|
if cmd == "list_threads":
|
|
198
217
|
return await self._list_threads(
|
|
199
218
|
args.get("channel_id"), args.get("archived", False)
|
|
@@ -310,6 +329,87 @@ class DiscordDaemon:
|
|
|
310
329
|
message = await channel.send(content)
|
|
311
330
|
return {"message_id": message.id, "success": True}
|
|
312
331
|
|
|
332
|
+
async def _get_message_attachments(
|
|
333
|
+
self,
|
|
334
|
+
channel_id,
|
|
335
|
+
message_id,
|
|
336
|
+
attachment_index=None,
|
|
337
|
+
download_content=False,
|
|
338
|
+
max_bytes=MAX_ATTACHMENT_BYTES_DEFAULT,
|
|
339
|
+
):
|
|
340
|
+
channel = self.client.get_channel(channel_id) or await self.client.fetch_channel(
|
|
341
|
+
channel_id
|
|
342
|
+
)
|
|
343
|
+
if not channel:
|
|
344
|
+
return {"error": "Channel not found"}
|
|
345
|
+
if not isinstance(channel, discord.abc.Messageable):
|
|
346
|
+
return {"error": NON_MESSAGEABLE_TEXT}
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
message = await channel.fetch_message(message_id)
|
|
350
|
+
except discord.NotFound:
|
|
351
|
+
return {"error": "Message not found"}
|
|
352
|
+
except discord.Forbidden:
|
|
353
|
+
return {"error": "Access denied to message"}
|
|
354
|
+
|
|
355
|
+
attachments = list(message.attachments)
|
|
356
|
+
if not attachments:
|
|
357
|
+
return {"error": "Message has no attachments"}
|
|
358
|
+
|
|
359
|
+
if attachment_index is not None:
|
|
360
|
+
if attachment_index < 0 or attachment_index >= len(attachments):
|
|
361
|
+
return {
|
|
362
|
+
"error": (
|
|
363
|
+
f"Attachment index {attachment_index} is out of range for "
|
|
364
|
+
f"{len(attachments)} attachment(s)"
|
|
365
|
+
)
|
|
366
|
+
}
|
|
367
|
+
indexed_attachments = [(attachment_index, attachments[attachment_index])]
|
|
368
|
+
else:
|
|
369
|
+
indexed_attachments = list(enumerate(attachments))
|
|
370
|
+
|
|
371
|
+
result = {
|
|
372
|
+
"message_id": message.id,
|
|
373
|
+
"attachments": [
|
|
374
|
+
{"index": index, **serialize_attachment(attachment)}
|
|
375
|
+
for index, attachment in indexed_attachments
|
|
376
|
+
],
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if not download_content:
|
|
380
|
+
return result
|
|
381
|
+
|
|
382
|
+
max_bytes = int(max_bytes)
|
|
383
|
+
downloads = []
|
|
384
|
+
skipped = []
|
|
385
|
+
for index, attachment in indexed_attachments:
|
|
386
|
+
if attachment.size is not None and attachment.size > max_bytes:
|
|
387
|
+
skipped.append(
|
|
388
|
+
{
|
|
389
|
+
"index": index,
|
|
390
|
+
"filename": attachment.filename,
|
|
391
|
+
"reason": f"size={attachment.size} exceeds max_bytes={max_bytes}",
|
|
392
|
+
}
|
|
393
|
+
)
|
|
394
|
+
continue
|
|
395
|
+
|
|
396
|
+
blob = await attachment.read()
|
|
397
|
+
downloads.append(
|
|
398
|
+
{
|
|
399
|
+
"index": index,
|
|
400
|
+
"filename": attachment.filename,
|
|
401
|
+
"content_type": attachment.content_type
|
|
402
|
+
or "application/octet-stream",
|
|
403
|
+
"content_base64": base64.b64encode(blob).decode("ascii"),
|
|
404
|
+
}
|
|
405
|
+
)
|
|
406
|
+
|
|
407
|
+
if downloads:
|
|
408
|
+
result["downloads"] = downloads
|
|
409
|
+
if skipped:
|
|
410
|
+
result["skipped"] = skipped
|
|
411
|
+
return result
|
|
412
|
+
|
|
313
413
|
async def _list_threads(self, channel_id, archived=False):
|
|
314
414
|
channel = self.client.get_channel(channel_id) or await self.client.fetch_channel(
|
|
315
415
|
channel_id
|
package/scripts/dcli.py
CHANGED
|
@@ -6,6 +6,7 @@ Commands:
|
|
|
6
6
|
daemon <start|stop|restart|status> - Manage daemon process
|
|
7
7
|
send-message --channel ID --content "msg"
|
|
8
8
|
read-messages --channel ID [--limit N] [--after TIME]
|
|
9
|
+
get-message-attachments --channel ID --message ID [--attachment-index N] [--download --output-dir DIR]
|
|
9
10
|
list-guilds
|
|
10
11
|
list-channels --guild ID
|
|
11
12
|
list-threads --channel ID [--archived]
|
|
@@ -28,6 +29,7 @@ Time formats for --after:
|
|
|
28
29
|
1704067200 - Unix timestamp
|
|
29
30
|
"""
|
|
30
31
|
|
|
32
|
+
import base64
|
|
31
33
|
import json
|
|
32
34
|
import os
|
|
33
35
|
import socket
|
|
@@ -36,6 +38,11 @@ import sys
|
|
|
36
38
|
import time
|
|
37
39
|
from pathlib import Path
|
|
38
40
|
|
|
41
|
+
SCRIPT_DIR = Path(__file__).resolve().parent
|
|
42
|
+
PROJECT_ROOT = SCRIPT_DIR.parent
|
|
43
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
44
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
45
|
+
|
|
39
46
|
from discord_py_self_mcp.cli_runtime import AUTH_FILE, PID_FILE, SOCKET_PATH
|
|
40
47
|
|
|
41
48
|
|
|
@@ -70,8 +77,7 @@ def start_daemon():
|
|
|
70
77
|
"""Auto-start daemon if not running."""
|
|
71
78
|
if not is_daemon_running():
|
|
72
79
|
print("Starting daemon...")
|
|
73
|
-
|
|
74
|
-
daemon_script = script_dir / "daemon.py"
|
|
80
|
+
daemon_script = SCRIPT_DIR / "daemon.py"
|
|
75
81
|
subprocess.Popen(
|
|
76
82
|
[sys.executable, str(daemon_script), "start"],
|
|
77
83
|
stdout=subprocess.DEVNULL,
|
|
@@ -170,6 +176,74 @@ def format_messages(messages, reverse=True, use_local_timezone=True):
|
|
|
170
176
|
print(f"[{created_at}] {author}: {content}")
|
|
171
177
|
|
|
172
178
|
|
|
179
|
+
def print_attachment_metadata(attachments):
|
|
180
|
+
for attachment in attachments:
|
|
181
|
+
details = [
|
|
182
|
+
f"[Attachment {attachment.get('index', '?')}] {attachment.get('filename', 'unknown')}"
|
|
183
|
+
]
|
|
184
|
+
if attachment.get("content_type"):
|
|
185
|
+
details.append(f"type={attachment['content_type']}")
|
|
186
|
+
if attachment.get("size") is not None:
|
|
187
|
+
details.append(f"size={attachment['size']}")
|
|
188
|
+
if attachment.get("width") and attachment.get("height"):
|
|
189
|
+
details.append(f"dimensions={attachment['width']}x{attachment['height']}")
|
|
190
|
+
if attachment.get("url"):
|
|
191
|
+
details.append(f"url={attachment['url']}")
|
|
192
|
+
print(" ".join(details))
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def cmd_get_message_attachments(
|
|
196
|
+
channel_id,
|
|
197
|
+
message_id,
|
|
198
|
+
attachment_index=None,
|
|
199
|
+
download=False,
|
|
200
|
+
output_dir=None,
|
|
201
|
+
max_bytes=None,
|
|
202
|
+
):
|
|
203
|
+
if download and not output_dir:
|
|
204
|
+
print("Error: --output-dir is required when --download is used")
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
args = {"channel_id": channel_id, "message_id": message_id}
|
|
208
|
+
if attachment_index is not None:
|
|
209
|
+
args["attachment_index"] = attachment_index
|
|
210
|
+
if download:
|
|
211
|
+
args["download_content"] = True
|
|
212
|
+
if max_bytes is not None:
|
|
213
|
+
args["max_bytes"] = max_bytes
|
|
214
|
+
|
|
215
|
+
result = send_request({"command": "get_message_attachments", "args": args})
|
|
216
|
+
if "error" in result:
|
|
217
|
+
print(f"Error: {result['error']}")
|
|
218
|
+
return
|
|
219
|
+
|
|
220
|
+
attachments = result.get("attachments", [])
|
|
221
|
+
print(f"Found {len(attachments)} attachment(s) on message {result.get('message_id')}:")
|
|
222
|
+
print("-" * 60)
|
|
223
|
+
print_attachment_metadata(attachments)
|
|
224
|
+
|
|
225
|
+
skipped = result.get("skipped", [])
|
|
226
|
+
if skipped:
|
|
227
|
+
print("\nSkipped downloads:")
|
|
228
|
+
for item in skipped:
|
|
229
|
+
print(
|
|
230
|
+
f" [Attachment {item.get('index', '?')}] {item.get('filename', 'unknown')}: {item.get('reason', 'Unknown reason')}"
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
downloads = result.get("downloads", [])
|
|
234
|
+
if not downloads:
|
|
235
|
+
return
|
|
236
|
+
|
|
237
|
+
output_path = Path(output_dir)
|
|
238
|
+
output_path.mkdir(parents=True, exist_ok=True)
|
|
239
|
+
print(f"\nSaved downloads to {output_path}:")
|
|
240
|
+
for item in downloads:
|
|
241
|
+
filename = item.get("filename") or f"attachment-{item.get('index', 'unknown')}"
|
|
242
|
+
destination = output_path / f"attachment-{item.get('index', 'unknown')}-{filename}"
|
|
243
|
+
destination.write_bytes(base64.b64decode(item["content_base64"]))
|
|
244
|
+
print(f" {destination}")
|
|
245
|
+
|
|
246
|
+
|
|
173
247
|
def cmd_send_message(channel_id, content):
|
|
174
248
|
result = send_request(
|
|
175
249
|
{
|
|
@@ -450,8 +524,7 @@ def cmd_create_thread(channel_id, name, message_id, content=None):
|
|
|
450
524
|
|
|
451
525
|
|
|
452
526
|
def cmd_daemon(action):
|
|
453
|
-
|
|
454
|
-
daemon_script = script_dir / "daemon.py"
|
|
527
|
+
daemon_script = SCRIPT_DIR / "daemon.py"
|
|
455
528
|
|
|
456
529
|
if action == "start":
|
|
457
530
|
if is_daemon_running():
|
|
@@ -487,6 +560,16 @@ def main():
|
|
|
487
560
|
read_parser.add_argument("--limit", "-l", type=int, default=10, help="Number of messages")
|
|
488
561
|
read_parser.add_argument("--after", "-a", type=str, help="Only show messages after this time (e.g. '4h', '30m', '2024-01-01T00:00:00')")
|
|
489
562
|
|
|
563
|
+
attachments_parser = subparsers.add_parser(
|
|
564
|
+
"get-message-attachments", help="Show attachment metadata for a message"
|
|
565
|
+
)
|
|
566
|
+
attachments_parser.add_argument("--channel", "-c", required=True, type=int, help="Channel ID")
|
|
567
|
+
attachments_parser.add_argument("--message", "-m", required=True, type=int, help="Message ID")
|
|
568
|
+
attachments_parser.add_argument("--attachment-index", type=int, help="Optional zero-based attachment index")
|
|
569
|
+
attachments_parser.add_argument("--download", action="store_true", help="Download selected attachments to disk")
|
|
570
|
+
attachments_parser.add_argument("--output-dir", type=str, help="Directory to write downloads into when --download is used")
|
|
571
|
+
attachments_parser.add_argument("--max-bytes", type=int, help="Skip downloads larger than this many bytes")
|
|
572
|
+
|
|
490
573
|
subparsers.add_parser("list-guilds", help="List all guilds")
|
|
491
574
|
|
|
492
575
|
channels_parser = subparsers.add_parser("list-channels", help="List channels in a guild")
|
|
@@ -555,6 +638,15 @@ def main():
|
|
|
555
638
|
cmd_send_message(args.channel, args.content)
|
|
556
639
|
elif args.command == "read-messages":
|
|
557
640
|
cmd_read_messages(args.channel, args.limit, args.after)
|
|
641
|
+
elif args.command == "get-message-attachments":
|
|
642
|
+
cmd_get_message_attachments(
|
|
643
|
+
args.channel,
|
|
644
|
+
args.message,
|
|
645
|
+
args.attachment_index,
|
|
646
|
+
args.download,
|
|
647
|
+
args.output_dir,
|
|
648
|
+
args.max_bytes,
|
|
649
|
+
)
|
|
558
650
|
elif args.command == "list-guilds":
|
|
559
651
|
cmd_list_guilds()
|
|
560
652
|
elif args.command == "list-channels":
|
package/server.json
CHANGED
|
@@ -6,14 +6,14 @@
|
|
|
6
6
|
"url": "https://github.com/Microck/discord.py-self-mcp",
|
|
7
7
|
"source": "github"
|
|
8
8
|
},
|
|
9
|
-
"version": "1.
|
|
9
|
+
"version": "1.3.0",
|
|
10
10
|
"packages": [
|
|
11
11
|
{
|
|
12
12
|
"registryType": "npm",
|
|
13
13
|
"registryBaseUrl": "https://registry.npmjs.org",
|
|
14
14
|
"identifier": "discord-selfbot-mcp",
|
|
15
15
|
"runtimeHint": "python",
|
|
16
|
-
"version": "1.
|
|
16
|
+
"version": "1.3.0",
|
|
17
17
|
"packageArguments": [],
|
|
18
18
|
"environmentVariables": [
|
|
19
19
|
{
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
},
|
|
39
39
|
{
|
|
40
40
|
"name": "DISCRAWL_BIN",
|
|
41
|
-
"description": "Optional path to discrawl executable for the run_discrawl MCP tool. Overrides the default
|
|
41
|
+
"description": "Optional path to discrawl executable for the run_discrawl MCP tool. Overrides the default Microck fork path at ../discrawl-self/bin/discrawl.",
|
|
42
42
|
"format": "string",
|
|
43
43
|
"isRequired": false
|
|
44
44
|
}
|