Skip to content
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ Any document in the tree matching these origins (and allowed to use `tools` perm

Once tools are registered, in-page agents can discover and invoke them using `getTools()` and `executeTool()`.

Calling `document.modelContext.getTools()` returns a promise that resolves with an array of `RegisteredTool` dictionary objects. Each object contains the tool's `name`, `description`, `inputSchema`, `origin`, and owner `window`. By default, `getTools()` only returns tools registered by documents same-origin with the caller in the frame tree. To retrieve cross-origin tools, you must explicitly list their origins in the `fromOrigins` option. This array only supports secure origins.
Calling `document.modelContext.getTools()` returns a promise that resolves with an array of `RegisteredTool` dictionary objects. Each object contains the tool's `name`, `description`, `inputSchema`, `outputSchema`, `origin`, and owner `window`. By default, `getTools()` only returns tools registered by documents same-origin with the caller in the frame tree. To retrieve cross-origin tools, you must explicitly list their origins in the `fromOrigins` option. This array only supports secure origins.

```js
// Discover tools exposed by same-origin frames in the tree (default)
Expand All @@ -362,7 +362,8 @@ const tools = await document.modelContext.getTools();
for (const tool of tools) {
console.log(`Tool: ${tool.name} (from ${tool.origin})`);
console.log(`Description: ${tool.description}`);
console.log(`Parameters schema:`, tool.inputSchema);
console.log(`Input schema:`, tool.inputSchema);
console.log(`Output schema:`, tool.outputSchema);
}

// Discover additional tools provided by a cross-origin frame (in addition to
Expand Down Expand Up @@ -524,8 +525,6 @@ As the WebMCP proposal continues to evolve with community and stakeholder feedba

- **Skills Integration**: Determining if the author should expose a higher-level "skill" to help the agent coordinate multiple related tools to fulfill a user journey. See [Issue #161](https://github.com/webmachinelearning/webmcp/issues/161).

- **Output schema**: Supporting structured `outputSchema` contracts (complementing `inputSchema`) to help LLMs reliably reason about the return values of tools. See [Issue #9](https://github.com/webmachinelearning/webmcp/issues/9).

- **User prompting and elicitation**: Exploring a way for a tool to prompt the user for confirmation when tools require explicit user authorization. This could be done by delegating to the agent and its harness, or by invoking native browser permission dialogue outside of the agent loop. See [Issue #165](https://github.com/webmachinelearning/webmcp/issues/165) and [Issue #50](https://github.com/webmachinelearning/webmcp/issues/50) for discussion about the `ModelContextClient` interface.

- **Tool progress reporting**: For long-running tasks (e.g., batch processing or generating content), the agent may want a way to track a tool's progress. We are exploring how this intersects with the established [MCP Progress](https://modelcontextprotocol.io/specification/2025-11-25/basic/utilities/progress) specification.
Expand Down
98 changes: 74 additions & 24 deletions index.bs
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,21 @@ A <dfn>tool definition</dfn> is a [=struct=] with the following [=struct/items=]
:: a [=string=].

: <dfn>input schema</dfn>
:: a [=string=].

Note: This is the stringified representation of the {{ModelContextTool/inputSchema}} passed to
{{ModelContext/registerTool()}}. [[!JSON-SCHEMA]]
:: a [=string=] that is either the empty string, indicating that no input schema was provided, or
Comment thread
beaufortfrancois marked this conversation as resolved.
Resolved
a JSON string representing a JSON Schema object [[!JSON-SCHEMA]], as returned by the
[=serialize a tool schema=] algorithm.

: <dfn>output schema</dfn>
:: a [=string=] that is either the empty string, indicating that no output schema was provided,
or a JSON string representing a JSON Schema object [[!JSON-SCHEMA]], as returned by the
[=serialize a tool schema=] algorithm.

Note: The [=tool definition/input schema=] and [=tool definition/output schema=] are only the
empty string when the corresponding {{ModelContextTool/inputSchema}} or
{{ModelContextTool/outputSchema}} member is omitted or explicitly set to undefined. Because
these members are of type {{object}}, a non-object value such as
"<code>inputSchema: ""</code>" does not produce the empty string; instead it causes
{{ModelContext/registerTool()}} to throw a {{TypeError}} during dictionary conversion.

: <dfn>execute steps</dfn>
:: an algorithm that takes a {{Document}} <var ignore>targetDocument</var>, a [=string=] <var
Expand Down Expand Up @@ -602,6 +613,30 @@ To <dfn for="model context">unregister a tool</dfn> given a {{ModelContext}} |mo

</div>

<div algorithm>
To <dfn>serialize a tool schema</dfn> given an {{object}}-or-undefined |schema|:

1. If |schema| is undefined, then return the empty string.

1. Return the result of [=serializing a JavaScript value to a JSON string=] given |schema|.

<div class="note" role="note"><span class="marker">Note:</span>
<span>The [=serialize a JavaScript value to a JSON string=] algorithm throws exceptions in the
following cases:</span>

<ol>
<li><p><i>Throws a new {{TypeError}}</i> when the backing "<code>JSON.stringify()</code>"
yields undefined, e.g.,
"<code>inputSchema: { toJSON() {return HTMLDivElement;}}</code>", or
"<code>outputSchema: { toJSON() {return undefined;}}</code>".</p></li>

<li><p><i>Re-throws exceptions</i> thrown by "<code>JSON.stringify()</code>", e.g., when
Comment thread
markafoltz marked this conversation as resolved.
Resolved
the <i>object</i> value has a circular reference.</p></li>
</ol>
</div>

</div>


<h2 id="api">API</h2>

Expand Down Expand Up @@ -657,7 +692,7 @@ is a [=model context=] [=struct=] created alongside the {{ModelContext}}.
<p>Registers a tool that [=agents=] can invoke. Returns a rejected promise if a tool with the
same name is already registered, if the given {{ModelContextTool/name}} or
{{ModelContextTool/description}} are empty strings, or if the {{ModelContextTool/inputSchema}}
is invalid.</p>
or {{ModelContextTool/outputSchema}} is invalid.</p>
</dd>

<dt><code><var ignore>document</var>.{{Document/modelContext}}.{{ModelContext/getTools(options)}}</code></dt>
Expand Down Expand Up @@ -725,26 +760,15 @@ The <dfn method for=ModelContext>registerTool(<var>tool</var>, <var>options</var
1. If |tool|'s {{ModelContextTool/description}} is the empty string, then return [=a promise
rejected with=] an {{InvalidStateError}} {{DOMException}}.

1. Let |stringified input schema| be the empty string.

1. If |tool|'s {{ModelContextTool/inputSchema}} [=map/exists=], then set |stringified input schema|
to the result of [=serializing a JavaScript value to a JSON string=], given |tool|'s
{{ModelContextTool/inputSchema}}. If this threw an exception, then return [=a promise rejected
with=] that exception.
1. Let |stringified input schema| be the result of [=serializing a tool schema=] given |tool|'s
{{ModelContextTool/inputSchema}} if it [=map/exists=], and undefined otherwise. If
[=serializing a tool schema=] threw an exception, then return [=a promise rejected with=] that
exception.

<div class="note">
<p>The serialization algorithm above throws exceptions in the following cases:</p>

<ol>
<li><p><i>Throws a new {{TypeError}}</i> when the backing "<code>JSON.stringify()</code>"
yields undefined, e.g.,
"<code>inputSchema: { toJSON() {return HTMLDivElement;}}</code>", or
"<code>inputSchema: { toJSON() {return undefined;}}</code>".</p></li>

<li><p><i>Re-throws exceptions</i> thrown by "<code>JSON.stringify()</code>", e.g., when
"<code>inputSchema</code>" is an object with a circular reference, etc.</p></li>
</ol>
</div>
1. Let |stringified output schema| be the result of [=serializing a tool schema=] given |tool|'s
{{ModelContextTool/outputSchema}} if it [=map/exists=], and undefined otherwise. If
[=serializing a tool schema=] threw an exception, then return [=a promise rejected with=] that
exception.

1. If |options|'s {{ModelContextRegisterToolOptions/signal}} [=map/exists=] and is
[=AbortSignal/aborted=], then return [=a promise rejected with=] |options|'s
Expand Down Expand Up @@ -793,6 +817,9 @@ The <dfn method for=ModelContext>registerTool(<var>tool</var>, <var>options</var
: [=tool definition/input schema=]
:: |stringified input schema|

: [=tool definition/output schema=]
:: |stringified output schema|

: [=tool definition/execute steps=]
:: An algorithm that takes a {{Document}} |targetDocument|, a [=string=] |inputArguments|, an
algorithm |completionSteps|, and a [=unique internal value=] |uuid|, and [=invokes the execute
Expand Down Expand Up @@ -916,6 +943,14 @@ The <dfn method for=ModelContext>getTools(<var>options</var>)</dfn> method steps
Note: This will never throw an exception, because the string stored in the tool
definition is always a valid JSON string.

: {{RegisteredTool/outputSchema}}
:: the result of [=parse a JSON string to a JavaScript value=] given |tool
definition|'s [=tool definition/output schema=], if |tool definition|'s [=tool
definition/output schema=] is not the empty string; otherwise undefined.

Note: This will never throw an exception, because the string stored in the tool
definition is always a valid JSON string.

: {{RegisteredTool/window}}
:: |targetDocument|'s [=relevant global object=]

Expand Down Expand Up @@ -1118,6 +1153,7 @@ dictionary ModelContextTool {
USVString title;
required DOMString description;
object inputSchema;
object outputSchema;
required ToolExecuteCallback execute;
ToolAnnotations annotations;
};
Expand Down Expand Up @@ -1161,6 +1197,13 @@ callback ToolExecuteCallback = Promise<any> (object inputObject, ToolExecuteCall
<p>A JSON Schema object describing the expected input parameters for the tool [[!JSON-SCHEMA]].
</dd>

<dt><code><var ignore>tool</var>["{{ModelContextTool/outputSchema}}"]</code></dt>
<dd>
<p>A JSON Schema object describing the expected output structure for the tool [[!JSON-SCHEMA]].
Tool output is expected to conform to the provided schema, and [=agents=] may reject outputs

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If this can be validated at the User Agent level, should this go a step further and throw an exception or fail the executeTools promise if the output does not match the schema?

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good question! I guess it's related to #92

that do not validate.
</dd>

<dt><code><var ignore>tool</var>["{{ModelContextTool/execute}}"]</code></dt>
<dd>
<p>A callback function that is invoked when an [=agent=] calls the tool. The function receives
Expand Down Expand Up @@ -1270,6 +1313,7 @@ dictionary RegisteredTool {
DOMString title;
required DOMString description;
object inputSchema;
object outputSchema;
Comment thread
markafoltz marked this conversation as resolved.
Resolved
required Window window;
required USVString origin;
ToolAnnotations annotations;
Expand All @@ -1294,6 +1338,12 @@ dictionary RegisteredTool {
[[!JSON-SCHEMA]]. It is a deep copy of the schema provided at tool registration, via
{{ModelContextTool/inputSchema}}.

: <code><var ignore>tool</var>["{{RegisteredTool/outputSchema}}"]</code>
:: A JSON Schema object describing the expected output structure for the tool [[!JSON-SCHEMA]].
Tool output is expected to conform to the provided schema, and [=agents=] may reject outputs
that do not validate. It is a deep copy of the schema provided at tool registration, via
{{ModelContextTool/outputSchema}}.

: <code><var ignore>tool</var>["{{RegisteredTool/window}}"]</code>
:: The {{Window}} of the document that registered the tool.

Expand Down
2 changes: 1 addition & 1 deletion security-privacy-questionnaire.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ Note: this behavior is not yet spec'd but is the intended direction.

> 20. Does your spec define when and how new kinds of errors should be raised?

Yes. `registerTool()` throws `InvalidStateError` for inactive documents, duplicate names, or invalid name/description; `NotAllowedError` when the `"tools"` Permissions Policy is disallowed; `SecurityError` for non-trustworthy [`exposedTo`](https://webmachinelearning.github.io/webmcp/#dom-modelcontextregistertooloptions-exposedto) origins; and `TypeError` when `inputSchema` serialization fails. These errors only reflect the page's own state and inputs, so they do not leak new information.
Yes. `registerTool()` throws `InvalidStateError` for inactive documents, duplicate names, or invalid name/description; `NotAllowedError` when the `"tools"` Permissions Policy is disallowed; `SecurityError` for non-trustworthy [`exposedTo`](https://webmachinelearning.github.io/webmcp/#dom-modelcontextregistertooloptions-exposedto) origins; and `TypeError` when `inputSchema` or `outputSchema` serialization fails. These errors only reflect the page's own state and inputs, so they do not leak new information.

> 21. Does your feature allow sites to learn about the user's use of assistive technology?

Expand Down
Loading