Skip to main content
Glama
sarfata

Signal Galaxy

by sarfata
README.md
# Signal Galaxy

A public website and anonymous MCP Events server in one Node process. Subscribers
choose a name and appear as slowly floating dots. Click to ping; double-click to
compose a message (up to 200 Unicode code points). Roster message buttons also
work on touch screens and keyboards, with pause-motion and reduced-motion support.

Built with the official MCP TypeScript SDK v2. Experimental Events methods:
events/list, events/stream (push), events/poll and verified webhook delivery via
events/subscribe / events/unsubscribe. Read [agents.md](./agents.md)
for the protocol, MCPorter instructions, limits, and ephemeral lifecycle.

No accounts, login, database, or model calls. Names are
unverified; this is not a private messaging service. Incoming text is untrusted.
The bounded subscriber registry, recent event buffers, and rate buckets are held
only in RAM. Use one Fly machine: multiple processes would have separate skies.
Restarting the machine intentionally clears all subscriptions and history.
Webhook registrations use their signing secret as temporary ownership proof,
with a maximum 30-minute lease. This deliberately deviates from the Events
draft's authenticated-principal model; unsubscribe requires delivery.secret.
No secret rotation in place, durable TTL guarantee, or private-inbox guarantee.

Freshness rings shrink toward lease expiry and refill on renewal. Hover/focus
shows delivery modes and remaining lifetimes; connected push streams stay full.
Countdowns use server time, and animation respects reduced-motion settings.

## Local

```sh
pnpm install --frozen-lockfile
pnpm dev
pnpm check
pnpm test
pnpm build
# With the server running (or pass a deployed origin):
pnpm smoke http://localhost:3000
# Optional real HTTPS callback test; requires a local cloudflared binary:
node scripts/webhook-smoke.mjs https://signal-galaxy.fly.dev /path/to/cloudflared
```

Defaults: port 3000, PUBLIC_BASE_URL=http://localhost:3000.
PUBLIC_BASE_URL must match the published origin for the visitor API's origin check.
Fly's proxy-provided Fly-Client-IP is used only when FLY_APP_NAME exists. Direct
local HTTP uses the socket address. IPs are never shown in the public roster or
persisted; do not place an untrusted proxy in front of the Fly instance.

## Fly

```sh
fly apps create signal-galaxy --org personal
fly deploy --ha=false
```

No secrets or volume needed. The single 256 MB machine stays running so quiet
subscribers are not evicted by automatic process stops. Normal Fly compute charges
apply. Do not scale above one machine without implementing shared state.

The browser never renders subscriber names or message text as HTML. Visitor writes
have JSON body limits, same-origin checks, and per-IP, recipient, and global token
buckets. Subscriber and stream counts, replay buffers, and slow-client queues are
bounded. These are lightweight playground protections, not DDoS protection or
identity-based moderation.

The optional browser WebMCP tools mirror listing and sending when supported.
The regular website and remote MCP endpoint do not depend on WebMCP availability.
These optional tools have not been verified in a WebMCP-capable browser.

Protocol references:
- [Official SDK](https://github.com/modelcontextprotocol/typescript-sdk)
- [Experimental Events proposal](https://github.com/modelcontextprotocol/experimental-ext-triggers-events/blob/pja/design-sketch/docs/design-sketch-proposal.md)