From e00f62ab4d0ca9f05a3bf68625dd92ff55019c50 Mon Sep 17 00:00:00 2001 From: Sam Dallstream Date: Sun, 8 Nov 2026 17:14:32 -0800 Subject: [PATCH 1/3] Support all-origin wildcard in WebMCP origin options Allow the special "*" string in exposedTo and fromOrigins while preserving explicit origin validation and existing policy checks. Update specification examples and related documentation. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 15 ++++-- index.bs | 84 +++++++++++++++++++++++++------ security-privacy-questionnaire.md | 4 +- 3 files changed, 83 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index bf2dfb3..b68f76c 100644 --- a/README.md +++ b/README.md @@ -344,7 +344,9 @@ await document.modelContext.registerTool({ }, { exposedTo: ["https://trusted-partner.example"] }); ``` -Any document in the tree matching these origins (and allowed to use `tools` permission) will: +The special string `"*"` exposes the tool to all origins in the current document's tree, including opaque origins, using `{ exposedTo: ["*"] }`. Other entries must still be valid, potentially trustworthy origins. Only the exact `"*"` string is special; subdomain wildcard patterns such as `"https://*.example.org"` are not supported. + +Any document in the tree matching these origins or covered by `"*"` (and allowed to use `tools` permission) will: - Receive the `toolchange` event on its `document.modelContext` when the tool is registered or unregistered. - Be able to discover and run the tools @@ -353,7 +355,9 @@ 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`, `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. +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, or use `{ fromOrigins: ["*"] }` to query all origins in that tree, including opaque origins. Entries other than `"*"` must be valid, potentially trustworthy origins. + +In either option, `"*"` does not bypass Permissions Policy, the other side's opt-in, or execution checks. Tools discovered from documents with opaque origins still cannot be executed through `executeTool()`. Prefer explicit origin lists when possible. Other entries are still validated even when `"*"` is present. ```js // Discover tools exposed by same-origin frames in the tree (default) @@ -371,6 +375,11 @@ for (const tool of tools) { const crossOriginTools = await document.modelContext.getTools({ fromOrigins: ["https://trusted-partner.example"] }); + +// Discover tools exposed to the caller by frames of any origin: +const allOriginTools = await document.modelContext.getTools({ + fromOrigins: ["*"] +}); ``` An agent executes a discovered `RegisteredTool` by passing the tool dictionary and input arguments along to `document.modelContext.executeTool()`. The browser securely mediates the execution, ensuring the [`exposedTo`](https://webmachinelearning.github.io/webmcp/#dom-modelcontextregistertooloptions-exposedto) and [`fromOrigins`](https://webmachinelearning.github.io/webmcp/#dom-modelcontextgettooloptions-fromorigins) agree, and the tool runs in the tool owner's execution context: @@ -517,7 +526,7 @@ As the WebMCP proposal continues to evolve with community and stakeholder feedba - **Cross-document tool response**: How should WebMCP handle tool responses when a tool (a form submission, for example) causes the page to navigate to another document? See [Issue #135](https://github.com/webmachinelearning/webmcp/issues/135). -- **Built-in agent exposure by default**:

