codex-imagegen-mcp 0.1.2

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.
Files changed (105) hide show
  1. package/CHANGELOG.md +96 -0
  2. package/LICENSE +201 -0
  3. package/NOTICE +19 -0
  4. package/README.md +373 -0
  5. package/dist/src/auth/borrowed.js +77 -0
  6. package/dist/src/auth/borrowed.js.map +1 -0
  7. package/dist/src/auth/browser-login.js +179 -0
  8. package/dist/src/auth/browser-login.js.map +1 -0
  9. package/dist/src/auth/device-login.js +39 -0
  10. package/dist/src/auth/device-login.js.map +1 -0
  11. package/dist/src/auth/jwt.js +69 -0
  12. package/dist/src/auth/jwt.js.map +1 -0
  13. package/dist/src/auth/manager.js +289 -0
  14. package/dist/src/auth/manager.js.map +1 -0
  15. package/dist/src/auth/oauth.js +246 -0
  16. package/dist/src/auth/oauth.js.map +1 -0
  17. package/dist/src/auth/pages.js +39 -0
  18. package/dist/src/auth/pages.js.map +1 -0
  19. package/dist/src/auth/pkce.js +14 -0
  20. package/dist/src/auth/pkce.js.map +1 -0
  21. package/dist/src/auth/store.js +75 -0
  22. package/dist/src/auth/store.js.map +1 -0
  23. package/dist/src/backend/images-client.js +223 -0
  24. package/dist/src/backend/images-client.js.map +1 -0
  25. package/dist/src/backend/ratelimits.js +183 -0
  26. package/dist/src/backend/ratelimits.js.map +1 -0
  27. package/dist/src/cli.js +407 -0
  28. package/dist/src/cli.js.map +1 -0
  29. package/dist/src/config.js +87 -0
  30. package/dist/src/config.js.map +1 -0
  31. package/dist/src/constants.js +52 -0
  32. package/dist/src/constants.js.map +1 -0
  33. package/dist/src/doctor.js +122 -0
  34. package/dist/src/doctor.js.map +1 -0
  35. package/dist/src/errors.js +76 -0
  36. package/dist/src/errors.js.map +1 -0
  37. package/dist/src/generation.js +183 -0
  38. package/dist/src/generation.js.map +1 -0
  39. package/dist/src/history.js +47 -0
  40. package/dist/src/history.js.map +1 -0
  41. package/dist/src/images/chroma.js +200 -0
  42. package/dist/src/images/chroma.js.map +1 -0
  43. package/dist/src/images/codec.js +159 -0
  44. package/dist/src/images/codec.js.map +1 -0
  45. package/dist/src/images/inputs.js +83 -0
  46. package/dist/src/images/inputs.js.map +1 -0
  47. package/dist/src/images/output.js +100 -0
  48. package/dist/src/images/output.js.map +1 -0
  49. package/dist/src/images/preview.js +20 -0
  50. package/dist/src/images/preview.js.map +1 -0
  51. package/dist/src/images/resize.js +135 -0
  52. package/dist/src/images/resize.js.map +1 -0
  53. package/dist/src/install/opencode.js +138 -0
  54. package/dist/src/install/opencode.js.map +1 -0
  55. package/dist/src/install/skill.js +128 -0
  56. package/dist/src/install/skill.js.map +1 -0
  57. package/dist/src/install/snippets.js +127 -0
  58. package/dist/src/install/snippets.js.map +1 -0
  59. package/dist/src/log.js +58 -0
  60. package/dist/src/log.js.map +1 -0
  61. package/dist/src/remove-background.js +101 -0
  62. package/dist/src/remove-background.js.map +1 -0
  63. package/dist/src/server/context.js +2 -0
  64. package/dist/src/server/context.js.map +1 -0
  65. package/dist/src/server/index.js +94 -0
  66. package/dist/src/server/index.js.map +1 -0
  67. package/dist/src/server/instructions.js +20 -0
  68. package/dist/src/server/instructions.js.map +1 -0
  69. package/dist/src/server/login-coordinator.js +73 -0
  70. package/dist/src/server/login-coordinator.js.map +1 -0
  71. package/dist/src/server/progress.js +33 -0
  72. package/dist/src/server/progress.js.map +1 -0
  73. package/dist/src/server/prompts.js +45 -0
  74. package/dist/src/server/prompts.js.map +1 -0
  75. package/dist/src/server/resources.js +73 -0
  76. package/dist/src/server/resources.js.map +1 -0
  77. package/dist/src/server/tools.js +338 -0
  78. package/dist/src/server/tools.js.map +1 -0
  79. package/dist/src/server/workspace.js +45 -0
  80. package/dist/src/server/workspace.js.map +1 -0
  81. package/dist/src/status.js +86 -0
  82. package/dist/src/status.js.map +1 -0
  83. package/dist/src/util/format.js +36 -0
  84. package/dist/src/util/format.js.map +1 -0
  85. package/dist/src/util/fs.js +117 -0
  86. package/dist/src/util/fs.js.map +1 -0
  87. package/dist/src/util/http.js +65 -0
  88. package/dist/src/util/http.js.map +1 -0
  89. package/dist/src/util/invocation.js +43 -0
  90. package/dist/src/util/invocation.js.map +1 -0
  91. package/dist/src/util/open.js +41 -0
  92. package/dist/src/util/open.js.map +1 -0
  93. package/docs/ARCHITECTURE.md +123 -0
  94. package/docs/AUTH.md +242 -0
  95. package/docs/BACKEND.md +169 -0
  96. package/docs/CLIENTS.md +154 -0
  97. package/docs/DEVELOPMENT.md +171 -0
  98. package/docs/README.md +84 -0
  99. package/docs/TOOLS.md +221 -0
  100. package/package.json +67 -0
  101. package/skill/imagegen/LICENSE.txt +201 -0
  102. package/skill/imagegen/SKILL.md +189 -0
  103. package/skill/imagegen/references/prompting.md +112 -0
  104. package/skill/imagegen/references/sample-prompts.md +418 -0
  105. package/skill/imagegen/references/tools.md +90 -0
