Skip to main content
Custom tools let you add structured WebMCP tools to websites that don’t register any, or wrap a multi-step flow in a single tool. You register a namespaced batch of definitions with a browser, and each tool appears in WebMCP discovery on every page its URL patterns match. Your agent then discovers and invokes it exactly like a tool the page registered itself. Use custom tools when you want an agent to repeat the same action on a site reliably. The agent gets a named tool with a typed input schema instead of inferring selectors or clicks on every run.

How custom tools work

Each definition in a batch has four parts: The two kinds run in different places: Because definitions contain functions, you send a batch as a JavaScript source string: an expression that evaluates to a non-empty array of definitions. It isn’t JSON.

Write a tool batch

This batch adds two tools to Hacker News. list_stories is a page tool that reads the current page. get_story_comments is a cdp tool that opens a story in the same tab and returns its top-level comments. Save it as hn-tools.js:
hn-tools.js

Page tools

A page tool’s execute is serialized and installed in each matching document, so it must be self-contained. It can’t reference variables or imports from the code that registered it. It receives the input object and returns a JSON-serializable value. Kernel doesn’t validate a page tool’s input against its inputSchema, so execute can receive missing, extra, or wrongly typed values. Check the values you depend on in execute, as list_stories does with its limit default.

CDP tools

A cdp tool’s execute runs in the browser’s REPL, so it can use every REPL helper, such as gotoUrl, waitForLoad, js, click, and cdp, along with state from earlier REPL cells. Kernel validates its input against inputSchema before running it, and its output against outputSchema when you provide one. A validation failure returns status: "error" with the reason in error_text, such as input failed JSON Schema validation: data/story_id must match pattern "^[0-9]+$". The second argument is a context object: Each entry in matches includes: When you invoke a cdp tool through the API, it runs independently of the page that registered it. That’s why get_story_comments can navigate its own tab and still return a result.
get_story_comments calls switchTab(), which changes the REPL’s attached tab for later REPL cells. Pass { targetId: matches[0].top_target_id } to js() instead when your tool only needs to read or evaluate code in the page.

Register tools

Call POST /browsers/{id_or_name}/webmcp/custom-tools with a namespace and the batch source. The batch is added atomically: if one definition is invalid, none of them are registered. These examples use an existing browser named hn-reader that’s open to https://news.ycombinator.com, and read the hn-tools.js file above. Set KERNEL_API_KEY in your environment and use Kernel SDK 0.112.0 or later.
The API returns HTTP 201 with the definitions it added. Kernel generates an id for each one:
A tool name must be unique within its namespace. Adding a name that already exists returns HTTP 409 and registers nothing. An invalid batch returns HTTP 400 with the reason, such as definition.match.url_patterns must be a non-empty array.

Discover and invoke custom tools

Custom tools appear in the same browser-wide snapshot as page-provided tools and are invoked through the same endpoint. A custom tool’s source also includes: A page can register a tool with the same name as one of yours, so select custom tools by source.custom.namespace as well as name. To list only page-provided tools, pass exclude_custom=true. The snippets below reuse the clients and session variables from register tools; place the Go snippet inside main. They list the first 5 stories, then fetch the top-level comments on the first one.
get_story_comments returns a result such as:
Custom tool invocations follow the same rules as page-provided tools: check status, never retry automatically after outcome_unknown or a transport failure, and discover again before selecting your next invocation. See invoke a tool and handle an unknown outcome.

Match URLs

Each entry in match.url_patterns has the form scheme://host/path: URL fragments are ignored. For example, https://shop.example.com/search* matches https://shop.example.com/search?q=shoes but not https://shop.example.com/cart. Patterns are checked against every top-level document and embedded frame, including out-of-process iframes. When any frame on a tab matches, the tool is exposed once on that tab’s top-level document. A cdp tool receives every matching frame in matches, so it can target the frame it needs.

Update and remove tools

Custom tools have two kinds of identifiers. Keep them straight:
  • A definition id (ct_…) identifies your registration. Use it to list and remove definitions. It stays the same until you remove the definition.
  • A tool_ref (wmcp_…) identifies one live registration on one tab. Use it only to invoke, and get a fresh one from discovery.
There’s no in-place update. To change one tool, remove its definition and add the new version, which gets a new id. To replace a whole batch, add it again with force_overwrite_namespace: true. That atomically swaps every tool in the namespace for the new batch, without touching other namespaces. Removing or replacing a definition stops it from appearing in discovery. Invocations already in progress keep running.
A successful removal returns HTTP 204. Removing an ID that doesn’t exist returns HTTP 404.

Keep tools registered

Custom tools belong to the browser’s REPL process. Anything that ends that process removes every custom tool in the browser, in every namespace:
  • A REPL execution that exceeds its timeout_sec, which returns repl_terminated: true
  • A REPL call with reset: true
  • A REPL crash
After any of these, list custom tools and register your batches again. If you run long REPL code in the same browser, give it a generous timeout_sec. Keep your batches in files like hn-tools.js so you can re-register them with one call.

Register tools from the REPL

In the REPL, webmcp has matching methods that take real functions instead of a source string:
  • await webmcp.addCustomTools({ namespace, tools, forceOverwriteNamespace }) adds a batch and returns the definitions with their generated IDs.
  • await webmcp.listCustomTools() returns every registered definition.
  • await webmcp.removeCustomTool(id) removes one definition and returns whether it existed.
A cdp tool registered this way can use bindings you created in earlier REPL cells. The webmcp helper in Playwright execution lists and invokes custom tools, but can’t register them. You can also manage custom tools with kernel browsers webmcp custom-tools in the CLI and the list_custom, add_custom, and remove_custom actions of the MCP server’s webmcp tool.

Limits

Security

  • Source is executable code. A page tool runs with the page’s access to its DOM, cookies, and origin. A cdp tool has unrestricted control of the whole browser. Only register source you wrote or reviewed.
  • Don’t embed secrets in source. Pass credentials as tool input when a tool needs them, or use vaults so they never reach your agent.
  • Treat output as untrusted. Tools read page content, and pages can include text written to manipulate agents. Handle custom tool output the same way you handle page-provided tool output.
  • Annotations are hints. Kernel doesn’t enforce readOnlyHint or other annotations on custom tools either. Set them accurately, because agents use them to decide when to ask for confirmation.

API reference