The [`exposedTo`](https://webmachinelearning.github.io/webmcp/#dom-modelcontextregistertooloptions-exposedto) array only takes origins, but we're considering introducing a new keyword like `native-agent`, letting authors control a tool's exposure to a built-in agent. The running idea is that by default in the top-level document, a missing `exposedTo` array would expose tools to the built-in agent, and in iframes, a missing `exposedTo` array would not expose tools to the built-in agent

+- **Built-in agent exposure by default**:

The [`exposedTo`](https://webmachinelearning.github.io/webmcp/#dom-modelcontextregistertooloptions-exposedto) array takes origins and the `"*"` wildcard for window-to-window exposure, but we're considering introducing a new keyword like `native-agent`, letting authors control a tool's exposure to a built-in agent. The running idea is that by default in the top-level document, a missing `exposedTo` array would expose tools to the built-in agent, and in iframes, a missing `exposedTo` array would not expose tools to the built-in agent

- **Transferable/streamable tool inputs and outputs**: AI models inherently support streaming data. WebMCP should consider enabling streaming tool inputs and outputs (such as chunked generation or large data transfers) without blocking on a massive copy. See [Issue #82](https://github.com/webmachinelearning/webmcp/issues/82). See also [MCP discussion](https://github.com/modelcontextprotocol/modelcontextprotocol/discussions/263) and [MCP Apps streaming tool inputs](https://github.com/modelcontextprotocol/ext-apps/blob/main/specification/draft/apps.mdx#notifications-host--view). diff --git a/index.bs b/index.bs index 847677e..1f10a74 100644 --- a/index.bs +++ b/index.bs @@ -173,6 +173,8 @@ An AI platform is a provider of agentic assistants such as OpenAI’s

Supporting concepts

+The origin wildcard is the [=string=] "*", representing all [=origins=]. + A model context is a [=struct=] with the following [=struct/items=]:
@@ -242,7 +244,7 @@ A tool definition is a [=struct=] with the following [=struct/items=] :: an [=annotations=]-or-null. : exposed origins - :: a [=list=] of [=origins=], initially [=list/empty=]. + :: a [=list=] whose items are [=origins=] or the [=origin wildcard=], initially [=list/empty=].
A local pending tool execution is a [=struct=] with the following [=struct/items=]: @@ -436,8 +438,8 @@ follows:
-To notify documents of a tool change given a {{Document}} |tool owner| and a [=list=] of -[=origins=] |exposed origins|, run these steps: +To notify documents of a tool change given a {{Document}} |tool owner| and a [=list=] +|exposed origins| whose items are [=origins=] or the [=origin wildcard=], run these steps: 1. [=Assert=]: these steps are running [=in parallel=]. @@ -488,10 +490,13 @@ To notify documents of a tool change given a {{Document}} |tool owner
To determine if a tool is exposed to an origin given an [=origin=] |tool owner origin|, -a [=list=] of [=origins=] |exposed origins|, and an [=origin=] |accessing origin|, run these steps: +a [=list=] |exposed origins| whose items are [=origins=] or the [=origin wildcard=], and an +[=origin=] |accessing origin|, run these steps: 1. If |tool owner origin| is [=same origin=] with |accessing origin|, then return true. +1. If |exposed origins| [=list/contains=] the [=origin wildcard=], then return true. + 1. [=list/For each=] |allowed origin| of |exposed origins|: 1. If |accessing origin| is [=same origin=] with |allowed origin|, then return true. @@ -842,12 +847,19 @@ The registerTool(tool, optionsgetTools(options) method steps 1. If |toolRequestor| is not [=allowed to use=] the "{{tools}}" feature, then return [=a promise rejected with=] a "{{NotAllowedError}}" {{DOMException}}. -1. Let |from origins| be an empty [=list=] of [=origins=]. +1. Let |from origins| be an empty [=list=] whose items will be [=origins=] or the + [=origin wildcard=]. 1. If |options|'s {{ModelContextGetToolOptions/fromOrigins}} [=map/exists=], then: 1. [=list/For each=] |origin| of |options|'s {{ModelContextGetToolOptions/fromOrigins}}: + 1. If |origin| is the [=origin wildcard=], then: + + 1. [=list/Append=] the [=origin wildcard=] to |from origins|. + + 1. [=iteration/Continue=]. + 1. Let |parsedURL| be the result of running the [=URL parser=] on |origin|. 1. If |parsedURL| is failure or its [=url/origin=] is not [$is origin potentially @@ -973,7 +992,8 @@ The getTools(options) method steps 1. Let |callerOrigin| be |toolRequestor|'s [=Document/origin=]. 1. Let |toolOwnerIsRequested| be true if |targetOrigin| is [=same origin=] with - |callerOrigin|, or if |from origins| [=list/contains=] |targetOrigin|; otherwise, false. + |callerOrigin|, or if |from origins| [=list/contains=] the [=origin wildcard=], or if + |from origins| [=list/contains=] |targetOrigin|; otherwise, false. 1. If |toolOwnerIsRequested| is false, then [=iteration/continue=]. @@ -1337,7 +1357,10 @@ dictionary ModelContextRegisterToolOptions {
: options["{{ModelContextRegisterToolOptions/exposedTo}}"] - :: An array of origins that control which documents this tool is exposed to, in the current document's tree. + :: An array of origin strings that control which documents this tool is exposed to, in the + current document's tree. The special string "*" is the [=origin wildcard=] and + exposes the tool to documents of all origins in that tree, including [=opaque origins=]. + An omitted or empty array only permits same-origin access by documents. : options["{{ModelContextRegisterToolOptions/signal}}"] :: An {{AbortSignal}} that unregisters the tool when aborted. @@ -1358,9 +1381,34 @@ dictionary ModelContextGetToolOptions { : options["{{ModelContextGetToolOptions/fromOrigins}}"] :: An array of origins from which to query tools. Documents whose origin appears in this list, or are same-origin with the caller, have their tools queried. An empty list only includes - same-origin documents. + same-origin documents. The special string "*" is the [=origin wildcard=] and + queries documents of all origins in the current document's tree, including [=opaque origins=]. + Tools must still be exposed to the caller, and documents must be [=allowed to use=] the + "{{tools}}" feature.
+
+ Use the [=origin wildcard=] to expose a tool to all origins or discover tools from all origins + in the current document's tree: + + ```js + await document.modelContext.registerTool({ + name: "public-status", + description: "Returns public service status", + execute: async () => ({ status: "available" }) + }, { exposedTo: ["*"] }); + + const tools = await document.modelContext.getTools({ fromOrigins: ["*"] }); + ``` + + Only the exact string "*" is special; strings such as + "https://*.example.org" are not supported wildcard patterns. Other entries are + still parsed and validated as origins, even when the array also contains "*". + + The wildcard does not bypass the checks in {{ModelContext/executeTool()}}. In particular, + tools discovered from documents with [=opaque origins=] cannot be executed through that method. +
+

ModelContextExecuteToolOptions Dictionary

The {{ModelContextExecuteToolOptions}} dictionary allows web applications to pass options to @@ -2037,13 +2085,14 @@ gates are satisfied:
  • Tool Provider Opt-In ({{ModelContextRegisterToolOptions/exposedTo}}): A {{Document}} registering a tool must explicitly opt in to cross-origin exposure by specifying - the permitted caller [=origins=] in {{ModelContextRegisterToolOptions/exposedTo}}. Each listed - [=origin=] must be a valid, [$is origin potentially trustworthy?|potentially trustworthy$] - [=origin=]. During both discovery ({{ModelContext/getTools()}}) and invocation + the permitted caller [=origins=] or the [=origin wildcard=] in + {{ModelContextRegisterToolOptions/exposedTo}}. Each entry other than the [=origin wildcard=] + must be a valid, [$is origin potentially trustworthy?|potentially trustworthy$] [=origin=]. + During both discovery ({{ModelContext/getTools()}}) and invocation ({{ModelContext/executeTool()}}), the [=user agent=] evaluates the [=tool is exposed to an origin=] algorithm, ensuring that only callers whose [=Document/origin=] is [=same origin=] with - the tool owner or explicitly listed in {{ModelContextRegisterToolOptions/exposedTo}} can access - the tool. + the tool owner, explicitly listed in {{ModelContextRegisterToolOptions/exposedTo}}, or covered + by the [=origin wildcard=] can access the tool.
  • Tool Consumer Opt-In ({{ModelContextGetToolOptions/fromOrigins}}): Even when an @@ -2051,7 +2100,8 @@ gates are satisfied: a tool to its parent [=origin=] via {{ModelContextRegisterToolOptions/exposedTo}}, {{ModelContext/getTools()}} only returns tools from [=same origin=] {{Document}}s by default. To discover tools from a cross-origin descendant [=navigable=], the calling {{Document}} must also - explicitly list the target [=origin=] in {{ModelContextGetToolOptions/fromOrigins}}. + explicitly list the target [=origin=] or the [=origin wildcard=] in + {{ModelContextGetToolOptions/fromOrigins}}.
  • @@ -2066,6 +2116,10 @@ third-party <{iframe}> could unilaterally expose tools with deceptive names or p descriptions ([[#metadata-description-attacks]]) into the parent page's {{ModelContext/getTools()}} results. +The [=origin wildcard=] is an explicit opt-in to all origins, not an assertion that those origins +are trusted. Authors should prefer explicit origin lists when possible. Using "*" +does not bypass the "{{tools}}" Permissions Policy or the other side's opt-in. + ```js // 1. Parent document (https://app.example.com) explicitly delegates the "tools" // feature to the embedded widget (