discord-selfbot-mcp 1.2.6 → 1.2.8

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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Microck
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 CHANGED
@@ -11,7 +11,7 @@
11
11
  <p align="center">
12
12
  <img src="https://img.shields.io/badge/license-MIT-green" alt="license">
13
13
  <img src="https://img.shields.io/badge/language-python-blue" alt="language">
14
- <img src="https://img.shields.io/badge/npm-%40discord--selfbot--mcp-orange" alt="npm">
14
+ <a href="https://www.npmjs.com/package/discord-selfbot-mcp"><img src="https://img.shields.io/npm/v/discord-selfbot-mcp?color=orange&label=npm" alt="npm"></a>
15
15
  <img src="https://img.shields.io/badge/mcp-sdk-orange" alt="mcp">
16
16
  <img src="https://img.shields.io/badge/skill-cli-purple" alt="skill">
17
17
  <a href="https://github.com/Microck/opencode-studio"><img src="https://img.shields.io/badge/opencode-studio-brown?logo=data%3Aimage%2Fpng%3Bbase64%2CiVBORw0KGgoAAAANSUhEUgAAAA4AAAAOCAYAAAAfSC3RAAABiElEQVR4nF2Sv0tWcRTGPyeVIpCWwmyJGqQagsqCsL2hhobsD3BvdWhoj/6CiIKaoqXBdMjKRWwQgqZ%2BokSvkIhg9BOT9xPn9Vx79cD3cu6953zP8zznCQB1V0S01d3AKeAKcBVYA94DjyJioru2k9SHE%2Bqc%2Bkd9rL7yf7TUm%2BpQ05yPUM%2Bo626Pp%2BqE2q7GGfWrOpjNnWnAOPAGeAK8Bb4U5D3AJ%2BAQsAAMAHfVvl7gIrAf2Kjiz8BZYB3YC/wFpoGDwHfgEnA0oU7tgHiheEShyXxY/Vn/n6ljye8DcBiYAloRcV3tAdrV1xMRG%2Bo94DywCAwmx33AJHASWK7iiAjzNFOBl7WapPYtYdyo8RlLqVpOVPvq9KoH1NUuOneycaRefqnP1ftdUyiOt5KS%2BqLWdDpVzTXMl5It4Jr6u%2BQ/nhyBc8C7jpowGxGvmxuPqT9qyYuFIKdP71B8WT3SOKexXLrntvqxq3BefaiuFMQ0wqZftxl3M78MjBasfiDN/SAi0kFbtf8ACtKBWZBDoJEAAAAASUVORK5CYII%3D" alt="Add with OpenCode Studio" /></a>
@@ -190,7 +190,7 @@ powered by the robust `discord.py-self` library.
190
190
  | **relationships** | 4 | list_friends, send_friend_request, add_friend, remove_friend |
191
191
  | **presence** | 2 | set_status, set_activity |
192
192
  | **interactions** | 3 | send_slash_command, click_button, select_menu |
193
- | **threads** | 5 | create_thread, send_thread_message, list_threads, read_thread_messages, archive_thread |
193
+ | **threads** | 5 | create_thread, send_thread_message, list_active_threads, read_thread_messages, archive_thread |
194
194
  | **members** | 5 | kick_member, ban_member, unban_member, add_role, remove_role |
195
195
  | **invites** | 3 | create_invite, list_invites, delete_invite |
196
196
  | **profile** | 1 | edit_profile |
@@ -298,13 +298,13 @@ built-in rate limiting to prevent account bans. configurable via environment var
298
298
 
299
299
  | variable | default | description |
300
300
  |----------|---------|-------------|
301
- | `RATE_LIMIT_ENABLED` | `false` | Enable/disable rate limiting |
301
+ | `RATE_LIMIT_ENABLED` | `true` | Enable/disable rate limiting |
302
302
  | `RATE_LIMIT_MESSAGES_PER_MINUTE` | `10` | Max messages per minute |
303
303
  | `RATE_LIMIT_MESSAGES_PER_SECOND` | `1` | Max messages per second |
304
304
  | `RATE_LIMIT_ACTIONS_PER_MINUTE` | `5` | Max actions (joins, etc.) per minute |
305
305
  | `RATE_LIMIT_COOLDOWN` | `60` | Cooldown duration when limit hit (seconds) |
306
306
 
307
- > **recommended**: Enable rate limiting (`RATE_LIMIT_ENABLED=true`) to reduce ban risk.
307
+ > rate limiting is enabled by default to reduce ban risk. Only disable it if you are deliberately taking responsibility for raw Discord API pacing yourself.
308
308
 
