muse-image-mcp

English ยท ็น้ซไธญๆ ยท ็ฎไฝไธญๆ
An MCP server that gives any MCP-capable agent โ Claude Code, Claude Desktop, Cursor โ image generation powered by the Meta Muse image model.
What it does
Three tools, covering the full loop of working with images in a conversation:
| Tool | What it's for |
|---|
generate_image | Text to image. 1โ10 images per call. |
edit_image | Edit from reference images โ local files or URLs. |
iterate_image | Conversational refinement. Keep saying "make it warmer" and it remembers. |
Why this server
Your context window survives. Every tool writes images to disk and returns an absolute file path โ never the image bytes. Generating a dozen images costs you a dozen lines of context instead of a dozen megabytes of base64. When you actually want to look at an image, open the path with a file-reading tool.
Multi-turn refinement without state. iterate_image returns a response_id; feed it back as previous_response_id and the next turn continues the same conversation. The server itself stores nothing โ conversation state lives on Meta's side, so the server stays restartable and stateless.
New models don't require a new release. Switch models with MUSE_MODEL or a per-call model parameter, and pass parameters this server has never heard of through extra_params. Fields that determine request structure are protected from being overwritten; everything else is a deliberate escape hatch.
You always know what it cost. Every response ends with the estimated cost of that call.
Requirements
- Node.js >= 20.12.0
- A Meta Muse API key
Getting an API key
Muse Image runs on the Meta Model API, so you register with Meta โ not with this project:
- Go to https://dev.meta.ai and sign in to the Meta Model API dashboard.
- Open API keys.
- Click Create API key and copy the value.
That value is what you pass as MUSE_API_KEY below. Meta's own documentation calls this variable MODEL_API_KEY; this server reads it as MUSE_API_KEY, talks to https://api.meta.ai/v1, and defaults to the model muse-image-1.0.
Reference: Model API docs ยท Image generation ยท Muse Image announcement
Keep the key out of source control โ use the MCP config's env block or a .env file, both described below.
Install
Option 1: npx (recommended โ no clone required)
claude mcp add muse-image --scope user --env MUSE_API_KEY=your-key -- npx -y muse-image-mcp
npx fetches and runs the latest version on demand โ nothing to install first.
--scope user applies it to every project; use --scope local for the current project only.
-y skips npx's install prompt. Without it the server hangs on an interactive question and the handshake fails.
- Everything after
-- is the launch command; --env before it belongs to claude mcp add.
For other MCP clients (Claude Desktop, Cursor), write the config by hand:
{
"mcpServers": {
"muse-image": {
"command": "npx",
"args": ["-y", "muse-image-mcp"],
"env": { "MUSE_API_KEY": "your-key" }
}
}
}
On Windows, if npx can't be found, use "command": "cmd" with "args": ["/c", "npx", "-y", "muse-image-mcp"].
Option 2: Local clone (when you want to change the code)
git clone https://github.com/kevintsai1202/muse-image-mcp.git
cd muse-image-mcp
npm install
npm run build
claude mcp add muse-image --scope user --env MUSE_API_KEY=your-key -- node <your-clone-path>/dist/index.js
After installing
Start a new session โ MCP servers are loaded at session start, so an existing session won't pick it up. Then confirm with claude mcp list, which should show muse-image: ... - Connected, and check that the three mcp__muse-image__* tools are available.
Configuring the API key
Precedence is the env block in your MCP config > a .env file.
Using the MCP config env block (the only route when installed via npx)
See --env MUSE_API_KEY=your-key above.
Using a .env file
The server searches these locations in order and uses the first one that exists:
<package root>/.env โ convenient for a local clone
~/.muse-image-mcp/.env โ the only location you control when installed via npx
Note that .env is not read from the directory you launched Claude Code in โ an MCP server's working directory is decided by the client, which makes it a poor place for configuration. When installed via npx the package itself lives in a hashed npm cache directory that gets cleaned up, so a .env there would be pointless.
Environment variables
All of these work in either .env or your MCP config's env block.
| Variable | Required | Default | Description |
|---|
MUSE_API_KEY | Yes | โ | API key. Without it the server exits immediately and explains itself on stderr |
MUSE_MODEL | No | muse-image-1.0 | Global default model ID |
MUSE_EXTRA_PARAMS | No | {} | JSON object string โ global default extra parameters |
MUSE_OUTPUT_DIR | No | <cwd>/generated-images | Output directory, created if missing |
MUSE_BASE_URL | No | https://api.meta.ai/v1 | API base URL |
MUSE_TIMEOUT_MS | No | 120000 | Per-request timeout in milliseconds |
The cwd in MUSE_OUTPUT_DIR's default is the working directory the MCP client launched the server from. In Claude Code that's the project root of your session, so images land in that project's generated-images/. If your client behaves differently, or you want a fixed location, set MUSE_OUTPUT_DIR to an absolute path.
Since v0.1.0 the default output directory changed from muse-output/ to generated-images/. The old directory is not deleted or migrated automatically.
Switching models and passing new parameters
When a new model ships you don't have to wait for this project to update โ switch models with an environment variable, send new parameters through extra_params.
Switching models
Globally, in .env or your MCP config:
MUSE_MODEL=muse-image-2.0
Per call, just ask for it in conversation and the agent will pass model:
{ "prompt": "a red fox", "model": "muse-image-2.0" }
Passing new parameters
Global defaults as a JSON object string:
MUSE_EXTRA_PARAMS={"quality":"ultra"}
Per-call overrides via extra_params, merged with the global setting โ the per-call value wins:
{ "prompt": "a red fox", "extra_params": { "style_preset": "anime" } }
Protected core fields
model, prompt, response_format, images, input, store, and previous_response_id determine the structure of the request and cannot be overwritten by extra_params. Setting them there has no effect, and the response will end with a warning listing the ignored keys.
To change models, use the model parameter or MUSE_MODEL โ not extra_params.
Outside those core fields, extra_params does override same-named regular parameters, including n, size, output_format, and reasoning_strength. The tool schema's validation for these (for example n being limited to 1โ10) does not apply on this path โ that's a deliberate escape hatch so a future model that changes parameter semantics isn't blocked by today's limits. When overriding n this way, watch your image count and cost.
Every tool saves images locally and returns absolute paths, never the image content itself โ this keeps base64 out of your conversation context. Open the path with a file-reading tool when you want to see the image.
generate_image โ text to image
| Parameter | Required | Default | Description |
|---|
prompt | Yes | โ | Image description |
n | No | 1 | Number of images, 1โ10 |
size | No | โ | Aspect ratio string such as 1792x1024 โ not an exact pixel resolution |
output_format | No | png | png / webp / jpeg |
reasoning_strength | No | high | high / low โ priced the same |
filename_prefix | No | muse | Output filename prefix |
model | No | โ | Model ID; omit to use the server default (see MUSE_MODEL) |
extra_params | No | โ | Object of extra parameters, merged with MUSE_EXTRA_PARAMS with per-call priority; core fields are protected (see above) |
edit_image โ edit from reference images
| Parameter | Required | Default | Description |
|---|
prompt | Yes | โ | Image description |
images | Yes | โ | Array of local file paths (png/jpg/jpeg/webp/gif) or http(s) URLs. Local files are base64-encoded automatically |
n | No | 1 | Number of images, 1โ10 |
size | No | โ | Aspect ratio string such as 1792x1024 โ not an exact pixel resolution |
output_format | No | png | png / webp / jpeg |
reasoning_strength | No | high | high / low โ priced the same |
filename_prefix | No | muse-edit | Output filename prefix |
model | No | โ | Model ID; omit to use the server default |
extra_params | No | โ | Same merge and protection rules as above |
iterate_image โ conversational refinement
| Parameter | Required | Description |
|---|
prompt | Yes | This turn's instruction |
previous_response_id | No | The id returned by the previous turn; omit to start a new conversation |
images | No | Reference images for the first turn |
reasoning_strength | No | Defaults to high |
size | No | Aspect ratio string such as 1024x1536 |
output_format | No | png / webp / jpeg. Unlike the other two tools, omitting it yields webp โ that's Meta's default for this endpoint |
filename_prefix | No | Defaults to muse-iter |
model | No | Model ID; omit to use the server default |
extra_params | No | Same merge and protection rules as above |
This tool has no n parameter โ the /v1/responses endpoint returns one image per turn. It does support size and output_format, but they travel inside the request's tools entry rather than at the top level; see the findings below.
The response always includes a response_id. Pass it as previous_response_id on the next call to continue the same conversation. This server stores no conversation state; Meta does.
Findings from /v1/responses
Request body: per-image settings live inside tools
reasoning_strength, size and output_format are not top-level parameters on this endpoint โ they belong to the image_generation tool entry. Sending reasoning_strength at the top level is not merely ignored; the API rejects the whole request:
HTTP 400 โ unknown parameter `reasoning_strength`
The correct shape:
{
"model": "muse-image-1.0",
"input": "now make the background deep navy",
"store": true,
"previous_response_id": "resp_abc123",
"tools": [
{ "type": "image_generation", "reasoning_strength": "low", "size": "1024x1536", "output_format": "png" }
]
}
Versions up to 0.1.0 sent reasoning_strength at the top level, which made iterate_image fail on every call. generate_image and edit_image were never affected โ they use /images/generations and /images/edits, where these are legitimate top-level parameters.
This also corrects an earlier claim in this README: size and output_format are supported here. Specifying output_format: "png" returns a genuine PNG (verified by the base64 header) rather than the endpoint's webp default. Only n is genuinely unavailable โ one image per turn.
scripts/probe-responses-api.mjs reproduces all of this against the live API, one hypothesis per case.
Response shape
Meta doesn't publish the response schema for /v1/responses. The original implementation was an educated guess modeled on OpenAI's Responses convention; it was later verified against real API calls. The actual structure:
{
"model": "muse-image-1.0",
"id": "resp_6aa4c23f99592ae0ac454928",
"object": "response",
"status": "completed",
"output": [
{ "type": "reasoning", "summary": [{ "type": "summary_text", "text": "..." }] },
{ "type": "message", "role": "assistant", "content": [{ "type": "output_text", "text": "" }] },
{ "type": "image_generation_call", "id": "ig_...", "status": "completed", "result": "<base64 image data>" }
]
}
Differences from the guessed shape:
id location: raw.id was correct, confirmed by testing. No change needed.
- Image data location: not in any
b64_json field, but in the result field of the output[] entry where type === "image_generation_call", as raw base64 with no data URL prefix. src/muse-client.ts's extractB64Images now recognizes both b64_json (kept for other possible shapes) and this verified shape.
- No
output_format field: the response has none. The original fallback default of "png" was wrong โ since the iterate request doesn't send an output_format parameter, Meta applies the same default as /images/generations, which is webp. The returned base64 decodes to a genuine WebP file (RIFF/WEBP header). The fallback is now "webp".
Multi-turn conversation is verified
Two real API calls were made in sequence: one iterate_image to obtain a response_id, then a second call using that id as previous_response_id. The second turn returned only the one image generated in that turn โ it did not re-send the first turn's image. extractB64Images's deep-traversal logic needed no changes for this.
(The test tooling in that run didn't capture the second turn's raw response byte-for-byte, so this conclusion rests on filesystem evidence โ exactly one output file, with no -2/-3 suffixes โ rather than a byte-level comparison. The pinning test added in tests/muse-client.test.ts uses the real response shape verified earlier.)
Pricing
US$0.01 per generated image, regardless of reasoning_strength. Every tool response discloses the estimated cost of that call.
This server is free and MIT-licensed โ you pay Meta for API usage, nothing else.
Development
npm test
npm run build
npm run smoke
License
MIT ยฉ Kevin Tsai