pygpt-net 2.6.50__py3-none-any.whl → 2.6.52__py3-none-any.whl

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.
pygpt_net/CHANGELOG.txt CHANGED
@@ -1,3 +1,13 @@
1
+ 2.6.52 (2025-09-17)
2
+
3
+ - Added MCP plugin: Provides access to remote tools via the Model Context Protocol (MCP), including stdio, SSE, and Streamable HTTP transports, with per-server allow/deny filtering, Authorization header support, and a tools cache.
4
+ - Fixed: tab tooltips reload on profile switch.
5
+
6
+ 2.6.51 (2025-09-16)
7
+
8
+ - Fix: Automatically reloading calendar notes.
9
+ - Fix: Context menu CSS background color for calendar items.
10
+
1
11
  2.6.50 (2025-09-16)
2
12
 
3
13
  - Optimized: Improved memory cleanup when switching profiles and unloading tabs.
pygpt_net/__init__.py CHANGED
@@ -6,15 +6,15 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.16 00:00:00 #
9
+ # Updated Date: 2025.09.17 00:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  __author__ = "Marcin Szczygliński"
13
13
  __copyright__ = "Copyright 2025, Marcin Szczygliński"
14
14
  __credits__ = ["Marcin Szczygliński"]
15
15
  __license__ = "MIT"
16
- __version__ = "2.6.50"
17
- __build__ = "2025-09-16"
16
+ __version__ = "2.6.52"
17
+ __build__ = "2025-09-17"
18
18
  __maintainer__ = "Marcin Szczygliński"
19
19
  __github__ = "https://github.com/szczyglis-dev/py-gpt"
20
20
  __report__ = "https://github.com/szczyglis-dev/py-gpt/issues"
pygpt_net/app.py CHANGED
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.11 08:00:00 #
9
+ # Updated Date: 2025.09.16 22:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import os
@@ -96,6 +96,7 @@ from pygpt_net.plugin.bitbucket import Plugin as BitbucketPlugin
96
96
  from pygpt_net.plugin.server import Plugin as ServerPlugin
97
97
  from pygpt_net.plugin.tuya import Plugin as TuyaPlugin
98
98
  from pygpt_net.plugin.wikipedia import Plugin as WikipediaPlugin
99
+ from pygpt_net.plugin.mcp import Plugin as MCPPlugin
99
100
 
100
101
  # agents (Llama-index)
101
102
  # from pygpt_net.provider.agents.llama_index.legacy.openai import OpenAIAgent
@@ -425,6 +426,7 @@ def run(**kwargs):
425
426
  launcher.add_plugin(ServerPlugin())
426
427
  launcher.add_plugin(TuyaPlugin())
427
428
  launcher.add_plugin(WikipediaPlugin())
429
+ launcher.add_plugin(MCPPlugin())
428
430
 
429
431
  # register custom plugins
430
432
  plugins = kwargs.get('plugins', None)
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.15 23:00:00 #
9
+ # Updated Date: 2025.09.16 22:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from .access import Access
@@ -156,9 +156,12 @@ class Controller:
156
156
  print(trans("status.reloading.profile.begin"))
157
157
 
158
158
  try:
159
+ self.ui.tabs.locked = True # lock tabs
159
160
  self.window.core.reload() # db, config, patch, etc.
160
161
  self.ui.tabs.reload()
161
162
  self.ctx.reload()
163
+ self.ui.tabs.locked = False # unlock tabs
164
+
162
165
  self.settings.reload()
163
166
  self.assistant.reload()
164
167
  self.attachment.reload()
@@ -181,7 +184,6 @@ class Controller:
181
184
  # post-reload
182
185
  self.ui.tabs.reload_after()
183
186
  self.ctx.reload_after()
184
- self.ui.tabs.restore_data() # restore opened tabs data
185
187
  self.kernel.restart()
186
188
  self.theme.reload_all() # do not reload theme if no change
187
189
 
@@ -6,11 +6,9 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.12.14 08:00:00 #
9
+ # Updated Date: 2025.09.16 11:00:00 #
10
10
  # ================================================== #
