About Notte
Notte is an MCP server published by nottelabs in the Developer Tools category: cloud browser infrastructure and web automation platform for your AI and coding agents.
The repository has 2.0K stars and 186 forks, with the last commit yesterday. Recent commit activity is the strongest signal that reported issues will get attention.
Install
git clone https://github.com/nottelabs/notteThis server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.
Ask AI
Ask AI about Notte
Powered by Claude · Grounded in docs
Security checks
Static checks derived from the repository yesterday. Heuristics on the source, not an audit.
- ✓README presentThe repository ships documentation.
- ✓License declaredUsable under a stated license.
- ✓Tests presentA test suite exists in the repository.
- ✓Dependencies pinnedA lockfile pins dependency versions.
- !Scoped permissionsDoesn't declare a permission scope. Assume it can do anything its process can.
1 check has not been run yet.
Releases
README
Browser infrastructure for AI agents and web automation
What is Notte?
Notte gives AI agents and scripts a reliable browser. Drive cloud browser sessions with built-in stealth, CAPTCHA solving and proxies, or a local browser with the same API; deploy automations as functions; and keep credentials and identities out of your code. Read more in our documentation here 🔥
Choose your starting point:
| Product | Use it for |
|---|---|
| Notte CLI | Drive browsers from the terminal, scripts or CI |
| Notte MCP | Give your coding agent a cloud browser |
| Browser sessions | Run Playwright or Puppeteer on stealth cloud browsers |
| Browser functions | Deploy a browser script as an API or scheduled job |
| Anything API | Turn a described task into a deployed API |
| Scraping | Extract structured data from a page |
Open-source core:
- Local browser sessions → A Playwright-based browser that turns pages into actionable observations and clean markdown
- Scraping → Markdown, images or structured data (Pydantic models) from any page
API service (recommended):
- Stealth browser sessions → Hosted browsers with CAPTCHA solving, proxies and anti-detection, reachable over CDP from Playwright, Puppeteer or Selenium
- Functions → Deploy a Python automation and run it on demand, on a schedule, or behind an API endpoint
- Secrets vaults → Credential management for emails, passwords, MFA tokens, credit cards
- Digital personas → Identities with their own email, phone number and automated 2FA
Coming from notte 1.9 or earlier? The agent loop (
notte.Agent,client.Agent,AgentFallback), workflows and local function execution were removed in 1.10. Coding agents such as Claude Code drive Notte through the CLI, the MCP server or this SDK instead. If you still need the agent APIs, pinnotte<1.10.
Quickstart
For JavaScript and TypeScript, see the Node SDK (npm install notte-sdk).
pip install notte
patchright install --with-deps chromium
Local sessions
Run a browser on your machine, observe the actions available on a page, execute them and scrape the result. No API key needed:
import notte
with notte.Session(headless=True) as session:
_ = session.execute(type="goto", url="https://www.notte.cc")
obs = session.observe()
print(obs.space.description)
markdown = session.scrape(only_main_content=True)
Structured scraping on a local session hands the page markdown to an extractor. Install the LLM extractor with pip install 'notte[llm]' and set OPENROUTER_API_KEY; it defaults to google/gemini-3.5-flash-lite. You can also pass your own extractor with notte.Session(extractor=...).
Set OPENROUTER_API_KEY to use OpenRouter with google/gemini-3.5-flash-lite by default.
For another OpenAI-compatible endpoint, set all three of NOTTE_LLM_BASE_URL,
NOTTE_LLM_API_KEY, and NOTTE_LLM_MODEL (the endpoint's own model ID):
- OpenAI:
NOTTE_LLM_BASE_URL=https://api.openai.com/v1,NOTTE_LLM_MODEL=gpt-4o. - Gemini:
NOTTE_LLM_BASE_URL=https://generativelanguage.googleapis.com/v1beta/openai,NOTTE_LLM_MODEL=gemini-3.5-flash-lite. - Ollama/custom:
NOTTE_LLM_BASE_URL=http://localhost:11434/v1, your local model ID, andNOTTE_LLM_API_KEY=ollamafor an unauthenticated local server. Ollama has not been live-tested.
NOTTE_LLM_API_KEY overrides OPENROUTER_API_KEY. The latter is used only at the default
OpenRouter URL, so it is never sent to a custom endpoint. The per-call model= argument
selects a model at the configured endpoint without translating its ID.
See notte-llm configuration for all settings.
Cloud sessions
The API hosts the browser sessions for you and adds stealth, CAPTCHA solving, proxies, live view and recordings. Sign up on the Notte Console and create a free Notte API key 🔑
from notte_sdk import NotteClient
client = NotteClient() # reads NOTTE_API_KEY
with client.Session(open_viewer=False) as session:
_ = session.execute(type="goto", url="https://news.ycombinator.com")
actions = session.observe(instructions="open the comments of the first post")
if actions:
_ = session.execute(actions[0])
comments = session.scrape(only_main_content=True)
Every cloud session exposes a CDP endpoint, so you can keep your existing Playwright code (pip install 'notte-sdk[playwright]'):
from notte_sdk import NotteClient
from playwright.sync_api import sync_playwright
client = NotteClient()
with client.Session(open_viewer=False) as session, sync_playwright() as p:
browser = p.chromium.connect_over_cdp(session.cdp_url())
page = browser.contexts[0].pages[0]
_ = page.goto("https://www.notte.cc")
print(page.title())
Session features
Stealth
Built-in proxies, CAPTCHA solving and real Chrome browsers to get through bot detection.
from notte_sdk import NotteClient
client = NotteClient()
with client.Session(
solve_captchas=True,
proxies=True, # US-based proxy
browser_type="chrome",
) as session:
_ = session.execute(type="goto", url="https://www.google.com/recaptcha/api2/demo")
result = session.execute(type="captcha_solve", captcha_type="recaptcha", raise_on_failure=False)
For a custom proxy, set PROXY_SERVER, PROXY_USERNAME, and PROXY_PASSWORD to your provider's connection details.
import os
from notte_sdk import NotteClient
from notte_sdk.types import ExternalProxy
client = NotteClient()
proxy_settings = ExternalProxy(
server=os.environ["PROXY_SERVER"],
username=os.environ["PROXY_USERNAME"],
password=os.environ["PROXY_PASSWORD"],
)
with client.Session(proxies=[proxy_settings]) as session:
_ = session.execute(type="goto", url="https://www.notte.cc")
File download / upload
Files uploaded to a session can be set on file inputs, and files downloaded by the browser are stored with the session.
Set UPLOAD_FILE_PATH to a local document and UPLOAD_URL to a page with a file input.
import os
from notte_sdk import NotteClient
from notte_sdk.types import FileSource
client = NotteClient()
with client.Session() as session:
storage = session.storage
uploaded_file = storage.upload(os.environ["UPLOAD_FILE_PATH"])
_ = session.execute(type="goto", url=os.environ["UPLOAD_URL"])
_ = session.execute(type="upload_file", selector="input[type=file]", file_path=uploaded_file.filename)
# Download the files the browser downloaded (100 files per page)
offset = 0
while True:
downloaded_files = storage.list(source=FileSource.SESSION_DOWNLOAD, offset=offset)
for file in downloaded_files.files:
storage.download(file_id=file.id, local_dir="./results")
offset += len(downloaded_files.files)
if offset >= downloaded_files.total or not downloaded_files.files:
break
Cookies / Auth Sessions
Cookies provide a flexible way to authenticate your sessions. While we recommend using the secure vault for credential management, cookies offer an alternative approach for certain use cases.
from notte_sdk import NotteClient
import json
client = NotteClient()
# Upload cookies for authentication
cookies = [
{
"name": "sb-db-auth-token",
"value": "base64-cookie-value",
"domain": "github.com",
"path": "/",
"expires": 9778363203.913704,
"httpOnly": False,
"secure": False,
"sameSite": "Lax"
}
]
with client.Session() as session:
session.set_cookies(cookies=cookies) # or cookie_file="path/to/cookies.json"
_ = session.execute(type="goto", url="https://github.com/nottelabs/notte")
# Get cookies from the session
cookies_resp = session.get_cookies()
with open("cookies.json", "w") as f:
json.dump(cookies_resp, f)
Bring your own browser
Point a session at any CDP-compatible browser (another provider, or a browser you run yourself) to use Notte's observe / execute / scrape on top of it. Set EXTERNAL_CDP_URL to the WebSocket URL of a running browser.
import os
from notte_sdk import NotteClient
client = NotteClient()
cdp_url = os.environ["EXTERNAL_CDP_URL"]
with client.Session(cdp_url=cdp_url, proxies=False, viewport_width=None, viewport_height=None) as session:
_ = session.execute(type="goto", url="https://www.notte.cc/pricing")
pricing = session.scrape(instructions="extract the pricing plans")
Vaults
Store credentials in a vault and let the session fill them in: your code (and the LLMs that write it) only ever sees placeholders. Set GITHUB_EMAIL and GITHUB_PASSWORD to try it.
import os
from notte import EMAIL, PASSWORD
from notte_sdk import NotteClient
client = NotteClient()
with client.Vault() as vault:
vault.add_credentials(
url="https://github.com",
email=os.environ["GITHUB_EMAIL"],
password=os.environ["GITHUB_PASSWORD"],
)
with client.Session(vault_id=vault.vault_id) as session:
_ = session.execute(type="goto", url="https://github.com/login")
_ = session.execute(type="form_fill", value={"email": EMAIL, "current_password": PASSWORD})
Personas
Personas are digital identities with a unique email address (and optionally a phone number) whose inbox your automation can read, e.g. for sign-ups and email verification codes.
from notte_sdk import NotteClient
client = NotteClient()
with client.Persona(create_phone_number=False) as persona:
print(f"Sign up with {persona.email}")
emails = persona.emails(only_unread=True)
Functions
Deploy a Python file with a run function and invoke it on the Notte cloud, from the SDK, the API or a schedule. Functions are where your automations, including the ones written by coding agents, run in production.
import tempfile
from notte_sdk import NotteClient
client = NotteClient()
code = '''
from notte_sdk import NotteClient
def run(url: str) -> str:
client = NotteClient()
with client.Session() as session:
_ = session.execute(type="goto", url=url)
return session.scrape(only_main_content=True)
'''
with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False) as f:
_ = f.write(code)
function = client.Function(path=f.name, name="scrape-page")
try:
result = function.run(url="https://www.notte.cc", stream=False)
print(result.result)
finally:
function.delete()
Scraping
For fast data extraction, the scraping endpoint creates and manages the session for you. Pass instructions or a Pydantic model for structured output.
from notte_sdk import NotteClient
from pydantic import BaseModel
client = NotteClient()
# Simple scraping
response = client.scrape(
url="https://notte.cc",
scrape_links=True,
only_main_content=True
)
# Structured scraping with custom instructions
class Post(BaseModel):
title: str
url: str
class Posts(BaseModel):
posts: list[Post]
response = client.scrape(
url="https://news.ycombinator.com",
response_format=Posts,
instructions="Extract the title and link of the first 5 posts"
)
Or directly with cURL
curl -X POST 'https://api.notte.cc/scrape' \
-H 'Authorization: Bearer <NOTTE-API-KEY>' \
-H 'Content-Type: application/json' \
-d '{
"url": "https://notte.cc",
"only_main_content": false
}'
License
This project is licensed under the Server Side Public License v1. See the LICENSE file for details.
Citation
If you use notte in your research or project, please cite:
@software{notte2025,
author = {Pinto, Andrea and Giordano, Lucas and {nottelabs-team}},
title = {Notte: Software suite for internet-native agentic systems},
url = {https://github.com/nottelabs/notte},
year = {2025},
publisher = {GitHub},
license = {SSPL-1.0}
version = {2.0.0},
}
Copyright © 2025 Notte Labs, Inc.
README fetched from the repository yesterday. The original is authoritative.