309
309
  ---
310
310
 
@@ -329,11 +329,15 @@ discord_py_self_mcp/
329
329
  ├── main.py
330
330
  ├── setup.py
331
331
  ├── rate_limiter.py
332
+ ├── tool_utils.py
333
+ ├── cli_runtime.py
334
+ ├── logging_utils.py
332
335
  ├── captcha/
333
336
  │ └── solver.py
334
337
  └── tools/
335
338
  ├── channels.py
336
339
  ├── discrawl.py
340
+ ├── embed.py
337
341
  ├── guilds.py
338
342
  ├── interactions.py
339
343
  ├── invites.py
@@ -360,7 +364,7 @@ in addition to the mcp server, this package also provides a **skill/cli mode** f
360
364
  npm install -g discord-selfbot-mcp
361
365
 
362
366
  # create .env file
363
- echo "DISCORD_TOKEN=your_token" > .env
367
+ echo "DISCORD_TOKEN=***" > .env
364
368
 
365
369
  # use skill mode (from package directory)
366
370
  python3 scripts/dcli.py send-message --channel 123 --content "Hello!"
@@ -373,6 +377,8 @@ python3 scripts/dcli.py daemon status # check daemon status
373
377
  python3 scripts/dcli.py send-message --channel CHANNEL_ID --content "Hello"
374
378
  python3 scripts/dcli.py list-guilds
375
379
  python3 scripts/dcli.py read-messages --channel CHANNEL_ID --limit 20
380
+ python3 scripts/dcli.py get-message-attachments --channel CHANNEL_ID --message MESSAGE_ID
381
+ python3 scripts/dcli.py get-message-attachments --channel CHANNEL_ID --message MESSAGE_ID --download --output-dir ./attachments
376
382
  ```
377
383
 
378
384
  **when to use skill mode**:
@@ -387,4 +393,18 @@ see [SKILL.md](SKILL.md) for detailed documentation.
387
393
 
388
394
  ### license
389
395
 
390
- mit
396
+ this project is licensed under the [mit license](./LICENSE).
397
+
398
+ ---
399
+
400
+ ### contributing
401
+
402
+ issues and pull requests are welcome at [github.com/Microck/discord.py-self-mcp](https://github.com/Microck/discord.py-self-mcp).
403
+
404
+ 1. fork the repository
405
+ 2. create a feature branch (`git checkout -b feature/my-feature`)
406
+ 3. commit your changes (`git commit -m 'add my feature'`)
407
+ 4. push to the branch (`git push origin feature/my-feature`)
408
+ 5. open a pull request
409
+
410
+ please ensure tests pass (`pytest`) before submitting.
package/SKILL.md CHANGED
@@ -123,9 +123,6 @@ python3 scripts/dcli.py pin-message --channel CHANNEL_ID --message MESSAGE_ID
123
123
  # Create thread from message (in text channel)
124
124
  python3 scripts/dcli.py create-thread --channel CHANNEL_ID --name "Thread Name" --message MESSAGE_ID
125
125
 
126
- # Create standalone thread (in text channel)
127
- python3 scripts/dcli.py create-thread --channel CHANNEL_ID --name "Thread Name"
128
-
129
126
  # Create thread in forum channel (with initial content)
130
127
  python3 scripts/dcli.py create-thread --channel FORUM_CHANNEL_ID --name "Thread Name" --content "Initial post content"
131
128
  ```
@@ -176,11 +173,10 @@ python3 scripts/dcli.py read-recent-threads --guild GUILD_ID --within 4 --limit-
176
173
 
177
174
  #### Get User Info
178
175
  ```bash
179
- # Get current user info
176
+ # Get current user info (supported in daemon mode)
180
177
  python3 scripts/dcli.py user-info
181
178
 
182
- # Get specific user info
183
- python3 scripts/dcli.py user-info --user USER_ID
179
+ # Specific user lookup is not currently supported in daemon mode
184
180
  ```
185
181
 
186
182
  #### List Threads in Channel
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "discord-selfbot-mcp",
3
- "version": "1.2.6",
3
+ "version": "1.2.8",
4
4
  "description": "Discord Selfbot MCP server - Node.js wrapper for Python implementation",
5
5
  "main": "index.js",
6
6
  "bin": {
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
- Path(__file__).parent.parent / ".env",
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(TOKEN))
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
- script_dir = Path(__file__).parent
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
- script_dir = Path(__file__).parent
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.2.6",
9
+ "version": "1.2.8",
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.2.6",
16
+ "version": "1.2.8",
17
17
  "packageArguments": [],
18
18
  "environmentVariables": [
19
19
  {