Skip to main content
Glama

Muse-Chat-MCP

Use Meta Muse — the muse.ai "Hatch" agent — from any MCP client or any OpenAI-compatible client, with your own account and quota.

Muse has no public API: its chat is an encrypted WebSocket (wss://hatch.metaaivm.com/v1/noise, an X25519 + HKDF + AES‑GCM + Ed25519 Noise transport). So instead of re‑implementing the protocol, this project drives your real, logged‑in Chrome with Playwright: the app performs all the crypto itself, and we type into the composer and read the rendered reply.

That gives you two front doors:

Front door

What it is

Use it from

MCP server (stdio)

7 tools: muse_status, muse_login, muse_new_chat, muse_chat, muse_read_last, muse_dump_dom, muse_close

Claude Desktop, opencode, Cursor, any MCP host

OpenAI-compatible shim (HTTP)

GET /v1/models, POST /v1/chat/completions (stream + non-stream, tool calling)

any OpenAI SDK / @ai-sdk/openai-compatible provider

Plus a tiny CLI (muse-cli.mjs) for one-shot generation from scripts.

IMPORTANT

This drivesyour browser and your Muse account. It ships with no credentials, no Chrome profile, and no captured traffic — you sign in to your own Meta account on first run. See Disclaimer.


Features

  • MCP tools over stdio — drop-in for Claude Desktop / opencode / any MCP host.

  • OpenAI-compatible HTTP shim with real streaming (SSE), correct finish_reason, and /v1 error objects.

  • Prompt-injected tool calling — expose OpenAI tools to Muse and get tool_calls back.

  • Attachments — send images/video with a prompt (MCP files, OpenAI image_url parts, CLI -f).

  • Sessions & media — list/open/read/send in any Muse chat, and pull out the images/videos Muse generates (links + download).

  • Muse Manual — a living guide to Muse's video/image/content capabilities at manual/MUSE_MANUAL.md, regenerated with node manual/interview.mjs.

  • Reuses your existing login via a dedicated Chrome profile, or attaches to a Chrome you already run with --remote-debugging-port=9222.

  • Never kills your browser: when attached over CDP it only disconnects on close.

  • Resilient: if the profile is locked by a running Chrome, it auto-attaches over CDP instead of failing.

  • Correct streaming: only stable, monotonic text is emitted, so a rewrite mid-answer never duplicates.

  • No browser download — it uses your installed Chrome via playwright-core.


Related MCP server: ChatGPT CDP MCP

Requirements

  • Node.js ≥ 18 (tested on v24).

  • Google Chrome (or Edge) installed.

  • A Meta account with access to Muse.

  • Windows / macOS / Linux (paths in the examples are Windows; adjust for your OS).


Install

git clone https://github.com/duclm1x1/Muse-Chat-MCP.git
cd Muse-Chat-MCP
npm install

npm install only pulls playwright-core (a library) — it does not download a browser.

First run (log in once)

npm run selftest          # launches Chrome, prints login/browser state, then closes

A Chrome window opens to https://muse.ai. Sign in with your Meta account. The session is stored in a dedicated profile (.muse-profile/, git-ignored) and reused afterwards. When muse_status reports "loggedIn": true, you're ready.


Use it as an MCP server

opencode

Add to ~/.config/opencode/opencode.jsonc:

{
  "mcp": {
    "muse": {
      "type": "local",
      "command": ["node", "/absolute/path/to/Muse-Chat-MCP/muse-server.mjs"],
      "enabled": true,
      "timeout": 300000
    }
  }
}

The timeout is the tool-listing timeout at startup, not per-call — long muse_chat calls are fine.

Claude Desktop

Add to claude_desktop_config.json:

{
  "mcpServers": {
    "muse": {
      "command": "node",
      "args": ["/absolute/path/to/Muse-Chat-MCP/muse-server.mjs"],
      "env": { "MUSE_PROFILE_DIR": "/absolute/path/to/Muse-Chat-MCP/.muse-profile" }
    }
  }
}

MCP tools

Tool

Arguments

Returns

muse_status

–

browser / login / composer state

muse_login

timeout_sec?

waits for Meta sign-in to complete

muse_new_chat

–

navigates to the home composer

muse_chat

prompt, timeout_sec?, new_thread?, files?, chat?

{ reply, messages, threadUrl, elapsedMs, … }

muse_read_last

–

latest assistant message (no send)

muse_chats

query?

list chats (Main chat / Channels / Side chats), optional title filter

muse_open_chat

target

open a chat (title, index, URL, or id)

muse_read_chat

chat?, max?

messages of a chat (all roles, with media links)

muse_media

chat?, download?, dir?

image/video/attachment links from a chat (optionally downloaded)

muse_dump_dom

max_chars?

element counts + transcript HTML (selector debugging)

muse_close

–

closes the browser (disconnect-only if CDP-attached)

Typical flow: muse_status → (if needed muse_login) → muse_chat { prompt }.

Attachments (images & video)

muse_chat accepts a files array — absolute paths or URLs — and attaches them to the message before sending:

{ "name": "muse_chat", "arguments": { "prompt": "What is in this image?", "files": ["C:\\path\\frame.jpg", "https://host/clip.mp4"] } }

Muse accepts images, video and documents (the composer's file input has no accept filter). Files are set directly on the hidden composer input — no OS file dialog.

Sessions (multiple chats)

Muse has a Main chat plus Channels and Side chats (each a muse.ai/thread/<id>). List them, then target any one for reading or sending:

  • MCP — muse_chats, muse_open_chat { target }, muse_read_chat { chat?, max? }, and muse_chat { …, chat } where the target is a title, index, thread URL, or thread id.

  • HTTP — GET /v1/muse/chats (list) and GET /v1/muse/chat?target=<name|index|url>&max=100 (read); send with header x-muse-chat: <name|index|url> (or body chat).

  • CLI — --list-chats, --read [<chat>], --chat <chat>.

Muse can generate images/video inside a chat (it replies with share links) — use a session read or the media tools to retrieve them.

Media (generated images & video)

When asked, Muse makes media and replies with a share link (https://muse.ai/files/<…>/….mp4|.png). Extract and download them:

  • MCP — muse_media { chat?, download?, dir? }.

  • HTTP — GET /v1/muse/media?target=<chat>&download=1&dir=<dir>.

  • CLI — --media [<chat>] [--download] [--dir <dir>].

Links are public (anyone with the link can view) but expire (~2 days) — download to keep them.


Use the OpenAI-compatible shim

muse-server.mjs starts the shim automatically on port 8787 (disable with MUSE_SHIM_PORT=0). Run it stand-alone (HTTP only, no MCP) with node muse-server.mjs --serve-only.

Route

Purpose

GET /v1/models

model list

POST /v1/chat/completions

chat completions (stream + non-stream, tools)

GET /health

browser / login state

GET /v1/muse/chats

list Muse chats

GET /v1/muse/chat?target=…

read a chat (optionally opening it first)

GET /v1/muse/media?target=…

media links in a chat (add download=1 to save)

curl http://127.0.0.1:8787/v1/models

curl http://127.0.0.1:8787/v1/chat/completions \
  -H "content-type: application/json" \
  -d '{"model":"muse-spark-1.3","messages":[{"role":"user","content":"Summarize what Muse is in 2 sentences."}]}'

Any OpenAI client

from openai import OpenAI
client = OpenAI(base_url="http://127.0.0.1:8787/v1", api_key="muse-local")
r = client.chat.completions.create(
    model="muse-spark-1.3",
    messages=[{"role": "user", "content": "Write a haiku about browsers."}],
    stream=True,
)
for chunk in r:
    print(chunk.choices[0].delta.content or "", end="")

opencode provider

"provider": {
  "muse-local": {
    "npm": "@ai-sdk/openai-compatible",
    "name": "Muse (local shim)",
    "options": { "baseURL": "http://127.0.0.1:8787/v1", "apiKey": "muse-local" },
    "models": {
      "muse-spark-1.3": { "name": "Muse Spark 1.3" },
      "muse": { "name": "Muse" }
    }
  }
}

Shim behavior

  • Real streaming — DOM text is re-emitted as OpenAI deltas. Only text that is a monotonic extension and has been stable for MUSE_STREAM_QUIET_MS (default 600 ms) is emitted; the remainder is flushed at the end. Each stream ends with exactly one finish_reason, then [DONE].

  • Tools — prompt-injected. Tool schemas are embedded with a decision-only rule ("do not execute"), so Muse returns {"tool_calls":[{"name","arguments"}]} instead of trying to actually run the action. Parsed into OpenAI tool_calls (finish_reason: "tool_calls"). Best-effort, not a native function-calling API.

  • Attachments — send images/video via OpenAI multimodal content ({"type":"image_url","image_url":{"url":…}}) or a top-level files array (local paths / URLs / data-URIs). Muse sees them like a normal chat attachment.

  • Plain message — the shim sends the latest user message verbatim: no ### USER/### ASSISTANT role markers and no "continue the conversation" wrapper. Muse flags roleplay-style wrappers as prompt-injection and refuses them, so the bridge never adds any. Prior context comes from Muse's own thread.

  • Sessions — target a chat with header x-muse-chat: <title|index|url> (or body chat); read chats via GET /v1/muse/chats and GET /v1/muse/chat.

  • Headers — x-muse-thread: new (navigate to / first), x-muse-timeout-ms.


Use the CLI

muse-cli.mjs talks to the shim (reusing a running one, or auto-starting --serve-only and shutting it down after).

node muse-cli.mjs "Explain what a B-tree is in 2 sentences."          # streams to stdout
node muse-cli.mjs --no-stream -s "Output ONLY raw code." "Write ..."   # exact final code
node muse-cli.mjs -f ./frame.jpg "Write a Facebook caption for this image."  # attach image/video
node muse-cli.mjs --list-chats                                        # list Muse chats
node muse-cli.mjs --read "Video creation capability"                  # read a chat
node muse-cli.mjs --chat "Reply with pong" "hi"                       # send into a chat
echo "<file>" | node muse-cli.mjs -s "Review this file"                # stdin prompt
npm run muse -- "hello"                                                # via package.json

Options: -s/--system, -m/--model, -t/--timeout, -f/--file <path|url> (repeatable), --chat <name|index|url>, --list-chats, --query <text>, --read [<chat>], --media [<chat>], --download, --dir <dir>, --new-thread, --no-stream, --json, --base (or env MUSE_SHIM_URL).


Architecture

MCP client (Claude Desktop / opencode / …)      OpenAI client (SDK / opencode provider)
        │ stdio (JSON-RPC)                                │ HTTP  /v1/*
        ▼                                                  ▼
   muse-server.mjs  ───────────►  muse-openai-shim.mjs  ────┘
        │  (MCP tools)                   (SSE + JSON)
        └──────────────┬──────────────────────────┘
                       ▼
                 muse-driver.mjs        playwright-core
                       ▼
        Chrome (dedicated profile ./.muse-profile)  ──►  https://muse.ai

Why a browser driver?

Captured from a real session (endpoints + WS frames), Muse chat is not REST/SSE:

Signal

Value

App

Next.js on Vercel, fronted by Meta fwdproxy

Auth

cookie-based: POST /api/auth/check → { ok, access_token, viewer_id }

Session

GET /api/session → assigned VM wss://<vm_id>.metaaivm.com/

Wake

POST /api/hatch/vm/wake

Chat transport

WebSocket wss://hatch.metaaivm.com/v1/noise — RPC methods chat.stream, chat.history, chat.mark_seen

Frames

encrypted binary (Noise handshake), signed auth_token/notary_token in the WS URL

/api/falco

telemetry only — not chat

So the only robust options are (1) drive the real browser (this project) or (2) re-implement the encrypted Noise client (roadmap).

Selectors

Purpose

Selector

Composer root

[data-hatch-composer-root]

Editor

[data-hatch-composer-root] textarea (fallback [data-lexical-editor="true"])

Send

Enter key

Attach

[data-hatch-composer-root] input[type="file"] (hidden; setInputFiles)

Chat list

[data-testid="hatch-thread-row"]

Streaming

[data-testid="hatch-composer-stop-button"]

Messages

[data-message-item] with data-message-role="user" | "assistant"

Error

[data-testid="assistant-response-error-notice"]

Auth probe

page-origin fetch('/api/auth/check', { method: 'POST' })


Reproduce this yourself (HAR → coding agent)

You don't have to reverse-engineer anything by hand. Capture what the app actually does, then let a coding agent read it and write the bridge for you.

Requirements: Google Chrome + some kind of coding agent — Claude Code, OpenAI Codex, opencode, Cursor, Cline, Aider, … anything that can read files.

  1. Open the app. In Chrome, go to https://muse.ai and sign in with your own account.

  2. Open DevTools. Press F12 → Network tab → tick Preserve log. Leave it open for the whole session so the WebSocket frames get recorded.

  3. Filter the traffic. Click Fetch/XHR to see the HTTP calls, and WS to see the chat WebSocket. Muse's chat is a WebSocket, not a REST call, so you want both.

  4. Send a few prompts (e.g. hi, what can you do?) so real traffic is recorded.

  5. Export a HAR. Right-click anywhere in the request list → Save all as HAR with content. Pick the version "with sensitive data" — the sanitized export strips cookies and WebSocket frames, which makes the capture useless.

  6. Hand it to your coding agent. Drop the file into your project (e.g. captures/muse.har) and give the agent a prompt like this:

    Analyze captures/muse.har from a web chat app and report:
    1) The chat transport: REST/SSE vs WebSocket. List every relevant endpoint
       (auth, session, wake, chat) and how authentication works (cookies? tokens?).
    2) The WebSocket: URL, subprotocol, and whether frames are encrypted/binary —
       e.g. a Noise handshake (X25519 + HKDF + AES-GCM + Ed25519) — plus the RPC method names.
    3) The most robust way to build a local bridge that exposes this chat as
       (a) an MCP tool and (b) an OpenAI-compatible /v1 endpoint, given there is no official API.

    The agent will read the HAR and tell you exactly what to build. In our capture it surfaced POST /api/auth/check, GET /api/session, POST /api/hatch/vm/wake, and the encrypted wss://hatch.metaaivm.com/v1/noise WebSocket with a chat.stream method — which is precisely why this project drives the real browser instead of calling a REST API. If your agent reaches the same conclusion, you're spot on.

WARNING

A HAR "with sensitive data" contains yoursession cookies and access tokens. Never commit it, never paste it into a chat, never share it. This repo's .gitignore already blocks *.har.


Configuration (environment)

Variable

Default

Meaning

MUSE_PROFILE_DIR

./.muse-profile

dedicated Chrome profile (holds your login)

MUSE_URL

https://muse.ai/

app URL

MUSE_CHANNEL

chrome

chrome or msedge

MUSE_HEADLESS=1

off

run headless (log in headed first)

MUSE_CDP

–

attach to an existing Chrome at this URL (e.g. http://127.0.0.1:9222) instead of launching

MUSE_STREAM_QUIET_MS

600

streaming stability window (skip mid-message draft rewrites)

MUSE_LAUNCH_TIMEOUT_MS

60000

launch / navigation timeout

MUSE_SHIM_PORT

8787

shim port (0 disables the shim)

MUSE_SHIM_HOST

127.0.0.1

shim bind host

MUSE_SHIM_TIMEOUT_MS

240000

default shim request timeout

MUSE_SHIM_MODELS

muse-spark-1.3,muse-spark-1.3-contributor,muse

advertised model ids

MUSE_SHIM_URL

http://127.0.0.1:8787/v1

shim base url used by the CLI

MUSE_CLI_AUTOSTART=0

off

disable the CLI auto-starting a shim


Troubleshooting

  • loggedIn: false → run muse_login (or npm run selftest) and sign in in the Chrome window.

  • composerReady: false after login → node muse-server.mjs --dump-dom; if the DOM changed, re-derive selectors and update SELECTORS in muse-driver.mjs.

  • Empty reply / timedOut → raise timeout_sec; agent tasks (browsing, VM work) can take minutes. needsApproval: true means Muse is waiting on an in-app approval you must click.

  • Chrome profile locked → expected if a Chrome already uses .muse-profile. The driver auto-attaches to http://127.0.0.1:9222 in that case; otherwise close that window, or start Chrome with --remote-debugging-port=9222 and set MUSE_CDP.

  • npm blocked in PowerShell → call & "C:\Program Files\nodejs\npm.cmd" instead of npm.

Limitations

  • One conversation. Muse is a single persistent thread; the in-app "new chat" control is unreliable. The shim sends the full transcript each call; x-muse-thread: new navigates to / first (best-effort fresh context).

  • Serialized — one browser, requests run one at a time (auto-queued).

  • Tool calling is prompt-injected — best-effort, not a native function-calling API.

  • DOM-driven — a Muse UI change can break selectors; muse_dump_dom is the escape hatch.


Roadmap

  • Phase 2 — headless Noise client. Talk to Muse directly over wss://hatch.metaaivm.com/v1/noise (X25519 + HKDF + AES‑GCM + Ed25519) with a MUSE_TRANSPORT=noise|browser switch, keeping the browser driver as fallback.

  • Native tool-calling passthrough for clients that support it.

  • Multi-thread support when Muse exposes a reliable switch.

Disclaimer

This is an unofficial, unaffiliated tool. It automates your own logged-in browser session and does not bundle, proxy, or share anyone's credentials. Use it only with an account you are entitled to use, and respect Muse's / Meta's Terms of Service and your local laws. The maintainers are not responsible for misuse or for any consequences of using this software. There is no API key, Chrome profile, or captured traffic in this repository — you bring your own.

License

MIT © 2026 duclm1x1

Available Tools

11 tools
muse_chatChat with MuseA

Send a prompt to Muse and return the assistant reply once streaming finishes. Uses your own logged-in session and token quota.

Inputs: chat, files, prompt, new_thread, +1 more.

muse_chatsList Muse chatsA

List the chats in the Muse sidebar (Main chat, Channels, Side chats) with an active flag. Optionally filter by title.

Inputs: query.

muse_closeClose Muse browserA

Close the dedicated Chrome window and release the profile.

Inputs: no named parameters.

muse_dump_domDump Muse DOMB

Diagnostics: element counts and transcript HTML. Use if selectors stop matching after a Muse update.

Inputs: max_chars.

muse_loginMuse loginA

Open muse.ai in the dedicated Chrome window and wait for Meta sign-in to complete. Use when muse_status reports loggedIn=false.

Inputs: timeout_sec.

muse_mediaMuse chat mediaA

Extract image/video/attachment links from a chat (Muse replies with share links for generated media). Optionally download them.

Inputs: dir, chat, download.

muse_new_chatMuse new chatA

Start a fresh Muse thread (navigates to the home composer).

Inputs: no named parameters.

muse_open_chatOpen Muse chatA

Open a specific Muse chat so later reads/sends target it (instead of the main chat).

Inputs: target (required).

muse_read_chatRead Muse chatA

Return the messages of a chat (all roles) without sending anything. Optionally open a chat first.

Inputs: max, chat.

muse_read_lastRead last Muse replyA

Return the most recent assistant message from the current thread without sending anything.

Inputs: no named parameters.

muse_statusMuse statusA

Report whether the Muse browser is running, whether you are logged in to muse.ai, and whether the chat composer is ready.

Inputs: no named parameters.

Tool Schema Changelog

Recent tool additions, removals, and schema changes observed during successful MCP inspections.

Loading tool schema changelog…

    TDQS

    A3.7/5.0

    Scored across 11 tools

    Disambiguation4/5

    Most tools have distinct purposes, but muse_chat (send prompt) and muse_chats (list chats) are easy to confuse due to near-identical names. muse_read_chat, muse_read_last, and muse_media also have some overlap in reading chat content, though descriptions clarify the boundaries.

    Naming Consistency4/5

    All tools use the muse_ prefix with snake_case, which is highly consistent. Minor deviations exist in verb/noun patterns (e.g., muse_status, muse_media, muse_chats are noun-like, while others are verb-led), but the overall convention remains readable and predictable.

    Tool Count5/5

    Eleven tools is well-scoped for a browser-automation MCP that manages login, chat navigation, reading, sending, media extraction, and diagnostics. Each tool appears to earn its place without excessive fragmentation.

    Completeness4/5

    The surface covers the core chat lifecycle: status/login, new/open/close, list/read/send, media extraction, and diagnostics. Minor gaps exist around chat management (e.g., delete, rename, or search), but these are not

    Maintenance

    ActivitySlowing
    ResponsivenessLimited data

    Related MCP Connectors

    • Hacker News MCP server for ChatGPT, Claude and any AI agent: list upvoted items, list hidden items, list favorites, list my comment threads, get an item with its comments, favorite an item and hide an item in your own Hacker News account. Cotlet works in the account through your Cloud Browser, a real Chrome where you sign in to Hacker News yourself; Cotlet does not store your password and you need no Hacker News API key. About the official Hacker News API: Hacker News has an official API, but it is read-only and public (checked 28 Oct 2026). It returns items, users and the story lists. Setup and FAQ: https://cotlet.dev/integrations/hacker-news-api-mcp-server

    • Mews MCP server for ChatGPT, Claude and any AI agent: list arrivals, list departures, list guests in house, search reservations, get customer profile, add reservation note, create task and cancel reservation in your own Mews account. Cotlet works in the account through your Cloud Browser, a real Chrome where you sign in to Mews yourself; Cotlet does not store your password and you need no Mews API key. About the official Mews API: Mews has a documented Connector API, but it has no official MCP server (checked 5 Nov 2026). Production credentials are issued only after a certification process, so a hotel cannot simply open the API for its own AI agent. Setup and FAQ: https://cotlet.dev/integrations/mews-api-mcp-server

      https://cotlet.dev/mcp/v1/mews
    • Substack MCP server for ChatGPT, Claude and any AI agent: create post, schedule post, publish Note, reply to Notes, send direct message, moderate comments, list subscribers and get post stats in your own Substack account. Cotlet works in the account through your Cloud Browser, a real Chrome where you sign in to Substack yourself; Cotlet does not store your password and you need no Substack API key. About the official Substack API: Substack has only a limited API (checked 11 Oct 2026). Its Developer API looks up public creator profiles after approval, and there is no public API to write posts or Notes, manage subscribers or send email. Setup and FAQ: https://cotlet.dev/integrations/substack-api-mcp-server

    • Chatwoot MCP server for ChatGPT, Claude and any AI agent: list conversations, reply to conversation, add private note, assign conversation, add labels and resolve or snooze conversation in your own Chatwoot account. Cotlet works in the account through your Cloud Browser, a real Chrome where you sign in to Chatwoot yourself; Cotlet does not store your password and you need no Chatwoot API key. About the official Chatwoot API: Chatwoot has a full REST API, the Application API, and an official command-line client, but no official MCP server (checked 22 Oct 2026). The API covers conversations, messages, contacts, labels, teams and assignments with an access token from Profile Settings. Setup and FAQ: https://cotlet.dev/integrations/chatwoot-api-mcp-server

    Related MCP Servers

    • A
      license
      Not graded
      quality
      B
      maintenance
      MCP server that turns one authenticated ChatGPT browser page into a bounded MCP server, exposing tools for creating, listing, searching, exporting, sending, editing chats and downloading media via a CDP driver.
      MIT
    • A
      license
      Not graded
      quality
      A
      maintenance
      A local MCP server that lets AI assistants drive your real, already-logged-in Chrome through a browser extension, so agents can read, click, type, navigate, screenshot, inspect network traffic, and manage cookies or storage on pages that require existing sessions. It works without opening a remote-debugging port and keeps all communication on your machine.
      1 npm
      ISC
    • A
      license
      Not graded
      quality
      A
      maintenance
      A local MCP server that lets AI coding tools drive the user's actual, already-logged-in Chrome browser — snapshotting pages, clicking, typing, navigating, reading content, taking screenshots, and inspecting cookies, network traffic, and console output. Browser automation happens through a Chrome extension using Native Messaging, so no remote-debugging port is exposed and all communication stays on the local machine.
      1 npm
      ISC