Skip to content

Sessions

Kelly Ferrone edited this page Sep 10, 2026 · 4 revisions

Sessions

Whether you pass session_id, and what happens when you get it wrong.

open_session always comes first

Nothing opens a browser implicitly. That is deliberate: open_session is the only place a window size and the timeouts can be chosen, and hiding the call hid the settings with it.

Two modes, and they are exclusive

Mode When The rule
saved the server can identify you never pass session_id — it is not even advertised
stateless it cannot, or you are on /browser/* session_id is required on every call

Using the wrong one fails every call the same way, so do not guess: read session://current, which reports the mode and links the reference that applies. Reading it never opens a browser — a null session_id means nothing is held yet.

If your client cannot read MCP resources, the current_session tool returns the same object. Add ?resources=off to make it visible.

Over the HTTP surface it is always session in, session out. That is its whole contract, so a workflow owns its session outright and can pass the id between steps.

How the server works out who you are

It takes the first of these it finds, and never invents one — a caller it cannot identify is stateless, not quietly handed somebody's browser.

Key How it is set How stable
a name you choose X-Session-Key header, else ?session=<name> on the MCP URL survives a client restart or reconnect
the MCP transport session the negotiated Mcp-Session-Id lasts the connection; a reconnect is a new key
stdio one process, one client lasts the process

The header wins over the query parameter. That ordering is a permission boundary, not a preference: the header is set inside a credential, which an administrator controls, while the query parameter is written by whoever wires up the call. Leaving the header unset delegates the choice to the implementor.

n8n opens a new MCP transport for every tool call, so the negotiated id is never the same twice and a saved session keyed on it can never be found. Name the session in the URL. See Installing.

What happens when the browser goes away

The Grid reaps idle browsers on its own timeout, so a remembered id can name one that is already gone. The next call notices, reopens, and returns to the page it was last on with the same settings — the refresh is invisible.

"The same settings" includes which browser it was. That is why the choice is stored as a setting rather than beside them: a Firefox session that came back as Chrome would be exactly the silent change of shape the stored settings exist to prevent. session://current reports it as browser.

A session is not a browser

The session outlives the browsers it holds. session_id is empty when it holds none — after the Grid reaps one, or an operator ends one from the admin UI — and that is an ordinary state.

What survives is the context: the browser choice, the window, the last page. So the recovery is always the same one call:

open_session()      # same browser, same window, back where you were

Which is also why you are never told "your browser was taken". You are told you have no browser, which is the branch you already handle.

Sessions themselves expire on SESSION_TTL, slid forward on every use — a day by default. Nothing else removes one.

There is one distinction that matters here, because getting it wrong strands browsers. A session that fails to answer is not necessarily gone:

The Grid says Means
200 healthy
500 unexpected alert open alive, and blocked by a dialog — answer it with dialog
404 invalid session id genuinely reaped

Only the last one counts as gone. Anything else, including a Grid that cannot be reached right now, is assumed alive: a wrong "alive" surfaces as a real error on the next call, while a wrong "dead" silently abandons a working browser and leaks its slot.

How long things last

Who owns it Typical
how long a browser lives the Grid — SE_NODE_SESSION_TIMEOUT 300s idle
how long a caller is remembered SESSION_TTL 3600s, slid forward on every call
where that is remembered SESSION_STORE memory, or redis to share it

Nothing runs a cleanup loop, and nothing should — the Grid expires browsers, the store expires its own keys.

Always close

end_browser frees the slot, and slots are the scarce resource: the Grid runs a handful of browsers in total and an abandoned one holds its place until it is reaped. Close on failure paths too.

If you are mid-task and the caller may come back, leaving it open is the right call — just know you are holding capacity while you do.

Clone this wiki locally