cdpfleet Docs Sign up free

Examples & benchmarks / #79 · 2026-10-31 · Example · Easy · for ai agents, llm data

MCP in action: an MCP client drives a cloud browser through cdpfleet.com/mcp

A script connects to the hosted MCP server with its API key and does what an AI assistant does: list the tools, launch a browser, open a page, read it as an accessibility snapshot, extract data and close the browser — once in one MCP session, and once with a new MCP session for every call, as ChatGPT does. In five languages, four of them on the official MCP SDK.

Chromium

Run on production on 2026-10-31: ✓ Node.js ✓ Python ✓ Java ✓ C# ✓ Go

The problem

Claude, ChatGPT, Gemini and Cursor reach cdpfleet through its hosted MCP server, https://cdpfleet.com/mcp. What they actually do there is a handful of JSON-RPC calls: initialize, list the tools, call them one by one. Doing the same from a script shows what each call costs, what comes back, and what happens to the browser when the client opens a new MCP session for every call — which is how ChatGPT talks to MCP servers.

What we used, and why

WhatWhy
https://cdpfleet.com/mcpThe hosted MCP server, over Streamable HTTP. Assistants sign in with OAuth; a script sends its API key as Authorization: Bearer <key>.
The official MCP SDKsTypeScript @modelcontextprotocol/sdk 1.32.2, Python mcp 2.3.0, Java io.modelcontextprotocol.sdk:mcp 2.1.0 and C# ModelContextProtocol 2.3.0. The Go program speaks the protocol with the standard library (JSON-RPC 2.0 over HTTP), because the official Go SDK needs Go 1.23 or newer.
launch_browserHeadless Chromium through your own proxy (proxy), without a recording (record: false). One browser at a time per API key.
navigate, snapshot, evaluateOpen the catalogue, read it as an accessibility snapshot (the whole page and just the product list), and run an extraction function in the page.
close_browser, accountEnd the browser session, then check the plan's live sessions.
A new MCP session per callThe second run initializes a fresh MCP session for each tool call and drops it without a DELETE, the way ChatGPT does.

How it works

  1. Connect: initialize an MCP session with the API key as bearer token and list the tools.
  2. Launch headless Chromium, open books.toscrape.com, take an accessibility snapshot of the page and of its product list, read the HTML length, and extract every book with evaluate.
  3. Close the browser, try one more navigate, and read account; end the MCP session.
  4. Then do it again with a new MCP session for every call: launch, open page 2, read where the browser is, close.

The code

The same program in five languages (also on GitHub, with the raw output). Set these environment variables first:

// npm install @modelcontextprotocol/[email protected]
// env: CDPFLEET_API_KEY, PROXY_URL
import { Client } from '@modelcontextprotocol/sdk/client/index.js';
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js';

const MCP = new URL('https://cdpfleet.com/mcp');
const KEY = process.env.CDPFLEET_API_KEY;
const PROXY = process.env.PROXY_URL;
const EXTRACT = "() => [...document.querySelectorAll('article.product_pod')].map((a) => ({ title: a.querySelector('h3 a').title, price: a.querySelector('.price_color').textContent }))";

// A script authenticates with its API key as a bearer token (assistants use OAuth instead).
async function connect() {
  const transport = new StreamableHTTPClientTransport(MCP, { requestInit: { headers: { authorization: `Bearer ${KEY}` } } });
  const client = new Client({ name: 'cdpfleet-case-79', version: '1.0.0' });
  await client.connect(transport); // initialize + initialized
  return { client, transport };
}

// One tool call, timed; the text of its result.
async function call(client, log, name, args = {}) {
  const t = Date.now();
  const r = await client.callTool({ name, arguments: args });
  const text = r.content.filter((c) => c.type === 'text').map((c) => c.text).join('\n');
  log.push({ tool: name, ms: Date.now() - t, error: Boolean(r.isError) });
  return { text, error: Boolean(r.isError) };
}

// A shared proxy drops the odd connection: one retry of a failed page load.
async function navigate(client, log, url) {
  const r = await call(client, log, 'navigate', { url });
  return r.error ? call(client, log, 'navigate', { url }) : r;
}

