Skip to main content
Glama
sac2665

apex-mcp-server

by sac2665
README.md
# apex-mcp-server

A read-only [Model Context Protocol](https://modelcontextprotocol.io) server for
the Apex Rides API, deployed as a Cloudflare Worker. It authenticates against AWS
Cognito with a service account and exposes four read-only tools over MCP's
Streamable HTTP transport.

## Tools

| Tool | Description |
| --- | --- |
| `popular_classes` | Top popular workouts by session count in a date range. |
| `search_classes` | Search workouts with filters (paginated). |
| `get_class` | Detailed info for a single on-demand class. |
| `list_instructors` | List all instructors (including archived). |

All tools are `GET`-based. There are no write operations and no generic passthrough.

## Setup & Deploy

### 1. Install

```bash
npm install
```

### 2. Set secrets

```bash
npx wrangler secret put APEX_SERVICE_PASSWORD   # the Cognito service user password
npx wrangler secret put MCP_SHARED_SECRET       # a strong random string
```

For local development, create a `.dev.vars` file (git-ignored) with the same keys:

```
APEX_SERVICE_PASSWORD=...
MCP_SHARED_SECRET=...
```

### 3. Deploy

```bash
npx wrangler deploy
```

## Usage

Connect an MCP client to the worker's MCP endpoint, with the shared secret as the
first path segment:

```
https://<worker>.workers.dev/<MCP_SHARED_SECRET>/mcp
```

Any request whose first path segment is missing or does not match
`MCP_SHARED_SECRET` gets a `401`.

The transport is MCP **Streamable HTTP** (a single endpoint handles both the
GET stream and POST messages). The legacy Node-only SSE transport does not run on
Cloudflare Workers, so it is not used here.

## Notes & Guardrails

- **No client secret.** `SECRET_HASH` is omitted entirely since the Cognito app
  client has no secret.
- **Token caching.** The access token is cached in module scope and refreshed via
  `REFRESH_TOKEN_AUTH` when near expiry.
- **Date validation.** `popular_classes` validates `from`/`to` against `YYYY-MM-DD`
  and appends `T23:59:59Z` to include the full final day.
- **Name search.** Since the Apex API has no text search, `search_classes` fetches
  up to 500 results and filters client-side when `name` is provided.
- **Read-only enforcement.** Only the four `GET`-based tools above are implemented.