mcp: add detectForms tool for structured form discovery

Add a detectForms MCP tool and lp.detectForms CDP command that return
structured form metadata from the current page. Each form includes its
action URL, HTTP method, and fields with names, types, required status,
values, select options, and backendNodeIds for use with the fill tool.

This lets AI agents discover and fill forms in a single step instead of
calling interactiveElements, filtering for form fields, and guessing
which fields belong to which form.

New files:
- src/browser/forms.zig: FormInfo/FormField structs, collectForms()

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Matt Van Horn
2026-03-21 08:40:50 -07:00
parent fdc79af55c
commit 78c6def2b1
4 changed files with 514 additions and 0 deletions

View File

@@ -32,6 +32,7 @@ pub fn processMessage(cmd: anytype) !void {
getSemanticTree,
getInteractiveElements,
getStructuredData,
detectForms,
clickNode,
fillNode,
scrollNode,
@@ -42,6 +43,7 @@ pub fn processMessage(cmd: anytype) !void {
.getSemanticTree => return getSemanticTree(cmd),
.getInteractiveElements => return getInteractiveElements(cmd),
.getStructuredData => return getStructuredData(cmd),
.detectForms => return detectForms(cmd),
.clickNode => return clickNode(cmd),
.fillNode => return fillNode(cmd),
.scrollNode => return scrollNode(cmd),
@@ -160,6 +162,32 @@ fn getStructuredData(cmd: anytype) !void {
}, .{});
}
fn detectForms(cmd: anytype) !void {
const bc = cmd.browser_context orelse return error.NoBrowserContext;
const page = bc.session.currentPage() orelse return error.PageNotLoaded;
const forms_data = try lp.forms.collectForms(
page.document.asNode(),
cmd.arena,
page,
);
// Register form and field nodes for backendNodeId references
var form_ids: std.ArrayList(Node.Id) = try .initCapacity(cmd.arena, forms_data.len);
for (forms_data) |form| {
const registered = try bc.node_registry.register(form.node);
form_ids.appendAssumeCapacity(registered.id);
for (form.fields) |field| {
_ = try bc.node_registry.register(field.node);
}
}
return cmd.sendResult(.{
.forms = forms_data,
.formNodeIds = form_ids.items,
}, .{});
}
fn clickNode(cmd: anytype) !void {
const Params = struct {
nodeId: ?Node.Id = null,