autodev-cli 1.4.150 → 1.4.152

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.
@@ -0,0 +1,204 @@
1
+ #!/usr/bin/env python3
2
+ # ---------------------------------------------------------------------------
3
+ # gimp_mcp_client.py — vendored, USER-VERIFIED GIMP MCP client (FastMCP stdio).
4
+ #
5
+ # Derived from libreearth/gimp-mcp (GPL-3.0; see media/gimp/LICENSE) and fixed by
6
+ # the user / AutoDev: reconnect via getpeername, reads > 1024 bytes, get_images.
7
+ # Launched by: uv run --with mcp==1.2.0 <this file>. The body below is kept
8
+ # byte-for-byte; the lines in THIS block are a provenance comment only.
9
+ # ---------------------------------------------------------------------------
10
+
11
+ # GIMP MCP Server Script
12
+ # Provides an MCP interface to control GIMP via a socket connection.
13
+
14
+ from mcp.server.fastmcp import FastMCP, Context
15
+ import socket
16
+ import json
17
+ import logging
18
+
19
+ logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
20
+ logger = logging.getLogger("GimpMCPServer")
21
+
22
+
23
+ class GimpConnection:
24
+ def __init__(self, host='127.0.0.1', port=9876):
25
+ self.host = host
26
+ self.port = port
27
+ self.sock = None
28
+
29
+ def close(self):
30
+ if self.sock:
31
+ try:
32
+ self.sock.close()
33
+ except Exception:
34
+ pass
35
+ self.sock = None
36
+
37
+ def connect(self):
38
+ if self.sock:
39
+ try:
40
+ self.sock.getpeername()
41
+ return
42
+ except Exception:
43
+ self.close()
44
+ try:
45
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
46
+ sock.settimeout(30)
47
+ sock.connect((self.host, self.port))
48
+ self.sock = sock
49
+ logger.info(f"Connected to GIMP at {self.host}:{self.port}")
50
+ except Exception as e:
51
+ self.close()
52
+ logger.error(f"Failed to connect: {e}")
53
+ raise ConnectionError(
54
+ "Could not connect to GIMP. Open GIMP and run "
55
+ "Filters > Development > Start MCP Server."
56
+ )
57
+
58
+ def send_command(self, command_type, params=None):
59
+ self.connect()
60
+ command = {"type": command_type, "params": params or {}}
61
+ try:
62
+ self.sock.sendall(json.dumps(command).encode('utf-8'))
63
+ chunks = []
64
+ while True:
65
+ data = self.sock.recv(65536)
66
+ if not data:
67
+ break
68
+ chunks.append(data)
69
+ try:
70
+ return json.loads(b''.join(chunks).decode('utf-8'))
71
+ except json.JSONDecodeError:
72
+ continue
73
+ raise Exception("GIMP closed the connection before sending a complete response")
74
+ except Exception as e:
75
+ logger.error(f"Communication error: {e}")
76
+ self.close()
77
+ raise Exception(f"Error communicating with GIMP: {e}")
78
+
79
+
80
+ _gimp_connection = None
81
+
82
+
83
+ def get_gimp_connection():
84
+ global _gimp_connection
85
+ if _gimp_connection is None:
86
+ _gimp_connection = GimpConnection()
87
+ _gimp_connection.connect()
88
+ return _gimp_connection
89
+
90
+
91
+ mcp = FastMCP("GimpMCP", description="GIMP integration through MCP")
92
+
93
+
94
+ @mcp.tool()
95
+ def call_api(ctx: Context, api_path: str, args: list = [], kwargs: dict = {}) -> str:
96
+ """Call any GIMP API method dynamically.
97
+
98
+ Parameters:
99
+ - api_path: The path to the API method (e.g., "Gimp.Image.get_by_id")
100
+ Use "exec" to run Python inside GIMP. Pass the code in args[0].
101
+ - args: List of positional arguments
102
+ - kwargs: Dictionary of keyword arguments
103
+
104
+ Returns:
105
+ - JSON string of the result or error message
106
+ """
107
+ try:
108
+ conn = get_gimp_connection()
109
+ result = conn.send_command("call_api", {"api_path": api_path, "args": args, "kwargs": kwargs})
110
+ if result["status"] == "success":
111
+ return json.dumps(result["result"])
112
+ return f"Error: {result['message']}"
113
+ except Exception as e:
114
+ return f"Error: {e}"
115
+
116
+
117
+ @mcp.tool()
118
+ def list_images(ctx: Context) -> str:
119
+ """List all open images in GIMP.
120
+
121
+ Returns:
122
+ - JSON string of image IDs and names
123
+ """
124
+ return call_api(ctx, "Gimp.get_images")
125
+
126
+
127
+ @mcp.tool()
128
+ def get_image_info(ctx: Context, image_id: int) -> str:
129
+ """Get information about a specific image.
130
+
131
+ Parameters:
132
+ - image_id: The ID of the image
133
+
134
+ Returns:
135
+ - JSON string of image details
136
+ """
137
+ return call_api(
138
+ ctx,
139
+ "exec",
140
+ args=[
141
+ f"""
142
+ image = Gimp.Image.get_by_id({int(image_id)})
143
+ if image is None:
144
+ _result = {{"error": "image not found"}}
145
+ else:
146
+ _result = {{
147
+ "id": image.get_id(),
148
+ "name": image.get_name(),
149
+ "width": image.get_width(),
150
+ "height": image.get_height(),
151
+ "layers": [{{"id": layer.get_id(), "name": layer.get_name()}} for layer in image.get_layers()],
152
+ }}
153
+ """
154
+ ],
155
+ )
156
+
157
+
158
+ @mcp.tool()
159
+ def apply_gaussian_blur(ctx: Context, image_id: int, radius: float = 5.0) -> str:
160
+ """Apply Gaussian blur to an image.
161
+
162
+ Parameters:
163
+ - image_id: The ID of the image
164
+ - radius: Blur radius
165
+
166
+ Returns:
167
+ - Success message or error
168
+ """
169
+ return call_api(
170
+ ctx,
171
+ "exec",
172
+ args=[
173
+ f"""
174
+ image = Gimp.Image.get_by_id({int(image_id)})
175
+ if image is None:
176
+ _result = "image not found"
177
+ else:
178
+ layers = image.get_layers()
179
+ if not layers:
180
+ _result = "no layers"
181
+ else:
182
+ drawable = layers[0]
183
+ blur = Gimp.DrawableFilter.new(drawable, "gegl:gaussian-blur", "")
184
+ cfg = blur.get_config()
185
+ try:
186
+ cfg.set_property("std-dev-x", {float(radius)})
187
+ cfg.set_property("std-dev-y", {float(radius)})
188
+ except Exception:
189
+ pass
190
+ drawable.append_filter(blur)
191
+ drawable.merge_filter(blur)
192
+ Gimp.displays_flush()
193
+ _result = "Applied Gaussian blur successfully"
194
+ """
195
+ ],
196
+ )
197
+
198
+
199
+ def main():
200
+ mcp.run()
201
+
202
+
203
+ if __name__ == "__main__":
204
+ main()