@isen/chatgpt-image-web-mcp 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ChatGPT Image Web MCP contributors
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 ADDED
@@ -0,0 +1,273 @@
1
+ # ChatGPT Web 图片生成 MCP
2
+
3
+ 通过一个**通用、持久化、单例的后台 Chrome 浏览器**,自动使用 ChatGPT 网页生成图片。自动化模式默认采用新版Headless Chrome,不显示窗口、不抢前台,也不接受用户鼠标或键盘操作。
4
+
5
+ 该MCP不包含任何项目、品牌或业务模板逻辑。不同代码仓库可以共同复用同一个登录Profile和浏览器实例;每次调用通过`prompt`、`referenceImages`和`outputDir`决定当前项目的输入与输出。默认相对输出目录会基于启动MCP进程的工作目录解析,也建议每个项目显式传入自己的`outputDir`。
6
+
7
+ ## 设计目标
8
+
9
+ - 所有项目的图片生成工作流复用一个通用专用浏览器实例,不为每个项目或任务重复启动浏览器。
10
+ - 使用独立 Chrome 用户目录,不读取日常 Chrome 的 Cookie/Profile。
11
+ - 登录、安全验证、限流提示由用户在专用窗口中处理。
12
+ - Agent 可以上传真实参考图、填写提示词、发送、等待并保存结果。
13
+ - 每个Agent自动绑定一个独立ChatGPT后台页面(等价于隔离标签页);同一Agent内串行,不同Agent可并行。
14
+ - 自动化模式没有可操作窗口,避免用户误触、焦点切换和输入干扰;只有人工登录模式临时显示窗口。
15
+ - 默认最多3个Agent标签页同时生成,可通过环境变量调整为1~8。
16
+ - 不调用、抓取或模拟 ChatGPT 私有接口,不导出 Cookie。
17
+ - 发送任务使用持久化状态机(`prepared → submitting → sent → completed/needs_human`);一旦发送动作开始,后续重复提交会被拒绝,避免重复消耗 Pro 网页额度。
18
+
19
+ > Chrome 本身采用多进程架构,活动监视器里会看到浏览器、渲染器、GPU等多个子进程;这里的“单例”指一个专用浏览器实例和一个持久化用户目录;自动化任务通过Agent专属标签页隔离,不代表操作系统中只有一个Chrome子进程。
20
+
21
+ ## 专用浏览器数据
22
+
23
+ macOS 默认位置:
24
+
25
+ ```text
26
+ ~/Library/Application Support/ChatGPTImageWebMCP/
27
+ ├── browser.json # 自动化模式
28
+ ├── login-browser.json # 人工登录模式
29
+ ├── chrome-profile/
30
+ ├── prepared-jobs/ # 按Agent隔离的待发送任务
31
+ ├── output-reservations/ # 防止不同Agent覆盖同名文件
32
+ ├── agent-job-locks/ # 每个Agent一个任务锁
33
+ ├── parallel-slots/ # 全局并行槽位
34
+ ├── launch.lock/ # 仅启动期间存在
35
+ ├── job-admission.lock/ # 接纳新任务时短暂存在
36
+ ├── maintenance.lock/ # 登录模式切换或关闭浏览器期间存在
37
+ └── tab-assignment.lock/ # 分配标签页时短暂存在
38
+ ```
39
+
40
+ 关闭浏览器不会删除登录状态。不要把这个目录同步、提交或分享给其他人。
41
+
42
+ 默认`IMAGE_BROWSER_AUTO_CLOSE=false`,Chrome会在任务完成后继续运行以便下次复用。设置为`true`后,成功完成`submit_prepared_job`、`run_image_job`、完整批次、手动下载或清除准备任务时会尝试自动关闭;`prepare_image_job`和`needs_human`结果会保持浏览器现场。若其他Agent正在运行,或任一Agent仍有prepared/待人工处理状态,自动关闭会安全跳过。可通过`browser_status.autoCloseAfterSuccess`确认实际配置。
43
+
44
+ 可用环境变量:
45
+
46
+ | 变量 | 用途 |
47
+ | --- | --- |
48
+ | `IMAGE_BROWSER_HOME` | 覆盖状态和专用Profile目录 |
49
+ | `IMAGE_BROWSER_EXECUTABLE` | 覆盖Chrome/Brave可执行文件 |
50
+ | `IMAGE_BROWSER_START_URL` | 默认 `https://chatgpt.com/` |
51
+ | `IMAGE_BROWSER_DEFAULT_OUTPUT_DIR` | 默认 `generated/web-images` |
52
+ | `IMAGE_BROWSER_AGENT_ID` | 手动指定当前MCP进程的Agent标识;默认使用`PI_SESSION_ID` |
53
+ | `IMAGE_BROWSER_HEADLESS` | `true`时后台无窗口运行并禁用用户操作;默认true |
54
+ | `IMAGE_BROWSER_MAX_PARALLEL_TABS` | 同时生成的Agent页面上限,默认3,范围1~8 |
55
+ | `IMAGE_BROWSER_AUTO_CLOSE` | `true`时在成功生成、成功下载或清除待发送任务后,空闲且无待人工处理任务时自动关闭Chrome;默认false |
56
+ | `IMAGE_BROWSER_ENABLE_STOP_TOOL` | `true`时注册`stop_current_generation`,允许明确停止当前Agent页面的生成;默认false |
57
+ | `IMAGE_BROWSER_STOP_ON_TIMEOUT` | `true`时等待图片超时后自动点击停止,并尝试保存停止前已出现的图片;默认false |
58
+
59
+ ## 安装和构建
60
+
61
+ 前置条件:
62
+
63
+ - Node.js >= 22.18;
64
+ - Google Chrome 或 Brave;
65
+ - 支持stdio MCP的客户端;Pi 用户还需要安装并启用 `pi-mcp-adapter`;
66
+ - 用户自己的ChatGPT账号,登录状态和网页额度不会由本项目提供或共享。
67
+
68
+ ### npm安装
69
+
70
+ 公开包发布后,MCP配置应锁定具体版本,不建议直接使用`latest`:
71
+
72
+ ```json
73
+ {
74
+ "mcpServers": {
75
+ "chatgpt-image-web": {
76
+ "command": "npx",
77
+ "args": [
78
+ "--yes",
79
+ "--package=@isen/chatgpt-image-web-mcp@0.3.0",
80
+ "chatgpt-image-web-mcp"
81
+ ],
82
+ "lifecycle": "lazy-keep-alive",
83
+ "requestTimeoutMs": 14400000,
84
+ "env": {
85
+ "IMAGE_BROWSER_AGENT_ID": "${PI_SESSION_ID}",
86
+ "IMAGE_BROWSER_HEADLESS": "true",
87
+ "IMAGE_BROWSER_MAX_PARALLEL_TABS": "2",
88
+ "IMAGE_BROWSER_AUTO_CLOSE": "false",
89
+ "IMAGE_BROWSER_ENABLE_STOP_TOOL": "false",
90
+ "IMAGE_BROWSER_STOP_ON_TIMEOUT": "false",
91
+ "IMAGE_BROWSER_DEFAULT_OUTPUT_DIR": "${HOME}/Pictures/ChatGPTImageOutput"
92
+ },
93
+ "directTools": true
94
+ }
95
+ }
96
+ }
97
+ ```
98
+
99
+ 仓库中的 `mcp.example.json` 提供同样的无账号占位模板。不要提交真实 `.mcp.json`;是否关闭工具批准应由用户在自己的可信环境决定,公共示例不会设置 `approveTools: false`。
100
+
101
+ `IMAGE_BROWSER_HOME` 默认值分别是:
102
+
103
+ - macOS:`~/Library/Application Support/ChatGPTImageWebMCP`
104
+ - Linux:`~/.local/share/chatgpt-image-web-mcp`
105
+ - Windows:`%LOCALAPPDATA%\\ChatGPTImageWebMCP`
106
+
107
+ 所有输出目录和`referenceImages`均是运行MCP用户自己机器上的路径,Profile目录不能共享或上传。非Pi客户端应为每个会话提供稳定且唯一的`IMAGE_BROWSER_AGENT_ID`,也可以省略并接受进程重启后生成新ID。
108
+
109
+ ### 从源码构建
110
+
111
+ ```bash
112
+ git clone <repository-url>
113
+ cd chatgpt-image-web-mcp
114
+ npm ci
115
+ npm run check
116
+ npm test
117
+ npm run smoke
118
+ ```
119
+
120
+ 修改源码后重新执行 `npm run build`,然后在 Pi 中执行 `/reload` 或 `/mcp reconnect chatgpt-image-web`。从单任务版升级到多标签版时,**所有当前打开的Agent会话都必须执行一次`/reload`**。新版本检测到旧版全局任务锁时会拒绝开始生成,避免旧、新进程同时操作页面。
121
+
122
+ ## 第一次使用
123
+
124
+ 1. 调用 `open_login_browser`。
125
+ 2. 它会以**无远程调试的普通模式**打开唯一专用 Chrome,人工登录 ChatGPT Pro。
126
+ 3. 人工完成可能出现的Google登录、验证码或安全检查。
127
+ 4. 不要在日常Chrome里登录代替;两个Profile完全隔离。
128
+ 5. 登录成功并看到ChatGPT对话页后,调用 `switch_to_automation_browser`。
129
+ 6. 工具会先关闭可见登录窗口,再用同一个Profile切换为无窗口自动化模式;确认 `composerReady=true`。
130
+
131
+ Google可能拒绝在自动化浏览器里登录,因此登录模式和自动化模式不会同时运行,而是在同一个专用Profile上串行切换。后续登录未失效时一直使用后台无窗口模式。不要手动打开或操作该专用Profile;需要重新登录时调用`open_login_browser`。
132
+
133
+ ## 推荐半自动流程
134
+
135
+ ### 可控模式
136
+
137
+ ```text
138
+ prepare_image_job
139
+
140
+ 用户查看返回的 prepared 截图中的附件、模型和提示词
141
+
142
+ submit_prepared_job
143
+
144
+ 自动等待并保存图片
145
+ ```
146
+
147
+ `prepare_image_job` **不会点击发送**。它会先清空当前Agent输入框中的旧Prompt和附件,再写入本次内容,适合先确认再消耗网页额度。`submit_prepared_job`发送前会核对Prompt和可检测到的附件数量;页面内容被修改后会拒绝发送,应清除并重新准备。
148
+
149
+ ### 已确认后的快捷模式
150
+
151
+ ```text
152
+ run_image_job
153
+ ```
154
+
155
+ 一次完成上传、填写、发送、等待和保存。`.mcp.json` 已关闭MCP工具批准,Agent调用后会直接执行。
156
+
157
+ ### 串行批量模式(推荐)
158
+
159
+ ```text
160
+ run_batch_image_jobs({
161
+ jobs: [
162
+ { prompt: "第1页……", referenceImages: ["参考图.png"], outputStem: "01" },
163
+ { prompt: "第2页……", referenceImages: ["参考图.png"], outputStem: "02" },
164
+ { prompt: "第3页……", referenceImages: ["参考图.png"], outputStem: "03" }
165
+ ],
166
+ outputDir: "/path/to/current-project/generated",
167
+ stopOnFailure: true
168
+ })
169
+ ```
170
+
171
+ 批量工具会占用当前Agent自己的任务锁,并在该Agent的专属标签页中按数组顺序执行:上一张检测完成并保存后,才上传并发送下一张。其他Agent可以同时在各自标签页执行,但不会创建第二个浏览器。
172
+
173
+ - 单批最多12张。
174
+ - 每张默认最多等待480秒。
175
+ - 两张之间默认等待3秒。
176
+ - 默认任一失败立即停止,浏览器保持现场供人工检查。
177
+ - 每完成一张都会更新批次JSON,即使中途失败也能确认已完成范围。
178
+ - 整批任务不弹出MCP批准对话框,但每个数组项都会消耗一次ChatGPT网页生成额度。
179
+
180
+ ### 多Agent并行
181
+
182
+ 每个MCP进程按以下优先级确定Agent身份:
183
+
184
+ ```text
185
+ IMAGE_BROWSER_AGENT_ID
186
+ → PI_SESSION_ID
187
+ → CODEX_THREAD_ID
188
+ → MCP进程随机ID
189
+ ```
190
+
191
+ Agent第一次调用浏览器工具时会在后台创建或认领自己的页面,并通过`window.name`持久标记。创建使用CDP `background:true`,不会激活页面或抢夺系统焦点。不同Agent的以下状态互相隔离:
192
+
193
+ - ChatGPT标签页和对话上下文
194
+ - 待发送Prompt与附件(`prepare`/`submit`两步模式也按Agent隔离;每次prepare会先清理该Agent页面的旧草稿)
195
+ - 任务锁和批量任务
196
+ - 图片检测与下载目标
197
+ - 输出文件名预留;会同时检查已有文件和并发预留,同目录同前缀冲突时自动添加Agent短标识,所有最终图片使用排他写入,禁止覆盖既有结果
198
+
199
+ 默认允许3个Agent同时生成;第4个Agent会收到“达到安全上限”,稍后重试即可。每个Agent的批量任务在自己的标签页内仍然严格串行。
200
+
201
+ ### 人工在网页调整后
202
+
203
+ 默认Headless模式没有可操作窗口。如果明确需要长期人工查看和调整,可在没有任务运行时设置`IMAGE_BROWSER_HEADLESS=false`并重新连接,使自动化浏览器以可见模式运行。用户在该专用窗口继续对话后可以调用:
204
+
205
+ ```text
206
+ download_latest_image
207
+ ```
208
+
209
+ 它只保存最近的生成图片,不发送消息。
210
+
211
+ ## MCP 工具
212
+
213
+ | 工具 | 是否启动浏览器 | 是否发送消息 | 说明 |
214
+ | --- | --- | --- | --- |
215
+ | `browser_status` | 否 | 否 | 查看自动化/登录模式状态 |
216
+ | `open_login_browser` | 串行切换 | 否 | 无远程调试的人工安全登录模式 |
217
+ | `switch_to_automation_browser` | 串行切换 | 否 | 保留登录状态并切回自动化模式 |
218
+ | `open_chatgpt_browser` | 必要时后台启动 | 否 | 打开/定位当前Agent专属后台页面,不抢前台 |
219
+ | `prepare_image_job` | 必要时启动一次 | 否 | 上传附件并填写Prompt |
220
+ | `clear_prepared_job` | 复用 | 否 | 取消并清空未发送任务 |
221
+ | `submit_prepared_job` | 复用 | 是 | 发送并等待、保存 |
222
+ | `run_image_job` | 必要时启动一次 | 是 | 完整快捷流程 |
223
+ | `run_batch_image_jobs` | 复用Agent专属页 | 是(Agent内串行) | 按队列生成1~12张 |
224
+ | `download_latest_image` | 复用Agent专属页 | 否 | 保存当前Agent页中最近图片 |
225
+ | `stop_current_generation` | 复用Agent专属页 | 否 | 可选工具;停止当前Agent页的生成,需`IMAGE_BROWSER_ENABLE_STOP_TOOL=true` |
226
+ | `close_agent_tab` | 否 | 否 | 只关闭当前Agent标签页 |
227
+ | `close_chatgpt_browser` | 否 | 否 | 空闲时关闭整个专用实例,保留Profile |
228
+
229
+ ## 输出
230
+
231
+ 默认写入项目根目录:
232
+
233
+ ```text
234
+ generated/web-images/
235
+ ├── <job-id>-prepared.png # 发送前现场截图
236
+ ├── <job-id>-result.png # 生成后/异常现场截图
237
+ ├── <job-id>-image.png # 下载的图片,实际扩展名按响应格式
238
+ ├── <job-id>.json # 单张Prompt、参考图、时间和保存方式
239
+ └── <batch-id>-batch.json # 批次实时进度和每张结果
240
+ ```
241
+
242
+ MCP 只返回路径和元数据,不把整张图片Base64塞进Agent上下文。准备任务更换`outputDir`时会在新目录重新预留名称;手动下载和批次清单也使用同一防覆盖机制。任务完成、取消或准备失败后会释放临时预留,已生成文件本身仍会阻止后续覆盖。
243
+
244
+ ## 单例与失败策略
245
+
246
+ - Chrome 使用固定专用 Profile;自动化模式默认使用`--headless=new`及随机本地CDP端口。
247
+ - 自动化模式无窗口,因此用户无法误触;同时禁用后台计时器/渲染节流,避免后台图片任务被暂停。
248
+ - Google/OpenAI登录使用无CDP的人工登录模式,成功后关闭并以同一Profile切回自动化模式。
249
+ - 两种模式严格串行,不会同时存在;端口及 PID 保存到状态文件。
250
+ - 启动使用原子目录锁;多个Pi/Codex会话同时启动时只有一个能创建浏览器。
251
+ - 标签页分配使用跨进程锁,两个Agent不会认领同一标签页。
252
+ - 每个Agent拥有独立任务锁;同一Agent不会重入,不同Agent使用并行槽位。
253
+ - 登录模式切换和关闭整个浏览器与新任务接纳使用同一套跨进程维护门禁;任何Agent仍运行时会拒绝维护,维护开始后也会拒绝新任务,避免竞态中断。
254
+ - 如果旧专用浏览器进程存在但无法连接,会先停止旧进程;无法停止时直接报错,绝不再启动第二个实例。
255
+ - 页面改版、验证码或无法检测图片时,浏览器保持打开并返回 `needs_human`。
256
+ - 默认超时不会取消网页任务;启用`IMAGE_BROWSER_STOP_ON_TIMEOUT`后才会自动点击停止,且绝不自动重发。启用`IMAGE_BROWSER_ENABLE_STOP_TOOL`后可明确调用`stop_current_generation`。
257
+ - `submitting`、`sent` 或 `needs_human` 状态不能再次提交;用户应先检查页面并下载已有结果,确需重试时明确清除并重新准备任务。
258
+
259
+ ## 局限
260
+
261
+ 这是网页半自动化,不是OpenAI官方API:
262
+
263
+ - ChatGPT页面结构变化后,选择器可能需要更新。
264
+ - Headless模式与可见Chrome仍可能存在服务端行为差异;检测到登录或安全验证时会停止并要求切换到人工登录模式,不会尝试绕过。
265
+ - 网页生成额度、排队和功能取决于Pro账户及当时服务状态;本地允许3个并行标签页不代表ChatGPT服务端一定接受3个并行生成。
266
+ - 安全验证必须人工处理,本项目不会绕过。
267
+ - 模型、图片工具或会话选择建议用户在专用浏览器中预先设置并保持。
268
+ - 下载优先获取原始网络图片;HTTP或页面fetch得到的字节必须通过PNG/JPEG/WebP/GIF/AVIF文件签名校验,避免把登录页或错误HTML保存成图片;若网页权限阻止下载,最后才使用元素截图,并在任务JSON中标记 `element-screenshot`。
269
+ - 使用时需遵守ChatGPT适用的服务条款。
270
+
271
+ ## 许可证
272
+
273
+ 本项目采用 [MIT License](LICENSE)。
@@ -0,0 +1,100 @@
1
+ import { type Browser, type BrowserContext, type Page } from "playwright-core";
2
+ export interface BrowserMetadata {
3
+ pid: number;
4
+ port: number;
5
+ headless: boolean;
6
+ executable: string;
7
+ profileDir: string;
8
+ launchedAt: string;
9
+ }
10
+ export interface LoginBrowserMetadata {
11
+ pid: number;
12
+ executable: string;
13
+ profileDir: string;
14
+ launchedAt: string;
15
+ }
16
+ export interface BrowserStatus {
17
+ running: boolean;
18
+ endpoint?: string;
19
+ metadata?: BrowserMetadata;
20
+ loginModeActive: boolean;
21
+ loginBrowser?: LoginBrowserMetadata;
22
+ maxParallelTabs: number;
23
+ autoCloseAfterSuccess: boolean;
24
+ stopToolEnabled: boolean;
25
+ stopOnTimeout: boolean;
26
+ activeJobs: Array<{
27
+ agentId: string;
28
+ pid: number;
29
+ startedAt: string;
30
+ }>;
31
+ profileDir: string;
32
+ stateDir: string;
33
+ reason?: string;
34
+ }
35
+ export interface ConnectedBrowser {
36
+ browser: Browser;
37
+ context: BrowserContext;
38
+ page: Page;
39
+ metadata: BrowserMetadata;
40
+ agentId: string;
41
+ tabName: string;
42
+ }
43
+ export declare class BrowserManager {
44
+ private connectedBrowser?;
45
+ private connectedContext?;
46
+ private connectedMetadata?;
47
+ readonly stateDir: string;
48
+ readonly profileDir: string;
49
+ readonly metadataPath: string;
50
+ readonly loginMetadataPath: string;
51
+ readonly activePortPath: string;
52
+ readonly launchLockPath: string;
53
+ readonly tabLockPath: string;
54
+ readonly jobAdmissionLockPath: string;
55
+ readonly maintenanceLockPath: string;
56
+ readonly legacyJobLockPath: string;
57
+ readonly jobLocksDir: string;
58
+ readonly slotLocksDir: string;
59
+ readonly maxParallelTabs: number;
60
+ readonly autoCloseAfterSuccess: boolean;
61
+ readonly stopToolEnabled: boolean;
62
+ readonly stopOnTimeout: boolean;
63
+ constructor(stateDir?: string);
64
+ private agentKey;
65
+ private tabName;
66
+ findExecutable(): string;
67
+ private readMetadata;
68
+ private readLoginMetadata;
69
+ private readActivePort;
70
+ private endpointIsHealthy;
71
+ private lockOwner;
72
+ private removeStaleLock;
73
+ private activeJobOwners;
74
+ private assertNoActiveJobs;
75
+ status(): Promise<BrowserStatus>;
76
+ private withDirectoryLock;
77
+ private withLaunchLock;
78
+ private withJobAdmissionLock;
79
+ private beginExclusiveMaintenance;
80
+ private withExclusiveMaintenance;
81
+ ensureRunning(): Promise<BrowserMetadata>;
82
+ openLoginBrowser(): Promise<LoginBrowserMetadata>;
83
+ switchToAutomationBrowser(agentId?: string): Promise<ConnectedBrowser>;
84
+ private browserConnection;
85
+ private readPageName;
86
+ private isChatGptPage;
87
+ private createBackgroundPage;
88
+ private pageForAgent;
89
+ connect(options?: {
90
+ navigateToChatGpt?: boolean;
91
+ agentId?: string;
92
+ bringToFront?: boolean;
93
+ }): Promise<ConnectedBrowser>;
94
+ acquireJobLock(agentId?: string): Promise<() => void>;
95
+ closeAgentTab(agentId: string): Promise<boolean>;
96
+ private hasPendingPreparedJobs;
97
+ closeDedicatedBrowser(options?: {
98
+ preservePreparedJobs?: boolean;
99
+ }): Promise<boolean>;
100
+ }