Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

7 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mcp-wake

English · 中文

Clients like Claude Code implement MCP only partially. Dispatch a long-running task over MCP — say, putting Codex to work — and you cannot subscribe to the server's events, so you never learn how the task is going.

mcp-wake implements that subscription on the standard MCP protocol and fills the gap on the client's behalf, so Claude Code — or any other client — can receive server-side notifications. It turns a server push into something every agent harness already understands: a process that exits, or a line that gets printed.

Why this exists

Most agent harnesses can only be woken up in two ways:

  1. A background process exits.
  2. A process prints a line.

That is the entire vocabulary. There is no "subscribe to a server push" entry point.

This is not a gap in MCP. The protocol has a perfectly good push channel: the client calls resources/subscribe, the server sends notifications/resources/updated. The gap is on the client side — many harnesses implement MCP only partially, and server-initiated notifications are the usual casualty. The client can read a resource but cannot subscribe to one, so a notification the server dutifully emits never reaches the agent.

The practical consequence: every time an agent needs to learn about something asynchronously — a long job finishing, an approval request appearing, a build going red — somebody hand-writes the same fragile glue again.

mcp-wake is that glue, written once. It is a real MCP client, speaking the standard protocol, and it knows nothing about any particular server.

How it works

connect to the MCP server (a remote URL, or a local process over stdio)
  → initialize
  → resources/subscribe
  → wait for notifications/resources/updated
  → resources/read to get the actual content
  → does it match what you're waiting for?
       once   → exit
       stream → print one line, keep waiting

Two design notes worth knowing:

  • MCP pushes carry no payload. notifications/resources/updated only tells you that a resource changed, not what changed. Reading it back is a required second step, not an inefficiency.
  • It reads once immediately after subscribing. A subscription only covers changes from that moment on, so without an initial read you would miss anything that already happened.

Install

npm install -g mcp-wake

Or run it without installing anything:

npx mcp-wake --help
Installing from a source checkout
node install.mjs

The installer packs a tarball first and installs that, rather than npm i -g <folder> — a folder install creates a symlink back into the source tree, so the command breaks the moment you move, delete, or switch branches on the repo. A tarball install is a real copy.

Worth knowing either way: a global install lands under the Node version you installed with. Switch Node versions and the command appears to vanish — just install again.

Usage

mcp-wake --server "<command to launch an MCP server>" \
         --resource "<resource uri>" \
         [--match "<regex>"] [--mode once|stream]

Launch a local MCP server over stdio and wake when one of its long-running jobs finishes:

mcp-wake \
  --server "node ./my-mcp-server.mjs" \
  --resource "jobs:///builds/42" \
  --match '"state":\s*"(succeeded|failed)"' \
  --timeout-minutes 150

Run it in the background; when it exits, your harness notifies you.

For per-event notifications instead — including mid-flight approvals — use --mode stream. It prints exactly one line per match and keeps watching until --until matches or the timeout hits. One line per notification is deliberate: for some callers, every printed line is a notification.

Matching

--match is a regular expression applied to the resource's text content. That is intentionally dumb: this tool does not understand any resource's internal structure, and should not. You know what you are waiting for.

Mind the whitespace. Many servers return pretty-printed JSON, so the text is "type": "turn.completed" with a space. Write "type":\s*"turn\.completed", not "type":"turn.completed". This bites everyone once.

Omit --match entirely and any change wakes you.

Exit codes

Code Meaning
0 Matched and finished
3 Timed out
1 Error — server wouldn't start, subscription refused, connection dropped

Every outcome is an explicit signal. The tool never simply goes quiet and leaves you guessing whether it is still working or long dead.

Options

Option Description
--url <endpoint> One of these two. HTTP endpoint of a remote MCP server
--server <command> One of these two. Local MCP server command to launch (stdio), as one string including arguments
--resource <uri> Required. The resource URI to subscribe to
--header <name: value> --url only; repeatable. This is how you pass authentication
--mode once|stream Default once
--match <regex> Only a matching resource body counts as a hit
--until <regex> stream only: stop and exit when the body matches
--timeout-minutes <n> Default 30; accepts fractions
--cwd <dir> Working directory for the spawned server
--print-content Include the resource body in the output
--trace Print handshake / subscribe / read progress to stderr

Transports: remote and local

Both are supported, and you pick with a single flag.

Remote, over MCP's HTTP transport — nothing runs locally except mcp-wake itself:

mcp-wake --url https://example.com/mcp \
         --header "Authorization: Bearer $TOKEN" \
         --resource "task:///jobs/123" \
         --match '"status":\s*"(done|failed)"'

--header may be repeated, which is how you pass authentication.

Local, over stdiomcp-wake launches the server itself:

mcp-wake --server "node ./some-mcp-server.mjs" --resource "..." 

Give exactly one of --url or --server.

Which servers can be watched

Not every MCP server. Resource subscription is an optional capability in MCP — a server that only exposes tools is perfectly valid and simply cannot be watched. The requirement is that the thing you care about is exposed as a resource, and that resource supports subscription. If the server does not offer this, mcp-wake says so and exits immediately rather than waiting forever for a push that can never arrive.

Concretely, the server must implement:

Older protocol 2026-07-28
Declare capabilities.resources.subscribe: true server/discover
Subscribe resources/subscribe subscriptions/listen
Push notifications/resources/updated same, preceded by an acknowledgment
Read resources/read same

Protocol support

mcp-wake speaks both generations of MCP and picks the right one automatically.

The 2026-07-28 revision made MCP stateless: it removed the initialize handshake, moved the protocol version and client capabilities into per-request _meta, and replaced resources/subscribe with the long-lived subscriptions/listen stream. The spec anticipates exactly this mixed world and prescribes the fallback: on stdio, send server/discover first.

That is what happens on startup:

Server answers server/discover Then
Yes → it is a 2026-07-28-era server Every request carries _meta; subscribe via subscriptions/listen, and verify the acknowledgment actually lists your resource
No (method not found) → it is an older server Fall back to the initialize handshake and resources/subscribe

Either way you get the same behavior out the other end. --trace shows which generation was detected.

Requirements per generation:

  • Older servers must declare capabilities.resources.subscribe. If not, mcp-wake says so and exits instead of waiting forever for a push that can never arrive.
  • 2026-07-28 servers must include your resource URI in the notifications/subscriptions/acknowledged reply. A server is allowed to honor only part of what you asked for, so this is checked rather than assumed.

Both paths are covered by real tests. test/fake-modern-server.mjs is a minimal 2026-07-28 server built straight from the spec — no real one existed at the time of writing, and shipping unverified protocol code seemed worse than building a stand-in.

Both transports are covered by real tests: test/fake-modern-server.mjs (stdio) and test/fake-http-server.mjs (HTTP, able to play either generation). Run them with node test/http-e2e.mjs.

Contributing

Issues and pull requests welcome. The codebase is deliberately small, has zero third-party dependencies, and uses only Node built-ins.

License

MIT

About

Claude Code 通过 MCP 跑异步长任务(比如指挥 Codex 干活)时,无法订阅服务端的事件,也拿不到任务的最新进展。本项目基于 MCP 标准协议实现了通知订阅,替客户端补足缺口,让 Claude Code 或任意其它客户端都能订阅服务端消息。

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages