refactor: share form node ID serialization between MCP and CDP

This commit is contained in:
Adrià Arrufat
2026-03-23 10:18:24 +09:00
parent ad83c6e70b
commit a6d2ec7610
3 changed files with 23 additions and 105 deletions

View File

@@ -40,6 +40,7 @@ pub const SelectOption = struct {
}; };
pub const FormField = struct { pub const FormField = struct {
backendNodeId: ?u32 = null,
node: *Node, node: *Node,
tag_name: []const u8, tag_name: []const u8,
name: ?[]const u8, name: ?[]const u8,
@@ -52,6 +53,11 @@ pub const FormField = struct {
pub fn jsonStringify(self: *const FormField, jw: anytype) !void { pub fn jsonStringify(self: *const FormField, jw: anytype) !void {
try jw.beginObject(); try jw.beginObject();
if (self.backendNodeId) |id| {
try jw.objectField("backendNodeId");
try jw.write(id);
}
try jw.objectField("tagName"); try jw.objectField("tagName");
try jw.write(self.tag_name); try jw.write(self.tag_name);
@@ -94,6 +100,7 @@ pub const FormField = struct {
}; };
pub const FormInfo = struct { pub const FormInfo = struct {
backendNodeId: ?u32 = null,
node: *Node, node: *Node,
action: ?[]const u8, action: ?[]const u8,
method: ?[]const u8, method: ?[]const u8,
@@ -102,6 +109,11 @@ pub const FormInfo = struct {
pub fn jsonStringify(self: *const FormInfo, jw: anytype) !void { pub fn jsonStringify(self: *const FormInfo, jw: anytype) !void {
try jw.beginObject(); try jw.beginObject();
if (self.backendNodeId) |id| {
try jw.objectField("backendNodeId");
try jw.write(id);
}
if (self.action) |v| { if (self.action) |v| {
try jw.objectField("action"); try jw.objectField("action");
try jw.write(v); try jw.write(v);

View File

@@ -173,18 +173,17 @@ fn detectForms(cmd: anytype) !void {
); );
// Register form and field nodes for backendNodeId references // 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| {
for (forms_data) |form| {
const registered = try bc.node_registry.register(form.node); const registered = try bc.node_registry.register(form.node);
form_ids.appendAssumeCapacity(registered.id); form.backendNodeId = registered.id;
for (form.fields) |field| { for (@constCast(form.fields)) |*field| {
_ = try bc.node_registry.register(field.node); const field_registered = try bc.node_registry.register(field.node);
field.backendNodeId = field_registered.id;
} }
} }
return cmd.sendResult(.{ return cmd.sendResult(.{
.forms = forms_data, .forms = forms_data,
.formNodeIds = form_ids.items,
}, .{}); }, .{});
} }

View File

@@ -450,82 +450,6 @@ fn handleStructuredData(server: *Server, arena: std.mem.Allocator, id: std.json.
try server.sendResult(id, protocol.CallToolResult([]const u8){ .content = &content }); try server.sendResult(id, protocol.CallToolResult([]const u8){ .content = &content });
} }
const FormWithId = struct {
backendNodeId: CDPNode.Id,
action: ?[]const u8,
method: ?[]const u8,
fields: []const FormFieldWithId,
pub fn jsonStringify(self: *const FormWithId, jw: anytype) !void {
try jw.beginObject();
try jw.objectField("backendNodeId");
try jw.write(self.backendNodeId);
if (self.action) |a| {
try jw.objectField("action");
try jw.write(a);
}
if (self.method) |m| {
try jw.objectField("method");
try jw.write(m);
}
try jw.objectField("fields");
try jw.beginArray();
for (self.fields) |field| {
try field.jsonStringify(jw);
}
try jw.endArray();
try jw.endObject();
}
};
const FormFieldWithId = struct {
backendNodeId: CDPNode.Id,
tag_name: []const u8,
name: ?[]const u8,
input_type: ?[]const u8,
required: bool,
value: ?[]const u8,
placeholder: ?[]const u8,
options: []const lp.forms.SelectOption,
pub fn jsonStringify(self: *const FormFieldWithId, jw: anytype) !void {
try jw.beginObject();
try jw.objectField("backendNodeId");
try jw.write(self.backendNodeId);
try jw.objectField("tagName");
try jw.write(self.tag_name);
if (self.name) |v| {
try jw.objectField("name");
try jw.write(v);
}
if (self.input_type) |v| {
try jw.objectField("inputType");
try jw.write(v);
}
if (self.required) {
try jw.objectField("required");
try jw.write(true);
}
if (self.value) |v| {
try jw.objectField("value");
try jw.write(v);
}
if (self.placeholder) |v| {
try jw.objectField("placeholder");
try jw.write(v);
}
if (self.options.len > 0) {
try jw.objectField("options");
try jw.beginArray();
for (self.options) |opt| {
try opt.jsonStringify(jw);
}
try jw.endArray();
}
try jw.endObject();
}
};
fn handleDetectForms(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arguments: ?std.json.Value) !void { fn handleDetectForms(server: *Server, arena: std.mem.Allocator, id: std.json.Value, arguments: ?std.json.Value) !void {
const Params = struct { const Params = struct {
url: ?[:0]const u8 = null, url: ?[:0]const u8 = null,
@@ -546,36 +470,19 @@ fn handleDetectForms(server: *Server, arena: std.mem.Allocator, id: std.json.Val
return server.sendError(id, .InternalError, "Failed to collect forms"); return server.sendError(id, .InternalError, "Failed to collect forms");
}; };
// Build output with backendNodeIds // Register form and field nodes for backendNodeId references
var results: std.ArrayList(FormWithId) = .empty; for (forms_data) |*form| {
for (forms_data) |form| {
const form_registered = try server.node_registry.register(form.node); const form_registered = try server.node_registry.register(form.node);
form.backendNodeId = form_registered.id;
var fields_with_ids: std.ArrayList(FormFieldWithId) = .empty; for (@constCast(form.fields)) |*field| {
for (form.fields) |field| {
const field_registered = try server.node_registry.register(field.node); const field_registered = try server.node_registry.register(field.node);
try fields_with_ids.append(arena, .{ field.backendNodeId = field_registered.id;
.backendNodeId = field_registered.id,
.tag_name = field.tag_name,
.name = field.name,
.input_type = field.input_type,
.required = field.required,
.value = field.value,
.placeholder = field.placeholder,
.options = field.options,
});
} }
try results.append(arena, .{
.backendNodeId = form_registered.id,
.action = form.action,
.method = form.method,
.fields = fields_with_ids.items,
});
} }
var aw: std.Io.Writer.Allocating = .init(arena); var aw: std.Io.Writer.Allocating = .init(arena);
try std.json.Stringify.value(results.items, .{}, &aw.writer); try std.json.Stringify.value(forms_data, .{}, &aw.writer);
const content = [_]protocol.TextContent([]const u8){.{ .text = aw.written() }}; const content = [_]protocol.TextContent([]const u8){.{ .text = aw.written() }};
try server.sendResult(id, protocol.CallToolResult([]const u8){ .content = &content }); try server.sendResult(id, protocol.CallToolResult([]const u8){ .content = &content });