Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/googlecolab/colab-mcp/llms.txt

Use this file to discover all available pages before exploring further.

ColabWebSocketServer is the transport backbone of Colab MCP. It starts a WebSocket server bound to localhost, generates a one-time secret token, and waits for the Google Colab browser session to connect. Once connected, it acts as a transparent bridge: messages arriving from the browser are placed into a read_stream that the MCP server reads from, and messages the MCP server writes to write_stream are forwarded to the browser. The server enforces strict origin validation, token-based authentication, and single-client exclusivity so that only one trusted Colab tab can communicate with the local agent at any moment.

Key Design Decisions

Random localhost port

The server binds to localhost with port=0, letting the OS assign a free ephemeral port. The chosen port is stored in server.port after startup and embedded in the Colab URL so the browser knows where to connect.

mcp subprotocol only

The WebSocket server advertises and requires the mcp subprotocol. Connections that do not negotiate this subprotocol are rejected before the handler runs.

Origin allow-list

Accepted origins are https://colab.research.google.com and https://colab.google.com. Any connection from another origin is rejected at the WebSocket handshake level by the websockets library.

One-time token

A 16-byte URL-safe token is generated with secrets.token_urlsafe(16) on construction. It is embedded in the Colab URL fragment and must be presented on every connection attempt.

Single-client lock

An asyncio.Lock called connection_lock is acquired for the lifetime of each connection. A second connection arriving while the lock is held is immediately closed with code 1013.

Live-connection event

An asyncio.Event called connection_live is set when a client connects and cleared when it disconnects. Other parts of the system await this event to know when the Colab session is reachable.
The server rejects WebSocket connections from any origin other than https://colab.research.google.com and https://colab.google.com. Attempts from other origins (including other localhost clients) are refused at the WebSocket handshake before the authentication logic even runs.

Authentication Flow

Every incoming connection is checked by _validate_authorization before the handler is entered. The method accepts a token presented in one of two ways:
1

URL query parameter (preferred by the Colab UI)

The Colab browser session appends the token as a query parameter when opening the WebSocket:
ws://localhost:<port>?access_token=<token>
If the request path contains access_token=<token> matching the server’s token, authentication succeeds immediately.
2

Authorization header (Bearer scheme)

Alternatively, clients may present the token in the HTTP Authorization header using the Bearer scheme:
Authorization: Bearer <token>
The method checks for the header, parses out the scheme and token value, and compares it to the server’s token.

HTTP Response Codes for Auth Failures

CodeReason
401Authorization header is absent and no access_token query parameter is present
400Authorization header is malformed (wrong scheme or unparseable format)
403Token is present and well-formed but does not match the server’s token
1013 (WebSocket close)A second connection arrives while one is already active

Message Loop

Once a connection is authenticated and the lock is acquired, two concurrent tasks run for the duration of the connection:
1

_read_from_socket

Iterates over incoming WebSocket messages. Each message is validated as a JSONRPCMessage using Pydantic. Valid messages are wrapped in a SessionMessage and sent into read_stream for the FastMCP server to consume. Validation errors are forwarded as Exception objects so upstream layers can handle malformed input gracefully.
async for msg in websocket:
    try:
        client_message = types.JSONRPCMessage.model_validate_json(msg)
    except ValidationError as exc:
        await self._read_stream_writer.send(exc)
        continue
    await self._read_stream_writer.send(SessionMessage(client_message))
2

_write_to_socket

Reads SessionMessage objects from write_stream — placed there by the FastMCP proxy — serializes them to JSON with model_dump_json, and sends them to the browser over the WebSocket. The loop exits cleanly when the connection closes or the write stream is closed.
msg = await self._write_stream_reader.receive()
json_obj = msg.message.model_dump_json(by_alias=True, exclude_none=True)
await websocket.send(json_obj)
When either task finishes (because the socket closed or an error occurred), the other task is cancelled, connection_live is cleared, and the lock is released — making the server ready to accept a new connection.

Using ColabWebSocketServer

ColabWebSocketServer is an async context manager. The server starts on __aenter__ and shuts down (closing streams and the underlying socket) on __aexit__.
async with ColabWebSocketServer() as server:
    print(f"Listening on ws://localhost:{server.port}")
    print(f"Token: {server.token}")
    await server.connection_live.wait()  # blocks until Colab connects
After __aenter__ returns, server.port holds the OS-assigned port and server.token holds the authentication secret. Awaiting server.connection_live.wait() blocks until a Colab browser session successfully authenticates and opens its WebSocket connection.

Build docs developers (and LLMs) love