11
11
 
12
- from typing import Optional
13
-
14
12
  from pygpt_net.core.tabs.tab import Tab
15
13
 
16
14
  from .note import Note
@@ -42,7 +40,7 @@ class Calendar:
42
40
 
43
41
  :return: True if calendar is loaded
44
42
  """
45
- return hasattr(self.window.ui, 'calendar') and hasattr(self.window.ui.calendar, 'select')
43
+ return hasattr(self.window.ui, 'calendar') and "select" in self.window.ui.calendar
46
44
 
47
45
  def update(self, all: bool = True):
48
46
  """
File without changes
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.16 02:00:00 #
9
+ # Updated Date: 2025.09.16 22:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional, List
@@ -1289,14 +1289,19 @@ class Ctx:
1289
1289
 
1290
1290
  def reload(self):
1291
1291
  """Reload ctx"""
1292
+ self.edit_meta_id = None
1293
+ self.edit_item_id = None
1294
+ self.group_id = None
1295
+ self.selected = []
1296
+ self._infinite_scroll_refresh = False
1292
1297
  self.reset_loaded_total() # reset paging
1293
1298
  self.window.core.ctx.reset()
1294
1299
  self.setup()
1295
- self.update()
1296
- self.refresh()
1297
1300
 
1298
1301
  def reload_after(self):
1299
1302
  """After reload"""
1303
+ self.update()
1304
+ self.refresh()
1300
1305
  self.new_if_empty()
1301
1306
 
1302
1307
  def add_selected(self, id: int):
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.16 02:00:00 #
9
+ # Updated Date: 2025.09.16 22:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Any, Optional, Tuple
@@ -40,12 +40,13 @@ class Tabs:
40
40
  self.create_new_on_tab = True
41
41
  self.col = {}
42
42
 
43
- def setup(self):
43
+ def setup(self, reload: bool = False):
44
44
  """Setup tabs"""
45
45
  w = self.window
46
46
  w.core.tabs.load()
47
47
  w.controller.notepad.load()
48
- self.setup_options()
48
+ if not reload:
49
+ self.setup_options()
49
50
  self.initialized = True
50
51
 
51
52
  def setup_options(self):
@@ -130,13 +131,31 @@ class Tabs:
130
131
  self.col[curr_column] = curr_tab.pid
131
132
  self.debug()
132
133
 
134
+ def unload(self):
135
+ """Unload tabs"""
136
+ self.active_idx = 0
137
+ self.prev_idx = 0
138
+ self.appended = False
139
+ self.current = 0
140
+ self.column_idx = 0
141
+ self.tmp_column_idx = 0
142
+ self.create_new_on_tab = True
143
+ self.col = {}
144
+ columns = self.window.ui.layout.columns
145
+ for col in columns:
146
+ col.setUpdatesEnabled(False)
147
+ self.window.core.tabs.remove_all()
148
+ for col in columns:
149
+ col.setUpdatesEnabled(True)
150
+
133
151
  def reload(self):
134
152
  """Reload tabs"""
153
+ self.unload()
135
154
  columns = self.window.ui.layout.columns
136
155
  for col in columns:
137
156
  col.setUpdatesEnabled(False)
138
- self.window.core.tabs.remove_all()
139
- self.window.core.tabs.reload()
157
+ self.setup(reload=True)
158
+ self.restore_data()
140
159
  self.window.dispatch(RenderEvent(RenderEvent.PREPARE))
141
160
  self.debug()
142
161
  for col in columns:
@@ -172,6 +191,9 @@ class Tabs:
172
191
  :param idx: tab index
173
192
  :param column_idx: column index
174
193
  """
194
+ if idx == -1:
195
+ return
196
+
175
197
  w = self.window
176
198
  core = w.core
177
199
  tabs_core = core.tabs
@@ -594,7 +616,9 @@ class Tabs:
594
616
 
595
617
  :param tooltip: tooltip text
596
618
  """