package/docs/AUTH.md ADDED
@@ -0,0 +1,242 @@
1
+ <p align="center">
2
+ <img src="assets/banner-auth.jpg" width="100%" alt="Authentication — a travel-poster illustration of a stone gatehouse with its wooden gates open on a desert road, a giant iron key leaning against the wall">
3
+ </p>
4
+
5
+ # Authentication
6
+
7
+ How the Codex image tool authenticates, how this server reproduces it, and how you sign in before using it. Every protocol detail was checked against the Codex source at tag `rust-v0.155.0-alpha.16` (the build inside Codex desktop 26.917.51856) and against the live service.
8
+
9
+ **On this page:** [TL;DR](#tldr) · [Credential sources](#credential-sources) · [Browser sign-in](#browser-sign-in) · [Device-code sign-in](#device-code-sign-in) · [Token storage](#token-storage) · [Refresh](#refresh) · [Request headers](#request-headers) · [Plans and workspaces](#plans-and-workspaces) · [Signing out](#signing-out) · [Troubleshooting](#troubleshooting) · [Security notes](#security-notes)
10
+
11
+ ## TL;DR
12
+
13
+ > [!TIP]
14
+ > - Image requests go to `https://chatgpt.com/backend-api/codex/images/{generations,edits}` with `Authorization: Bearer <ChatGPT access token>` and `ChatGPT-Account-ID: <workspace id>`.
15
+ > - The token comes from **OpenAI's OAuth server** (`auth.openai.com`) through the **Codex CLI's public client**. Browser sign-in uses authorization code with PKCE; headless machines use OpenAI's device-code variant.
16
+ > - Access tokens live about **10 days** and are renewed with **single-use, rotating** refresh tokens.
17
+ > - The server uses its **own** sign-in, or **borrows** an existing Codex or opencode ChatGPT sign-in **read-only**.
18
+
19
+ ## Credential sources
20
+
21
+ At every request, the server takes the first source that yields a valid token:
22
+
23
+ ```mermaid
24
+ flowchart LR
25
+ req(["image request"]):::ink --> own{"own sign-in<br/>auth.json"}:::cream
26
+ own -- "valid" --> use(["use it"]):::teal
27
+ own -- "expires within 5 min" --> refresh["refresh under<br/>the shared lock"]:::rust --> use
28
+ own -- "missing" --> codex{"Codex<br/>~/.codex/auth.json"}:::cream
29
+ codex -- "token still valid" --> use
30
+ codex -- "missing or expired" --> oc{"opencode<br/>ChatGPT sign-in"}:::cream
31
+ oc -- "token still valid" --> use
32
+ oc -- "missing or expired" --> none(["not signed in:<br/>run login or call sign_in"]):::ochre
33
+ classDef ink fill:#2A2523,stroke:#9A8C76,color:#E4D9C6
34
+ classDef rust fill:#A6553B,stroke:#7E3F2B,color:#FFFFFF
35
+ classDef ochre fill:#D9A05B,stroke:#B5813F,color:#2A2523
36
+ classDef teal fill:#4E6E63,stroke:#3A544B,color:#FFFFFF
37
+ classDef cream fill:#E4D9C6,stroke:#A89A80,color:#2A2523
38
+ ```
39
+
40
+ | # | Source | File | Refreshed by this server? |
41
+ |---|---|---|---|
42
+ | 1 | **Own sign-in** (`codex-imagegen-mcp login` or the `sign_in` tool) | `$CODEX_IMAGEGEN_HOME/auth.json`, by default `~/.local/share/codex-imagegen-mcp/auth.json` | **Yes**, automatically, under a cross-process lock |
43
+ | 2 | **Codex** CLI or desktop app | `$CODEX_HOME/auth.json`, by default `~/.codex/auth.json` | **No**, read-only |
44
+ | 3 | **opencode** "ChatGPT Plus/Pro" provider | the `openai` entry in `~/.local/share/opencode/auth.json` | **No**, read-only |
45
+
46
+ To pin a single source, set `CODEX_IMAGEGEN_CREDENTIALS=own|codex|opencode` (the default is `auto`). `codex-imagegen-mcp status` and the `auth_status` tool show every source and which one is active.
47
+
48
+ ### Why borrowed sign-ins are read-only
49
+
50
+ > [!IMPORTANT]
51
+ > OpenAI refresh tokens are **single-use**. Every refresh returns a new refresh token and invalidates the old one; reuse fails with `refresh_token_reused`, which Codex handles in `codex-rs/login/src/auth/manager.rs`. If this server refreshed Codex's or opencode's token, that app would later present an already-spent refresh token and be signed out.
52
+
53
+ So borrowed tokens are used only while their access token is valid, judged by the JWT `exp` claim with a 60-second margin. After that the server moves on to the next source. The owning app renews its own token the next time it runs; Codex, for example, refreshes when fewer than 5 minutes remain. Borrowed files are never written.
54
+
55
+ Codex users on the `keyring` credential store have no `auth.json`, so for them this source reports "not signed in". Use `login` instead.
56
+
57
+ ## Browser sign-in
58
+
59
+ ```bash
60
+ codex-imagegen-mcp login
61
+ ```
62
+
63
+ ```mermaid
64
+ sequenceDiagram
65
+ autonumber
66
+ actor U as You
67
+ participant CLI as codex-imagegen-mcp
68
+ participant B as Browser
69
+ participant O as auth.openai.com
70
+ CLI->>CLI: PKCE verifier + S256 challenge, random state
71
+ CLI->>CLI: listen on 127.0.0.1:1455 (fallback 1457)
72
+ CLI->>B: open /oauth/authorize?client_id=…&code_challenge=…&state=…
73
+ B->>O: sign in to ChatGPT and approve
74
+ O-->>B: 302 → http://localhost:1455/auth/callback?code=…&state=…
75
+ B->>CLI: GET /auth/callback
76
+ CLI->>O: POST /oauth/token (authorization_code + code_verifier)
77
+ O-->>CLI: id_token, access_token, refresh_token
78
+ CLI->>CLI: write auth.json (0600, atomic), revoke the previous sign-in
79
+ CLI-->>B: 302 → /success ("You're signed in")
80
+ CLI-->>U: ✓ Signed in as you@example.com (ChatGPT plus plan)
81
+ ```
82
+
83
+ 1. **PKCE and state.** The CLI generates a PKCE pair (64 random bytes as a base64url verifier, plus its S256 challenge) and a random `state`.
84
+ 2. **Loopback server.** It starts a server on `127.0.0.1:1455`, falling back to `1457`. These exact ports are required: OpenAI allow-lists only `http://localhost:1455/auth/callback` and `…:1457/…` for this client. If a stale Codex or codex-imagegen login server holds the port, it is asked to `GET /cancel`, as Codex does.
85
+ 3. **Authorize URL.** It opens this URL in your browser:
86
+
87
+ ```text
88
+ https://auth.openai.com/oauth/authorize?response_type=code&client_id=app_EMoamEEZ73f0CkXaXp7hrann
89
+ &redirect_uri=http://localhost:1455/auth/callback&scope=openid%20profile%20email%20offline_access
90
+ &code_challenge=…&code_challenge_method=S256&id_token_add_organizations=true
91
+ &codex_cli_simplified_flow=true&state=…&originator=codex-imagegen-mcp
92
+ ```
93
+
94
+ 4. **Callback.** You sign in and approve, and the browser returns to `/auth/callback?code=…&state=…`.
95
+ - A mismatched `state` is answered with HTTP 400, and the login keeps waiting.
96
+ - An `error` parameter ends the login with a readable message. `missing_codex_entitlement` means Codex isn't enabled for the workspace.
97
+ 5. **Token exchange.** The code is exchanged for tokens at `POST https://auth.openai.com/oauth/token`, form-encoded with `grant_type=authorization_code`, `code`, `redirect_uri`, `client_id` and `code_verifier`.
98
+ 6. **Save.** The tokens are saved and the browser shows *You're signed in*. If this tool already had a sign-in, its refresh token is revoked.
99
+
100
+ The flow times out after 10 minutes, and Ctrl+C cancels it. The `sign_in` MCP tool runs the same flow inside the server: it returns the link to the agent immediately, tries to open it, and finishes in the background.
101
+
102
+ > [!NOTE]
103
+ > **Scopes:** `openid profile email offline_access`. Codex also asks for `api.connectors.read api.connectors.invoke`, but the image endpoints don't need them, so this server asks for less.
104
+ >
105
+ > **Remote machines:** the redirect goes to `localhost`, so the browser must run on the same machine as the server. Over SSH, use the device code, or forward the port with `ssh -L 1455:localhost:1455 host` and open the printed URL locally.
106
+
107
+ ## Device-code sign-in
108
+
109
+ ```bash
110
+ codex-imagegen-mcp login --device
111
+ ```
112
+
113
+ ```mermaid
114
+ sequenceDiagram
115
+ autonumber
116
+ actor U as You (any device)
117
+ participant CLI as codex-imagegen-mcp
118
+ participant O as auth.openai.com
119
+ CLI->>O: POST /api/accounts/deviceauth/usercode {client_id}
120
+ O-->>CLI: device_auth_id, user_code, interval
121
+ CLI-->>U: open auth.openai.com/codex/device and enter ABCD-12345
122
+ loop every interval, for up to 15 minutes
123
+ CLI->>O: POST /api/accounts/deviceauth/token
124
+ O-->>CLI: 403 or 404 while approval is pending
125
+ end
126
+ U->>O: sign in and enter the code
127
+ O-->>CLI: authorization_code + code_verifier
128
+ CLI->>O: POST /oauth/token (redirect_uri = …/deviceauth/callback)
129
+ O-->>CLI: tokens
130
+ ```
131
+
132
+ 1. **Request a code.** `POST https://auth.openai.com/api/accounts/deviceauth/usercode` with `{client_id}` returns `{device_auth_id, user_code, interval}`.
133
+ 2. **Enter it.** You open **https://auth.openai.com/codex/device** on any device and enter the code, which is valid for 15 minutes.
134
+ 3. **Poll.** The CLI polls `POST …/api/accounts/deviceauth/token` with `{device_auth_id, user_code}`. HTTP 403 or 404 means approval is still pending.
135
+ 4. **Exchange.** Once you approve, the response carries `{authorization_code, code_verifier}`; the server supplies the PKCE pair. The code is exchanged at `/oauth/token` with `redirect_uri=https://auth.openai.com/deviceauth/callback`.
136
+
137
+ > [!WARNING]
138
+ > A 404 on step 1 means device codes are disabled for your account. Enable "device code authorization for Codex" in **ChatGPT → Settings → Security**. On a workspace, an admin may need to allow it.
139
+
140
+ ## Token storage
141
+
142
+ The server's own `auth.json` is written atomically (temp file, then rename) with mode `0600`, in a `0700` directory:
143
+
144
+ ```json
145
+ {
146
+ "version": 1,
147
+ "auth_mode": "chatgpt",
148
+ "tokens": {
149
+ "id_token": "<JWT>",
150
+ "access_token": "<JWT, ~10 day lifetime>",
151
+ "refresh_token": "<opaque, single-use>",
152
+ "account_id": "<chatgpt_account_id from the id_token>"
153
+ },
154
+ "last_refresh": "2026-09-23T01:02:03.000Z",
155
+ "login_method": "browser",
156
+ "created_at": "2026-09-23T01:02:03.000Z"
157
+ }
158
+ ```
159
+
160
+ The field names inside `tokens` match Codex's `auth.json`. Tokens are never logged; the log file records events only.
161
+
162
+ ## Refresh
163
+
164
+ **When it happens:**
165
+ - when the access token's `exp` is within **5 minutes**, as in Codex;
166
+ - once `last_refresh` is 8 days old, if the token has no `exp`;
167
+ - once after any **401** from the backend.
168
+
169
+ **The request:** `POST https://auth.openai.com/oauth/token` with the JSON body `{"client_id": "…", "grant_type": "refresh_token", "refresh_token": "…"}`. There is no `scope`, exactly like Codex.
170
+
171
+ **Concurrency.** MCP clients may run several server processes at once; opencode, for example, runs one per open project. Refreshes are therefore serialized:
172
+
173
+ ```mermaid
174
+ sequenceDiagram
175
+ participant P1 as server process 1
176
+ participant L as auth.lock + auth.json
177
+ participant P2 as server process 2
178
+ participant O as auth.openai.com
179
+ P1->>L: take the lock
180
+ P2->>L: take the lock (waits)
181
+ P1->>L: re-read auth.json: still the stale token
182
+ P1->>O: refresh with refresh token R1
183
+ O-->>P1: access token A2 + refresh token R2 (R1 is now spent)
184
+ P1->>L: write A2/R2 atomically, release the lock
185
+ P2->>L: lock acquired, re-read auth.json: already rotated to A2
186
+ Note over P2: adopts A2 and never spends R1 a second time
187
+ ```
188
+
189
+ Tests verify that three concurrent processes produce exactly one refresh call.
190
+
191
+ **When a refresh fails:**
192
+ - **Permanent:** `refresh_token_expired`, `refresh_token_reused`, `refresh_token_invalidated`, any 401, and 400 `invalid_grant`. You're asked to sign in again.
193
+ - **Transient:** anything else. The current token is kept while it's still valid, and the refresh is retried on the next use.
194
+
195
+ ## Request headers
196
+
197
+ | Header | Value |
198
+ |---|---|
199
+ | `Authorization` | `Bearer <access_token>` |
200
+ | `ChatGPT-Account-ID` | `tokens.account_id`, the workspace to bill |
201
+ | `originator` | `codex-imagegen-mcp` (override with `CODEX_IMAGEGEN_ORIGINATOR`) |
202
+ | `User-Agent` | `codex-imagegen-mcp/<version> (<os> <release>; <arch>) node/<version>` |
203
+ | `X-OpenAI-Fedramp` | `true`, only when the token's `chatgpt_account_is_fedramp` claim is true |
204
+
205
+ ## Plans and workspaces
206
+
207
+ - **Plan:** read from the JWT claim `chatgpt_plan_type`. The Codex client hides image generation on **Free**. This server still sends the request and reports the backend's answer clearly.
208
+ - **Workspace:** the sign-in's `chatgpt_account_id` claim. To use another workspace, sign in again and choose it on the consent screen; `sign_in` with `force: true` does this from the agent.
209
+ - **Quota:** usage counts against your plan's Codex limits. `status` and `auth_status` read `GET https://chatgpt.com/backend-api/wham/usage`, which costs nothing, to show the 5-hour and weekly windows.
210
+
211
+ ## Signing out
212
+
213
+ ```bash
214
+ codex-imagegen-mcp logout # revoke the refresh token at /oauth/revoke, then delete auth.json
215
+ codex-imagegen-mcp logout --no-revoke
216
+ ```
217
+
218
+ Borrowed Codex and opencode sign-ins are never touched; sign out of those apps separately.
219
+
220
+ ## Troubleshooting
221
+
222
+ | Message | Cause | Fix |
223
+ |---|---|---|
224
+ | `Not signed in to ChatGPT … (Checked — …)` | No source has a valid token | `login`, or open Codex/opencode once so their sign-in refreshes |
225
+ | `…refresh token was already used` | Another machine or process spent the same refresh token (e.g. a copied `auth.json`) | `login` again, and don't share `auth.json` between machines |
226
+ | `ports 1455 and 1457 are busy` | Another Codex or opencode login is waiting | Finish or close it, or use `--device` |
227
+ | `Device-code sign-in is not enabled for this account` | Device codes are disabled | Enable them in ChatGPT → Settings → Security, or use the browser flow |
228
+ | `Codex is not enabled for your ChatGPT workspace` | Workspace policy (`missing_codex_entitlement`) | Ask the workspace admin |
229
+ | `ChatGPT rejected the … (HTTP 401)` | The token was revoked or expired on the server | `login` again |
230
+ | `Access denied (HTTP 403)` | The plan or workspace lacks Codex image generation (e.g. Free) | Upgrade, or switch workspace |
231
+ | `blocked by Cloudflare` | Your network was flagged | Retry later or from another network |
232
+
233
+ ## Security notes
234
+
235
+ - **The OAuth client:** `app_EMoamEEZ73f0CkXaXp7hrann` is OpenAI's public native client for the Codex CLI. It has no client secret, and PKCE protects the flow. opencode's ChatGPT sign-in uses the same client. This project identifies itself honestly through `originator` and `User-Agent`.
236
+ - **Your tokens:** they grant access to your ChatGPT account's Codex features. Treat `auth.json` like a password; that's why it's `0600`.
237
+ - **What leaves your machine:** only requests to `auth.openai.com` and `chatgpt.com`, plus downloads of any `http(s)` input image URLs you pass. There is no telemetry.
238
+ - **Terms of use:** this is an unofficial integration of an internal API. Use it within OpenAI's Terms of Use.
239
+
240
+ ---
241
+
242
+ <p align="center"><a href="TOOLS.md">← Tools</a> &nbsp;·&nbsp; <a href="README.md">Docs home</a> &nbsp;·&nbsp; <a href="CLIENTS.md">Clients →</a></p>
@@ -0,0 +1,169 @@
1
+ <p align="center">
2
+ <img src="assets/banner-backend.jpg" width="100%" alt="Backend — a travel-poster illustration of a monumental concrete dam and powerhouse in a canyon, with transmission towers marching away toward a sun rising behind a peak">
3
+ </p>
4
+
5
+ # Backend
6
+
7
+ How the image generation in OpenAI Codex really works. These findings come from taking apart the Codex desktop app and its bundled CLI, reading the matching open-source code, and measuring the live service. Nothing here is guessed; unmeasured points are marked as such.
8
+
9
+ > [!NOTE]
10
+ > **Measured on** 2026-09-22 and 2026-09-23 with a ChatGPT **Plus** account.
11
+ > - **App:** Codex desktop `/Applications/ChatGPT.app`, bundle `com.openai.codex`, version 26.917.51856.
12
+ > - **CLI:** its bundled `Contents/Resources/codex`, version 0.155.0-alpha.16.
13
+ > - **Source:** `openai/codex@rust-v0.155.0-alpha.16`.
14
+
15
+ **On this page:** [Two layers](#two-layers-a-skill-and-a-built-in-tool) · [The HTTP API](#the-http-api) · [What the service honors](#what-the-service-honors) · [Newer image models](#newer-image-models) · [Quota and rate limits](#quota-and-rate-limits) · [Output files](#output-files) · [Compared with Codex](#compared-with-codexs-built-in-tool)
16
+
17
+ ## Two layers: a skill and a built-in tool
18
+
19
+ ```mermaid
20
+ flowchart LR
21
+ subgraph codex ["Codex app / CLI"]
22
+ skill["imagegen skill<br/>SKILL.md: when and how to prompt"]:::ochre
23
+ tool["built-in tool<br/>image_gen.imagegen"]:::rust
24
+ model["the model"]:::ink
25
+ end
26
+ backend["chatgpt.com/backend-api/codex<br/>/images/generations · /images/edits"]:::teal
27
+ file[("$CODEX_HOME/generated_images/<br/>thread/call_id.png")]:::cream
28
+ skill -. "read on demand" .-> model
29
+ model -- "prompt, reference images" --> tool
30
+ tool -- "Bearer token + ChatGPT-Account-ID" --> backend
31
+ backend -- "base64 PNG" --> tool
32
+ tool --> file
33
+ classDef ink fill:#2A2523,stroke:#9A8C76,color:#E4D9C6
34
+ classDef rust fill:#A6553B,stroke:#7E3F2B,color:#FFFFFF
35
+ classDef ochre fill:#D9A05B,stroke:#B5813F,color:#2A2523
36
+ classDef teal fill:#4E6E63,stroke:#3A544B,color:#FFFFFF
37
+ classDef cream fill:#E4D9C6,stroke:#A89A80,color:#2A2523
38
+ style codex fill:#D9A05B1F,stroke:#B5813F,color:#B5813F
39
+ ```
40
+
41
+ 1. **The `imagegen` skill** is instructions only.
42
+ - It's embedded in the `codex` binary and written to `$CODEX_HOME/skills/.system/imagegen/` on startup, fingerprinted in `.codex-system-skills.marker`.
43
+ - The model sees only its name, description and path, and reads `SKILL.md` on demand.
44
+ - It covers when to generate, how to structure prompts, and where to save.
45
+ - It also offers an API-key CLI fallback (`scripts/image_gen.py`) and a chroma-key helper (`scripts/remove_chroma_key.py`).
46
+ - A byte-exact copy is in [`upstream/codex-imagegen-skill/`](../upstream/codex-imagegen-skill/).
47
+ 2. **The built-in tool `image_gen.imagegen`** does the work.
48
+ - It's registered by the `codex-rs/ext/image-generation` crate as a namespaced function tool with three inputs: `prompt`, `referenced_image_paths` (up to 5 absolute paths) and `num_last_images_to_include` (1–5 recent images). The last two are mutually exclusive.
49
+ - It's offered only when **all** of these hold (`core/src/tools/spec_plan.rs`):
50
+ - the `image_generation` feature is on, which it is by default;
51
+ - the account is **not on Free**;
52
+ - the model accepts image input;
53
+ - the provider uses ChatGPT auth. API-key auth gets no tool.
54
+
55
+ ## The HTTP API
56
+
57
+ ```http
58
+ POST https://chatgpt.com/backend-api/codex/images/generations # no input images
59
+ POST https://chatgpt.com/backend-api/codex/images/edits # with input images
60
+ Authorization: Bearer <ChatGPT OAuth access token>
61
+ ChatGPT-Account-ID: <chatgpt_account_id>
62
+ originator: <client id, e.g. codex_cli_rs>
63
+ User-Agent: <originator>/<version> (<os>; <arch>) <terminal>
64
+ Content-Type: application/json
65
+
66
+ {"prompt":"…","background":"auto","model":"gpt-image-2","quality":"auto","size":"auto"}
67
+ # edits add: "images":[{"image_url":"data:image/png;base64,…"}] (max 5)
68
+ ```
69
+
70
+ - **Constants:** Codex hard-codes `model: "gpt-image-2"`, `quality: "auto"`, `size: "auto"` and `background: "auto"` (`ext/image-generation/src/tool.rs`). Upstream `main` was unchanged as of 2026-09-22.
71
+ - **Response:**
72
+ - Body: `{created, background, data:[{b64_json, generation_id}], output_format, quality, size, usage}`.
73
+ - Headers: `x-codex-imagegen-request-id`, `x-oai-request-id`, and the rate-limit headers below.
74
+ - **What Codex does with it:** it decodes the first image to `$CODEX_HOME/generated_images/<thread>/<call_id>.png` and returns it to the model with a note that the user can already see it. The desktop app renders it inline, with Edit/Canvas actions.
75
+
76
+ ## What the service honors
77
+
78
+ | Request | Result |
79
+ |---|---|
80
+ | `size: "17x17"` | 1536×1024; an invalid size is silently ignored |
81
+ | `size: "1024x1024"`, `quality: "medium"`, `output_format: "jpeg"`, `n: 2` | **1370×1148, quality `low`, PNG, one image**: all four ignored |
82
+ | `model: "gpt-image-bogus-does-not-exist"` | 200 OK and a normal image: the model is ignored |
83
+ | `background: "transparent"` (generation) | **An RGBA PNG** with real alpha (opaque areas carry alpha 251–254) |
84
+ | `background: "transparent"` (edit: "remove the table and backdrop") | **An RGBA PNG**, 55% fully transparent |
85
+ | `background: "opaque"` plus a prompt about keying out a green backdrop | **A transparent PNG anyway**; rewording the green as printed paper returned an opaque image |
86
+ | A prompt starting "Tall vertical 9:16 portrait phone wallpaper: …" | **941×1672**, exactly 9:16 |
87
+ | A prompt ending "Aspect ratio: 16:9, wide landscape (horizontal) canvas." | **1672×941**, exactly 16:9 |
88
+ | The same line with `21:9` / `1:1` | **1916×821** / **1254×1254** |
89
+ | An edit with a 21:9 reference image and a `21:9` line | **1916×821**; style carried over, the new composition followed the prompt |
90
+ | 4 requests in parallel on one account | All returned 200, in 17–25 s each |
91
+
92
+ > [!IMPORTANT]
93
+ > **Only `prompt`, `background` and `images` influence the result**, and even `background` is a strong hint rather than a switch. Model, quality, pixel size, count and format are chosen by the service. The canvas shape follows the **prompt text**.
94
+
95
+ That's why this server:
96
+ - exposes `aspect_ratio` as a prompt line;
97
+ - implements `n` as parallel requests;
98
+ - converts JPEG output locally;
99
+ - verifies the alpha channel on every transparent request;
100
+ - offers no `model`, `size` or `quality` parameter that would be silently ignored.
101
+
102
+ ## Newer image models
103
+
104
+ OpenAI released `gpt-image-2.5-sunburst` and `gpt-image-2.5-flare` on 2026-09-08. On the public Images API they add `xhigh`/`max` quality and native transparency. Through a **ChatGPT sign-in**:
105
+
106
+ - **The images endpoint** (`/backend-api/codex/images/*`) ignores the model name. Requests for a 2.5 model, `xhigh`/`max` quality or a 2K size came back at the service's default size, with `medium` or `low` quality.
107
+ - **The Responses endpoint** (`POST /backend-api/codex/responses` with the hosted `image_generation` tool) **rewrites the tool config** to `{"model":"gpt-image-2-codex","quality":"auto","size":"auto",…}`, as its `response.created` echo shows. Options the rewritten model doesn't support are rejected up front:
108
+
109
+ | Option | Error |
110
+ |---|---|
111
+ | `background: "transparent"` | `400 image_generation_user_error: "Transparent background is not supported for this model."` |
112
+ | `input_fidelity` | `400 invalid_input_fidelity_model: "The model 'gpt-image-2-codex' does not support the 'input_fidelity' parameter."` |
113
+ | An unknown model name | fails with `server_is_overloaded` |
114
+
115
+ This path also returns a `revised_prompt`, but it adds a chat-model turn, so it's slower and costlier.
116
+ - **The Codex app's "ImageGen 2.5" announcement** ("Image creation got a major upgrade") is gated by a Statsig flag. The client still sends `gpt-image-2`, so the upgrade happens on the server: whatever model backs `gpt-image-2` for Codex (reported as `gpt-image-2-codex`) is what every ChatGPT-signed-in client gets.
117
+
118
+ > [!TIP]
119
+ > A ChatGPT sign-in can't select a model. This server, like Codex, automatically gets OpenAI's server-side upgrades to the Codex image model. Picking `gpt-image-2.5-*` explicitly, with exact sizes or `xhigh`/`max` quality, requires the billed OpenAI Platform API, which is out of scope by design.
120
+
121
+ ## Quota and rate limits
122
+
123
+ **`GET https://chatgpt.com/backend-api/wham/usage`** takes the same auth headers and costs **no quota**. It returns:
124
+ - `plan_type`;
125
+ - `rate_limit.{allowed, limit_reached, primary_window, secondary_window}`, where the primary window is 5 hours (`limit_window_seconds: 18000`) and the secondary is weekly (`604800`), each with `used_percent`, `reset_at` and `reset_after_seconds`;
126
+ - `additional_rate_limits` and `credits`.
127
+
128
+ The Codex apps use it for their usage display, and this server uses it for `auth_status`, `status` and `doctor`.
129
+
130
+ Every image response also carries headers:
131
+
132
+ | Header | Meaning |
133
+ |---|---|
134
+ | `x-codex-plan-type` | The plan, e.g. `plus` |
135
+ | `x-codex-active-limit` | The limit being counted (observed: `premium`) |
136
+ | `x-codex-{primary,secondary}-{used-percent,window-minutes,reset-at,reset-after-seconds}` | Window state |
137
+ | `x-codex-credits-*` | Credits balance and flags |
138
+ | `x-image-gen-*` | A separate image limit (limit id `image_gen`), when one applies |
139
+
140
+ - **Exhausted limits:** a `429` returns `{"error":{"type":"usage_limit_reached","resets_at":…,"plan_type":…}}`, and `usage_not_included` means the plan lacks the feature.
141
+ - **Retries:** Codex retries 5xx and transport errors, never 429. So does this server.
142
+ - **Cost of a session:** generating the 15 images for these docs used about 5% of a Plus plan's 5-hour window and 1% of the weekly one.
143
+
144
+ ## Output files
145
+
146
+ - **Format and size:** PNG at about 1.5 MP, shaped by the prompt: 1536×1024, 1024×1536, 1672×941, 941×1672, 1916×821, 1254×1254 or 1370×1148.
147
+ - **Latency:** 12–48 s per request.
148
+ - **Provenance:** every file carries a **C2PA** manifest in a `caBX` chunk. It's signed by "OpenAI Media Service", with claim generator `ChatGPT`/`gpt-image` and `digitalSourceType` `trainedAlgorithmicMedia`. This server writes the service's PNG bytes unchanged, so the manifest survives; converting to JPEG drops it.
149
+
150
+ ## Compared with Codex's built-in tool
151
+
152
+ | | Codex built-in `image_gen` | codex-imagegen-mcp |
153
+ |---|---|---|
154
+ | **Clients** | Codex CLI, IDE and app only | Any MCP client |
155
+ | **Auth** | Codex's ChatGPT sign-in | Its own sign-in (browser or device), or Codex/opencode borrowed read-only |
156
+ | **Endpoint and body** | `/images/{generations,edits}`, as above | Identical |
157
+ | **Save location** | `$CODEX_HOME/generated_images/…`; the model copies files into the project | Directly to `output_path` in the workspace, never overwriting, or the library |
158
+ | **Inputs** | Absolute paths or recent images | Paths (workspace-relative works), URLs, data URLs |
159
+ | **Aspect ratio** | Via the prompt | The `aspect_ratio` parameter, written as a prompt line |
160
+ | **Variants** | One call per variant | `n` from 1 to 4, in parallel |
161
+ | **Transparency** | `background` not exposed ("ask for transparency") | `background: "transparent"` passed through, and the alpha verified |
162
+ | **Chroma key** | A Python script | The built-in `remove_background` tool, same algorithm |
163
+ | **Result for the model** | The full image | A 1024 px JPEG preview, with the full file on disk |
164
+
165
+ Authentication is covered in [Authentication](AUTH.md): OAuth (PKCE or device code) with the Codex public client, roughly 10-day access tokens and rotating single-use refresh tokens. The minimal scopes (`openid profile email offline_access`) are enough for the image endpoints.
166
+
167
+ ---
168
+
169
+ <p align="center"><a href="CLIENTS.md">← Clients</a> &nbsp;·&nbsp; <a href="README.md">Docs home</a> &nbsp;·&nbsp; <a href="ARCHITECTURE.md">Architecture →</a></p>
@@ -0,0 +1,154 @@
1
+ <p align="center">
2
+ <img src="assets/banner-clients.jpg" width="100%" alt="Clients — a travel-poster illustration of a desert railway junction where several tracks converge on one small station with a water tower">
3
+ </p>
4
+
5
+ # Clients
6
+
7
+ The server is a local **stdio** MCP server, so any MCP client can run it: many tracks, one station. opencode installs with one command, and every other client takes a short snippet that `codex-imagegen-mcp config <client>` prints for you.
8
+
9
+ **On this page:** [Support matrix](#support-matrix) · [opencode](#opencode-automated) · [Claude Code](#claude-code) · [OpenAI Codex](#openai-codex-cli-and-ide) · [Claude Desktop](#claude-desktop) · [Cursor](#cursor) · [VS Code](#vs-code-github-copilot-agent-mode) · [Windsurf](#windsurf) · [Gemini CLI](#gemini-cli) · [Any other client](#any-other-client)
10
+
11
+ ## Support matrix
12
+
13
+ | Client | Setup | Tools appear as |
14
+ |---|---|---|
15
+ | **opencode** | `install opencode`: writes `opencode.json[c]` and the skill (automated) | `imagegen_generate_image` |
16
+ | **Claude Code** | `claude mcp add …`, skill in `~/.claude/skills/` | `mcp__imagegen__generate_image` |
17
+ | **OpenAI Codex** | `config codex` → `~/.codex/config.toml` | `mcp__imagegen__generate_image` |
18
+ | **Claude Desktop** | `config claude-desktop` → `claude_desktop_config.json` | listed under `imagegen` |
19
+ | **Cursor** | `config cursor` → `~/.cursor/mcp.json` | listed under `imagegen` |
20
+ | **VS Code** (Copilot agent mode) | `config vscode` → `.vscode/mcp.json` | listed under `imagegen` |
21
+ | **Windsurf** | `config windsurf` → `~/.codeium/windsurf/mcp_config.json` | listed under `imagegen` |
22
+ | **Gemini CLI** | `config gemini` → `~/.gemini/settings.json` | listed under `imagegen` |
23
+
24
+ Every client needs two things:
25
+
26
+ 1. **The launch command.** After the [one-line install](../README.md#quick-start) (or `npm link` from a checkout) it's `codex-imagegen-mcp serve`; straight from a checkout it's `node /path/to/codex-imagegen-mcp/dist/src/cli.js serve`. `codex-imagegen-mcp config <client>` prints the exact snippet for your machine, with absolute paths where a GUI app needs them.
27
+ 2. **Optionally, the Agent Skill** in `skill/imagegen/`. It teaches the agent the prompting and save-path workflow. Clients without skill support can read the same content from the server as `imagegen://skill/SKILL.md`, and the essentials are also in the server's MCP instructions.
28
+
29
+ Then sign in once with `codex-imagegen-mcp login`, unless Codex or opencode is already signed in with ChatGPT. See [Authentication](AUTH.md).
30
+
31
+ ## opencode (automated)
32
+
33
+ ```bash
34
+ codex-imagegen-mcp install opencode # global: ~/.config/opencode/opencode.json[c] + skills/imagegen
35
+ codex-imagegen-mcp install opencode --project # this project only: ./opencode.json + .opencode/skills/imagegen
36
+ codex-imagegen-mcp install opencode --dry-run # show what would change
37
+ codex-imagegen-mcp uninstall opencode # remove the entry and the skill it installed
38
+ ```
39
+
40
+ The installer adds this entry, editing with `jsonc-parser` so comments, formatting and your other servers stay exactly as they were:
41
+
42
+ ```jsonc
43
+ "mcp": {
44
+ "imagegen": {
45
+ "type": "local",
46
+ "command": ["node", "/…/dist/src/cli.js", "serve"],
47
+ "enabled": true,
48
+ "timeout": 300000
49
+ }
50
+ }
51
+ ```
52
+
53
+ > [!NOTE]
54
+ > opencode applies `timeout` to **every** MCP request, tool calls included, and resets it on each progress notification. 300 s is a safe ceiling.
55
+
56
+ What the installer does, and what it refuses to do:
57
+
58
+ - **Backs up first.** The previous config is saved to `opencode.json.codex-imagegen-mcp.bak`.
59
+ - **Won't clobber a stranger.** If another server already uses the name, it refuses unless you pass `--force`; `--name` picks a different name.
60
+ - **Owns only what it installed.** The skill is copied to `~/.config/opencode/skills/imagegen` with a marker file, so `uninstall` removes only a copy it created.
61
+ - **Warns about duplicates.** opencode resolves duplicate skill names unpredictably, so the installer warns if another `imagegen` skill exists anywhere opencode looks: `~/.claude/skills`, `~/.agents/skills`, `~/.config/opencode/skill[s]`, `~/.opencode/skill[s]` and the project equivalents.
62
+
63
+ ```bash
64
+ opencode mcp list # ✓ imagegen connected
65
+ opencode run "Make a 16:9 hero image of a lighthouse at dusk and save it to assets/hero.png"
66
+ ```
67
+
68
+ - **Where files land:** opencode starts the server in the project directory and reports it as an MCP root, so relative `output_path`s land in the project.
69
+ - **Previews reach vision models on every provider.** With OpenAI models over ChatGPT, previews travel inside the tool result. GitHub Copilot and other providers receive them as a follow-up attachment. Both paths are verified with `openai/gpt-5.5` and `github-copilot/claude-sonnet-5`.
70
+ - **Options:** `--env KEY=VALUE` (repeatable) sets environment variables, for example `--env CODEX_IMAGEGEN_OUTPUT_DIR=~/Pictures/ai`. `--command "…"` overrides the launch command.
71
+
72
+ ## Claude Code
73
+
74
+ ```bash
75
+ claude mcp add --scope user imagegen -- node /path/to/codex-imagegen-mcp/dist/src/cli.js serve
76
+ mkdir -p ~/.claude/skills && cp -R /path/to/codex-imagegen-mcp/skill/imagegen ~/.claude/skills/
77
+ ```
78
+
79
+ - Tools appear as `mcp__imagegen__generate_image`, and so on.
80
+ - Generation takes 15–60 s. If you hit tool timeouts, start Claude Code with `MCP_TOOL_TIMEOUT=300000`.
81
+
82
+ > [!WARNING]
83
+ > opencode also reads `~/.claude/skills`. If you install the skill for both, keep only **one** copy, or keep the copies identical.
84
+
85
+ ## Claude Desktop
86
+
87
+ Edit `~/Library/Application Support/Claude/claude_desktop_config.json` on macOS, or `%APPDATA%\Claude\claude_desktop_config.json` on Windows:
88
+
89
+ ```json
90
+ {
91
+ "mcpServers": {
92
+ "imagegen": { "command": "/opt/homebrew/bin/node", "args": ["/path/to/codex-imagegen-mcp/dist/src/cli.js", "serve"] }
93
+ }
94
+ }
95
+ ```
96
+
97
+ - **Use absolute paths.** GUI apps don't inherit your shell `PATH`; `codex-imagegen-mcp config claude-desktop` prints the right ones.
98
+ - **Pass absolute `output_path`s.** Claude Desktop starts servers with `/` as the working directory. Without an `output_path`, images go to the image library.
99
+
100
+ ## Cursor
101
+
102
+ Add to `~/.cursor/mcp.json` (global) or `.cursor/mcp.json` (project):
103
+
104
+ ```json
105
+ { "mcpServers": { "imagegen": { "command": "node", "args": ["/path/to/codex-imagegen-mcp/dist/src/cli.js", "serve"] } } }
106
+ ```
107
+
108
+ ## VS Code (GitHub Copilot agent mode)
109
+
110
+ Add to `.vscode/mcp.json`, or run **MCP: Open User Configuration**:
111
+
112
+ ```json
113
+ { "servers": { "imagegen": { "type": "stdio", "command": "node", "args": ["/path/to/codex-imagegen-mcp/dist/src/cli.js", "serve"] } } }
114
+ ```
115
+
116
+ ## Windsurf
117
+
118
+ Add to `~/.codeium/windsurf/mcp_config.json`:
119
+
120
+ ```json
121
+ { "mcpServers": { "imagegen": { "command": "/opt/homebrew/bin/node", "args": ["/path/to/codex-imagegen-mcp/dist/src/cli.js", "serve"] } } }
122
+ ```
123
+
124
+ ## Gemini CLI
125
+
126
+ Add to `~/.gemini/settings.json`:
127
+
128
+ ```json
129
+ { "mcpServers": { "imagegen": { "command": "node", "args": ["/path/to/codex-imagegen-mcp/dist/src/cli.js", "serve"], "timeout": 300000 } } }
130
+ ```
131
+
132
+ ## OpenAI Codex (CLI and IDE)
133
+
134
+ When Codex is signed in with ChatGPT, it already has this capability built in (`image_gen`), so this entry is mainly useful for API-key Codex setups. Add to `~/.codex/config.toml`:
135
+
136
+ ```toml
137
+ [mcp_servers.imagegen]
138
+ command = "node"
139
+ args = ["/path/to/codex-imagegen-mcp/dist/src/cli.js", "serve"]
140
+ tool_timeout_sec = 300
141
+ ```
142
+
143
+ ## Any other client
144
+
145
+ Any MCP client that can launch a stdio server works: point it at `codex-imagegen-mcp serve`. For the best experience the client should:
146
+
147
+ - allow tool calls of at least about 90 s, or reset its timeout on progress notifications;
148
+ - support image content in tool results, so the model sees the preview. Without it, the model still gets the saved paths and metadata.
149
+
150
+ Skills: Claude Code reads `~/.claude/skills/`; clients that follow the Agent Skills convention read `~/.agents/skills/`; opencode reads the directories listed above.
151
+
152
+ ---
153
+
154
+ <p align="center"><a href="AUTH.md">← Authentication</a> &nbsp;·&nbsp; <a href="README.md">Docs home</a> &nbsp;·&nbsp; <a href="BACKEND.md">Backend →</a></p>