Codex supports MCP servers configured in ~/.codex/config.toml. browser.city ships a hosted MCP server, so you can give Codex real browser tools without running infrastructure locally.
1) Create an API key
Get a browser.city API key from your dashboard and export it:
Set BROWSERCITY_API_KEY in your environment.
2) Add browser.city to ~/.codex/config.toml
Add an MCP server entry (remote HTTP):
[mcp_servers.browsercity]url = "https://mcp.browser.city/mcp"bearer_token_env_var = "BROWSERCITY_API_KEY"[mcp_servers.browsercity]url = "https://mcp.browser.city/mcp"bearer_token_env_var = "BROWSERCITY_API_KEY"[mcp_servers.browsercity]url = "https://mcp.browser.city/mcp"bearer_token_env_var = "BROWSERCITY_API_KEY"[mcp_servers.browsercity]url = "https://mcp.browser.city/mcp"bearer_token_env_var = "BROWSERCITY_API_KEY"
If you prefer explicit headers, Codex also supports http_headers = { Authorization = "Bearer ${BROWSERCITY_API_KEY}" }.
3) Use it inside Codex
Once configured, Codex will discover browser.city tools (for example: browser_open, browser_navigate, browser_snapshot, browser_click, browser_markdown).
Try a prompt like:
Open a browser with browser.city, navigate to
https://example.com, and return the page as markdown.
4) Practical patterns (what actually ships)
Fast extraction (best for RAG / pipelines)
Use the Request API when you don’t need interaction:
const apiKey = process.env.BROWSERCITY_API_KEY!;const opts = { method: "POST", headers: { Authorization: `Bearer ${apiKey}` } };const res = await fetch("https://api.browser.city/v1/requests", { ...opts, body: JSON.stringify({ url: "https://example.com", markdown: true }),}).then((r) => r.json());console.log(res.content);import osimport requestsapi_key = os.environ["BROWSERCITY_API_KEY"]res = requests.post( "https://api.browser.city/v1/requests", headers={"Authorization": f"Bearer {api_key}"}, json={"url": "https://example.com", "markdown": True},).json()print(res["content"])using System.Net.Http.Headers;using System.Net.Http.Json;var apiKey = Environment.GetEnvironmentVariable("BROWSERCITY_API_KEY")!;var http = new HttpClient();http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);var res = await http.PostAsJsonAsync( "https://api.browser.city/v1/requests", new { url = "https://example.com", markdown = true });Console.WriteLine(await res.Content.ReadAsStringAsync());import java.net.URI;import java.net.http.*;var apiKey = System.getenv("BROWSERCITY_API_KEY");var http = HttpClient.newHttpClient();var req = HttpRequest.newBuilder() .uri(URI.create("https://api.browser.city/v1/requests")) .header("Authorization", "Bearer " + apiKey) .POST(HttpRequest.BodyPublishers.ofString( "{\"url\":\"https://example.com\",\"markdown\":true}")) .build();var res = http.send(req, HttpResponse.BodyHandlers.ofString());System.out.println(res.body());
Long-running workflows (logins, multi-step flows)
Use sessions + Playwright (keep your existing Playwright code):
import { chromium } from "playwright";const apiKey = process.env.BROWSERCITY_API_KEY!;const opts = { method: "POST", headers: { Authorization: `Bearer ${apiKey}` } };const session = await fetch("https://api.browser.city/v1/sessions", { ...opts, body: JSON.stringify({ browser: "chromium", egress: { mode: "managed", proxyType: "residential", country: "US" }, }),}).then((r) => r.json());const browser = await chromium.connect(session.endpoint, { headers: { Authorization: `Bearer ${session.token}` },});import osimport requestsfrom playwright.sync_api import sync_playwrightapi_key = os.environ["BROWSERCITY_API_KEY"]session = requests.post( "https://api.browser.city/v1/sessions", headers={"Authorization": f"Bearer {api_key}"}, json={"browser": "chromium", "egress": {"mode": "managed", "proxyType": "residential", "country": "US"}},).json()with sync_playwright() as p: browser = p.chromium.connect( session["endpoint"], headers={"Authorization": f"Bearer {session['token']}"}, )using Microsoft.Playwright;using System.Net.Http.Headers;using System.Net.Http.Json;var apiKey = Environment.GetEnvironmentVariable("BROWSERCITY_API_KEY")!;var http = new HttpClient();http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);var res = await http.PostAsJsonAsync( "https://api.browser.city/v1/sessions", new { browser = "chromium", egress = new { mode = "managed", proxyType = "residential", country = "US" } });var session = await res.Content.ReadFromJsonAsync<Session>() ?? throw new Exception("bad response");using var pw = await Playwright.CreateAsync();var browser = await pw.Chromium.ConnectAsync(session.endpoint, new() { Headers = new Dictionary<string, string> { ["Authorization"] = $"Bearer {session.token}", },});public record Session(string endpoint, string token);import com.microsoft.playwright.*;import java.net.URI;import java.net.http.*;var apiKey = System.getenv("BROWSERCITY_API_KEY");var http = HttpClient.newHttpClient();var res = http.send( HttpRequest.newBuilder() .uri(URI.create("https://api.browser.city/v1/sessions")) .header("Authorization", "Bearer " + apiKey) .POST(HttpRequest.BodyPublishers.ofString("{\"browser\":\"chromium\"}")) .build(), HttpResponse.BodyHandlers.ofString());var session = res.body();try (var pw = Playwright.create()) { var browser = pw.chromium().connect( extractEndpoint(session), new BrowserType.ConnectOptions().setHeaders( java.util.Map.of("Authorization", "Bearer " + extractToken(session))));}static String extractEndpoint(String json) { return ""; }static String extractToken(String json) { return ""; }