Metadata-Version: 2.5
Name: railway-mcp
Version: 0.1.1
Summary: Manage multiple Railway CLI logins on one machine, and let AI agents switch between them via MCP.
Project-URL: Homepage, https://github.com/ml-lubich/railway-mcp
Project-URL: Repository, https://github.com/ml-lubich/railway-mcp
Project-URL: Issues, https://github.com/ml-lubich/railway-mcp/issues
Author-email: Misha Lubich <misha@lupfr.com>
License-Expression: MIT
License-File: LICENSE
Keywords: cli,deployment,devops,mcp,multi-account,railway
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Utilities
Requires-Python: >=3.10
Requires-Dist: mcp>=1.2
Requires-Dist: rich>=13
Requires-Dist: typer>=0.12
Provides-Extra: dev
Requires-Dist: pytest-cov; extra == 'dev'
Requires-Dist: pytest>=8; extra == 'dev'
Requires-Dist: ruff; extra == 'dev'
Description-Content-Type: text/markdown

# railway-mcp — `rgate`

Manage multiple Railway CLI logins on one machine. Switch accounts instead of
logging out.

The Railway CLI holds exactly one login, in `~/.railway/config.json`.
`railway logout` throws it away, and there is no way back except a fresh
browser login. If you work across a personal account and a client account,
that is a login every time you change context.

`rgate` snapshots each login after you create it, and swaps them back in on
demand. **There is no `rgate logout`** — that omission is the point.

```console
$ railway login          # personal
$ rgate sync
synced michaelle.lubich@gmail.com (workspaces: Misha Lubich's Projects)

$ railway login          # client
$ rgate sync
synced ops@client.com (workspaces: Client Inc)

$ rgate ls
  #  email                        name          workspaces  token
  1  michaelle.lubich@gmail.com   Misha Lubich           1  valid
  2  ops@client.com               Ops                    1  valid   active

$ rgate switch 1
switched to michaelle.lubich@gmail.com
```

## Install

```bash
uv tool install railway-mcp    # or: pipx install railway-mcp
```

Needs the [Railway CLI](https://docs.railway.com/guides/cli) on `PATH`.

## Commands

| Command | What it does |
|---|---|
| `rgate sync` | Save the active `railway login` into the store |
| `rgate ls` | List stored accounts and credential status |
| `rgate switch <email\|id\|#>` | Make a stored account the active login |
| `rgate whoami` | Show the active account |
| `rgate projects` | Show the directories linked to a project |
| `rgate doctor` | Check the setup and credential health |
| `rgate path` | Print config and store locations |
| `rgate rm <selector>` | Forget a stored account |
| `rgate agent schema` | Machine-readable command list |

Run `rgate sync` after **every** `railway login` — that is what makes an
account switchable later.

## Project links move with the account

Railway keeps per-directory project links in the same file as the login. A
link created by one account is meaningless to another: it points at a project
the other account cannot see. So `rgate switch` carries each account's links
with it, saving the outgoing set and restoring the incoming one. Everything
else in `config.json` — editor, sandboxes, keys Railway adds later — is left
exactly as it was, and the file is backed up to `config.json.bak` first.

## "refreshable" is normal, not broken

Railway access tokens last about an hour. The CLI renews them from the refresh
token silently, so a stored credential whose expiry has passed is usually
fine — `rgate` labels that **refreshable** and switches to it without
complaint. Only a past expiry with *no* refresh token is **expired**, and only
that needs `railway login` again (or `rgate switch --force` to write it
anyway).

| Status | Means |
|---|---|
| `valid` | More than 24h left |
| `expiring-soon` | Under 24h left — still works |
| `refreshable` | Access token lapsed, refresh token will renew it — still works |
| `expired` | Lapsed with no refresh token — needs `railway login` |
| `unknown` | No expiry recorded |

## `RAILWAY_API_TOKEN` beats all of this

If `RAILWAY_API_TOKEN` or `RAILWAY_TOKEN` is set in your environment, the
Railway CLI uses it and ignores `config.json` entirely — so `rgate switch`
will appear to do nothing. `rgate doctor` checks for this.

## MCP

`rgate-mcp` exposes the same operations to AI agents: `railway_sync`,
`railway_list_accounts`, `railway_switch`, `railway_whoami`, `railway_doctor`.

```json
{ "mcpServers": { "rgate": { "command": "rgate-mcp" } } }
```

## Where things live

| | |
|---|---|
| Railway CLI state | `~/.railway/config.json` (override: `RAILWAY_CLI_CONFIG_DIR`) |
| Account store | `~/.config/railway-mcp/accounts.json`, mode `0600` (override: `RAILWAY_MCP_STORE`) |

Tokens are stored in plain JSON readable only by your user — the same
protection the Railway CLI gives its own config file, no more.

## Development

```bash
uv venv && uv pip install -e ".[dev]"
.venv/bin/pytest
```

See [CONTRACT.md](CONTRACT.md) for the behavioural spec the tests enforce.

MIT.
