Skip to main content
Glama
lagrangee

COROS Codex MCP Bridge

by lagrangee
README.md
# COROS Codex MCP Bridge

Local, read-only stdio bridge for using the COROS MCP gateway from Codex.

[![CI](https://github.com/lagrangee/coros-codex-mcp-bridge/actions/workflows/ci.yml/badge.svg)](https://github.com/lagrangee/coros-codex-mcp-bridge/actions/workflows/ci.yml)

## Why this bridge exists

The bridge works around a protocol mismatch between the current COROS MCP gateway and the Codex MCP client:

1. The COROS gateway currently answers `initialize` as a stateless JSON-RPC request. In the verified integration, the response does not include `Mcp-Session-Id`.
2. Codex's remote Streamable HTTP MCP lifecycle expects a session-aware initialization flow. Directly pointing Codex at the COROS endpoint can therefore fail during MCP startup before any sport query is available.
3. This project gives Codex a normal local stdio MCP server. The bridge translates Codex's stdio lifecycle into stateless, authenticated JSON-RPC calls to COROS.

The bridge does not replace the COROS service or implement a second data store. It is a small local protocol and credential boundary.

## Security and scope

- Local stdio only. The bridge does not listen on a network port.
- Read-only allowlist only. Tools must be in the verified COROS query allowlist and advertise `readOnlyHint: true`.
- FIT download, FIT URL, route URL, export, and other sensitive file-oriented tools are excluded.
- No write, delete, upload, or training-plan mutation tools are exposed.
- The bridge reads the token cache created by the official `@coros_open/coros-mcp-login` CLI.
- Access tokens are refreshed when needed and persisted atomically with file mode `0600`.
- Access and refresh tokens are never printed, logged, or committed.
- Upstream and OAuth requests, including response-body reads, time out after 30 seconds by default.
- There is no telemetry or background daemon.

The allowlist is intentionally conservative. If COROS adds a new read-only tool, it must be reviewed and added explicitly before this bridge exposes it.

## Requirements

- macOS or another system that can run the official COROS MCP login flow
- Node.js 22 or newer
- A Codex client with MCP configuration support
- A COROS account authorized through the official login CLI

## Install

```sh
git clone https://github.com/lagrangee/coros-codex-mcp-bridge.git
cd coros-codex-mcp-bridge

npm test
npm run check
```

There are no runtime npm dependencies. Use a Node executable that is visible to the Codex process; for desktop applications, an absolute Node path is often more reliable than relying on GUI `PATH` inheritance.

## Authorize COROS

Run the official login command in a terminal:

```sh
npx --yes @coros_open/coros-mcp-login login
```

Complete authentication in the browser. Do not paste a password, authorization code, access token, or refresh token into Codex or an issue.

The bridge discovers the first existing cache at:

```text
~/.coros-mcp-skill-gateway-ts/{cn,eu,us}/token.json
```

If the cache is missing or the refresh token has been revoked, run the official login command again.

## Configure Codex

Add or replace the `coros` entry in `~/.codex/config.toml`:

```toml
[mcp_servers.coros]
command = "/absolute/path/to/node"
args = ["/absolute/path/to/coros-codex-mcp-bridge/src/cli.mjs"]
startup_timeout_sec = 30
```

For a shell-installed Node, `command = "node"` may be sufficient. For Codex Desktop, use the absolute path to the Node runtime that the desktop process can execute.

After changing MCP configuration, start a fresh Codex process or task so it reloads the configuration. Verify the entry with:

```sh
codex mcp get coros
```

The bridge is started and stopped by Codex as a stdio child process. You do not need to keep a terminal running or supervise a daemon.

## Configuration overrides

All overrides are optional:

| Variable | Purpose | Default |
| --- | --- | --- |
| `COROS_TOKEN_PATH` | Explicit token cache path | Regional cache discovery |
| `COROS_REGION` | Regional endpoint selector: `cn`, `eu`, or `us` | Inferred from cache path, then `cn` |
| `COROS_OAUTH_ISSUER` | Override the OAuth issuer | Regional COROS issuer |
| `COROS_MCP_URL` | Override the upstream MCP URL | `${issuer}/mcp` |
| `COROS_REQUEST_TIMEOUT_MS` | Request and response-body timeout | `30000` |

Example:

```sh
COROS_REGION=eu COROS_REQUEST_TIMEOUT_MS=60000 node src/cli.mjs
```

## Architecture

```text
Codex
  | JSON-RPC over stdin/stdout
  v
coros-codex-mcp-bridge
  | token cache discovery and refresh
  | read-only tool allowlist
  | stateless JSON-RPC translation
  v
COROS MCP gateway
```

The bridge keeps the upstream implementation details local to the adapter:

- `src/cli.mjs` owns JSONL stdio and environment-based configuration.
- `src/bridge.mjs` owns the Codex-facing MCP surface and read-only policy.
- `src/coros-client.mjs` owns upstream initialization, pagination, session recovery, SSE parsing, and tool calls.
- `src/token-store.mjs` owns token-cache discovery input, refresh, atomic persistence, and file permissions.

## Development

```sh
npm test
npm run check
```

Tests use fake HTTP responses and temporary token files. They do not require a COROS account and must not contain real credentials.

Run the stdio server directly when debugging protocol integration:

```sh
node src/cli.mjs
```

The process reads newline-delimited JSON-RPC from stdin and writes responses to stdout. Diagnostic messages must stay off stdout because stdout is the MCP protocol channel.

## Troubleshooting

### COROS authorization is missing

Run:

```sh
npx --yes @coros_open/coros-mcp-login login
```

Then restart the Codex process so the bridge reloads the cache.

### Codex shows no `coros` tools

Check the command and absolute paths in `~/.codex/config.toml`, run `codex mcp get coros`, and start a fresh Codex process. A configuration edit is not necessarily hot-loaded into an already running process.

### A newly released COROS read-only tool is not visible

That is intentional. Review its semantics and add it to the allowlist in `src/bridge.mjs` with a regression test before exposing it.

## Contributing

See [CONTRIBUTING.md](CONTRIBUTING.md). In particular, do not add credentials, live personal sport data, or unreviewed write-capable tools to tests or fixtures.

## Security reports

See [SECURITY.md](SECURITY.md). Do not report token values or other private sport data in public issues.

## License

MIT. See [LICENSE](LICENSE).