597
- tabs = self.window.ui.layout.get_active_tabs()
619
+ tabs = self.window.ui.layout.get_tabs_by_idx(self.column_idx)
620
+ if tabs is not None and 0 <= self.current < tabs.count():
621
+ tabs.setTabToolTip(self.current, tooltip)
598
622
  tabs.setTabToolTip(self.current, tooltip)
599
623
  self.debug()
600
624
 
@@ -656,7 +680,7 @@ class Tabs:
656
680
  """
657
681
  if self.get_current_type() != Tab.TAB_CHAT:
658
682
  return
659
- tabs = self.window.ui.layout.get_active_tabs()
683
+ tabs = self.window.ui.layout.get_tabs_by_idx(self.column_idx)
660
684
  tooltip = title
661
685
  tabs.setTabToolTip(idx, tooltip)
662
686
  if len(title) > self.TAB_CHAT_MAX_CHARS:
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2024.12.14 08:00:00 #
9
+ # Updated Date: 2025.09.16 11:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import datetime
@@ -102,7 +102,7 @@ class Calendar:
102
102
  dt_key = datetime.datetime(note.year, note.month, note.day).strftime("%Y-%m-%d")
103
103
  if dt_key not in self.items:
104
104
  return False
105
- note.updated_at = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
105
+ note.updated = datetime.datetime.now().strftime("%Y-%m-%dT%H:%M:%S")
106
106
  self.items[dt_key] = note
107
107
  self.save(note.year, note.month, note.day)
108
108
  return True
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.08.24 23:00:00 #
9
+ # Updated Date: 2025.09.16 22:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from typing import Optional, List, Dict
@@ -71,7 +71,7 @@ class Output:
71
71
  col_idx = tab.column_idx
72
72
 
73
73
  col_map = self.mapping.get(col_idx)
74
- if col_map is None:
74
+ if col_map is None or not isinstance(col_map, dict):
75
75
  col_map = self.mapping[col_idx] = {}
76
76
  last_map = self.last_pids.get(col_idx)
77
77
  if last_map is None:
@@ -175,6 +175,8 @@ class Output:
175
175
  return None
176
176
  col_idx = tab.column_idx
177
177
  col_map = self.mapping.get(col_idx, {})
178
+ if not isinstance(col_map, dict):
179
+ return None
178
180
 
179
181
  candidates = [pid for pid, meta_id in col_map.items() if meta_id == meta.id]
180
182
  for pid in candidates:
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.16 02:00:00 #
9
+ # Updated Date: 2025.09.16 22:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import gc
@@ -1290,11 +1290,9 @@ class Renderer(BaseRenderer):
1290
1290
  """
1291
1291
  tab = node.get_tab()
1292
1292
  layout = tab.child.layout()
1293
- tab.child.remove_widget(node)
1293
+ tab.unwrap(node)
1294
1294
  self.window.ui.nodes['output'].pop(tab.pid, None)
1295
1295
 
1296
- node.on_delete()
1297
-
1298
1296
  view = ChatWebOutput(self.window)
1299
1297
  view.set_tab(tab)
1300
1298
  view.set_meta(meta)
@@ -1302,6 +1300,7 @@ class Renderer(BaseRenderer):
1302
1300
  view.signals.audio_read.connect(self.window.controller.chat.render.handle_audio_read)
1303
1301
 
1304
1302
  layout.addWidget(view) # tab body layout
1303
+ tab.add_ref(view)
1305
1304
  view.setVisible(True)
1306
1305
  self.window.ui.nodes['output'][tab.pid] = view
1307
1306
  try:
@@ -6,13 +6,15 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.14 20:00:00 #
9
+ # Updated Date: 2025.09.16 22:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  from datetime import datetime
13
13
  from typing import Dict, Any, Optional
14
14
  from dataclasses import dataclass, field
15
15
 
16
+ from PySide6.QtWidgets import QWidget
17
+
16
18
 
17
19
  @dataclass(slots=True)
18
20
  class Tab:
@@ -146,12 +148,37 @@ class Tab:
146
148
 