// ---- 1. One MCP session for the whole job, as Claude or Cursor keep one.
async function oneSession() {
  const log = [];
  let t = Date.now();
  const { client, transport } = await connect();
  log.push({ tool: 'initialize', ms: Date.now() - t, error: false });
  t = Date.now();
  const { tools } = await client.listTools();
  log.push({ tool: 'tools/list', ms: Date.now() - t, error: false });
  const out = { mcp_session: Boolean(transport.sessionId), tools: tools.map((x) => x.name).sort() };

  const launched = await call(client, log, 'launch_browser', { browser: 'chromium', headless: true, proxy: PROXY, record: false });
  // "Launched chromium 148.0.7778.5 (linux_headless, 1 thread(s)), session <id>. …"
  const m = /^Launched (\S+) (\S+) \((\w+), (\d+) thread/.exec(launched.text);
  Object.assign(out, { browser: m?.[1], version: m?.[2], resource_class: m?.[3], weight: Number(m?.[4]) });
  out.session_id = /session ([0-9a-f-]{36})/.exec(launched.text)?.[1] ?? null;
  out.navigate = (await navigate(client, log, 'https://books.toscrape.com/')).text.split('\n')[0];
  out.snapshot_chars = (await call(client, log, 'snapshot')).text.length;
  out.snapshot_list_chars = (await call(client, log, 'snapshot', { selector: 'ol.row' })).text.length;
  out.html_chars = Number((await call(client, log, 'evaluate', { expression: 'document.documentElement.outerHTML.length' })).text);
  const books = JSON.parse((await call(client, log, 'evaluate', { expression: EXTRACT })).text);
  out.books = books.length;
  out.first_book = books[0];
  out.closed = (await call(client, log, 'close_browser')).text.startsWith(`Session ${out.session_id} ended`);
  const after = await call(client, log, 'navigate', { url: 'https://books.toscrape.com/' });
  out.after_close = { error: after.error, text: after.text.split('\n')[0] };
  const account = JSON.parse((await call(client, log, 'account')).text);
  out.live_sessions_after = account.live_sessions;
  await transport.terminateSession(); // DELETE: ends the MCP session
  await client.close();
  return { ...out, calls: log };
}

// ---- 2. A new MCP session for every tool call, as ChatGPT does.
async function perCallSessions() {
  const log = [];
  const sessions = [];
  async function once(name, args) {
    const t = Date.now();
    const { client, transport } = await connect();
    const r = name === 'navigate' ? await navigate(client, log, args.url) : await call(client, log, name, args);
    log[log.length - 1].with_initialize_ms = Date.now() - t;
    sessions.push(transport.sessionId);
    await client.close(); // no DELETE: the client just goes away
    return r;
  }
  const launched = await once('launch_browser', { browser: 'chromium', headless: true, proxy: PROXY, record: false });
  const sessionId = /session ([0-9a-f-]{36})/.exec(launched.text)?.[1] ?? null;
  const nav = await once('navigate', { url: 'https://books.toscrape.com/catalogue/page-2.html' });
  const where = await once('evaluate', { expression: '({ url: location.href, books: document.querySelectorAll("article.product_pod").length })' });
  const closed = await once('close_browser', {});
  return {
    mcp_sessions: sessions.length,
    distinct_mcp_sessions: new Set(sessions).size,
    browser_session_id: sessionId,
    navigate: nav.text.split('\n')[0],
    later_session_sees: JSON.parse(where.text),
    closed_same_browser: closed.text.startsWith(`Session ${sessionId} ended`),
    calls: log,
  };
}

const one = await oneSession();
const perCall = await perCallSessions();
console.log(JSON.stringify({ language: 'node', one_session: one, per_call: perCall, measured_at: new Date().toISOString().slice(0, 10) }, null, 2));

What we got

MCP sessionCallTimeResultMeasured
one for the whole runinitialize261 mssession id issued2026-10-31
one for the whole runtools/list46 ms13 tools: account, click, close_browser, evaluate, launch_browser, list_browsers, live_view, navigate, press, screenshot, snapshot, type, wait_for2026-10-31
one for the whole runlaunch_browser584 mschromium 148.0.7778.96, linux_headless, 1 thread2026-10-31
one for the whole runnavigate3,317 ms200 https://books.toscrape.com/2026-10-31
one for the whole runsnapshot190 ms16,662 characters (whole page)2026-10-31
one for the whole runsnapshot121 ms9,955 characters (the product list)2026-10-31
one for the whole runevaluate77 msHTML: 50,989 characters2026-10-31
one for the whole runevaluate65 ms20 books, first: A Light in the Attic (£51.77)2026-10-31
one for the whole runclose_browser29 mssession ended2026-10-31
one for the whole runnavigate25 mserror: No browser is open. Call launch_browser first.2026-10-31
one for the whole runaccount107 ms0 live sessions2026-10-31
new for each call (1 of 4, 4 distinct)launch_browser486 ms (541 ms with initialize)browser session 87f73103…2026-10-31
new for each call (2 of 4, 4 distinct)navigate4,413 ms (4,480 ms with initialize)200 https://books.toscrape.com/catalogue/page-2.html2026-10-31
new for each call (3 of 4, 4 distinct)evaluate36 ms (137 ms with initialize)20 books at /catalogue/page-2.html2026-10-31
new for each call (4 of 4, 4 distinct)close_browser31 ms (91 ms with initialize)the same browser session ended2026-10-31

From the Node.js run on 2026-10-31. IP addresses are replaced with placeholders (203.0.113.x); equal addresses stay equal. The other languages produced the same findings.

Raw output (Node.js)
{
  "language": "node",
  "one_session": {
    "mcp_session": true,
    "tools": [
      "account",
      "click",
      "close_browser",
      "evaluate",
      "launch_browser",
      "list_browsers",
      "live_view",
      "navigate",
      "press",
      "screenshot",
      "snapshot",
      "type",
      "wait_for"
    ],
    "browser": "chromium",
    "version": "148.0.7778.96",
    "resource_class": "linux_headless",
    "weight": 1,
    "session_id": "5b23c145-66cd-4d42-9435-171dd9af475d",
    "navigate": "200 https://books.toscrape.com/",
    "snapshot_chars": 16662,
    "snapshot_list_chars": 9955,
    "html_chars": 50989,
    "books": 20,
    "first_book": {
      "title": "A Light in the Attic",
      "price": "£51.77"
    },
    "closed": true,
    "after_close": {
      "error": true,
      "text": "No browser is open. Call launch_browser first."
    },
    "live_sessions_after": 0,
    "calls": [
      {
        "tool": "initialize",
        "ms": 261,
        "error": false
      },
      {
        "tool": "tools/list",
        "ms": 46,
        "error": false
      },
      {
        "tool": "launch_browser",
        "ms": 584,
        "error": false
      },
      {
        "tool": "navigate",
        "ms": 3317,
        "error": false
      },
      {
        "tool": "snapshot",
        "ms": 190,
        "error": false
      },
      {
        "tool": "snapshot",
        "ms": 121,
        "error": false
      },
      {
        "tool": "evaluate",
        "ms": 77,
        "error": false
      },
      {
        "tool": "evaluate",
        "ms": 65,
        "error": false
      },
      {
        "tool": "close_browser",
        "ms": 29,
        "error": false
      },
      {
        "tool": "navigate",
        "ms": 25,
        "error": true
      },
      {
        "tool": "account",
        "ms": 107,
        "error": false
      }
    ]
  },
  "per_call": {
    "mcp_sessions": 4,
    "distinct_mcp_sessions": 4,
    "browser_session_id": "87f73103-8085-4ebe-956a-a86284f16b62",
    "navigate": "200 https://books.toscrape.com/catalogue/page-2.html",
    "later_session_sees": {
      "url": "https://books.toscrape.com/catalogue/page-2.html",
      "books": 20
    },
    "closed_same_browser": true,
    "calls": [
      {
        "tool": "launch_browser",
        "ms": 486,
        "error": false,
        "with_initialize_ms": 541
      },
      {
        "tool": "navigate",
        "ms": 4413,
        "error": false,
        "with_initialize_ms": 4480
      },
      {
        "tool": "evaluate",
        "ms": 36,
        "error": false,
        "with_initialize_ms": 137
      },
      {
        "tool": "close_browser",
        "ms": 31,
        "error": false,
        "with_initialize_ms": 91
      }
    ]
  },
  "measured_at": "2026-10-31"
}

Takeaways