readyroom 0.1.4 → 0.1.5
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/README.md +154 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -240,6 +240,160 @@ the stream is required and comes from the path segment or an explicit `stream` t
|
|
|
240
240
|
If neither is present, the tool call fails with an error naming both ways to supply it. No
|
|
241
241
|
default is invented.
|
|
242
242
|
|
|
243
|
+
## MCP Configuration
|
|
244
|
+
|
|
245
|
+
An MCP client reaches readyroom over one of two transports. How you run the server decides
|
|
246
|
+
which one is available.
|
|
247
|
+
|
|
248
|
+
### Entry points
|
|
249
|
+
|
|
250
|
+
| Command | Target | Transports | State |
|
|
251
|
+
|---|---|---|---|
|
|
252
|
+
| `npx readyroom` | Node, on the host | stdio + Streamable HTTP | `./.state` on disk |
|
|
253
|
+
| `npm start` (`node dist/server.mjs`) | Node, built bundle | stdio + Streamable HTTP | `./.state` on disk |
|
|
254
|
+
| `npm run dev` (`tsx watch --import ./scripts/html-loader-register.mjs src/index.ts`) | Node, development | stdio + Streamable HTTP | `./.state` on disk |
|
|
255
|
+
| `docker run … ghcr.io/therebelrobot/readyroom:latest` | Docker | stdio + Streamable HTTP | volume mount |
|
|
256
|
+
| `npm run dev:worker` (`wrangler dev`) | Cloudflare Worker, local | Streamable HTTP only | Durable Object |
|
|
257
|
+
| `npm run deploy:worker` (`wrangler deploy`) | Cloudflare Worker, edge | Streamable HTTP only | Durable Object |
|
|
258
|
+
|
|
259
|
+
On the Node targets a process resolves into a **daemon** or a **client** (see
|
|
260
|
+
[the singleton](#three-ways-aboard)): the first process becomes the daemon and serves both
|
|
261
|
+
transports on the port; later processes join as clients and serve stdio only, proxying their
|
|
262
|
+
tool calls to the daemon. The role is decided at start — there is nothing to configure.
|
|
263
|
+
|
|
264
|
+
### Transports
|
|
265
|
+
|
|
266
|
+
- **stdio** — Node targets only. The MCP client spawns the process and speaks the protocol over
|
|
267
|
+
its file descriptors. The server writes nothing to stdout but MCP frames; all logs go to
|
|
268
|
+
stderr. Select it by configuring the client to run a `command`.
|
|
269
|
+
- **Streamable HTTP** — every target. On Node the daemon mounts it at `POST /mcp` (methods
|
|
270
|
+
`GET`, `POST`, `DELETE`) on the same port. On the Worker it is the only transport, mounted at
|
|
271
|
+
`/mcp` (stream supplied as the `/mcp/{stream}` path segment). Select it by pointing the client at the URL.
|
|
272
|
+
|
|
273
|
+
The two are not exclusive on Node: one daemon serves stdio and HTTP at once, so one agent can
|
|
274
|
+
run over stdio and another over HTTP against the same queue.
|
|
275
|
+
|
|
276
|
+
#### The `/mcp` endpoint is Origin-guarded
|
|
277
|
+
|
|
278
|
+
Both HTTP mounts reject a request whose `Origin` header is missing or not on the allowlist,
|
|
279
|
+
with `403`. On Node the allowlist is built from the server's own origins — `http://127.0.0.1:<port>`
|
|
280
|
+
and `http://localhost:<port>` — plus any `READYROOM_ALLOWED_ORIGINS` entries. On the Worker it
|
|
281
|
+
is the Worker's own origin plus the same extras. See `src/security.ts`.
|
|
282
|
+
|
|
283
|
+
> [!NOTE]
|
|
284
|
+
> An HTTP MCP client must send an `Origin` header that matches the allowlist. A client that
|
|
285
|
+
> sends no `Origin` at all is rejected. If your client sends a different origin (or none), add
|
|
286
|
+
> it to `READYROOM_ALLOWED_ORIGINS`.
|
|
287
|
+
|
|
288
|
+
#### Streams over HTTP
|
|
289
|
+
|
|
290
|
+
On Node the stream defaults to `READYROOM_STREAM`, else `basename(cwd)`, and can be overridden
|
|
291
|
+
per call with the `stream` tool argument. On the Worker there is no `cwd`: the stream comes from
|
|
292
|
+
the `POST /mcp/{stream}` path segment, or an explicit `stream` argument. If neither is present,
|
|
293
|
+
the call fails with an error naming both ways to supply it.
|
|
294
|
+
|
|
295
|
+
### Configuration options
|
|
296
|
+
|
|
297
|
+
All six are read from the process environment on the Node targets, and, on the Worker, only
|
|
298
|
+
`READYROOM_TOKEN` and `READYROOM_ALLOWED_ORIGINS` are read from `[vars]` / `secrets` in
|
|
299
|
+
`wrangler.toml`. `src/config.ts` is the single source of truth.
|
|
300
|
+
|
|
301
|
+
| Variable | Default | Required? | Meaning |
|
|
302
|
+
|---|---|---|---|
|
|
303
|
+
| `READYROOM_STREAM` | `basename(cwd)` | optional | Default stream for tickets. Overridable per call via the `stream` tool argument. Not applicable on the Worker. |
|
|
304
|
+
| `READYROOM_TOKEN` | *(unset)* | **mandatory on Docker and the Worker**, optional on Node | Shared secret for `/api/*` and the Node `/internal/tool-call` proxy. |
|
|
305
|
+
| `READYROOM_BIND` | `127.0.0.1` | optional | Bind address. A non-loopback value refuses to start unless `READYROOM_TOKEN` is set. Not applicable on the Worker. |
|
|
306
|
+
| `READYROOM_STATE_DIR` | `./.state` | optional | Directory holding `tickets.json`, `audit.jsonl`, and `daemon.json`. Not applicable on the Worker. |
|
|
307
|
+
| `READYROOM_PORT` | `8971` | optional | TCP port. Must not collide with 3000-3899, which a worktree helper allocates. Not applicable on the Worker. |
|
|
308
|
+
| `READYROOM_ALLOWED_ORIGINS` | *(unset)* | optional | Comma-separated extra origins allowed on the `/mcp` Origin guard, appended to the server's own origin. |
|
|
309
|
+
|
|
310
|
+
The per-target Worker mapping is in [Environment variables](#environment-variables).
|
|
311
|
+
|
|
312
|
+
### MCP client configuration
|
|
313
|
+
|
|
314
|
+
#### stdio (Claude Desktop and other MCP clients)
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
{
|
|
318
|
+
"mcpServers": {
|
|
319
|
+
"readyroom": {
|
|
320
|
+
"command": "npx",
|
|
321
|
+
"args": ["-y", "readyroom"],
|
|
322
|
+
"env": {
|
|
323
|
+
"READYROOM_STREAM": "my-agent",
|
|
324
|
+
"READYROOM_STATE_DIR": "/home/you/.readyroom"
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
Every client that spawns readyroom becomes the daemon or a client automatically; you do not set
|
|
332
|
+
the role. Give each agent its own stream with `READYROOM_STREAM` (or the `stream` argument to
|
|
333
|
+
`ask_human`). If any process in a shared daemon group sets `READYROOM_TOKEN`, every process in
|
|
334
|
+
that group must set the same value — the client forwards it to the daemon.
|
|
335
|
+
|
|
336
|
+
For a built or installed copy, replace `command` with the absolute path to `dist/server.mjs`
|
|
337
|
+
(or the `readyroom` binary on `PATH`).
|
|
338
|
+
|
|
339
|
+
#### Streamable HTTP (Node daemon)
|
|
340
|
+
|
|
341
|
+
```json
|
|
342
|
+
{
|
|
343
|
+
"mcpServers": {
|
|
344
|
+
"readyroom": {
|
|
345
|
+
"url": "http://127.0.0.1:8971/mcp",
|
|
346
|
+
"headers": {
|
|
347
|
+
"Origin": "http://127.0.0.1:8971"
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### Streamable HTTP (Cloudflare Worker)
|
|
355
|
+
|
|
356
|
+
The stream is the path segment, so each agent gets its own URL.
|
|
357
|
+
|
|
358
|
+
```json
|
|
359
|
+
{
|
|
360
|
+
"mcpServers": {
|
|
361
|
+
"readyroom": {
|
|
362
|
+
"url": "https://readyroom.example.com/mcp/my-agent",
|
|
363
|
+
"headers": {
|
|
364
|
+
"Origin": "https://readyroom.example.com"
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
### Auth and secrets
|
|
372
|
+
|
|
373
|
+
`src/security.ts` holds both guards.
|
|
374
|
+
|
|
375
|
+
- **`READYROOM_TOKEN`** protects `/api/*` (the page's API) and, on Node, the `/internal/tool-call`
|
|
376
|
+
proxy between a client process and its daemon. The comparison is constant-time via the shared
|
|
377
|
+
`src/core/constantTimeEqual.ts`, so Node and the Worker get the identical guarantee.
|
|
378
|
+
- **The `/mcp` MCP endpoint is guarded by the `Origin` header, not by the token.** A missing or
|
|
379
|
+
mismatched `Origin` is rejected with `403`; the MCP specification requires this of HTTP
|
|
380
|
+
transports. `READYROOM_ALLOWED_ORIGINS` extends that allowlist.
|
|
381
|
+
- On Docker and the Worker the token is **mandatory**. On Node it is optional for a loopback
|
|
382
|
+
bind and mandatory for any non-loopback bind — the refusal rule in `src/config.ts` enforces it.
|
|
383
|
+
|
|
384
|
+
> [!NOTE]
|
|
385
|
+
> `src/config.ts` and the [Environment variables](#environment-variables) table describe
|
|
386
|
+
> `READYROOM_TOKEN` as the secret for "the MCP endpoint". In the shipped code the token is
|
|
387
|
+
> enforced on `/api/*` (and the Node `/internal/tool-call` proxy) only; the `/mcp` endpoint is
|
|
388
|
+
> authenticated by its `Origin` guard instead. This section documents the code's actual behavior.
|
|
389
|
+
|
|
390
|
+
##### A note on secrets
|
|
391
|
+
|
|
392
|
+
Treat `READYROOM_TOKEN` as a bearer secret. On the Worker, set it with
|
|
393
|
+
`wrangler secret put READYROOM_TOKEN` and never commit it. On Docker and npx, supply it through
|
|
394
|
+
an `--env-file` or your process manager, not a shell history. Never expose the server to a
|
|
395
|
+
public network directly — see [Never expose this to a public network directly](#never-expose-this-to-a-public-network-directly).
|
|
396
|
+
|
|
243
397
|
## Environment variables
|
|
244
398
|
|
|
245
399
|
Five variables, plus one optional extra.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "readyroom",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "An MCP server that lets coding agents request a human decision without hijacking whichever window is focused. Agents enqueue a ticket; the human drains the queue from one page when they choose to.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"keywords": [
|