One endpoint, one token, every profile. You register once per agent, not once per account.
$ lanes link mcp add --profile personal --workspace localWith no argument that covers every agent it finds. It runs each one's own mcp add rather than
writing to its config file, because the registration format is the agent's business.
lanes link mcp list shows where you are registered.
| Client | Local | Self-Hosted |
|---|---|---|
| Claude Code | lanes link mcp add claude | lanes link mcp add claude --workspace cloud |
| Codex | lanes link mcp add codex | lanes link mcp add codex --workspace cloud |
| Claude Desktop, Cowork | by hand, see below | it cannot be given a URL |
| claude.ai, ChatGPT, a phone | nothing there reaches your machine | a custom connector, by URL |
| Anything else | lanes link outputs | lanes link outputs --workspace cloud |
What the agent is told it is for
A registered endpoint is sixty tools and no account of what they are collectively for. Two things fix that, and both are automatic.
Every client gets the short version from the endpoint itself, generated per connection from what that client can actually reach, so it names your profiles and your connections and cannot go stale.
Claude Code and Codex also get a skill file, written by lanes link mcp add because neither has
a skill add command to delegate to:
| Claude Code | Codex | |
|---|---|---|
| skill | ~/.claude/skills/lanes-link/ | ~/.codex/skills/lanes-link/ |
| scout agent | ~/.claude/agents/ | no subagents |
Re-run mcp add after an upgrade to refresh them. It reports unchanged when there was nothing to
do, and --no-skill registers without writing anything.
Claude Code
$ lanes link mcp add claude --profile personal --workspace localThat registers at user scope, because your accounts are not one repository's tooling. Claude Code
stores the token as a value, so after lanes link token rotate run mcp add again with --force.
Codex
$ lanes link mcp add codex --profile personal --workspace local
$ export LANES_LINK_TOKEN="$(lanes link token show --raw)" # put this in your shell profileCodex stores the name of an environment variable and reads it at launch, so the token never reaches
~/.codex/config.toml and a rotation needs no re-registration. Nothing works until that export is
somewhere Codex will see it.
Claude Desktop, and Cowork
Desktop cannot be pointed at a URL. Its config validates every entry against { command, args?, env? },
and an entry carrying a url is silently dropped on launch. So Desktop spawns the endpoint instead of
connecting to it:
{
"mcpServers": {
"lanes-link": {
"command": "/Users/you/.bun/bin/lanes",
"args": ["link", "mcp", "stdio", "--profile", "personal", "--workspace", "local"]
}
}
}That goes in ~/Library/Application Support/Claude/claude_desktop_config.json on macOS, or
%APPDATA%\Claude\claude_desktop_config.json on Windows, beside whatever the file already holds.
Restart Desktop and it appears under Settings, Developer. One entry covers Cowork too.
Both flags are required. This client spawns the endpoint rather than being pointed at a URL, so its config file is the only place that can say which profile and workspace it serves. An entry without them fails to start, and the reason appears in the client's MCP log.
Two things to get right:
- Use an absolute path.
which lanesprints it. bunmust be on thePATHthe client passes down.lanesruns behind#!/usr/bin/env bun, so an absolute path to it is not enough on its own. Claude Desktop passes aPATHthat includes~/.bun/bin, so the entry above works as written. If a client fails withenv: bun: No such file or directory, name Bun yourself as thecommandand pass the CLI path as the first argument.
Three consequences of Desktop spawning the process:
- No token needed. The process is a child of the client, running as you, with no port for anyone else to reach.
lanes link startis not needed for it. Run the endpoint for the clients that use HTTP.- The tool list is fixed for the session. A skill added while Desktop is running appears next time it starts.
claude.ai, ChatGPT, and your phone
These need a deployment. There is no address they can be given that reaches a laptop.
Add a custom connector by URL, using the address lanes link outputs --workspace cloud prints. The
client registers itself, a browser opens on lanes.sh, you sign in and approve, and the client
comes back with a token of its own. Same flow on a laptop and on a phone.
You no longer paste anything. Until 0.8.0 the endpoint served its own approval page and asked for its bearer token, which proved possession of a secret rather than identity, and the most common failure was pasting the token of the wrong workspace. Now the endpoint asks Lanes who you are, and what it checks is whether a profile lists you as a member.
cloud in these commands is a workspace name, whatever your deployment is called. lanes link workspace list shows yours.
If the endpoint refuses you after you sign in, it is not a credential problem: no profile there
lists your subject. The page says so and gives the command its owner runs,
lanes link profile members add <subject> --profile <name>. If that is you, run it and reconnect.
ChatGPT
Its connector UI is off by default, and the setting is not where an older walkthrough will send you: Connectors was renamed Plugins, so there is no longer a Connectors, Advanced to find.
- Settings → Security and login → Developer mode, on. Plus, Pro, Business, Enterprise or Edu, and web only. A free account cannot.
- Plugins →
+→ New Plugin. Name it; the icon and description are optional. - Connection is Server URL. Tunnel is for a server on your own machine, which this is not.
Paste the
/mcpaddress, with the path: it is what the endpoint names as the protected resource, and the bare origin is a different string. - Authentication is OAuth. There is nowhere to paste a bearer token, and No authentication gets a 401.
- Open Advanced OAuth settings. Discovery runs from this panel, and leaving it unopened is enough
to make Create do nothing at all. Choose dynamic client registration, leave the client id and
secret empty, and take
mcp offline_accessas the scopes. - Tick the risk checkbox and Create, then connect. Your endpoint's own approval page opens,
naming
chatgpt.comas where the code goes. That is worth recognising, since registration is open by design and a client may call itself anything. Paste the token. - Enable the plugin in the composer. Individual tools can be switched off on its own page.
Nothing has to be entered by hand: offline_access is what keeps the connector signed in, and this
endpoint registers clients dynamically, so there is no OAuth client to create and no redirect URI to
register anywhere.
After lanes link connect adds an account, refresh the plugin. The endpoint is stateless, so it
sends no notification that the tool list changed, and a client that does not ask again keeps the list
it first saw. A connected account that never appears is this, not a broken deployment.
When a working connector drops
A connector that was working and then reports the server as unreachable, or asks to be authorised again, is one of five things. Your endpoint's own log for the minute it happened separates them:
| In the log | What it was |
|---|---|
rejected request {"reason":"missing"} | The client sent no credential. It discarded its own, or is starting discovery |
rejected request {"reason":"invalid"} | It sent one this endpoint does not know |
warn refresh token replayed | A second copy of the client presented a spent refresh token. Refused, and the live session is untouched |
A browser prompt with no /token line | The client's refresh failed at the network level, and the error was swallowed rather than surfaced |
| Nothing at all | The call never left the client |
The last row is worth knowing, because from the outside it looks exactly like the others and it is the only one where the endpoint is not involved. No line means no request: no cold start, no timeout, no refusal. The connector decided by itself that this endpoint was unavailable. Reconnect it; there is nothing here to change.
A deployment scaling to zero is not this. It shuts down and restarts many times a day, requests queue behind the boot, and a cold start is a couple of seconds, visible in the latency column rather than as a failure.
Anything else
$ lanes link outputs --profile personal --workspace local --show
Endpoint
http://127.0.0.1:7337/mcp running
Token
llk_…Any client that speaks streamable HTTP MCP with a static Authorization: Bearer header can be pointed
at those two values. --workspace cloud prints the deployed pair instead.
Next: Deploy to your own cloud, which is what claude.ai, ChatGPT, and your phone need.