Add beginnings of declarative WebMCP (1/N) [chromium/src : main]

0 views
Skip to first unread message

Khushal Sagar (Gerrit)

unread,
1:56β€―AMΒ (17 hours ago)Β 1:56β€―AM
to Mason Freed, AI Code Reviewer, Chromium LUCI CQ, chromium...@chromium.org, blink-rev...@chromium.org, blink-...@chromium.org
Attention needed from Mason Freed

Khushal Sagar added 8 comments

File third_party/blink/renderer/core/html/forms/html_form_element.cc
Line 163, Patchset 4 (Latest): if (is_valid_mcp_form == !active_webmcp_name_.IsNull() && !name_changed) {
Khushal Sagar . unresolved

nit: Put this in a `IsValidMcpForm() const` function?

Line 176, Patchset 4 (Latest): if (name != active_webmcp_name_ && !active_webmcp_name_.IsNull()) {
Khushal Sagar . unresolved

nit: The JS API throws an error if you try to register a tool with an existing name, even if all that's happening is a change in parameters. Maybe we should do the same here for consistency? It makes sure we teardown everything associated with the existing tool like ongoing executions.

If you go with that then you can put the unregister stuff before this if block. If there's a state change, always unregister the existing tool (if any). And then register the new one, if there is one.

File third_party/blink/renderer/core/script_tools/model_context.h
Line 22, Patchset 4 (Latest):using DeclarativeWebMCPCallback = base::RepeatingCallback<void(String)>;
using DeclarativeWebMCPUpdateInputSchemaCallback =
base::RepeatingCallback<String()>;
Khushal Sagar . unresolved

Could we use an interface instead?

```
class DeclarativeWebMCPTool {
// Executes the associated tool and invokes `done_callback` with the result when the execution is finished. The callback is invoked with a null string if the execution failed.
virtual void ExecuteTool(base::OnceCallback<String> done_callback) = 0;

// Returns the input json-schema associated with the tool.
virtual String ComputeInputSchema() = 0;
}
```
File third_party/blink/renderer/core/script_tools/model_context.cc
Line 109, Patchset 4 (Latest): // Always update the input schema, since the DOM might have changed.
Khushal Sagar . unresolved

Hmmm, since we have to cache the input schema for script based tools here anyway, seems better to do the same for declarative tools for consistency? we can add a InvalidateBlinkToolSchema API for form to know when it's dirty. And use the ComputeInputSchema API to lazily generate and cache it again.

Line 174, Patchset 4 (Latest): ExecuteBlinkTool(std::move(it->value->blink_tool_function),
Khushal Sagar . unresolved

nitty nit: Lets use declarative instead of Blink everywhere? :)

Line 175, Patchset 4 (Latest): input_arguments);
Khushal Sagar . unresolved

The `tool_execited_cb` needs to be carried forward for declarative ones too.

Line 191, Patchset 4 (Latest): if (tool_function.is_null()) {
return;
}
Khushal Sagar . unresolved

This should be a CHECK?

Line 306, Patchset 4 (Latest): script_tool->input_schema = "{}"; // For now
Khushal Sagar . unresolved

empty value is allowed btw but i guess it can never be empty for declarative tools? in that case input_schema being unset implies its dirty and needs to be computed?

Open in Gerrit

Related details

Attention is currently required from:
  • Mason Freed
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: I12d0583f9c72bc72e4dbcea5ccd8534d61069919
Gerrit-Change-Number: 7428254
Gerrit-PatchSet: 4
Gerrit-Owner: Mason Freed <mas...@chromium.org>
Gerrit-Reviewer: Khushal Sagar <khusha...@chromium.org>
Gerrit-Reviewer: Mason Freed <mas...@chromium.org>
Gerrit-CC: AI Code Reviewer <peep-gen...@system.gserviceaccount.com>
Gerrit-Attention: Mason Freed <mas...@chromium.org>
Gerrit-Comment-Date: Mon, 12 Jan 2026 06:56:40 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: No
satisfied_requirement
unsatisfied_requirement
open
diffy

Mason Freed (Gerrit)

unread,
5:24β€―PMΒ (1 hour ago)Β 5:24β€―PM
to Khushal Sagar, AI Code Reviewer, Chromium LUCI CQ, chromium...@chromium.org, blink-rev...@chromium.org, blink-...@chromium.org
Attention needed from Khushal Sagar

Mason Freed voted and added 9 comments

Votes added by Mason Freed

Commit-Queue+1

9 comments

File third_party/blink/renderer/core/html/forms/html_form_element.cc
Line 163, Patchset 4: if (is_valid_mcp_form == !active_webmcp_name_.IsNull() && !name_changed) {
Khushal Sagar . resolved

nit: Put this in a `IsValidMcpForm() const` function?

Mason Freed

Done

Line 176, Patchset 4: if (name != active_webmcp_name_ && !active_webmcp_name_.IsNull()) {
Khushal Sagar . unresolved

nit: The JS API throws an error if you try to register a tool with an existing name, even if all that's happening is a change in parameters. Maybe we should do the same here for consistency? It makes sure we teardown everything associated with the existing tool like ongoing executions.

If you go with that then you can put the unregister stuff before this if block. If there's a state change, always unregister the existing tool (if any). And then register the new one, if there is one.

Mason Freed

I'm not sure you really want to do that though. Many forms (e.g. React) are built incrementally, or at least imperatively. I'd be worried that a standard vdom operation might mutate the attributes while building the form. The end state is right, but the intermediate state might be different.

Since these tools are only needed to be correct when called, is there an issue? I can see the point of throwing an exception for imperatively-created tools, since that's likely a developer error. So helping the developer by telling them they've overwritten one is helpful. I'm not sure the same is true here.

Also, throwing exceptions for all of that means moving most of these checks way up front before the operation actually goes through. I.e. something like [here](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/dom/container_node.cc;l=239;drc=aeb46299fefab41457b0c50e5386616d2e8d64a2) for DOM insertions.

File third_party/blink/renderer/core/script_tools/model_context.h
Line 22, Patchset 4:using DeclarativeWebMCPCallback = base::RepeatingCallback<void(String)>;

using DeclarativeWebMCPUpdateInputSchemaCallback =
base::RepeatingCallback<String()>;
Khushal Sagar . resolved

Could we use an interface instead?

```
class DeclarativeWebMCPTool {
// Executes the associated tool and invokes `done_callback` with the result when the execution is finished. The callback is invoked with a null string if the execution failed.
virtual void ExecuteTool(base::OnceCallback<String> done_callback) = 0;

// Returns the input json-schema associated with the tool.
virtual String ComputeInputSchema() = 0;
}
```
Mason Freed

Great idea, done!

Line 79, Patchset 1: bool input_schema_dirty;
AI Code Reviewer . resolved

nit: Blink Style Guide: Precede boolean values with words like β€œis” and β€œdid”. Consider renaming 'input_schema_dirty' to 'is_input_schema_dirty'.

To keep this interaction as brief and non-intrusive as possible, please consider responding with one of following options:
**Done** | **OK But Won't Fix**: reason | **Later**: b/<bug_id> | **Invalid:** reason


_This comment was generated by [Experimental Blink C++ Code Review Agent](http://go/blink-c++-code-review-agent)._
_AI reviews can sometimes be inaccurate; We appreciate your πŸ™ feedback πŸ™ to help us improve._
_[File a bug](http://go/blink-c++-code-review-agent-feedback) | [Provide feedback on chat](https://chat.google.com/room/AAQA0zhQHe0?cls=4) | [Opt-out](https://ganpati2.corp.google.com/group/peep-genai-blink-agent-optout.prod)_

Mason Freed

Acknowledged

File third_party/blink/renderer/core/script_tools/model_context.cc
Line 109, Patchset 4: // Always update the input schema, since the DOM might have changed.
Khushal Sagar . unresolved

Hmmm, since we have to cache the input schema for script based tools here anyway, seems better to do the same for declarative tools for consistency? we can add a InvalidateBlinkToolSchema API for form to know when it's dirty. And use the ComputeInputSchema API to lazily generate and cache it again.

Mason Freed

Yeah, I started with this approach, but then realized that declarative tools are different in this regard. The form can (and very much does) change between the declaratively-created tool creation and this point in the code. I.e. I call `RegisterDeclarativeTool` as soon as the `<form>` is added to the document, but at that point, it has no children. So later it'll get children that will be part of the input schema. So I decided it would be best to always re-generate the input schema here, right before the model gets to see the schema and decides on a prompt object.

(See the new code, where this regen only happens for declarative tools.)

Does that make sense?

Line 174, Patchset 4: ExecuteBlinkTool(std::move(it->value->blink_tool_function),
Khushal Sagar . resolved

nitty nit: Lets use declarative instead of Blink everywhere? :)

Mason Freed

Done. Blink was a placeholder I meant to change, thanks for the reminder.

Line 175, Patchset 4: input_arguments);
Khushal Sagar . resolved

The `tool_execited_cb` needs to be carried forward for declarative ones too.

Mason Freed

Done

Line 191, Patchset 4: if (tool_function.is_null()) {
return;
}
Khushal Sagar . resolved

This should be a CHECK?

Mason Freed

Removed in the new version.

Line 306, Patchset 4: script_tool->input_schema = "{}"; // For now
Khushal Sagar . unresolved

empty value is allowed btw but i guess it can never be empty for declarative tools? in that case input_schema being unset implies its dirty and needs to be computed?

Mason Freed

Right, this will only be `"{}"` until the script tools are queried, and then it'll get filled in. If there's a good place to put a CHECK that it isn't empty, I'm happy to add that.

Open in Gerrit

Related details

Attention is currently required from:
  • Khushal Sagar
Submit Requirements:
  • requirement satisfiedCode-Coverage
  • requirement satisfiedCode-Owners
  • requirement is not satisfiedCode-Review
  • requirement is not satisfiedNo-Unresolved-Comments
  • requirement is not satisfiedReview-Enforcement
Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
Gerrit-MessageType: comment
Gerrit-Project: chromium/src
Gerrit-Branch: main
Gerrit-Change-Id: I12d0583f9c72bc72e4dbcea5ccd8534d61069919
Gerrit-Change-Number: 7428254
Gerrit-PatchSet: 6
Gerrit-Owner: Mason Freed <mas...@chromium.org>
Gerrit-Reviewer: Khushal Sagar <khusha...@chromium.org>
Gerrit-Reviewer: Mason Freed <mas...@chromium.org>
Gerrit-CC: AI Code Reviewer <peep-gen...@system.gserviceaccount.com>
Gerrit-Attention: Khushal Sagar <khusha...@chromium.org>
Gerrit-Comment-Date: Mon, 12 Jan 2026 22:24:11 +0000
Gerrit-HasComments: Yes
Gerrit-Has-Labels: Yes
Comment-In-Reply-To: Khushal Sagar <khusha...@chromium.org>
Comment-In-Reply-To: AI Code Reviewer <peep-gen...@system.gserviceaccount.com>
satisfied_requirement
unsatisfied_requirement
open
diffy

Khushal Sagar (Gerrit)

unread,
6:33β€―PMΒ (7 minutes ago)Β 6:33β€―PM
to Mason Freed, AI Code Reviewer, Chromium LUCI CQ, chromium...@chromium.org, blink-rev...@chromium.org, blink-...@chromium.org
Attention needed from Mason Freed

Khushal Sagar voted and added 4 comments

Votes added by Khushal Sagar

Code-Review+1

4 comments

File third_party/blink/renderer/core/html/forms/html_form_element.cc
Line 176, Patchset 4: if (name != active_webmcp_name_ && !active_webmcp_name_.IsNull()) {
Khushal Sagar . unresolved

nit: The JS API throws an error if you try to register a tool with an existing name, even if all that's happening is a change in parameters. Maybe we should do the same here for consistency? It makes sure we teardown everything associated with the existing tool like ongoing executions.

If you go with that then you can put the unregister stuff before this if block. If there's a state change, always unregister the existing tool (if any). And then register the new one, if there is one.

Mason Freed

I'm not sure you really want to do that though. Many forms (e.g. React) are built incrementally, or at least imperatively. I'd be worried that a standard vdom operation might mutate the attributes while building the form. The end state is right, but the intermediate state might be different.

Since these tools are only needed to be correct when called, is there an issue? I can see the point of throwing an exception for imperatively-created tools, since that's likely a developer error. So helping the developer by telling them they've overwritten one is helpful. I'm not sure the same is true here.

Also, throwing exceptions for all of that means moving most of these checks way up front before the operation actually goes through. I.e. something like [here](https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/dom/container_node.cc;l=239;drc=aeb46299fefab41457b0c50e5386616d2e8d64a2) for DOM insertions.

Khushal Sagar

Sorry that statement was ambiguous. I didn't mean throw an exception but to call unregisterTool first, to tear down all existing state for that tool in ModelContext, if any state about the tool changes. Instead of looking up the entry from the map and updating it.

We can ignore for now. It only matters for edge cases where a tool execution is in-flight when the state changes. We can also change the behaviour in `RegisterDeclarativeTool` itself.

File third_party/blink/renderer/core/script_tools/model_context.cc
Line 109, Patchset 4: // Always update the input schema, since the DOM might have changed.
Khushal Sagar . resolved

Hmmm, since we have to cache the input schema for script based tools here anyway, seems better to do the same for declarative tools for consistency? we can add a InvalidateBlinkToolSchema API for form to know when it's dirty. And use the ComputeInputSchema API to lazily generate and cache it again.

Mason Freed

Yeah, I started with this approach, but then realized that declarative tools are different in this regard. The form can (and very much does) change between the declaratively-created tool creation and this point in the code. I.e. I call `RegisterDeclarativeTool` as soon as the `<form>` is added to the document, but at that point, it has no children. So later it'll get children that will be part of the input schema. So I decided it would be best to always re-generate the input schema here, right before the model gets to see the schema and decides on a prompt object.

(See the new code, where this regen only happens for declarative tools.)

Does that make sense?

Khushal Sagar

Sounds good. We can consider caching the schema if it's expensive going forward.

Line 200, Patchset 6 (Latest): std::move(tool_executed_cb).Run("Hello from declarative WebMCP");
Khushal Sagar . unresolved

This should return `result` or `kToolInvocationFailed` in case of failure. LGTM if you want to defer that to a subsequent CL which wires up more things.

Line 306, Patchset 4: script_tool->input_schema = "{}"; // For now
Khushal Sagar . resolved

empty value is allowed btw but i guess it can never be empty for declarative tools? in that case input_schema being unset implies its dirty and needs to be computed?

Mason Freed

Right, this will only be `"{}"` until the script tools are queried, and then it'll get filled in. If there's a good place to put a CHECK that it isn't empty, I'm happy to add that.

Khushal Sagar

Acknowledged

Open in Gerrit

Related details

Attention is currently required from:
  • Mason Freed
Submit Requirements:
    • requirement satisfiedCode-Coverage
    • requirement satisfiedCode-Owners
    • requirement satisfiedCode-Review
    • requirement is not satisfiedNo-Unresolved-Comments
    • requirement satisfiedReview-Enforcement
    Inspect html for hidden footers to help with email filtering. To unsubscribe visit settings. DiffyGerrit
    Gerrit-MessageType: comment
    Gerrit-Project: chromium/src
    Gerrit-Branch: main
    Gerrit-Change-Id: I12d0583f9c72bc72e4dbcea5ccd8534d61069919
    Gerrit-Change-Number: 7428254
    Gerrit-PatchSet: 6
    Gerrit-Owner: Mason Freed <mas...@chromium.org>
    Gerrit-Reviewer: Khushal Sagar <khusha...@chromium.org>
    Gerrit-Reviewer: Mason Freed <mas...@chromium.org>
    Gerrit-CC: AI Code Reviewer <peep-gen...@system.gserviceaccount.com>
    Gerrit-Attention: Mason Freed <mas...@chromium.org>
    Gerrit-Comment-Date: Mon, 12 Jan 2026 23:33:15 +0000
    Gerrit-HasComments: Yes
    Gerrit-Has-Labels: Yes
    Comment-In-Reply-To: Mason Freed <mas...@chromium.org>
    Comment-In-Reply-To: Khushal Sagar <khusha...@chromium.org>
    satisfied_requirement
    unsatisfied_requirement
    open
    diffy
    Reply all
    Reply to author
    Forward
    0 new messages