147
149
  :param widget: widget reference
148
150
  """
149
- for ref in self.refs:
150
- if ref and ref is widget:
151
- self.refs.remove(ref)
152
- break
153
- if self.child and hasattr(self.child, 'delete_ref'):
154
- self.child.delete_ref(widget)
151
+ try:
152
+ for ref in self.refs:
153
+ if ref and ref is widget:
154
+ self.refs.remove(ref)
155
+ break
156
+ except Exception:
157
+ pass
158
+
159
+ try:
160
+ if self.child and hasattr(self.child, 'delete_ref'):
161
+ self.child.delete_ref(widget)
162
+ except Exception:
163
+ pass
164
+
165
+ def unwrap(self, widget: QWidget):
166
+ """
167
+ Unwrap widget from this tab
168
+
169
+ :param widget: widget reference
170
+ """
171
+ self.delete_ref(widget)
172
+ try:
173
+ if widget and hasattr(widget, 'on_delete'):
174
+ widget.on_delete()
175
+ except Exception:
176
+ pass
177
+ try:
178
+ if self.child and hasattr(self.child, 'unwrap'):
179
+ self.child.unwrap(widget)
180
+ except Exception:
181
+ pass
155
182
 
156
183
  def to_dict(self) -> Dict[str, Any]:
157
184
  """
@@ -159,7 +186,7 @@ class Tab:
159
186
 
160
187
  :return: dict
161
188
  """
162
- return {
189
+ data = {
163
190
  "uuid": str(self.uuid),
164
191
  "pid": self.pid,
165
192
  "idx": self.idx,
@@ -169,7 +196,6 @@ class Tab:
169
196
  "icon": self.icon,
170
197
  "tooltip": self.tooltip,
171
198
  "data_id": self.data_id,
172
- "child": str(self.child), # child widget
173
199
  "parent": str(self.parent), # parent column
174
200
  "custom_name": self.custom_name,
175
201
  "custom_idx": self.new_idx,
@@ -179,6 +205,13 @@ class Tab:
179
205
  "tool_id": self.tool_id,
180
206
  "refs": [str(ref) for ref in self.refs], # references to widgets
181
207
  }
208
+ if self.child:
209
+ try:
210
+ if hasattr(self.child, 'to_dict'):
211
+ data['child'] = self.child.to_dict()
212
+ except Exception:
213
+ pass
214
+ return data
182
215
 
183
216
  def __str__(self) -> str:
184
217
  """
@@ -6,7 +6,7 @@
6
6
  # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
7
  # MIT License #
8
8
  # Created By : Marcin Szczygliński #
9
- # Updated Date: 2025.09.16 02:00:00 #
9
+ # Updated Date: 2025.09.16 22:00:00 #
10
10
  # ================================================== #
11
11
 
12
12
  import uuid
@@ -299,20 +299,17 @@ class Tabs:
299
299
  if tab.type == Tab.TAB_CHAT:
300
300
  node = self.window.ui.nodes['output'].get(tab.pid)
301
301
  if node:
302
- node.unload() # unload web page
303
- tab.child.remove_widget(node)
302
+ node.unload() # unload page completely
303
+ tab.unwrap(node)
304
304
  self.window.ui.nodes['output'].pop(pid, None)
305
- node.on_delete()
306
305
  node_plain = self.window.ui.nodes['output_plain'].get(tab.pid)
307
306
  if node_plain:
308
- tab.child.remove_widget(node_plain)
307
+ tab.unwrap(node_plain)
309
308
  self.window.ui.nodes['output_plain'].pop(pid, None)
310
- node_plain.on_delete()
311
309
 
312
310
  if tab.type in (Tab.TAB_CHAT, Tab.TAB_NOTEPAD, Tab.TAB_TOOL):
313
- tab.cleanup() # unload assigned data from memory
314
-
315
- # tab.delete_refs()
311
+ tab.cleanup() # unload refs from memory
312
+ # IMPORTANT: leave refs to painter and calendar to keep only one instance of each
316
313
 
