Skip to main content
Glama
andychongyz

skylight-mcp-remote

by andychongyz
README.md
# skylight-mcp-remote

Remote MCP deployment scaffold for [`skylight-mcp`](https://rubygems.org/gems/skylight-mcp), built on Cloudflare Workers plus Cloudflare Containers.

Relevant upstream links:

- Skylight: [https://skylight.io/](https://skylight.io/)
- `skylight-mcp` on RubyGems: [https://rubygems.org/gems/skylight-mcp](https://rubygems.org/gems/skylight-mcp)
- `skylight-mcp` on Ruby China: [https://gems.ruby-china.com/gems/skylight-mcp/](https://gems.ruby-china.com/gems/skylight-mcp/)

This repo is for people who want to expose the Ruby `skylight-mcp` server through a hosted HTTP endpoint instead of running it locally over stdio.

## What It Does

- Terminates HTTP requests at a Cloudflare Worker.
- Protects `/mcp` with a shared bearer token.
- Forwards authenticated traffic to a containerized Node bridge.
- Runs `gem exec skylight-mcp --token "$SKYLIGHT_MCP_TOKEN"` inside the container.
- Adapts hosted MCP traffic into the stdio-based Ruby server.

If you found this repo while searching for Skylight MCP, Skylight Ruby performance tooling, or the `skylight-mcp` gem, start with [Skylight](https://skylight.io/) and the package pages on [RubyGems](https://rubygems.org/gems/skylight-mcp) or [Ruby China](https://gems.ruby-china.com/gems/skylight-mcp/).

## MCP Behavior

The bridge is intentionally tolerant of real hosted-client behavior:

- `OPTIONS /mcp` returns `204` for browser preflight requests.
- `HEAD /mcp` returns `200` for authenticated reachability checks.
- `POST /mcp` supports standard JSON-RPC requests such as `initialize`, `tools/list`, and `tools/call`.
- `notifications/initialized` is accepted as a notification and returns `202`.
- If a client sends follow-up traffic without a local in-memory session, the bridge bootstraps a replacement session before forwarding the request.

## Architecture

- `apps/worker`: Cloudflare Worker request handling, auth, and CORS.
- `apps/bridge`: HTTP-to-stdio bridge service that manages `skylight-mcp` child processes.
- `Dockerfile`: minimal container image for the bridge.
- `wrangler.jsonc`: Worker + container deployment config.

Request flow:

1. Client sends `POST /mcp` with `Authorization: Bearer <MCP_SHARED_BEARER_TOKEN>`.
2. Worker validates auth and forwards the request to the Cloudflare Container binding.
3. Bridge creates or resumes a session, then proxies JSON-RPC over stdio to `skylight-mcp`.
4. Bridge returns JSON-RPC responses, `mcp-session-id` headers when relevant, and browser-friendly CORS headers.

## Prerequisites

- Node 22+
- npm
- Docker with `buildx`
- Ruby is only required if you want to run the bridge outside the container image
- A Cloudflare account with Workers and Containers enabled
- A valid Skylight MCP token

## Configuration

Required secrets:

- `MCP_SHARED_BEARER_TOKEN`
- `SKYLIGHT_MCP_TOKEN`

Optional environment variables:

- `PORT` default `8080`
- `LOG_LEVEL` default `info`
- `BRIDGE_REQUEST_TIMEOUT_MS` default `30000`

`.env.example` contains the expected variable names for local development.

## Local Development

Install dependencies and run tests:

```bash
npm install
npm test
npm run build
```

Run the bridge directly:

```bash
export SKYLIGHT_MCP_TOKEN=your-skylight-token
export PORT=8080
npm run dev:bridge
```

Quick checks:

```bash
curl -i http://127.0.0.1:8080/healthz

curl -i \
  -H 'content-type: application/json' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"local-test","version":"1.0"}}}' \
  http://127.0.0.1:8080/mcp
```

## Deploying To Cloudflare

1. Authenticate Wrangler.
2. Build the workspace artifacts copied into the bridge image:

```bash
npm run build
```

3. Set Worker secrets:

```bash
npx wrangler secret put MCP_SHARED_BEARER_TOKEN
npx wrangler secret put SKYLIGHT_MCP_TOKEN
```

4. Build and push the bridge image:

```bash
docker buildx build --platform linux/amd64 --load -t skylight-mcp-remote:amd64 .
npx wrangler containers push skylight-mcp-remote:amd64
```

5. Update `wrangler.jsonc` with your pushed image digest.

6. Review the account-specific values in `wrangler.jsonc` before deploy:

- Worker `name`
- container `name`
- registry image reference under `containers[].image`

7. Deploy:

```bash
npx wrangler deploy
```

## Smoke Test

Set helpers:

```bash
export BASE_URL="https://<your-worker-subdomain>.workers.dev"
export MCP_SHARED_BEARER_TOKEN="<your-shared-bearer-token>"
```

Health check:

```bash
curl -i "$BASE_URL/healthz"
```

Initialize:

```bash
curl -i \
  -H "Authorization: Bearer $MCP_SHARED_BEARER_TOKEN" \
  -H 'content-type: application/json' \
  -H 'Mcp-Method: initialize' \
  -H 'MCP-Protocol-Version: 2025-11-25' \
  -d '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2025-11-25","capabilities":{},"clientInfo":{"name":"smoke-test","version":"1.0"}}}' \
  "$BASE_URL/mcp"
```

Follow with `notifications/initialized`:

```bash
curl -i \
  -H "Authorization: Bearer $MCP_SHARED_BEARER_TOKEN" \
  -H 'content-type: application/json' \
  -H 'Mcp-Method: notifications/initialized' \
  -H 'MCP-Protocol-Version: 2025-11-25' \
  -H 'mcp-session-id: <session-id>' \
  -d '{"jsonrpc":"2.0","method":"notifications/initialized"}' \
  "$BASE_URL/mcp"
```

List tools:

```bash
curl -i \
  -H "Authorization: Bearer $MCP_SHARED_BEARER_TOKEN" \
  -H 'content-type: application/json' \
  -H 'Mcp-Method: tools/list' \
  -H 'MCP-Protocol-Version: 2025-11-25' \
  -H 'mcp-session-id: <session-id>' \
  -d '{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' \
  "$BASE_URL/mcp"
```

## Limitations

- The repo is a deployment scaffold, not a published npm package.
- `wrangler.jsonc` is intentionally operational and must be edited for your own Cloudflare account.
- Session state is in-memory inside the bridge container. The Worker smooths over some hosted-client edge cases, but the architecture is still fundamentally a stateful bridge around a stdio server.

## License

MIT. See [LICENSE](./LICENSE).