Setup
Add the hosted or the local MCP server to Claude Desktop, Claude Code, Cursor or any other MCP client, what it checks over HTTP, and how a client signs people in with OAuth.
The MCP server runs in two ways. Boxline hosts it at https://mcp.boxline.dev/mcp, or your client starts it on your computer and talks to it over stdio. It is the same set of tools either way, and either way the server talks to the API over HTTPS. You can also host it yourself. To connect ChatGPT, Claude or Claude Code with a Boxline sign-in and no API key, read Connect Boxline to ChatGPT, Claude and Claude Code.
Open source (MIT licence): github.com/Boxline-dev/mcp. The package on npm: @boxline/mcp.
It speaks MCP revision 2026-07-28. Clients that speak the 2025 protocol keep working.
Hosted server
Connect to https://mcp.boxline.dev/mcp with Streamable HTTP, and send a token as Authorization: Bearer <token> on every request. The token is an API key (create one just for MCP in the console), or the access token an app got when you connected it with a Boxline sign-in (see Sign in with OAuth).
The server checks the token with the API before it serves anything. A missing, expired, revoked or unknown token gets 401, and an app then refreshes the token or asks you to sign in again.
claude mcp add --transport http boxline https://mcp.boxline.dev/mcp \ --header "Authorization: Bearer your-api-key"Cursor, and other clients that take a URL and headers in their config, use the same two values:
{ "mcpServers": { "boxline": { "url": "https://mcp.boxline.dev/mcp", "headers": { "Authorization": "Bearer your-api-key" } } }}Claude Desktop starts servers from its config file, so it uses the stdio setup below. See Over HTTP for what the hosted server checks and limits.
Stdio: what it needs
| Variable | Value |
|---|---|
BOXLINE_API_KEY | Your API key. Create one just for MCP in the console. |
BOXLINE_API_URL | The API address, https://api.boxline.dev. |
The command is npx -y @boxline/mcp, so the computer needs Node 20 or later.
Claude Desktop
Claude Desktop starts the server on your computer (stdio). Add it to claude_desktop_config.json and restart the app. On macOS the file is in ~/Library/Application Support/Claude/; on Windows, in %APPDATA%\Claude\.
{ "mcpServers": { "boxline": { "command": "npx", "args": ["-y", "@boxline/mcp"], "env": { "BOXLINE_API_KEY": "your-api-key", "BOXLINE_API_URL": "https://api.boxline.dev" } } }}Claude Code
For the hosted server, use the command above. To start the server on your computer instead:
claude mcp add boxline \ -e BOXLINE_API_KEY=your-api-key \ -e BOXLINE_API_URL=https://api.boxline.dev \ -- npx -y @boxline/mcpRun claude mcp list to check that it is there.
Cursor
The hosted block above and this one go in .cursor/mcp.json in your project, or in ~/.cursor/mcp.json to turn it on everywhere. To start the server on your computer instead:
{ "mcpServers": { "boxline": { "command": "npx", "args": ["-y", "@boxline/mcp"], "env": { "BOXLINE_API_KEY": "your-api-key", "BOXLINE_API_URL": "https://api.boxline.dev" } } }}Other clients
If the client can connect to a Streamable HTTP server and send a header, use the hosted server above. Otherwise start npx -y @boxline/mcp as a stdio server with both environment variables set.
Sessions: no default session
The server keeps nothing between calls. session_create returns a sessionId, and every tool that works in a session takes it as a required sessionId. A call without it is refused before anything reaches the API.
session_create,session_list,credentials_list,web_fetch,web_search,web_screenshot,web_extract,web_crawl_startandweb_crawl_gettake nosessionId.- Tools that act on a session (
browser_login,browser_navigate,browser_click,browser_type,browser_press,browser_computer,browser_act,shell_exec,files_writeandfiles_delete) resume a stopped session first, as it was. - Tools that read the browser (
browser_screenshotandbrowser_read) never resume a session, because a resume starts a machine and billing. On a stopped session they answer that it is stopped and to callsession_resume, then retry. - Tools that work on the session as it is (
session_stop,session_resume,session_delete,files_listandfiles_read): the stop, resume and delete tools, and the two that read files, which read a stopped session’s saved files without starting a machine. - A session without a browser (
browserfalse insession_create) is a shell-only session: thebrowser_*tools answerbrowser_disabled, and the shell, file and session tools work as usual. A session without a shell answersshell_disabledtoshell_exec. - Lost a
sessionId?session_listfinds it again. - A session has no proxy unless
session_createasks for one. Withouttimeout, its time is your plan’s longest session, at most 30 minutes (15 minutes if the server cannot read your plan).
Every tool also has a title and read-only, destructive and open-world annotations. Read-only: session_list, browser_screenshot, browser_read, files_list, files_read, credentials_list, web_fetch, web_search, web_screenshot, web_extract and web_crawl_get. Destructive: session_delete, shell_exec, files_write and files_delete (files_write overwrites). Open-world: everything that reaches the web, which is every tool except session_create, session_list, session_stop, session_resume, session_delete, files_list, files_read, files_write, files_delete, credentials_list and web_crawl_get. Each tool’s page has its own.
Over HTTP
The hosted server and a server you start with --http behave the same way. See Host it yourself for the command and its settings.
- Stateless. Each request is served on its own: no
Mcp-Session-Id, nothing remembered on the server. Any number of copies behind a load balancer can serve any request, and a restart loses nothing.GET /mcpandDELETE /mcpanswer405. - Your token, per request. The server holds no key and never logs a token. It builds its API client from the token of each request, and checks the token with the API first. The answer, good or refused, is remembered for a minute by the token’s hash, so a token that was just revoked can take up to a minute to be refused here.
- No live view URL.
session_createandsession_resumeleave outliveUrlover HTTP, because anyone who has that link can control the browser. - Long calls stay open. A long tool call sends a keep-alive comment every 15 seconds, so a load balancer’s idle timeout does not cut it.
- Health check.
GET /healthzanswers200 okon any host, with no key. - Discovery.
GET /.well-known/oauth-protected-resource(also under/mcp) tells an OAuth client which authorization server to use, with no token. It is404on a server where OAuth is not set up.
| Check | Answer |
|---|---|
A path other than /mcp, /healthz and the /.well-known/ pages | 404 |
A Host not in MCP_ALLOWED_HOSTS, or a browser Origin that is not one of those hosts (clients that send no Origin pass) | 403 |
No Authorization: Bearer token | 401, with WWW-Authenticate: Bearer. Where OAuth is set up, it also names the discovery page: resource_metadata="…" |
| A body over 4 MiB | 413 |
More requests at once than MCP_MAX_IN_FLIGHT (32 by default) | 503 |
| A token the API refuses: expired, revoked or unknown | 401. Where OAuth is set up, WWW-Authenticate also has error="invalid_token" |
Only a 401 from the API refuses a token. Any other answer, such as the API being down, lets the request through, and the tool call reports the problem.
Sign in with OAuth
A client that supports OAuth can sign a person in to Boxline instead of asking for an API key. ChatGPT and Claude do this when someone chooses Connect. Boxline is the authorization server, at https://api.boxline.dev. This section is for people who build or review a client. To connect an app, read Connect Boxline to ChatGPT, Claude and Claude Code.
- OAuth 2.1 authorization code with PKCE. Only
S256is accepted. - Public clients only: there are no client secrets, and
token_endpoint_auth_methodisnone. - A client registers itself (dynamic client registration, RFC 7591). Client ID Metadata Documents are not supported.
- If a client sends a
resource(RFC 8707), it must behttps://mcp.boxline.dev/mcp. Responses carryiss(RFC 9207).
The flow
- Call the MCP endpoint with no token. The answer is
401withWWW-Authenticate: Bearer resource_metadata="…", scope="boxline openid email offline_access"(RFC 9728). - Read the protected resource metadata it points to. It names the authorization server. Then read that server’s metadata at
https://api.boxline.dev/.well-known/oauth-authorization-server(RFC 8414;/.well-known/openid-configurationanswers too). - Register once with
POST /oauth/register. - Send the person’s browser to
/oauth/authorizewith a PKCE challenge. They sign in to Boxline, pick a project and allow it. The browser returns to your redirect URI withcode,stateandiss, or witherror=access_deniedif they deny. - Call
POST /oauth/tokenwith the code and your PKCE verifier. You get an access token and a refresh token, and anid_tokenwhen the scope includesopenid. - Call the MCP endpoint with
Authorization: Bearer <access token>. Before it expires, call/oauth/tokenwith the refresh token for a new pair.
Endpoints
| Endpoint | What it takes |
|---|---|
POST /oauth/register | JSON with redirect_uris (1 to 10, required) and client_name (up to 100 characters). The answer is 201 with a client_id. 60 requests an hour per IP address. |
GET /oauth/authorize | response_type=code, client_id, redirect_uri, code_challenge and code_challenge_method=S256. Optional: state (up to 512 characters), scope, resource and nonce. 60 requests per 15 minutes per IP address. |
POST /oauth/token | A form body (JSON works too). grant_type=authorization_code with code, redirect_uri, client_id and code_verifier, or grant_type=refresh_token with refresh_token and client_id. 300 requests per 15 minutes per IP address, and 600 per client. |
POST /oauth/revoke | token, an access or a refresh token. It ends the whole connection. The answer is always 200. |
GET /oauth/userinfo | Authorization: Bearer with an access token that has openid. Returns sub, and with email also email, email_verified and name. |
GET /oauth/jwks | The public key that signs the id_token (ES256). |
A redirect URI must be https, or http on localhost or 127.0.0.1, and it is compared exactly. For a registered loopback address only, the port may differ, as Claude Code needs. A URI has no fragment or user name, and is at most 2048 characters. A registration that goes unused for 30 days, and has no live connection, is deleted.
| Scope | What it allows |
|---|---|
boxline | Use Boxline for the person: browser and shell sessions and their files, in the project they chose. The default. Without it, a token reaches nothing. |
openid | Know which Boxline account this is: an id_token, and /oauth/userinfo. |
email | See the person’s email address and name. |
offline_access | Accepted for compatibility. Refresh tokens are always issued, whether or not a client asks for this scope. |
Tokens
| Token | Lasts | Notes |
|---|---|---|
| Authorization code | 60 seconds, once | Used twice, it ends the connection its first use made. |
Access token, bxo_… | 1 hour | The Bearer token for the MCP server. |
Refresh token, bxr_… | 30 days from when it was issued | Replaced on every use. An old one used again ends the whole connection, and so does a refresh after the person lost their role in the project. |
id_token | 1 hour | A JWT signed with ES256, with the openid scope. |
- Errors follow RFC 6749:
400witherroranderror_description(such asinvalid_request,invalid_grantandinvalid_scope),401invalid_clientfor an unknown client, and429withRetry-After. An error never says whether a token was wrong or dead. - What a token can reach. An access token works only for the routes the tools use, in the project the person chose, with their role. Anything else gets
403oauth_route_forbidden, a request that names a saved credential getsoauth_credentials_refused, and a token without theboxlinescope getsinsufficient_scope. The server gives it thedirectorytools: nobrowser_loginand nocredentials_list. - The person can end a connection at any time under Settings → Connected apps. See Connected apps.
Tools
The server has 26 tools. Each one has a page with its inputs, an example call and what it returns.
| Tool | What it does |
|---|---|
session_create | Start a new session and get its sessionId. |
session_list | Find your sessions again. |
session_stop | Stop a session and keep it. |
session_resume | Bring a stopped session back. |
session_delete | Delete a session for good. |
browser_login | Sign in with a saved password, in one call. |
browser_navigate | Open a URL in the session's browser. |
browser_click | Click an element or a point on the page. |
browser_type | Type text, or a saved credential. |
browser_press | Press a key, a key combination, or several in a row. |
browser_computer | Run one computer-use action from a model, and see the screen. |
browser_act | Do one or more steps in the browser, in plain English or as exact actions. |
browser_screenshot | Take a screenshot of the current tab. |
browser_read | Read the current page as text. |
shell_exec | Run a bash command in the session's shell. |
files_list | List files in the workspace. |
files_read | Read a text file from the workspace. |
files_write | Write a text file into the workspace. |
files_delete | Delete a file from the workspace. |
credentials_list | List the saved credentials (never their values). |
web_fetch | Fetch a page without a session. |
web_search | Search the web, with no session. |
web_screenshot | Screenshot a web page, with no session. |
web_extract | Get JSON from up to 10 pages, with no session. |
web_crawl_start | Crawl a site from a start URL, with no session. |
web_crawl_get | Get a crawl's state and its next pages. |
Results longer than 40,000 characters are cut, with a note at the end. A failed call comes back marked as an error, with the API’s error code and message.
These docs describe a beta API. Endpoints, fields and SDK methods may change before general availability; breaking changes will be listed in the changelog.