317
314
  except Exception as e:
318
315
  print(f"Error unloading tab {pid}: {e}")
@@ -328,6 +325,7 @@ class Tabs:
328
325
  """Remove all tabs"""
329
326
  for pid in list(self.pids.keys()):
330
327
  self.remove(pid) # delete from PIDs and UI
328
+ self.window.controller.chat.render.remove_pid(pid) # remove pid data from renderer registry
331
329
  self.pids = {}
332
330
  self.window.core.ctx.output.clear() # clear mapping
333
331
 
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.50",
4
- "app.version": "2.6.50",
5
- "updated_at": "2025-09-16T00:00:00"
3
+ "version": "2.6.52",
4
+ "app.version": "2.6.52",
5
+ "updated_at": "2025-09-17T00:00:00"
6
6
  },
7
7
  "access.audio.event.speech": false,
8
8
  "access.audio.event.speech.disabled": [],
@@ -434,8 +434,8 @@
434
434
  "render.code_syntax.final_max_chars": 350000,
435
435
  "render.code_syntax.final_max_lines": 1500,
436
436
  "render.code_syntax.stream_max_lines": 1000,
437
- "render.code_syntax.stream_n_line": 25,
438
- "render.code_syntax.stream_n_chars": 5000,
437
+ "render.code_syntax.stream_n_line": 10,
438
+ "render.code_syntax.stream_n_chars": 1000,
439
439
  "render.engine": "web",
440
440
  "render.memory.limit": "2.5GB",
441
441
  "render.msg.user.collapse.px": 1500,
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "__meta__": {
3
- "version": "2.6.50",
4
- "app.version": "2.6.50",
5
- "updated_at": "2025-09-16T08:03:34"
3
+ "version": "2.6.52",
4
+ "app.version": "2.6.52",
5
+ "updated_at": "2025-09-17T08:03:34"
6
6
  },
