/** sha256 (first 12 hex) of the canonical media/gimp/gimp_mcp_server.py. */ export declare const GIMP_PLUGIN_SOURCE_HASH = "882fdb94b5c9"; /** Full body of the user-verified GIMP 3.x MCP server plug-in (media/gimp/gimp_mcp_server.py). */ export declare const GIMP_PLUGIN_SOURCE = "#!/usr/bin/env python3\n# ---------------------------------------------------------------------------\n# gimp_mcp_server.py \u2014 vendored, USER-VERIFIED GIMP 3.2 MCP bridge (plug-in).\n#\n# Derived from libreearth/gimp-mcp (GPL-3.0; see media/gimp/LICENSE) and fixed by\n# the user / AutoDev for GIMP 3.2 (Store / MSIX). Verified working on real GIMP\n# 3.2.44: a menu-triggered PLUGIN (Tools -> MCP / Filters -> Development -> Start\n# MCP Server) that listens on 127.0.0.1:9877 and drives GIMP. Keep the \"GIMP MCP\n# Server\" window open \u2014 GLib.MainLoop() holds the out-of-process plug-in alive.\n#\n# This is the user's verified file; the body below is kept byte-for-byte. The\n# lines in THIS block are a provenance comment only \u2014 do not edit the code below.\n# ---------------------------------------------------------------------------\n\n# GIMP MCP Server Plugin (GIMP 3.2)\n# Listens on localhost:9877 so it does not collide with Blender MCP (9876).\n\nimport gi\ngi.require_version('Gimp', '3.0')\nfrom gi.repository import Gimp\nfrom gi.repository import GLib\nfrom gi.repository import Gio\nfrom gi.repository import GObject\n\nimport io\nimport json\nimport os\nimport socket\nimport sys\nimport threading\nimport traceback\n\nHOST = '127.0.0.1'\nPORT = 9877\n# Store GIMP redirects GLib config dir into the MSIX LocalCache. Also write\n# to the real roaming profile so the log is findable.\n_LOG_PATHS = [\n os.path.join(GLib.get_user_config_dir(), 'GIMP', '3.2', 'gimp_mcp_server.log'),\n os.path.join(os.environ.get('APPDATA', ''), 'GIMP', '3.2', 'gimp_mcp_server.log'),\n]\n\n_server_lock = threading.Lock()\n_server_started = False\n_exec_globals = None\n_main_loop = None\n\n\ndef _log(msg):\n line = msg.rstrip() + '\\n'\n for path in _LOG_PATHS:\n if not path:\n continue\n try:\n os.makedirs(os.path.dirname(path), exist_ok=True)\n with open(path, 'a', encoding='utf-8') as fh:\n fh.write(line)\n except Exception:\n pass\n print(msg, flush=True)\n\n\ndef N_(message):\n return message\n\n\ndef _(message):\n return GLib.dgettext(None, message)\n\n\ndef _serialize(obj):\n if obj is None or isinstance(obj, (str, int, float, bool)):\n return obj\n if isinstance(obj, (list, tuple)):\n return [_serialize(item) for item in obj]\n if isinstance(obj, dict):\n return {str(k): _serialize(v) for k, v in obj.items()}\n data = {'type': obj.__class__.__name__}\n if hasattr(obj, 'get_id'):\n try:\n data['id'] = int(obj.get_id())\n except Exception:\n pass\n elif hasattr(obj, 'ID'):\n data['id'] = obj.ID\n if hasattr(obj, 'get_name'):\n try:\n data['name'] = obj.get_name()\n except Exception:\n pass\n if 'id' not in data and len(data) == 1:\n return str(obj)\n return data\n\n\ndef _resolve_api(api_path):\n parts = [p for p in str(api_path).split('.') if p]\n if parts and parts[0] == 'Gimp':\n parts = parts[1:]\n elif len(parts) >= 3 and parts[0] == 'gi' and parts[1] == 'repository' and parts[2] == 'Gimp':\n parts = parts[3:]\n\n aliases = {\n 'list_images': 'get_images',\n 'list_fonts': 'get_fonts',\n 'fonts_get_list': 'get_fonts',\n }\n\n if not parts:\n return Gimp\n\n obj = Gimp\n for part in parts:\n part = aliases.get(part, part)\n obj = getattr(obj, part)\n return obj\n\n\ndef _exec_namespace():\n global _exec_globals\n if _exec_globals is None:\n ns = {\n '__builtins__': __builtins__,\n '__name__': '__main__',\n 'Gimp': Gimp,\n 'GLib': GLib,\n 'Gio': Gio,\n 'GObject': GObject,\n 'sys': sys,\n 'os': os,\n 'json': json,\n }\n try:\n gi.require_version('Gegl', '0.4')\n from gi.repository import Gegl\n ns['Gegl'] = Gegl\n except Exception as exc:\n _log(f'Gegl import failed: {exc}')\n try:\n gi.require_version('Babl', '0.1')\n from gi.repository import Babl\n ns['Babl'] = Babl\n except Exception:\n pass\n _exec_globals = ns\n return _exec_globals\n\n\ndef _extract_code(args, kwargs):\n if kwargs.get('code'):\n return str(kwargs['code'])\n if not args:\n return ''\n if len(args) >= 2 and args[0] in ('exec', 'eval', 'pyGObject-console'):\n payload = args[1]\n if isinstance(payload, (list, tuple)):\n return '\\n'.join(str(item) for item in payload)\n return str(payload)\n if isinstance(args[0], (list, tuple)):\n return '\\n'.join(str(item) for item in args[0])\n return str(args[0])\n\n\ndef _run_python(code):\n ns = _exec_namespace()\n stdout = io.StringIO()\n old_stdout = sys.stdout\n sys.stdout = stdout\n try:\n exec(code, ns)\n finally:\n sys.stdout = old_stdout\n printed = stdout.getvalue()\n result = ns.get('_result')\n if result is not None:\n return {'printed': printed, 'result': _serialize(result)}\n return {'printed': printed, 'result': None}\n\n\nclass MCPPlugin(Gimp.PlugIn):\n def do_set_i18n(self, name):\n return False\n\n def do_query_procedures(self):\n return ['plug-in-mcp-server']\n\n def do_create_procedure(self, name):\n procedure = Gimp.Procedure.new(\n self, name, Gimp.PDBProcType.PLUGIN, self.run, None\n )\n procedure.set_menu_label(_('Start MCP Server'))\n procedure.set_documentation(\n _('Starts an MCP server to control GIMP externally'),\n _('Listens on 127.0.0.1:9877 for the GIMP MCP client'),\n name,\n )\n procedure.set_attribution('GIMP MCP', 'GIMP MCP', '2026')\n procedure.set_sensitivity_mask(Gimp.ProcedureSensitivityMask.ALWAYS)\n procedure.add_enum_argument(\n 'run-mode',\n _('Run mode'),\n _('The run mode'),\n Gimp.RunMode,\n Gimp.RunMode.INTERACTIVE,\n GObject.ParamFlags.READWRITE,\n )\n procedure.add_menu_path('/Tools/MCP')\n procedure.add_menu_path('/Filters/Development/')\n return procedure\n\n def run(self, procedure, config, data):\n global _main_loop\n started, message = self.ensure_server()\n _log(message)\n if not started:\n return procedure.new_return_values(Gimp.PDBStatusType.EXECUTION_ERROR, GLib.Error(message))\n\n # GIMP 3 launches Python plug-ins out of process. Returning from run()\n # would exit that process and kill the socket thread. Stay alive and\n # dispatch GLib.idle_add work (API calls) on this main loop.\n # A visible window is required: otherwise \"Start MCP Server\" looks like\n # it did not open anything.\n try:\n self._show_status_window(message)\n except Exception as exc:\n _log(f'Status window failed: {exc}\\n{traceback.format_exc()}')\n\n _main_loop = GLib.MainLoop()\n _log('Entering GLib.MainLoop to keep MCP server process alive')\n try:\n _main_loop.run()\n except Exception as exc:\n _log(f'MainLoop exited: {exc}')\n return procedure.new_return_values(Gimp.PDBStatusType.SUCCESS, GLib.Error())\n\n def _show_status_window(self, message):\n gi.require_version('Gtk', '3.0')\n gi.require_version('GimpUi', '3.0')\n from gi.repository import Gtk\n from gi.repository import GimpUi\n\n GimpUi.init('gimp_mcp_server.py')\n window = Gtk.Window(title='GIMP MCP Server')\n window.set_default_size(360, 120)\n window.set_keep_above(False)\n window.set_skip_taskbar_hint(False)\n box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=8)\n box.set_border_width(16)\n label = Gtk.Label(label=message + '\\nLeave this window open.')\n label.set_line_wrap(True)\n box.pack_start(label, True, True, 0)\n button = Gtk.Button(label='Stop server')\n button.connect('clicked', lambda *_: window.destroy())\n box.pack_start(button, False, False, 0)\n window.add(box)\n window.connect('destroy', self._on_status_closed)\n window.show_all()\n _log('Status window opened')\n\n def _on_status_closed(self, *_args):\n global _main_loop\n _log('Status window closed; stopping MCP server')\n if _main_loop is not None:\n _main_loop.quit()\n\n def ensure_server(self):\n global _server_started\n with _server_lock:\n if _server_started:\n return True, f'MCP Server already listening on {HOST}:{PORT}'\n try:\n thread = threading.Thread(target=self.start_server, daemon=True)\n thread.start()\n _server_started = True\n return True, f'MCP Server started on {HOST}:{PORT}'\n except Exception as exc:\n return False, f'Failed to start MCP Server: {exc}'\n\n def start_server(self):\n sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n try:\n sock.bind((HOST, PORT))\n sock.listen(8)\n _log(f'Server listening on {HOST}:{PORT}')\n except Exception as exc:\n _log(f'Bind/listen failed: {exc}')\n return\n\n while True:\n try:\n client, addr = sock.accept()\n _log(f'Connected to {addr}')\n threading.Thread(target=self.handle_client, args=(client,), daemon=True).start()\n except Exception as exc:\n _log(f'Server accept error: {exc}')\n break\n sock.close()\n\n def handle_client(self, client):\n buffer = b''\n try:\n client.settimeout(120)\n while True:\n chunk = client.recv(65536)\n if not chunk:\n break\n buffer += chunk\n while buffer:\n try:\n command = json.loads(buffer.decode('utf-8'))\n buffer = b''\n except json.JSONDecodeError:\n break\n GLib.idle_add(self.execute_command, command, client)\n except Exception as exc:\n _log(f'Client error: {exc}')\n finally:\n try:\n client.close()\n except Exception:\n pass\n\n def execute_command(self, command, client):\n cmd_type = command.get('type')\n params = command.get('params', {}) or {}\n try:\n if cmd_type == 'ping':\n response = {'status': 'success', 'result': 'pong'}\n elif cmd_type == 'exec':\n code = _extract_code(params.get('args', []), params.get('kwargs', {}))\n if not code:\n code = str(params.get('code', ''))\n response = {'status': 'success', 'result': _run_python(code)}\n elif cmd_type == 'call_api':\n api_path = params.get('api_path', '')\n args = params.get('args', []) or []\n kwargs = params.get('kwargs', {}) or {}\n if api_path in ('exec', 'eval', '__exec__'):\n response = {'status': 'success', 'result': _run_python(_extract_code(args, kwargs))}\n else:\n method = _resolve_api(api_path)\n result = method(*args, **kwargs)\n response = {'status': 'success', 'result': _serialize(result)}\n else:\n response = {'status': 'error', 'message': f'Unknown command: {cmd_type}'}\n except Exception as exc:\n response = {\n 'status': 'error',\n 'message': str(exc),\n 'traceback': traceback.format_exc(),\n }\n _log(f'Command error: {response[\"message\"]}\\n{response[\"traceback\"]}')\n\n try:\n payload = json.dumps(response).encode('utf-8')\n client.sendall(payload)\n except Exception as exc:\n _log(f'Response send failed: {exc}')\n return False\n\n\nGimp.main(MCPPlugin.__gtype__, sys.argv)\n";