-
Notifications
You must be signed in to change notification settings - Fork 0
Sessions
Name your session, and the browser that belongs to that name is yours.
There is no session id anywhere in this API. No tool takes one, no endpoint takes one, no result carries one. You say who you are and the server hands you the browser that name is holding.
| How to name it | Looks like | Use it when |
|---|---|---|
| a URL parameter | …/mcp?session=research-bot |
the usual case — one credential, each caller named in its own URL |
| a header | X-Session-Key: research-bot |
an operator pins one session to one credential |
| stdio | nothing to do | one process serves one client, and it is named stdio
|
Sending both is a 400. Not a precedence one of them wins: a request carrying two names has two ideas about who is calling, and quietly picking one hides that from whoever wired it up.
Sending neither is a 400 on anything touching a browser, with a message
saying how to fix it. The one exception is the flow library, which falls back to
the shared global library — which everyone may read and run, and nobody may
write.
The name has to be usable as a directory name, because it is one: it is where
your flows and your kept files live. Letters, digits, dots, dashes and
underscores, starting with a letter or digit, 64 characters at most. stdio and
global are reserved.
n8n opens a new MCP transport for every tool call, so nothing the transport negotiates is ever the same twice. Name the session in the URL and it simply works. See Installing.
A session name is a credential. The bearer token is the only thing guarding it, so anyone who can call this server can name your session and drive your browser. Sharing one is deliberate and by name; doing it by accident is now easy, which is worth knowing.
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.
Call it again later with the same name — after a reconnect, a client restart, a week — and you are back on the same browser at the same page.
session://current reports the session name, which browser it is running, the
page it is on, whether one is open at all (live), whether you are inside a
frame, and the window size. Reading it never opens a browser.
A client whose model cannot read MCP resources reads it with
read_resource(uri="session://current"). Over HTTP it is GET /browser.
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.
The session outlives the browsers it holds. live is false 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.
| Who owns it | Typical | |
|---|---|---|
| how long a browser lives | the Grid — SE_NODE_SESSION_TIMEOUT
|
300s idle |
| how long a session 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.
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.
The action pages are generated from openapi.yaml, which is itself generated from the live MCP tool schemas — so they describe the server that shipped, not the one someone remembered. Prose belongs in wiki-notes/<tool>.md in the repo.
selenium-flow · MIT
Start here
Guides
Lifecycle
Going places
Doing things
Getting things out