7
7
  "items": {
8
8
  "SpeakLeash/bielik-11b-v2.3-instruct:Q4_K_M": {
@@ -139,4 +139,9 @@ QCalendarWidget QAbstractItemView::item:hover {{
139
139
  .DebugList::item:selected:focus {{
140
140
  color: #000;
141
141
  selection-color: #000000;
142
+ }}
143
+
144
+ QCalendarWidget QMenu::item:selected:focus,
145
+ QCalendarWidget QMenu::item:selected {{
146
+ background: #3a4045;
142
147
  }}
@@ -0,0 +1,12 @@
1
+ [LOCALE]
2
+ servers.description = Configure MCP servers. Supported transports: 'stdio: <command ...>' for stdio servers, 'http(s)://...' for Streamable HTTP, and 'http(s)://.../sse' (or 'sse://', 'sse+http(s)://') for SSE. Use 'label' as a short, human-friendly server name used in tool names. Use 'authorization' to send an Authorization header for HTTP/SSE connections. Use 'allowed_commands' (comma-separated) to whitelist tools; if provided, only those tools are exposed. Use 'disabled_commands' to blacklist tools from this server.
3
+ servers.label = MCP servers
4
+ servers.tooltip = Requires the MCP Python SDK. Install: pip install "mcp[cli]"
5
+ tools_cache_enabled.label = Cache tools list
6
+ tools_cache_enabled.description = Enable an in-memory cache of discovered tools to avoid re-discovery on every prompt.
7
+ tools_cache_enabled.tooltip = If enabled, tool discovery results are cached per server for the TTL duration.
8
+ tools_cache_ttl.label = Cache TTL (seconds)
9
+ tools_cache_ttl.description = Time-to-live for the tools cache per server.
10
+ tools_cache_ttl.tooltip = Set to 0 to disable TTL (not recommended).
11
+ plugin.description = Provides access to remote tools via the Model Context Protocol (MCP), including stdio, SSE, and Streamable HTTP transports, with per-server allow/deny filtering, Authorization header support, and a tools cache.
12
+ plugin.name = MCP
@@ -0,0 +1,12 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2025.09.16 22:00:00 #
10
+ # ================================================== #
11
+
12
+ from .plugin import *
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env python3
2
+ # -*- coding: utf-8 -*-
3
+ # ================================================== #
4
+ # This file is a part of PYGPT package #
5
+ # Website: https://pygpt.net #
6
+ # GitHub: https://github.com/szczyglis-dev/py-gpt #
7
+ # MIT License #
8
+ # Created By : Marcin Szczygliński #
9
+ # Updated Date: 2025.09.16 22:00:00 #
10
+ # ================================================== #
11
+
12
+ from pygpt_net.plugin.base.config import BaseConfig, BasePlugin
13
+
14
+
15
+ class Config(BaseConfig):
16
+ def __init__(self, plugin: BasePlugin = None, *args, **kwargs):
17
+ super(Config, self).__init__(plugin)
18
+ self.plugin = plugin
19
+
20
+ def from_defaults(self, plugin: BasePlugin = None):
21
+ """
22
+ Set default options for plugin
23
+
24
+ :param plugin: plugin instance
25
+ """
26
+ keys = {
27
+ "active": "bool",
28
+ "label": "text",
29
+ "server_address": "text",
30
+ "authorization": "text",
31
+ "allowed_commands": "text",
32
+ "disabled_commands": "text",
33
+ }
34
+ items = [
35
+ {
36
+ "active": False,
37
+ "label": "quickstart",
38
+ "server_address": "stdio: uv run server fastmcp_quickstart stdio",
39
+ "authorization": "",
40
+ "allowed_commands": "",
41
+ "disabled_commands": "",
42
+ },
43
+ {
44
+ "active": False,
45
+ "label": "local_http",
46
+ "server_address": "http://localhost:8000/mcp",
47
+ "authorization": "",
48
+ "allowed_commands": "",
49
+ "disabled_commands": "",
50
+ },
51
+ {
52
+ "active": False,
53
+ "label": "local_sse",
54
+ "server_address": "http://localhost:8000/sse",
55
+ "authorization": "",
56
+ "allowed_commands": "",
57
+ "disabled_commands": "",
58
+ },
59
+ {
60
+ "active": False,
61
+ "label": "deep_wiki",
62
+ "server_address": "https://mcp.deepwiki.com/mcp",
63
+ "authorization": "",
64
+ "allowed_commands": "",
65
+ "disabled_commands": "",
66
+ },
67
+ ]
68
+ desc = (
69
+ "Configure MCP servers. Supported transports: "
70
+ "'stdio: <command ...>' for stdio servers, 'http(s)://...' for Streamable HTTP, "
71
+ "and 'http(s)://.../sse' (or 'sse://', 'sse+http(s)://') for SSE. "
72
+ "Use 'label' as a short, human-friendly server name used in tool names. "
73
+ "Use 'authorization' to send an Authorization header for HTTP/SSE connections. "
74
+ "Use 'allowed_commands' (comma-separated) to whitelist tools; "
75
+ "use 'disabled_commands' to blacklist tools."
76
+ )
77
+ tooltip = "Requires MCP Python SDK. Install: pip install \"mcp[cli]\""
78
+ plugin.add_option(
79
+ "servers",
80
+ type="dict",
81
+ value=items,
82
+ label="MCP servers",
83
+ description=desc,
84
+ tooltip=tooltip,
85
+ keys=keys,
86
+ )
87
+
88
+ plugin.add_option(
89
+ "tools_cache_enabled",
90
+ type="bool",
91
+ value=True,
92
+ label="Cache tools list",
93
+ description="Enable in-memory cache for discovered tools to avoid re-discovery on each prompt.",
94
+ tooltip="If enabled, tools discovery results are cached per server.",
95
+ )
96
+ plugin.add_option(
97
+ "tools_cache_ttl",
98
+ type="text",
99
+ value="300",
100
+ label="Cache TTL (seconds)",
101
+ description="Time-to-live for tools cache per server.",
102
+ tooltip="Set to 0 to disable TTL (not recommended).",
103
+ )